Преглед изворни кода

Extending the interpreter -- String token

simon пре 3 година
родитељ
комит
a3f5444ae7
9 измењених фајлова са 111 додато и 10 уклоњено
  1. 13 6
      ast/ast.go
  2. 15 1
      evaluator/evaluator.go
  3. 32 0
      evaluator/evaluator_test.go
  4. 14 0
      lexer/lexer.go
  5. 4 1
      lexer/lexer_test.go
  6. 8 0
      object/object.go
  7. 4 0
      parser/parser.go
  8. 18 0
      parser/parser_test.go
  9. 3 2
      token/token.go

+ 13 - 6
ast/ast.go

@@ -57,9 +57,7 @@ type LetStatement struct {
 	Value Expression
 }
 
-func (ls *LetStatement) TokenLiteral() string {
-	return ls.Token.Literal
-}
+func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
 func (ls *LetStatement) String() string {
 	var out bytes.Buffer
 
@@ -73,9 +71,7 @@ func (ls *LetStatement) String() string {
 	out.WriteString(";")
 	return out.String()
 }
-func (ls *LetStatement) statementNode() {
-	panic("implement me")
-}
+func (ls *LetStatement) statementNode() {}
 
 type Identifier struct {
 	Token token.Token // the token.IDENT token
@@ -143,6 +139,17 @@ func (i *IntegerLiteral) String() string { return i.Token.Literal }
 
 func (i *IntegerLiteral) expressionNode() {}
 
+type StringLiteral struct {
+	Token token.Token
+	Value string
+}
+
+func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
+
+func (sl *StringLiteral) String() string { return sl.Token.Literal }
+
+func (sl *StringLiteral) expressionNode() {}
+
 // PrefixExpression 前缀表示式 e.g. !a -6
 type PrefixExpression struct {
 	Token    token.Token // The prefix token, e.g. !

+ 15 - 1
evaluator/evaluator.go

@@ -63,6 +63,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
 	case *ast.FunctionLiteral:
 		params := node.Parameters
 		body := node.Body
+
 		return &object.Function{Parameters: params, Body: body, Env: env}
 	case *ast.CallExpression:
 		function := Eval(node.Function, env)
@@ -75,6 +76,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
 		}
 
 		return applyFunction(function, args)
+	case *ast.StringLiteral:
+		return &object.String{Value: node.Value}
 	}
 
 	return nil
@@ -150,6 +153,8 @@ func evalInfixExpression(operator string, left object.Object, right object.Objec
 	switch {
 	case left.Type() == object.IntegerObj && right.Type() == object.IntegerObj:
 		return evalIntegerInfixExpression(operator, left, right)
+	case left.Type() == object.StringObj && right.Type() == object.StringObj:
+		return evalStringInfixExpression(operator, left, right)
 	case operator == "==":
 		return nativeBooleanObject(left == right)
 	case operator == "!=":
@@ -214,7 +219,16 @@ func evalIntegerInfixExpression(operator string,
 		return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
 	}
 }
-
+func evalStringInfixExpression(operator string,
+	left object.Object, right object.Object) object.Object {
+	// Only identifier + is valid
+	if operator != "+" {
+		return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
+	}
+	leftVal := left.(*object.String).Value
+	rightVal := right.(*object.String).Value
+	return &object.String{Value: leftVal + rightVal}
+}
 func evalMinusPreOperatorExpression(right object.Object) object.Object {
 	if right.Type() != object.IntegerObj {
 		return newError("unknown operator: -%s", right.Type())

+ 32 - 0
evaluator/evaluator_test.go

@@ -165,6 +165,10 @@ func TestErrorHandling(t *testing.T) {
 			"foobar",
 			"identifier not found: foobar",
 		},
+		{
+			`"Hello" - "World"`,
+			"unknown operator: STRING - STRING",
+		},
 	}
 
 	for _, tt := range tests {
@@ -290,3 +294,31 @@ func TestClosures(t *testing.T) {
 
 	testIntegerObject(t, testEval(input), 4)
 }
+
+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)
+	}
+}
+
+func TestStringConcatenation(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)
+	}
+}

+ 14 - 0
lexer/lexer.go

@@ -59,6 +59,17 @@ 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 == '"' {
+			break
+		}
+	}
+	return l.input[position:l.position]
+
+}
 
 func (l *Lexer) NextToken() token.Token {
 	var tok token.Token
@@ -109,6 +120,9 @@ func (l *Lexer) NextToken() token.Token {
 	case 0:
 		tok.Type = token.EOF
 		tok.Literal = ""
+	case '"':
+		tok.Type = token.STRING
+		tok.Literal = l.readString()
 	default:
 		if isLetter(l.ch) {
 			tok.Literal = l.readIdentifier()

+ 4 - 1
lexer/lexer_test.go

@@ -28,7 +28,8 @@ func TestNextToken(t *testing.T) {
         
         10 == 10;
         10 != 9;
-        `
+        "foobar"
+		"foo bar"`
 	tests := []struct {
 		expectedType    token.TypeToken
 		expectedLiteral string
@@ -105,6 +106,8 @@ func TestNextToken(t *testing.T) {
 		{token.NOT_EQ, "!="},
 		{token.INT, "9"},
 		{token.SEMICOLON, ";"},
+		{token.STRING, "foobar"},
+		{token.STRING, "foo bar"},
 		{token.EOF, ""},
 	}
 

+ 8 - 0
object/object.go

@@ -17,6 +17,7 @@ const (
 	ReturnValueObj = "RETURN_VALUE"
 	ErrorObj       = "ERROR"
 	FunctionObj    = "FUNCTION"
+	StringObj      = "STRING"
 )
 
 // Object source code as an Object
@@ -82,3 +83,10 @@ func (f *Function) Inspect() string {
 
 	return out.String()
 }
+
+type String struct {
+	Value string
+}
+
+func (s *String) Type() ObjType   { return StringObj }
+func (s *String) Inspect() string { return s.Value }

+ 4 - 0
parser/parser.go

@@ -72,6 +72,7 @@ func New(l *lexer.Lexer) *Parser {
 	p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
 	p.registerPrefix(token.IF, p.parseIfExpression)
 	p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
+	p.registerPrefix(token.STRING, p.parseStringLiteral)
 
 	p.infixParseFns = make(map[token.TypeToken]infixParseFn)
 	p.registerInfix(token.PLUS, p.parseInfixExpression)
@@ -201,6 +202,9 @@ func (p *Parser) parseIntegerLiteral() ast.Expression {
 
 	return lit
 }
+func (p *Parser) parseStringLiteral() ast.Expression {
+	return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
+}
 func (p *Parser) parseCallExpression(left ast.Expression) ast.Expression {
 	defer untrace(trace("parseCallExpression"))
 	// add(2,3) --> Function: add

+ 18 - 0
parser/parser_test.go

@@ -693,3 +693,21 @@ 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";`
+
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParseErrors(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.Fatalf("literal.Value not %q. got=%q", "hello world", literal.Value)
+	}
+}

+ 3 - 2
token/token.go

@@ -12,8 +12,9 @@ const (
 	EOF     = "EOF"
 
 	// Identifiers + literals
-	IDENT = "IDENT" // add, foobar, x, y ...
-	INT   = "INT"   // 123456
+	IDENT  = "IDENT" // add, foobar, x, y ...
+	INT    = "INT"   // 123456
+	STRING = "STRING"
 
 	// Operators
 	ASSIGN   = "="