So I challenged myself to writing a BASIC intepreter over the weekend,
unfortunately I did not succeed.
What I did was take an existing monkey-repl and extend it with a series of changes to make sure that I understood all the various parts of the intepreter design.
Initially I was just making basic changes:
- Added support for single-line comments.
- For example "
// This is a comment
".
- Added support for multi-line comments.
- For example "
/* This is a multi-line comment */
".
- Expand
\n
and \t
in strings.
- Allow the index operation to be applied to strings.
- For example
"Steve Kemp"[0]
would result in S
.
- Added a type function.
- For example "
type(3.13)
" would return "float".
- For example "
type(3)
" would return "integer".
- For example "
type("Moi")
" would return "string".
Once I did that I overhauled the built-in functions, allowing
callers to register golang functions to make them available to their
monkey-scripts. Using this I wrote a simple "standard library" with
some simple math, string, and file I/O functions.
The end result was that I could read files, line-by-line, or even
just return an array of the lines in a file:
// "wc -l /etc/passwd" - sorta
let lines = file.lines( "/etc/passwd" );
if ( lines ) {
puts( "Read ", len(lines), " lines\n" )
}
Adding file I/O was pretty neat, although I only did reading. Handling looping
over a file-contents is a little verbose:
// wc -c /etc/passwd, sorta.
let handle = file.open("/etc/passwd");
if ( handle < 0 ) {
puts( "Failed to open file" )
}
let c = 0; // count of characters
let run = true; // still reading?
for( run == true ) {
let r = read(handle);
let l = len(r);
if ( l > 0 ) {
let c = c + l;
}
else {
let run = false;
}
};
puts( "Read " , c, " characters from file.\n" );
file.close(handle);
This morning I added some code to interpolate hash-values into a string:
// Hash we'll interpolate from
let data = { "Name":"Steve", "Contact":"+358449...", "Age": 41 };
// Expand the string using that hash
let out = string.interpolate( "My name is ${Name}, I am ${Age}", data );
// Show it worked
puts(out + "\n");
Finally I added some type-conversions, allowing strings/floats to be converted
to integers, and allowing other value to be changed to strings. With the
addition of a math.random
function we then got:
// math.random() returns a float between 0 and 1.
let rand = math.random();
// modify to make it from 1-10 & show it
let val = int( rand * 10 ) + 1 ;
puts( "math.random() -> ", val , "\n");
The only other signification change was the addition of a new form of
function definition. Rather than defining functions like this:
let hello = fn() { puts( "Hello, world\n" ) };
I updated things so that you could also define a function like this:
function hello() { puts( "Hello, world\n" ) };
(The old form still works, but this is "clearer" in my eyes.)
Maybe next weekend I'll try some more BASIC work, though for the moment
I think my monkeying around is done. The world doesn't need another
scripting language, and as I mentioned there are a bunch of implementations
of this around.
The new structure I made makes adding a real set of standard-libraries
simple, and you could embed the project, but I'm struggling to think of
why you would want to. (Though I guess you could pretend you're embedding
something more stable than anko and
not everybody loves javascript as
a golang extension language.)
Tags: golang, intepreters, monkey
|