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

View File

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

View File

@@ -91,6 +91,9 @@ func (l *Lexer) NextToken() token.Token {
case 0:
tok.Literal = ""
tok.Type = token.EOF
case '"':
tok.Type = token.STRING
tok.Literal = l.readString()
default:
if isLetter(l.ch) {
tok.Literal = l.readIdentifier()
@@ -145,3 +148,14 @@ func (l *Lexer) skipWhitespace() {
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) {
input := `let five = 5;
let ten = 10;
let ten = 10;
let add = fn(x, y) {
let add = fn(x, y) {
x + y;
};
};
let result = add(five, ten);
!-/*5;
5 < 10 > 5;
let result = add(five, ten);
!-/*5;
5 < 10 > 5;
if (5 < 10) {
if (5 < 10) {
return true;
} else {
} else {
return false;
}
}
10 == 10;
10 != 9;
`
10 == 10;
10 != 9;
"foobar"
"foo bar"
`
tests := []struct {
expectedType token.TokenType
@@ -113,6 +115,8 @@ if (5 < 10) {
{token.INT, "9"},
{token.SEMICOLON, ";"},
{token.STRING, "foobar"},
{token.STRING, "foo bar"},
{token.EOF, ""},
}

View File

@@ -16,6 +16,7 @@ const (
RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR"
FUNCTION_OBJ = "FUNCTION"
STRING_OBJ = "STRING"
)
type Object interface {
@@ -85,11 +86,11 @@ type Function struct {
Env *Environment
}
func (f Function) Type() ObjectType {
func (f *Function) Type() ObjectType {
return FUNCTION_OBJ
}
func (f Function) Inspect() string {
func (f *Function) Inspect() string {
var out bytes.Buffer
params := []string{}
@@ -106,3 +107,15 @@ func (f Function) Inspect() 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.IF, p.parseIfExpression)
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
p.registerPrefix(token.STRING, p.parseStringLiteral)
p.infixParseFns = make(map[token.TokenType]infixParseFn)
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) {
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 {
if s.TokenLiteral() != "let" {
t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())

View File

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