From 9b0ec1e704e7e6a5fade53854587cc58a5ea2452 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 22 Jun 2021 19:52:02 +0300 Subject: [PATCH 1/5] Added `null` token. --- token/token.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/token/token.go b/token/token.go index cc10d4e..3ca989b 100644 --- a/token/token.go +++ b/token/token.go @@ -54,6 +54,7 @@ const ( MOD = "%" NOT_CONTAINS = "!~" NOT_EQ = "!=" + NULL = "null" OR = "||" PERIOD = "." PLUS = "+" @@ -88,6 +89,7 @@ var keywords = map[string]Type{ "if": IF, "in": IN, "let": LET, + "null": NULL, "return": RETURN, "switch": SWITCH, "true": TRUE, From f5fbd2df2134881ae97e80ea7416d932ce8f97b8 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 22 Jun 2021 19:52:11 +0300 Subject: [PATCH 2/5] Parse a null-literal --- parser/parser.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/parser/parser.go b/parser/parser.go index 4bbba00..c056e55 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -136,6 +136,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) p.registerPrefix(token.LPAREN, p.parseGroupedExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.NULL, p.parseNull) p.registerPrefix(token.REGEXP, p.parseRegexpLiteral) p.registerPrefix(token.REGEXP, p.parseRegexpLiteral) p.registerPrefix(token.STRING, p.parseStringLiteral) @@ -504,6 +505,11 @@ func (p *Parser) parseBoolean() ast.Expression { return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} } +// parseNull parses a null keyword +func (p *Parser) parseNull() ast.Expression { + return &ast.NullLiteral{Token: p.curToken} +} + // parsePrefixExpression parses a prefix-based expression. func (p *Parser) parsePrefixExpression() ast.Expression { expression := &ast.PrefixExpression{ From e5287e306d928de51e01643d2c15158fd87783df Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 22 Jun 2021 19:52:20 +0300 Subject: [PATCH 3/5] Handle execution of a null-literal --- evaluator/evaluator.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index d01efc9..8c18553 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -62,6 +62,8 @@ func EvalContext(ctx context.Context, node ast.Node, env *object.Environment) ob return &object.Float{Value: node.Value} case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) + case *ast.NullLiteral: + return NULL case *ast.PrefixExpression: right := Eval(node.Right, env) if isError(right) { From d7920168a5fa5f333a61d8ee110b8e9712603fe2 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 22 Jun 2021 19:52:39 +0300 Subject: [PATCH 4/5] Define a NullLiteral type --- ast/ast.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ast/ast.go b/ast/ast.go index f3f9f39..f723237 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -300,6 +300,20 @@ func (pe *PostfixExpression) String() string { return out.String() } +// NullLiteral represents a literal null +type NullLiteral struct { + // Token holds the actual token + Token token.Token +} + +func (n *NullLiteral) expressionNode() {} + +// TokenLiteral returns the literal token. +func (n *NullLiteral) TokenLiteral() string { return n.Token.Literal } + +// String returns this object as a string. +func (n *NullLiteral) String() string { return n.Token.Literal } + // Boolean holds a boolean type type Boolean struct { // Token holds the actual token From e4d5eb62d58d7e780e1de7211a20a0b60525eaa8 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 22 Jun 2021 19:52:48 +0300 Subject: [PATCH 5/5] Added lexer-tests --- lexer/lexer_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index b549c2b..48abb9d 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -6,6 +6,30 @@ import ( "github.com/skx/monkey/token" ) +func TestNull(t *testing.T) { + input := "a = null;" + tests := []struct { + expectedType token.Type + expectedLiteral string + }{ + {token.IDENT, "a"}, + {token.ASSIGN, "="}, + {token.NULL, "null"}, + {token.SEMICOLON, ";"}, + {token.EOF, ""}, + } + l := New(input) + for i, tt := range tests { + tok := l.NextToken() + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong, expected=%q, got=%q", i, tt.expectedType, tok.Type) + } + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - Literal wrong, expected=%q, got=%q", i, tt.expectedLiteral, tok.Literal) + } + } +} + func TestNextToken1(t *testing.T) { input := "%=+(){},;?|| &&`/bin/ls`++--***=.." @@ -200,6 +224,28 @@ func TestUnicodeLexer(t *testing.T) { } } +func TestString(t *testing.T) { + input := `"\n\r\t\\\""` + + tests := []struct { + expectedType token.Type + expectedLiteral string + }{ + {token.STRING, "\n\r\t\\\""}, + {token.EOF, ""}, + } + l := New(input) + for i, tt := range tests { + tok := l.NextToken() + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong, expected=%q, got=%q", i, tt.expectedType, tok.Type) + } + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - Literal wrong, expected=%q, got=%q", i, tt.expectedLiteral, tok.Literal) + } + } + +} func TestSimpleComment(t *testing.T) { input := `=+// This is a comment // This is still a comment