Skip to content

Commit

Permalink
feat(interpreter): add support to strings (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
vit0rr authored Oct 5, 2024
1 parent 3fe956a commit 9fea90c
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 14 deletions.
13 changes: 13 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,16 @@ func (p *Program) TokenLiteral() string {
return ""
}
}

type StringLiteral struct {
Token token.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
}
2 changes: 2 additions & 0 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,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
Expand Down
14 changes: 14 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,20 @@ 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 testNullObject(t *testing.T, obj object.Object) bool {
if obj != NULL {
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
Expand Down
15 changes: 15 additions & 0 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,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()
Expand All @@ -92,6 +95,18 @@ func (l *Lexer) NextToken() token.Token {
return tok
}

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]
}

func (l *Lexer) readNumber() string {
position := l.position
for isDigit(l.ch) {
Expand Down
4 changes: 4 additions & 0 deletions lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func TestNextToken(t *testing.T) {
10 == 10;
10 != 9;
"foobar"
"foo bar"
`

tests := []struct {
Expand Down Expand Up @@ -105,6 +107,8 @@ func TestNextToken(t *testing.T) {
{token.NOT_EQ, "!="},
{token.INT, "9"},
{token.SEMICOLON, ";"},
{token.STRING, "foobar"},
{token.STRING, "foo bar"},
{token.EOF, ""},
}

Expand Down
8 changes: 8 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR"
FUNCTIONS_OBJ = "FUNCTIONS"
STRING_OBJ = "STRING"
)

type Object interface {
Expand Down Expand Up @@ -81,3 +82,10 @@ 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 }
5 changes: 5 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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)
Expand All @@ -92,6 +93,10 @@ func New(l *lexer.Lexer) *Parser {
return p
}

func (p *Parser) parseStringLiteral() ast.Expression {
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
}

func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
exp := &ast.CallExpression{Token: p.curToken, Function: function}
exp.Arguments = p.parseCallArguments()
Expand Down
67 changes: 55 additions & 12 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,14 +500,27 @@ func TestCallExpressionParsing(t *testing.T) {
testInfixExpression(t, exp.Arguments[2], 4, "+", 5)
}

func TestcallExpressionParameterParsing(t *testing.T) {
func TestCallExpressionParameterParsing(t *testing.T) {
tests := []struct {
input string
expectedParams []interface{}
input string
expectedIdent string
expectedArgs []string
}{
{"add();", []interface{}{}},
{"add(1);", []interface{}{1}},
{"add(1, 2 * 3, 4 + 5);", []interface{}{1, 2 * 3, 4 + 5}},
{
input: "add();",
expectedIdent: "add",
expectedArgs: []string{},
},
{
input: "add(1);",
expectedIdent: "add",
expectedArgs: []string{"1"},
},
{
input: "add(1, 2 * 3, 4 + 5);",
expectedIdent: "add",
expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"},
},
}

for _, tt := range tests {
Expand All @@ -517,16 +530,46 @@ func TestcallExpressionParameterParsing(t *testing.T) {
checkParserErrors(t, p)

stmt := program.Statements[0].(*ast.ExpressionStatement)
exp := stmt.Expression.(*ast.CallExpression)
exp, ok := stmt.Expression.(*ast.CallExpression)
if !ok {
t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T",
stmt.Expression)
}

if len(exp.Arguments) != len(tt.expectedParams) {
t.Errorf("length arguments wrong. want %d, got=%d",
len(tt.expectedParams), len(exp.Arguments))
if !testIdentifier(t, exp.Function, tt.expectedIdent) {
return
}

for i, param := range tt.expectedParams {
testLiteralExpression(t, exp.Arguments[i], param)
if len(exp.Arguments) != len(tt.expectedArgs) {
t.Fatalf("wrong number of arguments. want=%d, got=%d",
len(tt.expectedArgs), len(exp.Arguments))
}

for i, arg := range tt.expectedArgs {
if exp.Arguments[i].String() != arg {
t.Errorf("argument %d wrong. want=%q, got=%q", i,
arg, exp.Arguments[i].String())
}
}
}
}

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)
}
}

Expand Down
5 changes: 3 additions & 2 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ const (
EOF = "EOF"

// Identifiers + literals
IDENT = "IDENT" // add, foobar, x, y, ...
INT = "INT" // 1343456
IDENT = "IDENT" // add, foobar, x, y, ...
INT = "INT" // 1343456
STRING = "STRING"

// Operators
ASSIGN = "="
Expand Down

0 comments on commit 9fea90c

Please sign in to comment.