🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Musings on Epoch

Published July 19, 2008
Advertisement
I decided to bite the bullet and go ahead and begin rudimentary syntax work. This is for a couple of reasons.

First, I'm running out of things to add to the language without getting into highly nontrivial areas like type theory, dynamic memory management, and so on. Second, it's such a pain to do anything - trivial or not - in the current setup. The VM code is designed to have a parser front-end plugged into it; and although it works fine to "write" Epoch programs within the VM itself, it's a righteous pain in the ass to do so.

Therefore, I'm digging out boost::spirit and trying to recollect all my old parser theory. The goal is to get a parser working that can correctly read a simple Epoch program and then feed it into the VM for execution.

I'm going to be doing this in two phases. First will be a very stripped-down core of the syntax, which uses function-call syntax for everything. The second phase will be introducing syntactic sugar that turns the language into something more closely resembling C.


Here's the guessing game, written in the first syntax mode I came up with:

entrypoint : () -> (){	string(inputstring, "")	int(inputnumber, 0)	int(randomnumber, 0)	boolean(quit, false)	assign(randomnumber, random(99))	assign(randomnumber, randomnumber + 1)	debugwritestring("Guess my secret number... it's between 1 and 100.")	do	{		assign(inputstring, debugreadstring())		assign(inputnumber, lexicalcast(int, inputstring))		if(equal(inputnumber, 0))		{			assign(quit, true)		}		else		{			if(less(inputnumber, randomnumber))			{				debugwritestring("Too low! Try again.")			}			if(greater(inputnumber, randomnumber))			{				debugwritestring("Too high! Try again.")			}			if(equal(inputnumber, randomnumber))			{				debugwritestring("You're right! You win!")				assign(quit, true)			}		}	}	while(notequal(quit, true))}



As you can see, it's drawing a fair bit of inspiration from C, but has lots of minor differences (like no semicolons). The uniform function-call syntax is designed for a simple, pragmatic purpose: it keeps the first-phase parser minimally simple, while mapping each operation's ID string directly to an Operation class in the VM.

One thing I'd like to point out is the lexicalcast function. Its first parameter is a type ID, not a variable ID; this is similar to template parameters in C++, but I'm taking them a step further - it is actually legal to pass a type to a function rather than simply a variable of that type. This allows for some powerful alternatives to function overloading, as well as opening up all kinds of metaprogramming options.

This is a foretaste of something I really want to get into the language eventually, which is the ability to pass functions, operators, even entire blocks of code around as objects. These "metaobjects" can be used for some truly awesome code generation that draws heavily from Lisp's macro concept.

Anyways, back to parsing.

What will happen in the second parser phase is pretty straightforward; there will actually be a preprocessor that converts sugar-laced syntax to this raw, "pure" syntax. My goal is to make the preprocessor open ended so that entirely new forms of syntax can be introduced. That means there's no requirement that one Epoch program use anything remotely like the syntax of another Epoch program. This in turn solves the high-priority goal of being easy to adapt to domain-specific uses.

So what does this sugarized syntax look like? Here's a rough version:
entrypoint : () -> (){	string(inputstring, "")	int(inputnumber, 0)	int(randomnumber, 0)	boolean(quit, false)	randomnumber = random(99)	randomnumber = randomnumber + 1	debugwritestring("Guess my secret number... it's between 1 and 100.")	do	{		inputstring = debugreadstring()		inputnumber = lexicalcast(int, inputstring)		if(inputnumber == 0)			quit = true		else if(inputnumber < randomnumber)			debugwritestring("Too low! Try again.")		else if(inputnumber > randomnumber)			debugwritestring("Too high! Try again.")		else if(inputnumber == randomnumber)		{			debugwritestring("You're right! You win!")			quit = true		}	}	while(!quit)}


There are four principal additions made in this version of the syntax:
  1. Blocks of one line do not require braces

  2. Infix operators are permitted

  3. Special characters for operators/functions are permitted rather than only string IDs

  4. If/else blocks can be chained


So there you have it - the official first draft of Epoch syntax. (Actually, it's not really the first - I think I've even posted other drafts here in the past. But this one is the first that I'm actually going to bother trying to parse.)

Depending on how long it takes for me to get my brain wrapped around boost::spirit, I should probably have a rudimentary phase-one parser ready in a couple of weekends. At that point, it will be time to stitch up the package, do some documentation and cleanup work, and make the first official open-source release of the Epoch language.

My secret personal goal is to have something ready to demo for potentially interested parties by next year's GDC. That gives me several months to get the parsers finished and polish up the VM, plus implement some more interesting features.


Call me a total nerd, but I'm really excited about this.
Previous Entry Hello world
Next Entry Mmmm, boost::spirit
0 likes 1 comments

Comments

Jotaf
What would be really awesome would be having a native type "Code" and another native type "Type". That would make everything really clear, without using all the fancy words we love like metaprogramming and metaobjects :)

And by the way, this is coming along nicely!
July 20, 2008 04:14 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement