selectors support for hash
This commit is contained in:
@@ -577,6 +577,36 @@ func TestHashLiterals(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashSelectorExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
`{"foo": 5}.foo`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{"foo": 5}.bar`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`{}.foo`,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
integer, ok := tt.expected.(int)
|
||||
if ok {
|
||||
testIntegerObject(t, evaluated, int64(integer))
|
||||
} else {
|
||||
testNullObject(t, evaluated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
5
examples/selectors.monkey
Normal file
5
examples/selectors.monkey
Normal file
@@ -0,0 +1,5 @@
|
||||
let d = {"foo": 1, "bar": 2}
|
||||
|
||||
assert(d.foo == 1, "d.foo != 1")
|
||||
assert(d.bar == 2, "d.bar != 2")
|
||||
assert(d.bogus == null, "d.bogus != null")
|
||||
@@ -107,6 +107,8 @@ func (l *Lexer) NextToken() token.Token {
|
||||
tok = newToken(token.SEMICOLON, l.ch)
|
||||
case ',':
|
||||
tok = newToken(token.COMMA, l.ch)
|
||||
case '.':
|
||||
tok = newToken(token.DOT, l.ch)
|
||||
case '(':
|
||||
tok = newToken(token.LPAREN, l.ch)
|
||||
case ')':
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestNextToken(t *testing.T) {
|
||||
"foo bar"
|
||||
[1, 2];
|
||||
{"foo": "bar"}
|
||||
"foo \"bar\""
|
||||
d.foo
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
@@ -138,7 +138,9 @@ func TestNextToken(t *testing.T) {
|
||||
{token.COLON, ":"},
|
||||
{token.STRING, "bar"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.STRING, "foo \"bar\""},
|
||||
{token.IDENT, "d"},
|
||||
{token.DOT, "."},
|
||||
{token.IDENT, "foo"},
|
||||
{token.EOF, ""},
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ var precedences = map[token.TokenType]int{
|
||||
token.ASTERISK: PRODUCT,
|
||||
token.LPAREN: CALL,
|
||||
token.LBRACKET: INDEX,
|
||||
token.DOT: INDEX,
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -89,6 +90,7 @@ func New(l *lexer.Lexer) *Parser {
|
||||
p.registerInfix(token.LPAREN, p.parseCallExpression)
|
||||
p.registerInfix(token.LBRACKET, p.parseIndexExpression)
|
||||
p.registerInfix(token.ASSIGN, p.parseAssignmentExpression)
|
||||
p.registerInfix(token.DOT, p.parseSelectorExpression)
|
||||
|
||||
// Read two tokens, so curToken and peekToken are both set
|
||||
p.nextToken()
|
||||
@@ -570,3 +572,9 @@ func (p *Parser) parseComment() ast.Statement {
|
||||
func (p *Parser) parseNull() ast.Expression {
|
||||
return &ast.Null{Token: p.curToken}
|
||||
}
|
||||
|
||||
func (p *Parser) parseSelectorExpression(expression ast.Expression) ast.Expression {
|
||||
p.expectPeek(token.IDENT)
|
||||
index := &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
|
||||
return &ast.IndexExpression{Left: expression, Index: index}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,10 @@ func TestOperatorPrecedenceParsing(t *testing.T) {
|
||||
"add(a * b[2], b[1], 2 * [1, 2][1])",
|
||||
"add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))",
|
||||
},
|
||||
{
|
||||
"d.foo * d.bar",
|
||||
"((d[foo]) * (d[bar]))",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -831,6 +835,40 @@ func TestParsingArrayLiterals(t *testing.T) {
|
||||
testInfixExpression(t, array.Elements[2], 3, "+", 3)
|
||||
}
|
||||
|
||||
func TestParsingSelectorExpressions(t *testing.T) {
|
||||
input := "myHash.foo"
|
||||
|
||||
l := lexer.New(input)
|
||||
p := New(l)
|
||||
program := p.ParseProgram()
|
||||
checkParserErrors(t, p)
|
||||
stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
|
||||
t.Logf("stmt: %#v", stmt)
|
||||
|
||||
exp, ok := stmt.Expression.(*ast.IndexExpression)
|
||||
if !ok {
|
||||
t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression)
|
||||
}
|
||||
|
||||
ident, ok := exp.Left.(*ast.Identifier)
|
||||
if !ok {
|
||||
t.Fatalf("exp.Left not *ast.Identifier. got=%T", stmt.Expression)
|
||||
}
|
||||
|
||||
if !testIdentifier(t, ident, "myHash") {
|
||||
return
|
||||
}
|
||||
|
||||
index, ok := exp.Index.(*ast.StringLiteral)
|
||||
if !ok {
|
||||
t.Fatalf("exp.Index not *ast.StringLiteral. got=%T", stmt.Expression)
|
||||
}
|
||||
|
||||
if index.Value != "foo" {
|
||||
t.Fatalf("index.Value != \"foo\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingIndexExpressions(t *testing.T) {
|
||||
input := "myArray[1 + 1]"
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const (
|
||||
COMMA = ","
|
||||
SEMICOLON = ";"
|
||||
COLON = ":"
|
||||
DOT = "."
|
||||
|
||||
LPAREN = "("
|
||||
RPAREN = ")"
|
||||
|
||||
@@ -328,6 +328,16 @@ func TestHashLiterals(t *testing.T) {
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
func TestSelectorExpressions(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{`{"foo": 5}.foo`, 5},
|
||||
{`{"foo": 5}.bar`, nil},
|
||||
{`{}.foo`, nil},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
func TestIndexExpressions(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"[1, 2, 3][1]", 2},
|
||||
|
||||
Reference in New Issue
Block a user