Repository: skx/monkey (go)

Issue:
Implement the ternary-operator.
Sample usage is the obvious:

      function max(a, b) {
          return( a > b ? a : b );
      }


Pull request:
55 ternary
When this pull-request is complete, and merged, it will close #55 by implementing the ternary-operator into our language.

Patch:
diff --git a/README.md b/README.md
index 3411cd1..ee3d524 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@
     * [2.4.1 The Standard Library](#241-the-standard-library)
   * [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.8 Comments](#28-comments)
   * [2.9 Postfix Operators](#29-postfix-operators)
@@ -73,6 +74,7 @@ The interpreter in _this_ repository has been significantly extended from the st
   * 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](#261-ternary-expressions).
 
 
 ## 1. Installation
@@ -368,6 +370,20 @@ The same thing works for literal functions:
     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 For-loop statements
 
 `monkey` supports a golang-style for-loop statement.
diff --git a/ast/ast.go b/ast/ast.go
index 51d5434..ff37ecd 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -376,6 +376,42 @@ func (ie *IfExpression) String() string {
 	return out.String()
 }
 
+// TernaryExpression holds a ternary-expression.
+type TernaryExpression struct {
+	// Token is the actual token.
+	Token token.Token
+
+	// Condition is the thing that is evaluated to determine
+	// which expression should be returned
+	Condition Expression
+
+	// IfTrue is the expression to return if the condition is true.
+	IfTrue Expression
+
+	// IFFalse is the expression to return if the condition is not true.
+	IfFalse Expression
+}
+
+func (te *TernaryExpression) expressionNode() {}
+
+// TokenLiteral returns the literal token.
+func (te *TernaryExpression) TokenLiteral() string { return te.Token.Literal }
+
+// String returns this object as a string.
+func (te *TernaryExpression) String() string {
+	var out bytes.Buffer
+
+	out.WriteString("(")
+	out.WriteString(te.Condition.String())
+	out.WriteString(" ? ")
+	out.WriteString(te.IfTrue.String())
+	out.WriteString(" : ")
+	out.WriteString(te.IfFalse.String())
+	out.WriteString(")")
+
+	return out.String()
+}
+
 // ForLoopExpression holds a for-loop
 type ForLoopExpression struct {
 	// Token is the actual token
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index 216a137..9b34a44 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -71,6 +71,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
 		return evalBlockStatement(node, env)
 	case *ast.IfExpression:
 		return evalIfExpression(node, env)
+	case *ast.TernaryExpression:
+		return evalTernaryExpression(node, env)
 	case *ast.ForLoopExpression:
 		return evalForLoopExpression(node, env)
 	case *ast.ReturnStatement:
@@ -562,6 +564,9 @@ func evalStringInfixExpression(operator string, left, right object.Object) objec
 		left.Type(), operator, right.Type())
 }
 
+// evalIfExpression handles an `if` expression, running the block
+// if the condition matches, and running any optional else block
+// otherwise.
 func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object {
 	condition := Eval(ie.Condition, env)
 	if isError(condition) {
@@ -576,6 +581,23 @@ func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Obje
 	}
 }
 
+// evalTernaryExpression handles a ternary-expression.  If the condition
+// is true we return the contents of evaluating the true-branch, otherwise
+// the false-branch.  (Unlike an `if` statement we know that we always have
+// an alternative/false branch.)
+func evalTernaryExpression(te *ast.TernaryExpression, env *object.Environment) object.Object {
+
+	condition := Eval(te.Condition, env)
+	if isError(condition) {
+		return condition
+	}
+
+	if isTruthy(condition) {
+		return Eval(te.IfTrue, env)
+	}
+	return Eval(te.IfFalse, env)
+}
+
 func evalAssignStatement(a *ast.AssignStatement, env *object.Environment) (val object.Object) {
 	evaluated := Eval(a.Value, env)
 	if isError(evaluated) {
diff --git a/lexer/lexer.go b/lexer/lexer.go
index f981517..b2be721 100644
--- a/lexer/lexer.go
+++ b/lexer/lexer.go
@@ -107,6 +107,8 @@ func (l *Lexer) NextToken() token.Token {
 		}
 	case rune(';'):
 		tok = newToken(token.SEMICOLON, l.ch)
+	case rune('?'):
+		tok = newToken(token.QUESTION, l.ch)
 	case rune('('):
 		tok = newToken(token.LPAREN, l.ch)
 	case rune(')'):
diff --git a/parser/parser.go b/parser/parser.go
index 7ccc421..214097a 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -25,6 +25,7 @@ const (
 	LOWEST
 	COND         // OR or AND
 	ASSIGN       // =
+	TERNARY      // ? :
 	EQUALS       // == or !=
 	REGEXP_MATCH // !~ ~=
 	LESSGREATER  // > or <
@@ -39,6 +40,7 @@ const (
 
 // each token precedence
 var precedences = map[token.Type]int{
+	token.QUESTION:     TERNARY,
 	token.ASSIGN:       ASSIGN,
 	token.EQ:           EQUALS,
 	token.NOT_EQ:       EQUALS,
@@ -95,6 +97,11 @@ type Parser struct {
 	// postfixParseFns holds a map of parsing methods for
 	// postfix-based syntax.
 	postfixParseFns map[token.Type]postfixParseFn
+
+	// are we inside a ternary expression?
+	//
+	// Nested ternary expressions are illegal :)
+	tern bool
 }
 
 // New returns our new parser-object.
@@ -148,6 +155,7 @@ func New(l *lexer.Lexer) *Parser {
 	p.registerInfix(token.SLASH_EQUALS, p.parseAssignExpression)
 	p.registerInfix(token.CONTAINS, p.parseInfixExpression)
 	p.registerInfix(token.NOT_CONTAINS, p.parseInfixExpression)
+	p.registerInfix(token.QUESTION, p.parseTernaryExpression)
 
 	p.postfixParseFns = make(map[token.Type]postfixParseFn)
 	p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression)
@@ -382,6 +390,38 @@ func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
 	return expression
 }
 
+// parseTernaryExpression parses a ternary expression
+func (p *Parser) parseTernaryExpression(condition ast.Expression) ast.Expression {
+
+	if p.tern {
+		msg := fmt.Sprintf("nested ternary expressions are illegal, around line %d", p.l.GetLine())
+		p.errors = append(p.errors, msg)
+		return nil
+	}
+
+	p.tern = true
+	defer func() { p.tern = false }()
+
+	expression := &ast.TernaryExpression{
+		Token:     p.curToken,
+		Condition: condition,
+	}
+	p.nextToken() //skip the '?'
+	precedence := p.curPrecedence()
+	expression.IfTrue = p.parseExpression(precedence)
+
+	if !p.expectPeek(token.COLON) { //skip the ":"
+		return nil
+	}
+
+	// Get to next token, then parse the else part
+	p.nextToken()
+	expression.IfFalse = p.parseExpression(precedence)
+
+	p.tern = false
+	return expression
+}
+
 // parseGroupedExpression parses a grouped-expression.
 func (p *Parser) parseGroupedExpression() ast.Expression {
 	p.nextToken()
diff --git a/token/token.go b/token/token.go
index c2231a8..275b0d6 100644
--- a/token/token.go
+++ b/token/token.go
@@ -62,6 +62,7 @@ const (
 	PERIOD          = "."
 	CONTAINS        = "~="
 	NOT_CONTAINS    = "!~"
+	QUESTION        = "?"
 	ILLEGAL         = "ILLEGAL"
 )
 


Test patch:
diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go
index 253c9b4..a50fbdc 100644
--- a/lexer/lexer_test.go
+++ b/lexer/lexer_test.go
@@ -7,12 +7,13 @@ import (
 )
 
 func TestNextToken1(t *testing.T) {
-	input := `=+(){},;`
+	input := "%=+(){},;?|| &&`/bin/ls`++--***="
 
 	tests := []struct {
 		expectedType    token.Type
 		expectedLiteral string
 	}{
+		{token.MOD, "%"},
 		{token.ASSIGN, "="},
 		{token.PLUS, "+"},
 		{token.LPAREN, "("},
@@ -21,6 +22,14 @@ func TestNextToken1(t *testing.T) {
 		{token.RBRACE, "}"},
 		{token.COMMA, ","},
 		{token.SEMICOLON, ";"},
+		{token.QUESTION, "?"},
+		{token.OR, "||"},
+		{token.AND, "&&"},
+		{token.BACKTICK, "/bin/ls"},
+		{token.PLUS_PLUS, "++"},
+		{token.MINUS_MINUS, "--"},
+		{token.POW, "**"},
+		{token.ASTERISK_EQUALS, "*="},
 		{token.EOF, ""},
 	}
 	l := New(input)
@@ -61,6 +70,8 @@ if(5<10){
 0.3
 世界
 for
+2 >= 1
+1 <= 3
 `
 	tests := []struct {
 		expectedType    token.Type
@@ -156,6 +167,12 @@ for
 		{token.FLOAT, "0.3"},
 		{token.IDENT, "世界"},
 		{token.FOR, "for"},
+		{token.INT, "2"},
+		{token.GT_EQUALS, ">="},
+		{token.INT, "1"},
+		{token.INT, "1"},
+		{token.LT_EQUALS, "<="},
+		{token.INT, "3"},
 		{token.EOF, ""},
 	}
 	l := New(input)
@@ -497,6 +514,7 @@ func TestRegexp(t *testing.T) {
 	input := `if ( f ~= /steve/i )
 if ( f ~= /steve/m )
 if ( f ~= /steve/mi )
+if ( f !~ /steve/mi )
 if ( f ~= /steve/miiiiiiiiiiiiiiiiimmmmmmmmmmmmmiiiii )`
 
 	tests := []struct {
@@ -524,6 +542,12 @@ if ( f ~= /steve/miiiiiiiiiiiiiiiiimmmmmmmmmmmmmiiiii )`
 		{token.IF, "if"},
 		{token.LPAREN, "("},
 		{token.IDENT, "f"},
+		{token.NOT_CONTAINS, "!~"},
+		{token.REGEXP, "(?mi)steve"},
+		{token.RPAREN, ")"},
+		{token.IF, "if"},
+		{token.LPAREN, "("},
+		{token.IDENT, "f"},
 		{token.CONTAINS, "~="},
 		{token.REGEXP, "(?mi)steve"},
 		{token.RPAREN, ")"},