diff options
| author | Bobby <[email protected]> | 2024-03-29 20:19:31 +0000 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-03-29 20:19:31 +0000 |
| commit | cf6dd4b0e5efa1f8041d5570497a5228c737c454 (patch) | |
| tree | c15fa426e3a2c74ff6aa5261452ebadfbb3e0fed | |
| parent | c6a3c37f268ba3189a4902b852121e979c70817f (diff) | |
| download | mana-cf6dd4b0e5efa1f8041d5570497a5228c737c454.tar.xz mana-cf6dd4b0e5efa1f8041d5570497a5228c737c454.zip | |
Strings
| -rw-r--r-- | ast/ast.go | 9 | ||||
| -rw-r--r-- | evaluator/evaluator.go | 3 | ||||
| -rw-r--r-- | evaluator/evaluator_test.go | 14 | ||||
| -rw-r--r-- | lexer/lexer.go | 15 | ||||
| -rw-r--r-- | lexer/lexer_test.go | 4 | ||||
| -rw-r--r-- | object/object.go | 8 | ||||
| -rw-r--r-- | parser/parser.go | 8 | ||||
| -rw-r--r-- | parser/parser_test.go | 19 | ||||
| -rw-r--r-- | tokens/tokens.go | 5 |
9 files changed, 83 insertions, 2 deletions
@@ -297,3 +297,12 @@ func (ce *CallExpression) String() string { return out.String() } + +type StringLiteral struct { + Token tokens.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 87bfc48..9129a98 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -83,6 +83,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return &object.Function{Parameters: params, Body: body, Env: env} + case *ast.StringLiteral: + return &object.String{Value: node.Value} + case *ast.ReturnStatement: val := Eval(node.ReturnValue, env) if isError(val) { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 75480dc..cf9e320 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -313,3 +313,17 @@ func testNullObject(t *testing.T, obj object.Object) bool { } return true } + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} diff --git a/lexer/lexer.go b/lexer/lexer.go index 589ed60..5774120 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -65,6 +65,9 @@ func (l *Lexer) NextToken() tokens.Token { tok = newToken(tokens.LBRACE, l.ch) case '}': tok = newToken(tokens.RBRACE, l.ch) + case '"': + tok.Type = tokens.STRING + tok.Literal = l.readString() case 0: tok.Literal = "" tok.Type = tokens.EOF @@ -145,3 +148,15 @@ func (l *Lexer) readNumber() string { } return l.input[position:l.position] } + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + + return l.input[position:l.position] +} diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index 8d2bbe5..7df8db2 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -27,6 +27,8 @@ func TestNextToken(t *testing.T) { 10 == 10; 10 != 9; + "foobar" + "foo bar" ` var tests = []struct { @@ -106,6 +108,8 @@ func TestNextToken(t *testing.T) { {tokens.NOT_EQ, "!="}, {tokens.INT, "9"}, {tokens.SEMICOLON, ";"}, + {tokens.STRING, "foobar"}, + {tokens.STRING, "foo bar"}, {tokens.EOF, ""}, } diff --git a/object/object.go b/object/object.go index 793ff6b..731be0b 100644 --- a/object/object.go +++ b/object/object.go @@ -15,6 +15,7 @@ const ( NULL_OBJ = "NULL" RETURN_VALUE_OBJ = "RETURN_VALUE" FUNCTION_OBJ = "FUNCTION" + STRING_OBJ = "STRING" ERROR_OBJ = "ERROR" ) @@ -47,6 +48,10 @@ type Function struct { Env *Environment } +type String struct { + Value string +} + func (i *Integer) Type() ObjectType { return INTEGER_OBJ } @@ -104,3 +109,6 @@ func (f *Function) Inspect() string { func (e *Error) Type() ObjectType { return ERROR_OBJ } func (e *Error) Inspect() string { return "ERROR:" + e.Message } + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } diff --git a/parser/parser.go b/parser/parser.go index 3fe3d9f..6ca88b3 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -71,6 +71,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(tokens.IF, p.parseIfExpression) p.registerPrefix(tokens.FUNCTION, p.parseFunctionLiteral) p.registerPrefix(tokens.LPAREN, p.parseGroupedExpression) + p.registerPrefix(tokens.STRING, p.parseStringLiteral) // Initialize the infix parse functions. p.infixParseFns = make(map[tokens.TokenType]infixParseFn) @@ -436,6 +437,13 @@ func (p *Parser) parseCallArguments() []ast.Expression { return args } +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{ + Token: p.curToken, + Value: p.curToken.Literal, + } +} + // curTokenIs returns true if the current token is of the given type. func (p *Parser) curTokenIs(t tokens.TokenType) bool { return p.curToken.Type == t diff --git a/parser/parser_test.go b/parser/parser_test.go index 08539b7..3a574a7 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -772,3 +772,22 @@ func TestCallExpressionParsing(t *testing.T) { testInfixExpression(t, exp.Arguments[1], 2, "*", 3) testInfixExpression(t, exp.Arguments[2], 4, "+", 5) } + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + var l *lexer.Lexer = lexer.New(input) + var p *Parser = New(l) + var program *ast.Program = p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} diff --git a/tokens/tokens.go b/tokens/tokens.go index 5a74cd4..64f44fa 100644 --- a/tokens/tokens.go +++ b/tokens/tokens.go @@ -12,8 +12,9 @@ const ( EOF = "EOF" // Identifiers + literals - IDENT = "IDENT" - INT = "INT" + IDENT = "IDENT" + INT = "INT" + STRING = "STRING" // Operators ASSIGN = "=" |
