From ca46690f9166681e4b32af90e28fb215c12f76c0 Mon Sep 17 00:00:00 2001 From: Bobby Date: Wed, 10 Apr 2024 20:42:41 +0000 Subject: hashes --- parser/parser.go | 30 +++++++++++++++ parser/parser_test.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) (limited to 'parser') 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) + } +} -- cgit v1.2.3