Skip to content
This repository was archived by the owner on Sep 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 45 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
* [2.5 Functions](#25-functions)
* [2.6 If-else statements](#26-if-else-statements)
* [2.6.1 Ternary expressions](#261-ternary-expressions)
* [2.7 For-loop statements](#27-for-loop-statements)
* [2.7.1 Foreach statements](#271-foreach-statements)
* [2.8 Comments](#28-comments)
* [2.9 Postfix Operators](#29-postfix-operators)
* [2.10 Command Execution](#210-command-execution)
* [2.11 Regular Expressions](#211-regular-expressions)
* [2.12 File I/O](#212-file-io)
* [2.13 File Operations](#213-file-operations)
* [2.7 Switch statements](#27-switch-statements)
* [2.8 For-loop statements](#28-for-loop-statements)
* [2.8.1 Foreach statements](#281-foreach-statements)
* [2.9 Comments](#29-comments)
* [2.10 Postfix Operators](#29-postfix-operators)
* [2.11 Command Execution](#211-command-execution)
* [2.12 Regular Expressions](#212-regular-expressions)
* [2.13 File I/O](#213-file-io)
* [2.14 File Operations](#214-file-operations)
* [3. Object Methods](#3-object-methods)
* [3.1 Defininig New Object Methods](#31-defininig-new-object-methods)
* [Github Setup](#github-setup)
Expand Down Expand Up @@ -80,6 +81,8 @@ The interpreter in _this_ repository has been significantly extended from the st
* 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".


## 1. Installation
Expand Down Expand Up @@ -397,7 +400,33 @@ would expect with a C-background:

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

## 2.7 For-loop statements
## 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](examples/switch.mon).


## 2.8 For-loop statements

`monkey` supports a golang-style for-loop statement.

Expand All @@ -415,7 +444,7 @@ Note that in the interests of clarity nested ternary-expressions are illegal!
puts(sum(100)); // Outputs: 4950


## 2.7.1 Foreach statements
## 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.

Expand All @@ -437,15 +466,15 @@ The same style of iteration works for Arrays, Hashes, and the characters which m
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.8 Comments
## 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.9 Postfix Operators
## 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`:

Expand Down Expand Up @@ -473,7 +502,7 @@ The update-operators work with integers and doubles by default, when it comes to
puts( str ); // -> "Forename Surname\n"


## 2.10 Command Execution
## 2.11 Command Execution

As with many scripting languages commands may be executed via the backtick
operator (`\``).
Expand All @@ -491,7 +520,7 @@ The output will be a hash with two keys `stdout` and `stderr`. NULL is
returned if the execution fails. This can be seen in [examples/exec.mon](examples/exec.mon).


## 2.11 Regular Expressions
## 2.12 Regular Expressions

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

Expand All @@ -511,7 +540,7 @@ You can also perform matching (complete with captures), with a literal regular e
printf("Matched! %s.%s.%s.%s\n", $1, $2, $3, $4 );
}

## 2.12 File I/O
## 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:

Expand Down Expand Up @@ -553,7 +582,7 @@ By default three filehandles will be made available, as constants:
* Used for writing messages.


## 2.13 File Operations
## 2.14 File Operations

The primitive `stat` will return a hash of details about the given file, or
directory entry.
Expand Down
74 changes: 74 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,3 +785,77 @@ func (as *AssignStatement) String() string {
out.WriteString(as.Value.String())
return out.String()
}

// CaseExpression handles the case within a switch statement
type CaseExpression struct {
// Token is the actual token
Token token.Token

// Default branch?
Default bool

// The thing we match
Expr []Expression

// The code to execute if there is a match
Block *BlockStatement
}

func (ce *CaseExpression) expressionNode() {}

// TokenLiteral returns the literal token.
func (ce *CaseExpression) TokenLiteral() string { return ce.Token.Literal }

// String returns this object as a string.
func (ce *CaseExpression) String() string {
var out bytes.Buffer

if ce.Default {
out.WriteString("default ")
} else {
out.WriteString("case ")

tmp := []string{}
for _, exp := range ce.Expr {
tmp = append(tmp, exp.String())
}
out.WriteString(strings.Join(tmp, ","))
}
out.WriteString(ce.Block.String())
return out.String()
}

// SwitchExpression handles a switch statement
type SwitchExpression struct {
// Token is the actual token
Token token.Token

// Value is the thing that is evaluated to determine
// which block should be executed.
Value Expression

// The branches we handle
Choices []*CaseExpression
}

func (se *SwitchExpression) expressionNode() {}

// TokenLiteral returns the literal token.
func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal }

// String returns this object as a string.
func (se *SwitchExpression) String() string {
var out bytes.Buffer
out.WriteString("\nswitch (")
out.WriteString(se.Value.String())
out.WriteString(")\n{\n")

for _, tmp := range se.Choices {
if tmp != nil {
out.WriteString(tmp.String())
}
}
out.WriteString("}\n")

return out.String()
}
60 changes: 60 additions & 0 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return evalAssignStatement(node, env)
case *ast.HashLiteral:
return evalHashLiteral(node, env)
case *ast.SwitchExpression:
return evalSwitchStatement(node, env)
}
return nil
}
Expand Down Expand Up @@ -731,6 +733,64 @@ func evalAssignStatement(a *ast.AssignStatement, env *object.Environment) (val o
return evaluated
}

func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object {

// Get the value.
obj := Eval(se.Value, env)

// Try all the choices
for _, opt := range se.Choices {

// skipping the default-case, which we'll
// handle later.
if opt.Default {
continue
}

// Look at any expression we've got in this case.
for _, val := range opt.Expr {

// Get the value of the case
out := Eval(val, env)

// Is it a literal match?
if obj.Type() == out.Type() &&
(obj.Inspect() == out.Inspect()) {

// Evaluate the block and return the value
out := evalBlockStatement(opt.Block, env)
return out
}

// Is it a regexp-match?
if out.Type() == object.REGEXP_OBJ {

m := matches(obj, out, env)
if m == TRUE {

// Evaluate the block and return the value
out := evalBlockStatement(opt.Block, env)
return out

}
}
}
}

// No match? Handle default if present
for _, opt := range se.Choices {

// skip default
if opt.Default {

out := evalBlockStatement(opt.Block, env)
return out
}
}

return nil
}

func evalForLoopExpression(fle *ast.ForLoopExpression, env *object.Environment) object.Object {
rt := &object.Boolean{Value: true}
for {
Expand Down
44 changes: 44 additions & 0 deletions examples/switch.mon
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// A simple test-function for switch-statements.
function test( name ) {

// Did we match?
m = false;

switch( name ) {
case /^steve$/ , /^STEVE$/i {
printf("Hello Steve - we matched you via a regexp\n");
m = true;
}
case "St" + "even" {
printf("Hello SteveN, you were matched via an expression\n" );
m = true;
}
case 3, 6, 9 {
printf("Hello multiple of three, we matched you literally: %d\n", int(name));
m = true;
}
default {
printf("Default case: %s\n", string(name) );
}
}

// Show we matched, if we did.
if ( m ) { printf( "\tMatched!\n"); }
}

// Test the switch statement
test( "Steve" ); // Regexp match
test( "steve" ); // Regexp match
test( "Steven" ); // Literal match
test( 3 ); // Literal match

// Unhandled/Default cases
test( "Bob" );
test( false );

// Try some other numbers - only one will match
foreach number in 1..10 {
test(number);
}

printf( "All done\n" );
Loading