Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write a custom syntax interpreter in java?

I am about to begin coding a demo program for a lecture I'm about to give. I want to let each student in the class download this application, then be able to create instances of objects (and their graphical representations) interactively via a command line. I decided to write in java, not because it's the language I'm most familiar with, but because it's got easy graphics classes and I can be pretty sure that the jar is going to run on their computers.

Intro over. Now the question:

What is a good way to implement some custom command line syntax for this program? I want to use a simple, arbitrary syntax like:

CREATE Monster Bob;    
Bob.jump();   
LS Bob //to list Bob's methods or something.   
LS CREATE //to list all the classes    

First I'll spiel about what first came to mind when I thought of this problem.

I can imagine that I could have a set of maps in a tree type linkage. I could parse each key word as the key to the next map. So "CREATE Monster Bob" could be evaluated like

1) Search keyword map for key "CREATE". Return the value, which is a reference to the map of classes. 2) Search classes map for key "Monster". Return the value, which is a factory class implementing some interface Leaf that lets me know it is a leaf value (I'll check using instanceof).
3) Maybe the Leaf interface will contain a method called execute() that will do whatever it wants. In this case it would create an object of Monster, adding this object to a map called Objects with the name Bob. (This Leaf business sounds ugly but it could be cleaned up.)

Cool. But this statement is a little harder for me: Bob.jump();

1)Search some map of objects for "Bob". Return some object implementing an interface with a method like "evaluate(String s)" and pass it the string "jump()"
2)Bob searches some internal map of methods for "jump()", then...? In c++ I would have the key be a pointer to the member function Monster.jump() which would be executed. But there is no such thing as a function pointer in java I don't believe. I have read you can use an anonymous class to accomplish this, though I haven't tried. Looks like it would work.

So, this will work, but is there a more elegant way to go about this? I've never written any type of interpreter before. I'd like to do it a nice way and learn something in the process if somebody has some tips. This seems like a potentially error prone way to do things if I'm not very structured, especially when Bob and every other object start parsing their own instructions and using anonymous functions. In addition, it looks like every class will need a runtime-ready interface besides its normal code.

I also don't know Java all that well, so if there are some spots where I might hit a brick wall, then I'd like to know too.

Thanks for the help in advance.

like image 851
user487100 Avatar asked Dec 12 '22 09:12

user487100


2 Answers

I would actually suggest using Python -- unless there is a really good reason not to.

This is because:

  1. Python has a really nice IDE/REPL called IDLE. I can't say enough about using a good Read-Eval-Print-Loop: the short feedback cycle is very good for learning/playing. Adventurous students might even jump right in!
  2. Graphics support is cross-platform and well-supported via TkInter.
  3. I find it a better language for beginners and/or non-programmers than Java. (Python actually is not my favorite language, but it is very beginner-friendly and again, has a very nice IDE/REPL.)
  4. It is much less work for you ;-)

This is how the Python code for the demo might look:

Bob = BigMonster()
Bob.jump()
dir(Bob)
dir(Monters)

Since all of this is just regular Python syntax there is no parsing -- just create a few classes, perhaps implement the __dir__ protocol, and everything is ready to go. If Java integration is a requirement there is also Jython, although I have never tried that with IDLE (or know if it's supported as such).

Happy coding.

An Image-based SmallTalk such as Sqeak is far more interactive than Python as the code is part of the persistent running environment. However, it takes some time to find a good image -- Squeak is hardly the best implementation, but it is free -- and learn the particular SmallTalk environment. Thus, while the integration can ultimately have big payouts it does take more acclimatization :)


But, alas, to pursue a simple parser in Java, these will be of interest:

  1. A lexer which turns the input text into a steam of tokens, and;
  2. And a recursive descent parser (this is a really easy parsing approach) which either
    1. Builds an AST (Abstract Syntax Tree) which can be walked (read: "run") later, or;
    2. Or "does stuff" right now (immediate evaluation)

A Simple Recursive Descent Parser is a Java crash-course introduction to the concepts above. Here is some code for a recursive-descent parser for "neutrino syntax", whatever that is -- look at the comments and how well a recursive-descent parser can match the EBNF grammar.

Now, it's "just" a matter of defining the semantic rules of this pseudo/mini-language and implementing it ;-)


Exploring the semantics/Java approach a little bit more (parts are just a simplification/re-statement of the original post):

CREATE Monster Bob

Would create a new MonsterObject. Some approaches might be:

  1. Create the object with reflection, or;
  2. a map of Factory classes (from String -> FactoryObject) as talked about, or;
  3. a simple static if-else-branch.

The result would be stored in in a "variable hash" which maps Name -> MonsterObject.

Bob.jump()

Parse this to [object Bob] [method jump] [p1], [p2], ..., [pn], look up the object in the "variable hash" and then:

  1. Use reflection to invoke a method, or;
  2. have a map (retrieved via a method of the MonsterObject) of Name -> MethodEvaluatorObject (e.g. has eval(Object ... params) method), or;
  3. call a method of the form eval(String action, String[] ... parameters) and have it use an if-else-branch to "do stuff" (note that the parameters, if any, are already separated out during the parsing).

LS Bob and LS Monster rely a good bit on how the previous two are implemented.

While Java does not have "function pointers", they can be emulated through the use of objects with a given interface (that is, the objects themselves function as the pointers). Functional Java has F/F2/.../F8 classes to attempt to handle this uniformly with generics. However, in Java there is usually a separate one-off interface (or class) created like Runnable with a single "action" method that is modified to accept the appropriate parameters and return the appropriate result (such as the MethodEvaluatorObjects or FactoryObjects).

If there are any specific questions about one of the topics (reflection, recursive descent, anonymous types, [emulated] closures, etc.), then feel free to ask another SO question with a specific focus. (And, as always, due-diligence in research pays off ;-)

like image 188
11 revsuser166390 Avatar answered Jan 08 '23 11:01

11 revsuser166390


If you really not going to build a new programming language, you may just split commands into parts (using space as separator) and then perform a lookup for the first part: CREATE Monster Bob; => create, monster, bob:

String operation = parts[0];
if(operation.equals(`create`)) {
  String type = parts[1];
  String name = parts[2];
  // your logic here
} else if(operation.equals(`...`)) {
  ...
}
like image 33
Andrey Agibalov Avatar answered Jan 08 '23 10:01

Andrey Agibalov