This commit is contained in:
Chuck Smith
2024-01-20 13:20:13 -05:00
parent 10821fc88a
commit 13c9062fed
9 changed files with 140 additions and 23 deletions

View File

@@ -264,3 +264,16 @@ func (ce *CallExpression) String() string {
return out.String() return out.String()
} }
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() {}

View File

@@ -94,6 +94,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return applyFunction(function, args) return applyFunction(function, args)
case *ast.StringLiteral:
return &object.String{Value: node.Value}
} }
return nil return nil
@@ -178,6 +181,8 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
switch { switch {
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right) return evalIntegerInfixExpression(operator, left, right)
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
return evalStringInfixExpression(operator, left, right)
case operator == "==": case operator == "==":
return nativeBoolToBooleanObject(left == right) return nativeBoolToBooleanObject(left == right)
case operator == "!=": case operator == "!=":
@@ -189,6 +194,17 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
} }
} }
func evalStringInfixExpression(operator string, left object.Object, right object.Object) object.Object {
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 evalIntegerInfixExpression(operator string, left, right object.Object) object.Object { func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(*object.Integer).Value leftVal := left.(*object.Integer).Value
rightVal := right.(*object.Integer).Value rightVal := right.(*object.Integer).Value

View File

@@ -205,6 +205,10 @@ func TestErrorHandling(t *testing.T) {
"foobar", "foobar",
"identifier not found: foobar", "identifier not found: foobar",
}, },
{
`"Hello" - "World"`,
"unknown operator: STRING - STRING",
},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -294,6 +298,34 @@ func TestClosures(t *testing.T) {
testIntegerObject(t, testEval(input), 4) 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)
}
}
func testEval(input string) object.Object { func testEval(input string) object.Object {
l := lexer.New(input) l := lexer.New(input)
p := parser.New(l) p := parser.New(l)

View File

@@ -91,6 +91,9 @@ func (l *Lexer) NextToken() token.Token {
case 0: case 0:
tok.Literal = "" tok.Literal = ""
tok.Type = token.EOF tok.Type = token.EOF
case '"':
tok.Type = token.STRING
tok.Literal = l.readString()
default: default:
if isLetter(l.ch) { if isLetter(l.ch) {
tok.Literal = l.readIdentifier() tok.Literal = l.readIdentifier()
@@ -145,3 +148,14 @@ func (l *Lexer) skipWhitespace() {
l.readChar() l.readChar()
} }
} }
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]
}

View File

@@ -7,25 +7,27 @@ import (
func TestNextToken(t *testing.T) { func TestNextToken(t *testing.T) {
input := `let five = 5; input := `let five = 5;
let ten = 10; let ten = 10;
let add = fn(x, y) { let add = fn(x, y) {
x + y; x + y;
}; };
let result = add(five, ten); let result = add(five, ten);
!-/*5; !-/*5;
5 < 10 > 5; 5 < 10 > 5;
if (5 < 10) { if (5 < 10) {
return true; return true;
} else { } else {
return false; return false;
} }
10 == 10; 10 == 10;
10 != 9; 10 != 9;
` "foobar"
"foo bar"
`
tests := []struct { tests := []struct {
expectedType token.TokenType expectedType token.TokenType
@@ -113,6 +115,8 @@ if (5 < 10) {
{token.INT, "9"}, {token.INT, "9"},
{token.SEMICOLON, ";"}, {token.SEMICOLON, ";"},
{token.STRING, "foobar"},
{token.STRING, "foo bar"},
{token.EOF, ""}, {token.EOF, ""},
} }

View File

@@ -16,6 +16,7 @@ const (
RETURN_VALUE_OBJ = "RETURN_VALUE" RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR" ERROR_OBJ = "ERROR"
FUNCTION_OBJ = "FUNCTION" FUNCTION_OBJ = "FUNCTION"
STRING_OBJ = "STRING"
) )
type Object interface { type Object interface {
@@ -85,11 +86,11 @@ type Function struct {
Env *Environment Env *Environment
} }
func (f Function) Type() ObjectType { func (f *Function) Type() ObjectType {
return FUNCTION_OBJ return FUNCTION_OBJ
} }
func (f Function) Inspect() string { func (f *Function) Inspect() string {
var out bytes.Buffer var out bytes.Buffer
params := []string{} params := []string{}
@@ -106,3 +107,15 @@ func (f Function) Inspect() string {
return out.String() return out.String()
} }
type String struct {
Value string
}
func (s *String) Type() ObjectType {
return STRING_OBJ
}
func (s *String) Inspect() string {
return s.Value
}

View File

@@ -64,6 +64,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.LPAREN, p.parseGroupedExpression) p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
p.registerPrefix(token.IF, p.parseIfExpression) p.registerPrefix(token.IF, p.parseIfExpression)
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
p.registerPrefix(token.STRING, p.parseStringLiteral)
p.infixParseFns = make(map[token.TokenType]infixParseFn) p.infixParseFns = make(map[token.TokenType]infixParseFn)
p.registerInfix(token.PLUS, p.parseInfixExpression) p.registerInfix(token.PLUS, p.parseInfixExpression)
@@ -430,3 +431,7 @@ func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
p.infixParseFns[tokenType] = fn p.infixParseFns[tokenType] = fn
} }
func (p *Parser) parseStringLiteral() ast.Expression {
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
}

View File

@@ -670,6 +670,25 @@ func TestCallExpressionParameterParsing(t *testing.T) {
} }
} }
func TestStringLiteralExpression(t *testing.T) {
input := `"hello world";`
l := lexer.New(input)
p := New(l)
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)
}
}
func testLetStatement(t *testing.T, s ast.Statement, name string) bool { func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
if s.TokenLiteral() != "let" { if s.TokenLiteral() != "let" {
t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())

View File

@@ -14,6 +14,7 @@ const (
// Identifiers + literals // Identifiers + literals
IDENT = "IDENT" // add, foobar, x, y IDENT = "IDENT" // add, foobar, x, y
INT = "INT" // 123456 INT = "INT" // 123456
STRING = "STRING"
// Operators // Operators
ASSIGN = "=" ASSIGN = "="