Strings
This commit is contained in:
13
ast/ast.go
13
ast/ast.go
@@ -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() {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ if (5 < 10) {
|
|||||||
|
|
||||||
10 == 10;
|
10 == 10;
|
||||||
10 != 9;
|
10 != 9;
|
||||||
|
"foobar"
|
||||||
|
"foo bar"
|
||||||
`
|
`
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -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, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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 = "="
|
||||||
|
|||||||
Reference in New Issue
Block a user