Giter Site home page Giter Site logo

monkey's Introduction

Go Report Card license Release

Monkey

This repository contains an interpreter for the "Monkey" programming language, as described in Write an Interpreter in Go.

My changes

The interpreter in this repository has been significantly extended from the starting point:

  • Added single-line & multi-line comments.
  • Added postfix operators (i++, i--).
  • Allow accessing individual characters of a string via the index-operator.
  • Added a driver to read from STDIN, or a named file, rather than a REPL.
    • This allows executing the examples easily (for example "./monkey examples/hello.mon".)
  • Added a collection of standard-library functions.
    • Including file input/output, type-discovery, string, and math functions.
  • Added a new way to define functions, via function.
  • Added the general-purpose comparision functions <= & >=.
  • Allow string comparisons via ==, !=, <=, & >=.
  • Allow comparisions to be complex:
    • if ( a >= 'a' && a <= 'z' ) ..
    • if ( a || b ) ..
  • Allow assignments without let.
    • This also allows operators such as "+=", "-=", "*=", & "/=" to work.
  • Added command-line handling, so that scripts can read their own arguments.
  • Added global-constants available by default
    • For example PI, E, STDIN, STDOUT, & STDERR.
  • Most scripts will continue running in the face of errors.
    • To correct/detect "obvious" errors add pragma("strict"); to your script, which will cause the interpreter to show a suitable error-message and terminate.
  • Function arguments may have defaults. For example:
    • function greet( name = "World" ) { puts("Hello, " + name + "\n"); }
  • Moved parts of the standard-library to 100% pure monkey, rather than implementing it in go.
  • Added the eval function.
    • Which allows executing monkey-code from a string.
  • Improved error-reporting from the parser.
    • It will now show the line-number of failures (where possible).
  • Added support for regular expressions, both literally and via match
    • if ( name ~= /steve/i ) { puts( "Hello Steve\n"); }
  • Added support for ternary expressions.
  • Added support for creating arrays of consecutive integers via the range operator (1..10).
  • Added the ability to iterate over the contents of arrays, hashes, and strings via the foreach statement.
  • Added printf and sprintf primitives, which work as you would expect.
    • printf( "%d %s", 3, "Steve" );
  • Added support for switch statements, with block-based case expressions.
    • No bugs due to C-style "fall-through".
  • Add support for explicit null usage:
    • a = null; if ( a == null ) { .. }

See Also

If you enjoyed this repository you might find the related ones interesting:

Finally I put together a couple of "complex" compilers, which convert input into AMD64 assembly language:

1. Installation

Due to the embedded standard-library implementation, which is implemented in monkey, you'll need to compile this project with go version 1.16beta1 or higher.

You can install from source like so:

git clone https://github.com/skx/monkey
cd monkey
go install

Binary Releases

Alternatively you could install a binary-release, from the release page.

If you're an emacs user might also wish to install the monkey.el file, which provides syntax highlighting for monkey-scripts.

1.1 Usage

To execute a monkey-script simply pass the name to the interpreter:

 $ monkey ./example/hello.mon

Scripts can be made executable by adding a suitable shebang line:

 $ cat hello.mon
 #!/usr/bin/env monkey
 puts( "Hello, world!\n" );

Execution then works as you would expect:

 $ chmod 755 hello.mon
 $ ./hello.mon
 Hello, world!

If no script-name is passed to the interpreter it will read from STDIN and execute that instead, allowing simple tests to be made.

2 Syntax

NOTE: Example-programs can be found beneath examples/ which demonstrate these things, as well as parts of the standard-library.

2.1 Definitions

Variables are defined using the let keyword, with each line ending with ;.

  let a = 3;
  let b = 1.2;

Variables may be integers, floats, strings, or arrays/hashes (which are discussed later).

Some variables are defined by default, for example:

puts( PI ); // Outputs: 3.14159..
puts( E );  // Outputs: 2.71828..

Variables may be updated without the need for let, for example this works as you would expect:

let world = "Earth";
world = "world";
puts( "Hello, " + world + "!\n");

If you're not running with pragma("strict"); you can also declare and use variables without the need for let, but that should be avoided as typos will cause much confusion!

 name = "Steve";
 puts( "Hello, " + name + "\n");

2.2 Arithmetic operations

monkey supports all the basic arithmetic operation of int and float types.

The int type is represented by int64 and float type is represented by float64.

   let a = 3;
   let b = 1.2;

   puts( a + b  );  // Outputs: 4.2
   puts( a - b  );  // Outputs: 1.8
   puts( a * b  );  // Outputs: 3.6
   puts( a / b  );  // Outputs: 2.5
   puts( 2 ** 3 ) ; // Outputs: 8

Here ** is used to raise the first number to the power of the second. When operating with integers the modulus operator is available too, via %.

2.3 Builtin containers

monkey contains two builtin containers: array and hash.

2.3.1 Arrays

An array is a list which organizes items by linear sequence. Arrays can hold multiple types.

 let a = [1, 2.3, "array"];
 let b = [false, true, "Hello World", 3, 3.13];

Adding to an array is done via the push function:

 let a = push(a, "another");

You can iterate over the contents of an array like so:

 let i = 0;
 for( i < len(a) ) {
    puts( "Array index ", i, " contains ", a[i], "\n");
    i++
 }

With the definition we included that produces this output:

 Array index 0 contains 1
 Array index 1 contains 2.3
 Array index 2 contains array
 Array index 3 contains another

As a helper you may define an array of consecutive integers via the range operator (..):

 let a = 1..10;

2.3.2 Hashes

A hash is a key/value container, but note that keys may only be of type boolean, int and string.

let a = {"name":"monkey",
         true:1,
         7:"seven"};

puts(a); // Outputs: {name: monkey, true: 1, 7: seven}

puts(a["name"]); // Outputs: monkey

Updating a hash is done via the set function, but note that this returns an updated hash - rather than changing in-place:

let b = set(a, 8, "eight");
puts(b);  // Outputs: {name: monkey, true: 1, 7: seven, 8: eight}

You can iterate over the keys in a hash via the keys function, or delete keys via delete (again these functions returns an updated value rather than changing it in-place).

Hash functions are demonstrated in the examples/hash.mon sample.

2.4 Builtin functions

The core primitives are:

  • delete
    • Deletes a hash-key.
  • int
    • convert the given float/string to an integer.
  • keys
    • Return the keys of the specified array.
  • len
    • Yield the length of builtin containers.
  • match
    • Regular-expression matching.
  • pragma
    • Allow the run-time environment to be controlled.
    • We currently support only pragma("strict");.
  • push
    • push an elements into the array.
  • puts
    • Write literal value of objects to STDOUT.
  • printf
    • Write values to STDOUT, via a format-string.
  • set
    • insert key value pair into the map.
  • sprintf
    • Create strings, via a format-string.
  • string
    • convert the given item to a string.
  • type
    • returns the type of a variable.

The following functions are also part of our standard library, but are implemented in 100% pure monkey:

  • first
    • yield the first element of array.
  • last
    • yield the last element of array.
  • rest
    • yield an array which excludes the first element.

2.4.1 The Standard Library

In addition to the core built-in functions we also have a minimal-standard library. The library includes some string/file primitives, a regular-expression matcher, and some maths-helpers.

You can see the implementation of the go-based standard-library beneath evaluator/stdlib*, and several of these functions are documented in the various examples/.

NOTE: Parts of our standard-library are implemented in 100% pure monkey, and these are embedded in our compiled interpreter. The source of the functions can be viewed in data/stdlib.mon.

If you wish to make changes to the monkey-based standard-library you'll need to rebuild the interpreter after making your changes, to ensure they are bundled into the executable.

Nothing special is required, the following will suffice as you'd expect:

go build .

2.5 Functions

monkey uses fn to define a function which will be assigned to a variable for naming/invocation purposes:

let add = fn(a, b) { return a + b;};
puts(add(1,2));  // Outputs: 3

// functions can be used via their variables
let addTwo = fn(a,b, f) { return 2 + f(a, b);};
puts( addTwo(1,2, add) ); // outputs: 5.

It is also possible to define a function without the use of let, via the function keyword. This was added to make the language feel more natural to C-developers:

function hello() { puts "Hello, world\n" ; };
hello();   // Outputs: Hello, world" to the console.

You may specify a default value for arguments which are not provided, for example:

let foo = fn( name = "World!") {
  puts( "Hello, " + name + "\n" );
};

foo();
foo( "Steve" );

This will output what you expect:

Hello, World!
Hello, Steve

The same thing works for literal functions:

// Function with a default (string) argument
function meh( arg = "Steve" ) {
  puts( "Argument:", arg, " has type:", type(arg), "\n");
};

// Call it with no argument and the default will be used.
meh();

// But of course all the rest work just fine.
meh( 1 );
meh( 1/3.0 );
meh( "Steve" );
meh( [1,2,3,4] );
meh( {"Steve":"Kemp", true:1, false:0, 7:"seven"} );

2.6 If-else statements

monkey supports if-else statements.

let max = fn(a, b) {
  if (a > b) {
    return a;
  } else {
    return b;
    }
};

puts( max(1, 2) );  // Outputs: 2

2.6.1 Ternary Expressions

monkey supports the use of ternary expressions, which work as you would expect with a C-background:

function max(a,b) {
  return( a > b ? a : b );
};

puts( "max(1,2) -> ", max(1, 2), "\n" );
puts( "max(-1,-2) -> ", max(-1, -2), "\n" );

Note that in the interests of clarity nested ternary-expressions are illegal!

2.7 Switch Statements

Monkey supports the switch and case expressions, as the following example demonstrates:

  name = "Steve";

  switch( name ) {
    case /^steve$/i {
       printf("Hello Steve - we matched you via a regexp\n");
    }
    case "St" + "even" {
       printf("Hello SteveN, you were matched via an expression\n" );
    }
    case 3 {
       printf("Hello number three, we matched you literally.\n");
    }
    default {
       printf("Default case: %s\n", string(name) );
    }
  }

See also examples/switch.mon.

2.8 For-loop statements

monkey supports a golang-style for-loop statement.

 let sum = fn(x) {
    let i = 1;
    let sum = 0;

    for (i < x) {
       sum += i;
       i++;
    }
    return sum;
 };

 puts(sum(100));  // Outputs: 4950

2.8.1 Foreach statements

In addition to iterating over items with the for statement, as shown above, it is also possible to iterate over various items via the foreach statement.

For example to iterate over an array:

 a = [ "My", "name", "is", "Steve" ]
 foreach item in a {
      puts( "\t",  item , "\n");
 }

Here you see that we've iterated over the items of the array, we can also see their offsets like so:

 foreach offset, item in a {
      puts( offset, "\t",  item , "\n");
 }

The same style of iteration works for Arrays, Hashes, and the characters which make up a string. You can see examples of this support in examples/iteration.mon.

When iterating over hashes you can receive either the keys, or the keys and value at each step in the iteration, otherwise you receive the value and an optional index.

2.9 Comments

monkey support two kinds of comments:

  • Single-line comments begin with // and last until the end of the line.
  • Multiline comments between /* and */.

2.10 Postfix Operators

The ++ and -- modifiers are permitted for integer-variables, for example the following works as you would expect showing the numbers from 0 to 5:

let i = 0;
for ( i <= 5 ) {
   puts( i, "\n" );
   i++;
}

Another feature borrowed from C allows variables to be updated in-place via the operators +=, -=, *=, & /=.

Using += our previous example could be rewritten as:

let i = 0;
for ( i <= 5 ) {
   puts( i, "\n" );
   i += 1;
}

The update-operators work with integers and doubles by default, when it comes to strings the only operator supported is +=, allowing for a string-append:

let str = "Forename";
str += " Surname";
str += "\n";
puts( str );           // -> "Forename Surname\n"

2.11 Command Execution

As with many scripting languages commands may be executed via the backtick operator (``).

  let uptime = `/usr/bin/uptime`;

  if ( uptime["exitCode"] == 0 ) {
      puts( "STDOUT: ", uptime["stdout"].trim() , "\n");
  } else {
      puts( "An error occurred while running the command: ", uptime["stderr"].trim(), "\n");
  }

The output will be a hash containing the keys stdout, stderr, and exitCode, as demonstrated in examples/exec.mon.

2.12 Regular Expressions

The match function allows matching a string against a regular-expression.

If a match fails NULL will be returned, otherwise a hash containing any capture groups in the match.

This is demonstrated in the examples/regexp.mon example.

You can also perform matching (complete with captures), with a literal regular expression object:

if ( Name ~= /steve/i ) { puts( "Hello Steve\n" ); }
if ( Name !~ /[aeiou]/i ) { puts( "You have no vowels.\n" ); }

// captures become $1, $2, $N, etc.
ip = "192.168.1.1";
if ( ip ~= /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/  ) {
    printf("Matched! %s.%s.%s.%s\n", $1, $2, $3, $4 );
}

2.13 File I/O

The open primitive is used to open files, and can be used to open files for either reading, or writing:

// Open a file for reading
fh = open( "/etc/passwd" );
fh = open( "/etc/passwd", "r" );

// Open a file for writing
fh = open( "/tmp/blah", "w" );

// Open a file for appending
fh = open( "/tmp/blah", "wa" );

Once you have a file-object you can invoke methods upon it:

  • read()
    • Read a line of input, returning that input as a string.
  • readlines()
    • Read the lines of the given file, and return them as an array.
  • write(data)
    • Write the data to the given file.

These are demonstrated in the following examples:

By default three filehandles will be made available, as constants:

  • STDIN
    • Use for reading STDIN.
  • STDOUT
  • STDERR
    • Used for writing messages.

2.14 File Operations

The primitive stat will return a hash of details about the given file, or directory entry.

You can change the permissions of a file via the chmod function, but note that the second argument is an octal string:

chmod( "/tmp/evil.sh", "755")
chmod( "/tmp/normal", "644")

To remove a file, use unlink:

unlink( "/tmp/trash.txt" )

And finally to make a directory:

mkdir( "/tmp/blah" );

3. Object Methods

There is now support for "object-methods". Object methods are methods which are defined against a type. For example all of our primitive types allow a methods() method, which returns the methods which are available against them.

Similarly each of them implement a type() function which returns the type involved:

let i = 1;
puts( i.type() );

let s = "Steve";
puts( s.type() );

Or even:

puts( "Steve".type() );

Seeing methods available works as you would expect:

a = [ "Array", "Is", "Here" ];

let i = 0;
for ( i < len(a.methods() ) ) {
   puts( "Method " + a.methods()[i] + "\n" );
   i++;
}

This shows:

Method find
Method len
Method methods
Method string

The string object has the most methods at the time of writing, but no doubt things will change over time.

3.1 Defininig New Object Methods

The object-methods mentioned above are implemented in Go, however it is also possible to define such methods in 100% monkey!

You can define a method via something like:

function string.steve() {
   puts( "Hello, I received '", self, "' as an argument\n" );
}

Note that the function has access to the object it was invoked upon via the implicit self name. Invocation would look as you expect:

let s = "Hello, world";
s.steve();   -> Hello, I received 'Hello, world' as an argument

You can see data/stdlib.mon implements some primitives in this fashion, for example the functional-programming methods array.map, array.filter, string.toupper, etc, etc.

Github Setup

This repository is configured to run tests upon every commit, and when pull-requests are created/updated. The testing is carried out via .github/run-tests.sh which is used by the github-action-tester action.

Releases are automated in a similar fashion via .github/build, and the github-action-publish-binaries action.

Fuzz Testing

Fuzz-testing involves creating random input, and running the program to test with that, to see what happens.

The intention is that most of the random inputs will be invalid, so you'll be able to test your error-handling and see where you failed to consider specific input things.

The 1.18 release of the golang compiler/toolset has integrated support for fuzz-testing, and you can launch it like so:

go test -fuzztime=300s -parallel=1 -fuzz=FuzzMonkey -v

Sample output looks like this:

$ go test -fuzztime=300s -parallel=1 -fuzz=FuzzMonkey -v
=== RUN   FuzzMonkey
fuzz: elapsed: 0s, gathering baseline coverage: 0/240 completed
fuzz: elapsed: 0s, gathering baseline coverage: 240/240 completed, now fuzzing with 1 workers
fuzz: elapsed: 3s, execs: 4321 (1440/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 6s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
cfuzz: elapsed: 9s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 12s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 15s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 18s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 21s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 24s, execs: 4321 (0/sec), new interesting: 6 (total: 246)
fuzz: elapsed: 27s, execs: 73463 (23060/sec), new interesting: 17 (total: 257)
fuzz: elapsed: 30s, execs: 75639 (725/sec), new interesting: 18 (total: 258)
fuzz: elapsed: 33s, execs: 125712 (16701/sec), new interesting: 25 (total: 265)
fuzz: elapsed: 36s, execs: 139338 (4543/sec), new interesting: 34 (total: 274)
fuzz: elapsed: 39s, execs: 173881 (11511/sec), new interesting: 49 (total: 289)
fuzz: elapsed: 42s, execs: 198046 (8055/sec), new interesting: 55 (total: 295)
fuzz: elapsed: 45s, execs: 210203 (4054/sec), new interesting: 75 (total: 315)
fuzz: elapsed: 48s, execs: 262945 (17580/sec), new interesting: 85 (total: 325)
fuzz: elapsed: 51s, execs: 297505 (11517/sec), new interesting: 108 (total: 348)
fuzz: elapsed: 54s, execs: 308672 (3722/sec), new interesting: 116 (total: 356)
fuzz: elapsed: 57s, execs: 341614 (10984/sec), new interesting: 123 (total: 363)
fuzz: elapsed: 1m0s, execs: 366053 (8146/sec), new interesting: 131 (total: 371)
fuzz: elapsed: 1m3s, execs: 396575 (10172/sec), new interesting: 137 (total: 377
...

Steve

monkey's People

Contributors

ckganesan avatar deadlysurgeon avatar gaufung avatar idigitalflame avatar josephzidell avatar paralax avatar skx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

monkey's Issues

Add regular expression support

This should work:

 if ( "Steve" ~= /steve/i ) {
       ..

We should also see:

  print( type( /foo/ ) ) ;   -> "regexp"

(So it is a distinct type, not a string.)

We need some kind of string-matching function.

Right now if we want to do a string-find / string-match we must use the index-operator to do it manually.

There are two ways to go here:

  • We could implement various string matching functions:
    • string.has_prefix( "Steve", "test" ) -> "false"
    • string.has_suffix "Steve", "eve" ) -> "true"
    • string.contains( "Hello, world!", "world" ) -> true
  • We could implement a regular expression matcher.
    • let result = steve =~ /e[vV]e/;

The regular expression method is probably cleaner, but we'd need to think about what it would return:

  • NIL on failure to match
  • A hash with captures, if any were present:

I'd expect:

 let ip = "10.1.2.234";
 let result = ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/;
 puts(result[0]) -> "10";
 puts(result[1]) -> "1";
 puts(result[2]) -> "2";
 puts(result[3]) -> "234";

But the use of the result there is a bit nasty. The only alternative would be to create a regexp-type so we could say:

     let regexp = "^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$";
     let result = match(regexp, "string input" );
     puts( result[0] );

That might make the most sense, but feedback welcome.

Allow a "strict" mode.

Currently built-in functions can return errors - primarily when they're invoked on objects of the wrong type.

For example calling len against a hash is an error:

 let a = { "Name": "Steve" };
 puts( len(a), "\n");

This will output:

 Error calling `len` : ERROR: argument to `len` not supported, got=HASH

If we had a strict-mode we could terminate the process at this point, similarly on the use of an undefined variable:

 let steve = "Foo";
 puts( steven );

I think we'll add use( strict ); or pragma( "strict" ); to enable this.

Crash with array.find

The following program contains a crash, which seems to be as a result of #61:

   a = [ "steve", "kemp" ];
   let out = a.find( "steve" );

String equality testing is broken.

Consider this code:

  let a = 4;
  let b = 4;

  if ( a == b ) {
    puts( "Integers are identical.\n");
  } else {
    puts( "Integers are different - this is a bug!!\n");
  }

  

  let a = "Steve";
  let b = "Steve";

  if ( a == b ) {
    puts( "Strings are identical.\n");
  } else {
    puts( "Strings are different - this is a bug!!\n");
  }

The output shows:

    deagol ~/go/src/github.com/skx/monkey $ ./monkey e.mon 
    Integers are identical.
    Strings are different - this is a bug!!

Allow "+=", "-=", etc.

Once #13 is implemented the following will work:

     let x = 4;
     x = x + 3;

However this will not:

        let x = 4;
        x += 3;

We need to handle +=, -=, *= and /=. No doubt there are other cases we could consider but they will be sufficient to make me happy.

Our string matching should be improved.

Right now you can't write this:

 let str = "\"";

(i.e. Trying to write a quote in a string will cause a syntax error.)

This will become important if we want to allow quotes in regular-expressions, as per #8.

Implement `foreach`

My evalfilter project uses foreach to iterate over:

  • arrays
  • characters in strings.

We can port that easily, and once done we'll also support iterating over hash key/value pairs.

Allow definition of class-methods.

We now have support for calling things like:

 "Steve".count("e");

We should allow the user to define similar methods in pure Monkey, rather than in go.

The initial case should allow:

 function TYPE.name( self );

Where TYPE is string, array, hash, etc. It might be I have to cheat and use ":" instead of "." to simplify life, but I could live with that. The important thing is that it is possible at all.

Allow method-calls on objects.

I suspect I went down the wrong-path when I started adding the abliity to run things like this:

 puts( strings.toupper( "steve" ) );

The idea of a standard-library is appealing, but really when we want to turn a string into a modified version we should probably allow that to happen via the object itself. So I'd prefer users to write:

  puts( "steve".toupper() );

Of course not everything is an object, but a similar approach could be applied to at least:

  • arrays
  • hashes
  • strings

Integers/Floats/Booleans will probably be left alone, and we'd keep the "standard library" for files/etc.

This will be a reasonably large change:

  • Allow parsing method-calls via a "."
    • e.g. "foo.bar()" is a method call to function "bar" with implicit arg "foo".
  • Remove much of the standard library.
    • e.g. strings.toupper, strings.tolower(), etc.

Fun times :)

If function parameters are missing we need to handle that.

This causes an ugly crash:

    function foo( name ) {
      puts( "Hello " + name  + "\n" );
    }
    foo();

(i.e. A parameter is expected but not found.)

The crash is here:

    panic: runtime error: index out of range

    goroutine 1 [running]:
    github.com/skx/monkey/evaluator.extendFunctionEnv(0xc42007a600, 0x0, 0x0, 0x0, 0x44013f)
    	/home/skx/go/src/github.com/skx/monkey/evaluator/evaluator.go:797 +0x19f
    github.com/skx/monkey/evaluator.applyFunction(0x548260, 0xc42007a600, 0x0, 0x0, 0x0, 0x0, 0x0)

We should just call len on the two parameter arrays and abort if one is too short. (Though that might need more care when implementing #16 )

Sorting an array of booleans fails ..

Calling this code led me on a chase:

 [ true, false, true ].uniq();

array.uniq does everything else, up to the point where it sorts the keys of the (temporary) hash it used.

The comparison makes no sense:

if ( true < false ) { ... }

So either :

  • Disallow this.
  • Fake an order (false comes first?)
  • Don't sort the array when the types are mixed/bogus.

Suspect there's no real right/wrong answer here. Just a choice to be made.

Allow command execution.

It might be nice to allow fork/exec/popen, etc, but they're a bit of work. (popen can probably be handled like fopen.)

For the moment I'd suggest we just implement backtick-execution:

  let out = `uptime`

  puts( out['STDOUT'] );
  puts( out['STDERR'] );
  puts( out['ERROR'] );

Allow declaring constants

Related to #19 we could move PI, & E, into the monkey-based init-file(s) if we had the ability to define constants, which couldn't be modified.

Something like:

const PI=3.1.4..

Instead of:

let PI=3.14..

This should be a simple matter of updating our environment.

Nested for-loops contain an error

This code works:

function array.sort() {
   let pass = 0;

   for( ! self.sorted?() ) {
      let i = 1;
      let l = len(self);
      for( i < l ) {
         if ( self[i] < self[i-1] ) {
           self = self.swap( i-1, i);
         }
         i++;
      }
      pass +=1;
   }
   return self;
 }

However remove the use of pass and it panics. Seems to be the return-value of the for looks is causing isseus - suspect it doesn't return a value and it should.

Allow our mutation operators to work on mixed types

We recently added mutation-operators in #14, allowing:

 let x = 4;
 x += 44;

However type-mixing isn't permitted, meaning this code fails:

  let y = 3;
  y += 3.14;

This fails because we only implemented +=, -=, *= and /= for integer types. We should allow floats, and float/int pairs to work. It probably also makes sense to allow string concatenation via "+=":

let s = "Steve";
s += " Kemp";
s += "\n";

We need to parse numbers better, to handle periods.

The following code works as you'd expect:

   let a = 3;
   let b = 3.312;

   puts( a.type() );
   puts( b.type() );

It outputs integer, then float. However this code fails:

  puts( 3.type() );
  puts( 3.14.type() );

The reason for the failure is that the lexer thinks we're seeing an invalid number, rather than stopping when it become obvious that is not the case.

   puts( 3.type() );
   -> no prefix parse function for ILLEGAL found

pragma function could be improved

Right now if you call pragma(), with no arguments, it returns the pragmas in-use.

If you give it an argument pragma("strict"); it returns true.

It should always return the enabled pragmas.

Add monkey-tests

If we added an assert method we could write some tests in the standard-library, and run them every time we launched.

(We used to test some methods in the evaluator-test-cases, but because they're loaded in package main, via static.go we had to remove them..)

Implement switch-statements

I want to allow this to succeed:


function test( name ) {
  switch( name ) {
    case /^steve$/i {
       printf("I'm a regexp\n");
    }
    case "Steven" {
	printf("I know you!\n" );
    }
    default {
	printf("I don't know who you are\n" );
    }
  }
}

test( "Steve" );    // Regexp match
test( "Steven" );   // Literal match
test( "Bob" );      // Unhandled case

NOTE: I explicitly use blocks here, because fall-throughs are evil :)

Allow file writing.

We can replicate read as write to allow writing to an arbitrary open handle.

Some of our standard library could be implemented in monkey ..

Specifically the first and rest functions could be implemented in pure-monkey, rather than C.

 function first( array ) {
   puts( "first() implemented in monkey\n");
   return( array[0] );
 }
 function rest( array ) {
   let result = [];

   if ( len(array) > 1 ) {
     let i = 1;
     for( i < len(a) ) {
        result = push(result, array[i]);
        i++;
     }
   }
   puts( "rest() is implemented in monkey!!!!\n");
   return result;
 }


 //
 // Test-code
 //
 let a = [ "Steve", "Kemp", "Kirsi", "Kemp" ];
 puts( "First element is ", first(a), "\n");

 a = rest(a);
 let i = 0;
 for( i < len(a) ) {
   puts( "Element ", i, " is " , a[i], "\n");
   i++;
 }

The output of this code is as-expected:

  frodo ~/go/src/github.com/skx/monkey $ ./monkey t.mon 
  first() implemented in monkey
  First element is Steve
  rest() is implemented in monkey!!!!
  Element 0 is Kemp
  Element 1 is Kirsi
  Element 2 is Kemp

Sadly it seems the rest of our functions are required to be implemented in C, but we could write and embed a standard-library of monkey-functions..

Functions/identifiers should allow digits in their names.

e.g. This should work:

function max(a,b) {
   return( a > b ? a : b );
}

function max2(a,b) {
    if ( a > b ) {
        return a;
    } else {
        return b;
    }
}

puts( "max(1,2) -> ", max(1, 2), "\n" );
puts( "max2(-1,-2) -> ", max2(-1, -2), "\n" );

Implement eval(str);

Current we have :

assert( bool, str );

Would be nice to evaluate the expression:

assert( str, str );

We could do that if we had eval

We should allow binary and hex literals.

The following code should be valid:

   let a = 0xff;
   let b = 0b10101010;

We'll need to update the lexer to recognize these values as numbers, and the parse to convert them to decimal as processed.

.methods doesn't return the full set of results.

When we allowed object-methods to be defined they were originally written 100% in golang.

That meant that the golang-implemented methods call could work, outputting the names of all known methods. However now there are methods implemented in monkey.

We should use reflection/introspection to get all methods that work on a given-type, or we should remove the facility entirely. Having wrong results is the worst possible compromise!

Copies of constant objects should be modifiable.

This code fails:

 let a = PI;
 let a = a.to_i();
 > Error: Attempting to modify 'a' denied; it was defined as a constant.

However this works:

let a = PI;
puts( a.to_i() );

The reason for this is the assignment-operation copies the const-value of the source variable (PI in this case).

Our file handling is "unusual"

At the moment you open a file via file.open() then use file.read or file.lines.

I think it would be better to have a file object. You'd receive that via calling open. From there you could call methods on the object:

let p = open( "/etc/passwd", "r" ); 
let contents = p.readlines();
p.close();

We should also implement stat to return a hash of meta-data about the given path.

For directory handling we could do something similar for example:

// implemented in golang.
let d = opendir( "/etc" );
d.readdir()
d.closedir();

// all entries, including "." and ".." - implement in monkey, via readdir:
let entries = d.entries(); 

// entries matching the pattern -> implement in monkey, via readdir.
let matched = d.glob( "pas*" );

This would break old code/examples, but I think the consistency would be worth it.

We could rethink our environmental variables.

At the moment all environmental variables are imported as variables with a $-prefix.

For example this shows your current username, assuming a Unix system:

  puts( "Hello ", $USER , "\n");

or:

  puts( "Hello " + $USER + "\n");

However this importing is read-only. So when you execute processes you cannot change the values.

It might make more sense to clone environmental variables to a hash:

  puts( "The PATH is:" ,ENV["PATH"], " You are :" , ENV["USER"], "\n" );

Using a hash would still allow iteration, via keys, but we could add special handling such that writing ot the ENV-hash would allow updating the contents of an environmental variable.

I'm not 100% sure if this would be a useful change, but it feels like it might be useful?

Allow bare (let-free) assignments.

This program works:

   let x = 10;
   let x = x * 10;

However this does not:

    let x = 10;
    x = x * 10;

We should allow bare (let-free assignments).

Add more file/directory primitives.

#47 covers a reworking of file/directory handling.

In addition to the work described there we should add:

  • file.stat()
    • As mentioned.
  • unlink
    • Delete a file/directory.
  • mkdir
    • Make a directory.
    • mkdir( "/tmp/foo/bar", { "mode": "0755" } )

Constants are accidentally global

The following code fails:

  const a =3;

  function foo() {
      let a = "Hello";
      puts( "A:" , a, "\n" );
  }
  puts( "A:", a, "\n");
  foo();

The output is:

  A:3
  Attempting to modify constant denied - a

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.