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.
I would actually suggest using Python -- unless there is a really good reason not to.
This is because:
- 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!
- Graphics support is cross-platform and well-supported via TkInter.
- 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.)
- 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:
- A lexer which turns the input text into a steam of tokens, and;
- And a recursive descent parser (this is a really easy parsing approach) which either
- Builds an AST (Abstract Syntax Tree) which can be walked (read: "run") later, or;
- 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:
- Create the object with reflection, or;
- a map of Factory classes (from String -> FactoryObject) as talked about, or;
- 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:
- Use reflection to invoke a method, or;
- have a map (retrieved via a method of the MonsterObject) of Name -> MethodEvaluatorObject (e.g. has
eval(Object ... params)
method), or;
- 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 ;-)
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(`...`)) {
...
}
Have you considered using a parser generator like ANTLR? It can produce parsers for many kinds of languages and output the parser in a variety of languages including Java. It could speed up your task considerably and the software is free (although the books are for sale, but hey, your time is worth something, right?).
http://en.wikipedia.org/wiki/ANTLR
On the other hand, you could probably roll your own parser for a simple language like PST is talking about, but I wouldn't overcomplicate it. Just make yourself a function to break up the file into string tokens (lexer), and another that requests a token at a time and determines what to do with it. If your language is simple, that might be enough.