Skip to content

Commit f9b03a6

Browse files
ldemaillyccoVeille
andauthored
Adding map.key as short for map["key"] (#117)
* Adding map.key as short for map["key"] * readme typo * readme update for actual run * fix for continuation needed in middle of expression like `map = {` * fix precedence for dot notation * new file missing * Now show the info.all_ids stack as an array but each layer is a map with actual values (can get long/expensive though) * Update examples/sample.gr Co-authored-by: ccoVeille <[email protected]> --------- Co-authored-by: ccoVeille <[email protected]>
1 parent 64ac9f3 commit f9b03a6

File tree

14 files changed

+133
-60
lines changed

14 files changed

+133
-60
lines changed

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ $ fact = func(n) {if (n<=1) {return 1} n*self(n-1)} // could be n*fact(n-1) too
3737
return 1
3838
}
3939
n * self(n - 1)
40-
}
40+
} // could be n*fact(n-1) too
4141
== Eval ==> func(n){if n<=1{return 1}n*self(n-1)}
4242
$ n=fact(6)
4343
== Parse ==> n = fact(6)
@@ -48,20 +48,20 @@ $ m=fact(7)
4848
$ m/n
4949
== Parse ==> m / n
5050
== Eval ==> 7
51-
$ func fx(n) {if n>0 {return fx(n-1)}; info["all_ids"]}; fx(3)
51+
$ func fx(n) {if n>0 {return fx(n-1)}; info.all_ids}; fx(3)
5252
== Parse ==> func fx(n) {
5353
if n > 0 {
5454
return fx(n - 1)
5555
}
56-
(info["all_ids"])
56+
info.all_ids
5757
}
5858
fx(3)
59-
== Eval ==> {0:["E","PI","abs","fx","log2","printf"],1:["n","self"],2:["fx","n","self"],3:["fx","n","self"],4:["fx","n","self"]}
60-
$ info["gofuncs"]
61-
== Parse ==> (info["gofuncs"])
62-
== Eval ==> ["acos","asin","atan","ceil","cos","exp","floor","ln","log10","pow","round","sin","sprintf","sqrt","tan","trunc"]
63-
$ info["keywords"]
64-
== Parse ==> (info["keywords"])
59+
== Eval ==> {0:["E","PI","abs","fact","fx","log2","n","printf"],1:["n","self"],2:["fx","n","self"],3:["fx","n","self"],4:["fx","n","self"]}
60+
$ info["gofuncs"] // other way to access map keys, for when they aren't strings for instance
61+
== Parse ==> info["gofuncs"] // other way to access map keys, for when they aren't strings for instance
62+
== Eval ==> ["acos","asin","atan","ceil","cos","eval","exp","floor","json","ln","log10","pow","round","sin","sprintf","sqrt","tan","trunc","unjson"]
63+
$ info.keywords
64+
== Parse ==> info.keywords
6565
== Eval ==> ["else","error","false","first","func","if","len","log","macro","print","println","quote","rest","return","true","unquote"]
6666
```
6767

@@ -71,7 +71,7 @@ Functional int, float, string and boolean expressions
7171

7272
Functions, lambdas, closures (including recursion in anonymous functions, using `self()`)
7373

74-
Arrays, maps
74+
Arrays, maps (including map.key as map["key"] shorthand access)
7575

7676
print, log
7777

ast/ast.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,11 @@ func (ie IndexExpression) PrettyPrint(out *PrintState) *PrintState {
422422
out.Print("(")
423423
}
424424
ie.Left.PrettyPrint(out)
425-
out.Print("[")
425+
out.Print(ie.Literal())
426426
ie.Index.PrettyPrint(out)
427-
out.Print("]")
427+
if ie.Token.Type() == token.LBRACKET {
428+
out.Print("]")
429+
}
428430
if out.ExpressionLevel > 0 {
429431
out.Print(")")
430432
}

eval/eval.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,13 @@ func (s *State) evalInternal(node any) object.Object { //nolint:funlen // quite
217217
return s.evalMapLiteral(node)
218218
case *ast.IndexExpression:
219219
left := s.evalInternal(node.Left)
220-
index := s.evalInternal(node.Index)
220+
var index object.Object
221+
if node.Token.Type() == token.DOT {
222+
// index is the string value and not an identifier.
223+
index = object.String{Value: node.Index.Value().Literal()}
224+
} else {
225+
index = s.evalInternal(node.Index)
226+
}
221227
return evalIndexExpression(left, index)
222228
case *ast.Comment:
223229
return object.NULL

eval/eval_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func TestEvalBooleanExpression(t *testing.T) {
137137
{"(1 > 2) == false", true},
138138
{`"hello" == "world"`, false},
139139
{`"hello" == "hello"`, true},
140+
{`info["all_ids"] == info.all_ids`, true},
140141
}
141142

142143
for _, tt := range tests {
@@ -611,14 +612,26 @@ func TestMapIndexExpressions(t *testing.T) {
611612
`{"foo": 5}["foo"]`,
612613
5,
613614
},
615+
{
616+
`{"foo": 5}.foo`, // dot notation
617+
5,
618+
},
614619
{
615620
`{"foo": 5}["bar"]`,
616621
nil,
617622
},
623+
{
624+
`{"foo": 5}.bar`,
625+
nil,
626+
},
618627
{
619628
`key = "foo"; {"foo": 5}[key]`,
620629
5,
621630
},
631+
{
632+
`key = "foo"; {"foo": 5}.key`, // doesn't work (on puprpose), dot notation is string not eval.
633+
nil,
634+
},
622635
{
623636
`{}["foo"]`,
624637
nil,

examples/namespaced_function.gr

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// A map is basically defining a namespace thanks to the DOT access syntax.
2+
n1 = {
3+
"f1": func() {
4+
println("f1")
5+
},
6+
"f2": func(x) {
7+
x+1
8+
},
9+
}
10+
11+
n1.f1()
12+
n1.f2(41)

examples/sample.gr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ m={"key": a, 73: 29} // so do maps
2828
println("m is:", m) // stdout print
2929
println("Outputting a smiley: 😀")
3030

31-
first(m["key"]) // get the value from key from map, which is an array, and the first element of the array is our factorial 5
32-
// could also have been m["key"][0]
31+
first(m.key) // get the value from key from map, which is an array, and the first element of the array is our factorial 5
32+
// (dot notation .key is shorthand for ["key"]), could also have been first(m["key"]) or m["key"][0] or m.key[0]
3333

3434
// ^^^ gorepl sample.gr should output 120

lexer/lexer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ func (l *Lexer) NextToken() *token.Token {
7777
l.pos++
7878
return token.ConstantTokenChar2(ch, nextChar)
7979
}
80+
if !isDigit(nextChar) {
81+
return token.ConstantTokenChar(ch) // DOT token
82+
}
8083
// number can start with . eg .5
8184
return token.Intern(l.readNumber(ch))
8285
default:

lexer/lexer_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ j--
4343
/* This is a
4444
multiline comment */
4545
..
46+
a.b
4647
@
4748
`
4849
tests := []struct {
@@ -163,6 +164,9 @@ j--
163164
{token.BLOCKCOMMENT, "/*/*/"},
164165
{token.BLOCKCOMMENT, "/* This is a\n multiline comment */"},
165166
{token.DOTDOT, ".."},
167+
{token.IDENT, "a"},
168+
{token.DOT, "."},
169+
{token.IDENT, "b"},
166170
{token.ILLEGAL, "@"},
167171
{token.EOF, ""},
168172
}
@@ -279,7 +283,7 @@ func TestReadFloatNumber(t *testing.T) {
279283
{"1.23e-abc", "1.23"},
280284
{"123..", "123."},
281285
{"1..23", "1."},
282-
{".e3", "."},
286+
{".e3", "."}, // that's now the DOT and not a (bad) float.
283287
{"100..", "100."},
284288
{"1000_000.5", "1000_000.5"},
285289
{"1000_000.5_6", "1000_000.5_6"},
@@ -293,6 +297,12 @@ func TestReadFloatNumber(t *testing.T) {
293297
tok := l.NextToken()
294298
tokT := tok.Type()
295299
result := tok.Literal()
300+
if tt.expected == "." {
301+
if tokT != token.DOT {
302+
t.Errorf("input: %q, expected a DOT, got: %#v", tt.input, tok.DebugString())
303+
}
304+
continue
305+
}
296306
isFloat := (tokT == token.FLOAT)
297307
if !isFloat {
298308
t.Errorf("input: %q, expected a float number, got: %#v", tt.input, tok.DebugString())

main_test.txtar

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,20 @@ stdout '^3$'
113113
!stderr .
114114

115115
# eval() runs in the same context as when called
116-
grol -quiet -c 'func foo(x) {eval("info")};foo(1)["all_ids"][1]'
117-
stdout '^\["self","x"]$'
116+
grol -quiet -c 'func foo(x) {eval("info")};foo(42)["all_ids"][1]'
117+
stdout '^{"self":func foo\(x\){eval\("info"\)},"x":42}$'
118118
!stderr .
119119

120120
# json of (nested) arrays
121121
grol -quiet -c 'a=[1,2,3,["a", "b"],4];println(json(a))'
122122
stdout '^\[1,2,3,\["a","b"],4]$'
123123
!stderr .
124+
125+
# dot notation and priority with functions
126+
grol -quiet -c 'n1={"f1":func(){println("f1")},"f2":func(x){x+1}}n1.f1()n1.f2(41)'
127+
stdout '^f1\n42$'
128+
!stderr .
129+
124130
-- foo.inp --
125131
foo
126132
-- sample_test.gr --

object/state.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,20 @@ func (e *Environment) BaseInfo() Map {
6969
}
7070

7171
func (e *Environment) Info() Object {
72-
allKeys := make(Map, e.depth)
72+
allKeys := make([]Object, e.depth+1)
7373
for {
74-
keys := make([]Object, 0, e.Len())
75-
for k := range e.store {
76-
keys = append(keys, String{Value: k})
74+
val := make(Map, e.Len())
75+
for k, v := range e.store {
76+
val[String{Value: k}] = v
7777
}
78-
arr := Array{Elements: keys}
79-
sort.Sort(arr)
80-
allKeys[Integer{Value: int64(e.depth)}] = arr
78+
allKeys[e.depth] = val
8179
if e.outer == nil {
8280
break
8381
}
8482
e = e.outer
8583
}
8684
info := e.BaseInfo()
87-
info[String{"all_ids"}] = allKeys
85+
info[String{"all_ids"}] = Array{Elements: allKeys}
8886
return info
8987
}
9088

0 commit comments

Comments
 (0)