Skip to content

Commit f9e6f14

Browse files
committed
4.2 Data Types (strings)
Extend the interpreter by adding new data types.
1 parent 1b95af8 commit f9e6f14

File tree

9 files changed

+93
-2
lines changed

9 files changed

+93
-2
lines changed

ast/ast.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,17 @@ func (ce *CallExpression) String() string {
372372

373373
return out.String()
374374
}
375+
376+
// StringLiteral represents a literal string and holds a string value.
377+
type StringLiteral struct {
378+
Token token.Token
379+
Value string
380+
}
381+
382+
func (sl *StringLiteral) expressionNode() {}
383+
384+
// TokenLiteral prints the literal value of the token associated with this node.
385+
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
386+
387+
// String returns a stringified version of the AST for debugging.
388+
func (sl *StringLiteral) String() string { return sl.Token.Literal }

evaluator/evaluator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
6868
case *ast.IntegerLiteral:
6969
return &object.Integer{Value: node.Value}
7070

71+
case *ast.StringLiteral:
72+
return &object.String{Value: node.Value}
73+
7174
case *ast.Boolean:
7275
return nativeBoolToBooleanObject(node.Value)
7376

evaluator/evaluator_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,20 @@ addTwo(2);`
331331
testIntegerObject(t, testEval(input), 4)
332332
}
333333

334+
func TestStringLiteral(t *testing.T) {
335+
input := `"Hello World!"`
336+
337+
evaluated := testEval(input)
338+
str, ok := evaluated.(*object.String)
339+
if !ok {
340+
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
341+
}
342+
343+
if str.Value != "Hello World!" {
344+
t.Errorf("String has wrong value. got=%q", str.Value)
345+
}
346+
}
347+
334348
func testEval(input string) object.Object {
335349
l := lexer.New(input)
336350
p := parser.New(l)

lexer/lexer.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ func (l *Lexer) NextToken() token.Token {
7373
tok = newToken(token.LT, l.ch)
7474
case '>':
7575
tok = newToken(token.GT, l.ch)
76+
case '"':
77+
tok.Type = token.STRING
78+
tok.Literal = l.readString()
7679
case 0:
7780
tok.Literal = ""
7881
tok.Type = token.EOF
@@ -149,6 +152,19 @@ func (l *Lexer) readNumber() string {
149152
return l.input[position:l.position]
150153
}
151154

155+
func (l *Lexer) readString() string {
156+
position := l.position + 1
157+
for {
158+
// Call readChar until it encounters either a closing double quote or
159+
// the end of the input.
160+
l.readChar()
161+
if l.ch == '"' || l.ch == 0 {
162+
break
163+
}
164+
}
165+
return l.input[position:l.position]
166+
}
167+
152168
// In Monkey whitespace only acts as a separator of tokens and doesn’t have
153169
// meaning, so we need to skip over it entirely.
154170
// Otherwise, we get an ILLEGAL token for the whitespace character. Example,

lexer/lexer_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ if (5 < 10) {
2828
2929
10 == 10;
3030
10 != 9;
31+
"foobar"
32+
"foo bar"
3133
`
3234

3335
tests := []struct {
@@ -107,6 +109,8 @@ if (5 < 10) {
107109
{token.NOT_EQ, "!="},
108110
{token.INT, "9"},
109111
{token.SEMICOLON, ";"},
112+
{token.STRING, "foobar"},
113+
{token.STRING, "foo bar"},
110114
{token.EOF, ""},
111115
}
112116

object/object.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const (
1919
// BOOLEAN_OBJ is the Boolean object type.
2020
BOOLEAN_OBJ = "BOOLEAN"
2121

22+
// STRING_OBJ is the String object type.
23+
STRING_OBJ = "STRING"
24+
2225
// NULL_OBJ is the Null object type.
2326
NULL_OBJ = "NULL"
2427

@@ -140,3 +143,15 @@ func (f *Function) Inspect() string {
140143

141144
return out.String()
142145
}
146+
147+
// String is the string type used to represent string literals and holds an
148+
// internal string value.
149+
type String struct {
150+
Value string
151+
}
152+
153+
// Type returns the type of the object.
154+
func (s *String) Type() ObjectType { return STRING_OBJ }
155+
156+
// Inspect returns a stringified version of the object for debugging.
157+
func (s *String) Inspect() string { return s.Value }

parser/parser.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func New(l *lexer.Lexer) *Parser {
7979
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
8080
p.registerPrefix(token.IDENT, p.parseIdentifier)
8181
p.registerPrefix(token.INT, p.parseIntegerLiteral)
82+
p.registerPrefix(token.STRING, p.parseStringLiteral)
8283
p.registerPrefix(token.BANG, p.parsePrefixExpression)
8384
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
8485
p.registerPrefix(token.TRUE, p.parseBoolean)
@@ -276,6 +277,10 @@ func (p *Parser) parseIntegerLiteral() ast.Expression {
276277
return lit
277278
}
278279

280+
func (p *Parser) parseStringLiteral() ast.Expression {
281+
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
282+
}
283+
279284
func (p *Parser) parsePrefixExpression() ast.Expression {
280285
expression := &ast.PrefixExpression{
281286
Token: p.curToken,

parser/parser_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,25 @@ func TestCallExpressionParameterParsing(t *testing.T) {
723723
}
724724
}
725725

726+
func TestStringLiteralExpression(t *testing.T) {
727+
input := `"hello world";`
728+
729+
l := lexer.New(input)
730+
p := New(l)
731+
program := p.ParseProgram()
732+
checkParserErrors(t, p)
733+
734+
stmt := program.Statements[0].(*ast.ExpressionStatement)
735+
literal, ok := stmt.Expression.(*ast.StringLiteral)
736+
if !ok {
737+
t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression)
738+
}
739+
740+
if literal.Value != "hello world" {
741+
t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value)
742+
}
743+
}
744+
726745
func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {
727746
integ, ok := il.(*ast.IntegerLiteral)
728747
if !ok {

token/token.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ const (
1414
//
1515
// Identifiers + literals
1616
//
17-
IDENT = "IDENT" // add, foobar, x, y, ...
18-
INT = "INT" // an integer, e.g: 1343456
17+
IDENT = "IDENT" // add, foobar, x, y, ...
18+
INT = "INT" // an integer, e.g: 1343456
19+
STRING = "STRING" // a string, e.g: "foobar"
1920

2021
//
2122
// Operators

0 commit comments

Comments
 (0)