Scripting our way to a better tomorrow.

Published February 20, 2007
Advertisement
Topnote: I know that I really hate it when developers write really dry jargon-infused entries, and I like to think that I've been pretty good about. I must warn you however, this is one of them. [smile]


Today mainly saw development work on my game-oriented scripting language, GameScribe. This is mainly because after a nice lengthy Computer Science assignment in Scheme, my mind refused to go back to Membrane Massacre. Better name still pending.

Feature-wise, I implemented both single-line and multi-line comments, the latter supporting nesting. Normal Scheme (which the language is modeled after) only supports single-line comments (via ;'s), but I'm a fellow who likes his multi-line comments. Here's what a sample script looks like with them:

!..... Program: First GameScribe script ever... Author: Stephen Whitmore.. Purpose: To show (hopefully!) that the GameScribe interpreter works :)!. Aaaah! Nested! .!...!(define a (+ 9 10))(define b a)(define say_hello "Hello world!")(print a ", " b)(print (+ a b))(print (<= (pow a 5) (* 2 b)))  ;; This line isn't confusing. At all!(print "This concludes the first ever GameScribe script. (" say_hello ")")(print);; Whew, all done!


Nothing that fancy. I'm sure "!." and ".!" look weird as comment header/footers, so I'm open to ideas.

Additionally, several nastier bugs were cleaned up. One of which is that I was sillily (word?) forgetting to recursively identify identifiers (read: unknown 'token' in the script) recursively, meaning if I had "(define a 12)" and "(define b a)" and wrote "(print b)" it would spit out "$a", which is the notation for some unknown identifier. With that, it seems that running scripts from disk works just fine now.

The other burden was getting some in-source documenting done. My last clean-up session was making the interpreter object-oriented, and today's fun was no less exciting. [smile] As a result though, I've refamiliarized myself with the code again, and fixed some inconsistancies.

Next on the list is user-defined functions, and proper variable scope. I've decided not to implement functions like Scheme does (as lists, technically), or even have true lambdas. Functionality > complexity for this scripting language. [smile]

Despite the dry tone of this entry, I'm actually really excited about GameScribe. (I blame the tiredness!) Writing your own programming language is a really fun exercise that I recommend to anyone willing to give it a shot. I think just about every programmer who is here has wanted to make their own language their way. I urge you to do so -- it's certainly worth it!
Previous Entry Hello GD.NET world!
0 likes 15 comments

Comments

Ravuya
Yay! I want to use it. [grin]
February 21, 2007 12:47 AM
Aardvajk
Great job, HD.

When you've implemented functions, you should add Allegro-interfacing functionality, then we could write entire games in it.
February 21, 2007 02:35 AM
Stagz
Looks somewhat like LISP with all them there brackets, but pretty good for a roll-yer-own scripting language.

I do like the [Verb][Noun]* notation somewhat for scripting. Makes it easier to add in new commands at a whim. As an added bonus, your code becomes somewhat alien looking..

(> 0 (sum (mul wow_factor 1000) (- readability) (?! omg_wtf))).

Edit: On a second look very similar to LISP

Edit again: Please excuse my ignorance. I pretty much just read the code (I read code better than prose when my eyes are tired). I realise now that it is based on Scheme which (after some reading) I also realise is similar to Lisp.
February 21, 2007 03:55 AM
Ravuya
It should look similar to lisp; it's scheme.

I don't find lisp/scheme to be unreadable; they are pretty much the abstract syntax tree for a user-defined language.
February 21, 2007 06:35 AM
Todo
Cool! One of the few courses that actually had my attention in my second year at uni was Scheme as a functional programming language. But admittedly, this book had something to do with that. Believe it or not, depending on the project, I like to use a Lisp dialect as the scripting language.
February 21, 2007 07:30 AM
HopeDagger
Quote:Original post by Anonymous Poster
Corner case. What about starting a multi-line comment, running it to the end of the source file and not closing it? ;)


I briefly considered this, and luckily it will just reach the end within the comment and gracefully assume the script is over. All expressions before the comment's start will sill work properly. [smile]


Quote:Original post by EasilyConfused
Great job, HD.

When you've implemented functions, you should add Allegro-interfacing functionality, then we could write entire games in it.


Thanks! Admittedly I was thinking about writing a small graphics/game library for it, but I'd definitely write my own layer above Allegro for access. Allegro's macro-laden 'organization' is nothing near clean, hehe. [grin]


Quote:Original post by Todo
Cool! One of the few courses that actually had my attention in my second year at uni was Scheme as a functional programming language. But admittedly, this book had something to do with that. Believe it or not, depending on the project, I like to use a Lisp dialect as the scripting language.


Aye, definitely opt for the Scheme course. I found it very unwieldy initially, but the way that the language is so compact (well, at least the foundations are :P) and clearly defined makes it such a nice model to work with, and has helped me understand programming languages in general a lot better. Best of luck!
February 21, 2007 08:45 AM
Jotaf
Have you considered having special code for handling regular mathematical expressions? I know, it's great that math functions are nothing more than any other function, but still, the code readability increases 10-fold, and it isn't that much more work (believe me, I rewrote that like 5 times from scratch for all my scripting language projects :P and didn't break a sweat) :)
February 21, 2007 10:51 AM
HopeDagger
Quote:Original post by Jotaf
Have you considered having special code for handling regular mathematical expressions? I know, it's great that math functions are nothing more than any other function, but still, the code readability increases 10-fold, and it isn't that much more work (believe me, I rewrote that like 5 times from scratch for all my scripting language projects :P and didn't break a sweat) :)


I know it wouldn't be hard to do, but I'm interested in keeping the clean and clear Scheme model in-tact. To do so would break that conistency that I originally planned. I agree that it would add more readability (some of those Scheme expressions still scare me :P), but the cost is too steep IMHO.

You mentioned you have some scripting languages of your own written? Nifty -- anything on the 'net I can check out. I'm always interested in seeing how other people implemented theirs. [smile]
February 21, 2007 11:11 AM
Ravuya
Haskell has a little hack that allows you to change prefix operators to infix operators. Might be a neat idea, if you've got the time.
February 21, 2007 11:13 AM
Aardvajk
Quote:Original post by HopeDagger
I'm always interested in seeing how other people implemented theirs. [smile]


Hope you meant that [smile].

My game script, invented to provide scripting for Udo but very multipurpose, uses a virtual machine and has a seperate compiler that turns the actual script into virtual assembly for fairly efficient execution.

The current language is more like C than LISP or Scheme, although it supports declare-anyway locals like C99 and reference parameters like C++. Looks a bit like:


f(int &a)
{
    a=a*(-2+6)/((3*-a)-1);
}

string g(int z)
{
    if(z==10) return "hello";
    else if(z==9)
        {
        int b=10;
        while(b>0)
            {
            out "looping - ",b,"\n"; b=b-1;
            }
        }

    return "goodbye";
}

proc 0 // this is one of potentially many entry points
{
    out g(10),"\n",g(9),"\n";
}

proc 1
{
    int x=10; f(x); out x,"\n";
}







A couple of things I like about the seperate vm/compiler approach are that:

a) the bit you need to distribute is a lot harder to view or modify by an end-user
b) you can invent an entirely new language and compiler to generate executable files for the same virtual machine

Also, the virtual machine which needs to form part of the game which uses it is suprisingly simple, with about 40 instructions supported. Most of the complexity is moved over to the compiler which keeps the complexity out of the game code base.

This is the entire instruction set for the current vm:


namespace ic
{
	enum code { end,call,ret,svc,
             		        setrx,setax,
				movra,movar,movrs,
				getrg,putrg,
				get,put,
				mpush,mpop,
				push,popn,poke,peek,
				add,sub,mul,div,neg,
				eq,neq,lt,lteq,gt,gteq,
				land,lor,lnot,
				jmp,jz,
				outn,outs,outb };
}
February 21, 2007 03:26 PM
HopeDagger
@Easily: How long did it take you whip all of that up? As I recall, it seemed nearly overnight that you had a fully fledged assembly-like language + VM ready to go for Udo.

Additionally, how do you manage communication/access between the script and the actual C++ program? Your language is already far more feature-complete than mine. [smile]
February 21, 2007 04:23 PM
Aardvajk
Quote:Original post by HopeDagger
@Easily: How long did it take you whip all of that up? As I recall, it seemed nearly overnight that you had a fully fledged assembly-like language + VM ready to go for Udo.

Additionally, how do you manage communication/access between the script and the actual C++ program? Your language is already far more feature-complete than mine. [smile]


Well, for this one about 24 hours, but I spent a couple of years working on stuff like this back when I only had a 286 so got engrossed in writing language compilers, so most of it was rehashing work I'd done before.

My interaction with programs is quite basic. You provide the vm with a pointer to a "service" function. Within the language, you have access to 32 integer registers plus a special "service" function. You put data in the registers then call service(23) or whatever. The service function is then called in the owning application and has access to the registers.

For example, Udo's service function supports a service to return the x and y of the player via code 4 (or something), so the service function looks a bit like:


bool Service(int Code,int *Regs,CImp &Imp)
{
    switch(Code)
        {
        /* ... */

        case 4: Regs[0]=PC->GetX(); Regs[1]=PC->GetY(); break;

        /* ... */
        }

    return true;
}


then that can be wrapped in a function in the script like:


getplayerxy(int &x,int &y)
{
    service(4); x=register(0); y=register(1);
}


Obviously you can pass information into the service function via the registers as well. In fact, if you cast a string address in the script to a register, then use that in the service function to query the vm's TextAt() method, you can even pass strings out of the vm.

Only thing you can't do is pass strings back in, since the data segment of the vm's memory is static.

The system works quite well since for a new game, you just implement the service function in your game code, then write an Imp script header that wraps the service calls into more meaningful functions.

Not quite as flexible as scripts that let you call arbitrary functions in the owning application, but I couldn't figure out how to pass parameters like that.
February 21, 2007 04:47 PM
Jotaf
EasilyConfused: It's always cool to know how other people make their scripting languages, actually I already knew that since I follow your journal as well!

The service/register method seems like a nice trade-off between easy implementation and flexibility, so I'll remember that :)


HopeDagger: I don't have anything on the net, unfortunately. The first projects were too newbish and ridden with hacks, and the latest were just too advanced (several thousands of lines of code).


Just in case you're interested, a brief list of my projects :)

The first one was a "trigger" editor, similar to the one for Starcraft. It was coded in Visual Basic. I was very proud of it, since I was just a kid and managed to emulate a feature from my favourite game ;)

The second one was a bit more serious; also in VB, it compiled to bytecode, and supported math expressions (I kinda came up with a stack on my own), calling functions, control and moving numbers around.

Then I did about the same thing in C++, with cleaner code of course, and went as far as actual integration into a game, object-orientedness and all that fluff. One of the neatest features was named arguments.

I also made a smaller interpreter for an AI project. It was pretty cool, as it was closely tied to a complex finite state-machine. Another one, part of on-going research in the compiler subject, is a different programming paradigm on its own; we'll see how that turns out.
February 22, 2007 08:52 AM
Todo
Quote:Original post by HopeDagger
Aye, definitely opt for the Scheme course. I found it very unwieldy initially, but the way that the language is so compact (well, at least the foundations are :P) and clearly defined makes it such a nice model to work with, and has helped me understand programming languages in general a lot better. Best of luck!


I think I put it wrong there, I'm close to graduating. My second year is now years behind me ;-).
February 23, 2007 07:40 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement