aboutsummaryrefslogtreecommitdiff
path: root/parser
diff options
context:
space:
mode:
Diffstat (limited to 'parser')
-rw-r--r--parser/parser.go30
-rw-r--r--parser/parser_test.go104
2 files changed, 134 insertions, 0 deletions
diff --git a/parser/parser.go b/parser/parser.go
index 4fa36b1..2de9e56 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -75,6 +75,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(tokens.LPAREN, p.parseGroupedExpression)
p.registerPrefix(tokens.STRING, p.parseStringLiteral)
p.registerPrefix(tokens.LBRACKET, p.parseArrayLiteral)
+ p.registerPrefix(tokens.LBRACE, p.parseHashLiteral)
// Initialize the infix parse functions.
p.infixParseFns = make(map[tokens.TokenType]infixParseFn)
@@ -494,6 +495,35 @@ func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
return exp
}
+func (p *Parser) parseHashLiteral() ast.Expression {
+ hash := &ast.HashLiteral{Token: p.curToken}
+ hash.Pairs = make(map[ast.Expression]ast.Expression)
+
+ for !p.peekTokenIs(tokens.RBRACE) {
+ p.nextToken()
+ key := p.parseExpression(LOWEST)
+
+ if !p.expectPeek(tokens.COLON) {
+ return nil
+ }
+
+ p.nextToken()
+ value := p.parseExpression(LOWEST)
+
+ hash.Pairs[key] = value
+
+ if !p.peekTokenIs(tokens.RBRACE) && !p.expectPeek(tokens.COMMA) {
+ return nil
+ }
+ }
+
+ if !p.expectPeek(tokens.RBRACE) {
+ return nil
+ }
+
+ return hash
+}
+
// curTokenIs returns true if the current token is of the given type.
func (p *Parser) curTokenIs(t tokens.TokenType) bool {
return p.curToken.Type == t
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 7b347f5..e38301a 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -859,3 +859,107 @@ func TestParsingIndexExpressions(t *testing.T) {
return
}
}
+
+func TestParsingHashLiteralsStringKeys(t *testing.T) {
+ input := `{"one": 1, "two": 2, "three": 3}`
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+
+ var program *ast.Program = p.ParseProgram()
+ checkParserErrors(t, p)
+
+ stmt := program.Statements[0].(*ast.ExpressionStatement)
+ hash, ok := stmt.Expression.(*ast.HashLiteral)
+ if !ok {
+ t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression)
+ }
+
+ if len(hash.Pairs) != 3 {
+ t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs))
+ }
+
+ expected := map[string]int64{
+ "one": 1,
+ "two": 2,
+ "three": 3,
+ }
+
+ for key, value := range hash.Pairs {
+ literal, ok := key.(*ast.StringLiteral)
+ if !ok {
+ t.Errorf("key is not ast.StringLiteral. got=%T", key)
+ }
+
+ expectedValue := expected[literal.Value]
+ testIntegerLiteral(t, value, expectedValue)
+ }
+}
+
+func TestParsingEmptyHashLiteral(t *testing.T) {
+ input := `{}`
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+
+ var program *ast.Program = p.ParseProgram()
+ checkParserErrors(t, p)
+
+ stmt := program.Statements[0].(*ast.ExpressionStatement)
+ hash, ok := stmt.Expression.(*ast.HashLiteral)
+ if !ok {
+ t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression)
+ }
+
+ if len(hash.Pairs) != 0 {
+ t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs))
+ }
+}
+
+func TestParsingHashLiteralsWithExpressions(t *testing.T) {
+ input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}`
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+
+ var program *ast.Program = p.ParseProgram()
+ checkParserErrors(t, p)
+
+ stmt := program.Statements[0].(*ast.ExpressionStatement)
+ hash, ok := stmt.Expression.(*ast.HashLiteral)
+ if !ok {
+ t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression)
+ }
+
+ if len(hash.Pairs) != 3 {
+ t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs))
+ }
+
+ tests := map[string]func(ast.Expression){
+ "one": func(e ast.Expression) {
+ testInfixExpression(t, e, 0, "+", 1)
+ },
+ "two": func(e ast.Expression) {
+ testInfixExpression(t, e, 10, "-", 8)
+ },
+ "three": func(e ast.Expression) {
+ testInfixExpression(t, e, 15, "/", 5)
+ },
+ }
+
+ for key, value := range hash.Pairs {
+ literal, ok := key.(*ast.StringLiteral)
+ if !ok {
+ t.Errorf("key is not ast.StringLiteral. got=%T", key)
+ continue
+ }
+
+ testFunc, ok := tests[literal.Value]
+ if !ok {
+ t.Errorf("no test function for key %q found", literal.Value)
+ continue
+ }
+
+ testFunc(value)
+ }
+}