diff --git a/ast/ast.go b/ast/ast.go index 69978d8..2fbc53f 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -134,6 +134,15 @@ func (i *Identifier) expressionNode() {} func (i *Identifier) TokenLiteral() string { return i.Token.Literal } func (i *Identifier) String() string { return i.Value } +// Null represents a null value +type Null struct { + Token token.Token +} + +func (n *Null) expressionNode() {} +func (n *Null) TokenLiteral() string { return n.Token.Literal } +func (n *Null) String() string { return n.Token.Literal } + type Boolean struct { Token token.Token Value bool diff --git a/compiler/compiler.go b/compiler/compiler.go index e15f3fd..800ae7a 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -155,6 +155,9 @@ func (c *Compiler) Compile(node ast.Node) error { integer := &object.Integer{Value: node.Value} c.emit(code.OpConstant, c.addConstant(integer)) + case *ast.Null: + c.emit(code.OpNull) + case *ast.Boolean: if node.Value { c.emit(code.OpTrue) diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 1251e47..ce48f88 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -100,6 +100,14 @@ func TestBooleanExpressions(t *testing.T) { code.Make(code.OpPop), }, }, + { + input: "null", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpNull), + code.Make(code.OpPop), + }, + }, { input: "1 > 2", expectedConstants: []interface{}{1, 2}, diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 775c6f3..7b453c2 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -87,6 +87,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object { case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) + case *ast.Null: + return NULL + case *ast.PrefixExpression: right := Eval(node.Right, env) if isError(right) { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index c09187f..aa2c999 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -623,6 +623,11 @@ func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { return true } +func TestNullExpression(t *testing.T) { + evaluated := testEval("null") + testNullObject(t, evaluated) +} + func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { result, ok := obj.(*object.Boolean) if !ok { diff --git a/parser/parser.go b/parser/parser.go index 10fa5d7..807ab1c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -64,6 +64,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.MINUS, p.parsePrefixExpression) p.registerPrefix(token.TRUE, p.parseBoolean) p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.NULL, p.parseNull) p.registerPrefix(token.LPAREN, p.parseGroupedExpression) p.registerPrefix(token.IF, p.parseIfExpression) p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) @@ -552,3 +553,7 @@ func (p *Parser) parseAssignmentStatement() ast.Statement { func (p *Parser) parseComment() ast.Statement { return &ast.Comment{Token: p.curToken, Value: p.curToken.Literal} } + +func (p *Parser) parseNull() ast.Expression { + return &ast.Null{Token: p.curToken} +} diff --git a/parser/parser_test.go b/parser/parser_test.go index af88119..36c26d9 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -458,6 +458,29 @@ func TestIfExpression(t *testing.T) { } } +func TestNullExpression(t *testing.T) { + l := lexer.New("null") + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + _, ok = stmt.Expression.(*ast.Null) + if !ok { + t.Fatalf("exp not *ast.Null. got=%T", stmt.Expression) + } +} + func TestIfElseExpression(t *testing.T) { input := `if (x < y) { x } else { y }` diff --git a/token/token.go b/token/token.go index bacf912..1427ab0 100644 --- a/token/token.go +++ b/token/token.go @@ -52,6 +52,7 @@ const ( LET = "LET" TRUE = "TRUE" FALSE = "FALSE" + NULL = "NULL" IF = "IF" ELSE = "ELSE" RETURN = "RETURN" @@ -64,6 +65,7 @@ var keywords = map[string]TokenType{ "true": TRUE, "false": FALSE, "if": IF, + "null": NULL, "else": ELSE, "return": RETURN, "while": WHILE, diff --git a/vm/vm_test.go b/vm/vm_test.go index 65b75b5..2f40d84 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -233,6 +233,7 @@ func TestBooleanExpressions(t *testing.T) { tests := []vmTestCase{ {"true", true}, {"false", false}, + {"null", nil}, {"!true", false}, {"!false", true}, {"!5", false},