Dict literal now accepts expressions as keys. Key values are computed at evalutetion time and still must be integer or string

This commit is contained in:
2026-05-20 05:14:22 +02:00
parent 1aea1c14d2
commit 1055569dd6
6 changed files with 60 additions and 46 deletions
+8 -1
View File
@@ -30,7 +30,14 @@ func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if param, err = tree.Compute(ctx); err != nil {
break
}
items[key] = param
var keyValue any
if keyValue, err = (key.(*scan.Term)).Compute(ctx); err == nil {
if kern.IsInteger(keyValue) || kern.IsString(keyValue) {
items[keyValue] = param
} else {
err = key.(*scan.Term).Errorf("dict key can be integer or string, got %s", kern.TypeName(keyValue))
}
}
}
if err == nil {
v = &items
+39 -22
View File
@@ -257,24 +257,41 @@ func (parser *parser) parseIterDef(scanner *scan.Scanner, ctx parserContext) (su
return
}
func (parser *parser) parseDictKey(scanner *scan.Scanner) (key any, err error) {
tk := parser.Next(scanner)
if tk.Sym == scan.SymError {
err = tk.Error()
return
}
if tk.Sym == scan.SymClosedBrace || tk.Sym == scan.SymEos {
return
}
if tk.Sym == scan.SymInteger || tk.Sym == scan.SymString {
tkSep := parser.Next(scanner)
if tkSep.Sym != scan.SymColon {
// func (parser *parser) parseDictKey(scanner *scan.Scanner) (key any, err error) {
// tk := parser.Next(scanner)
// if tk.Sym == scan.SymError {
// err = tk.Error()
// return
// }
// if tk.Sym == scan.SymClosedBrace || tk.Sym == scan.SymEos {
// return
// }
// if tk.Sym == scan.SymInteger || tk.Sym == scan.SymString || tk.Sym == scan.SymIdentifier {
// tkSep := parser.Next(scanner)
// if tkSep.Sym != scan.SymColon {
// err = tkSep.ErrorExpectedGot(":")
// } else {
// key = tk.Value
// }
// } else {
// err = tk.ErrorExpectedGot("dictionary-key or }")
// }
// return
// }
func (parser *parser) parseDictKey(scanner *scan.Scanner) (key *scan.Term, err error) {
var keyTree *scan.Ast
if keyTree, err = parser.parseItem(scanner, parserNoFlags, scan.SymColon, scan.SymClosedBrace); err == nil {
key = keyTree.Root()
tkSep := scanner.Previous()
sym := tkSep.Sym
if sym == scan.SymClosedBrace || sym == scan.SymEos {
if key != nil {
err = tkSep.ErrorExpectedGot(":")
}
} else if sym != scan.SymColon {
err = tkSep.ErrorExpectedGot(":")
} else {
key = tk.Value
}
} else {
err = tk.ErrorExpectedGot("dictionary-key or }")
}
return
}
@@ -284,11 +301,11 @@ func (parser *parser) parseDictionary(scanner *scan.Scanner, ctx parserContext)
lastSym := scan.SymUnknown
itemExpected := false
for lastSym != scan.SymClosedBrace && lastSym != scan.SymEos {
var subTree *scan.Ast
var valueTree *scan.Ast
var key any
if key, err = parser.parseDictKey(scanner); err != nil {
break
} else if key == nil {
} else if key.(*scan.Term) == nil {
tk := scanner.Previous()
lastSym = tk.Sym
if itemExpected {
@@ -296,9 +313,9 @@ func (parser *parser) parseDictionary(scanner *scan.Scanner, ctx parserContext)
}
break
}
if subTree, err = parser.parseItem(scanner, ctx, scan.SymComma, scan.SymClosedBrace); err == nil {
if subTree.Root() != nil {
args[key] = subTree.Root()
if valueTree, err = parser.parseItem(scanner, ctx, scan.SymComma, scan.SymClosedBrace); err == nil {
if valueTree.Root() != nil {
args[key] = valueTree.Root()
} else /*if key != nil*/ {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("dictionary-value")
@@ -464,7 +481,7 @@ func (parser *parser) parseGeneral(scanner *scan.Scanner, ctx parserContext, ter
selectorTerm = nil
continue
} else {
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.Source())
err = tk.ErrorExpectedOneOfGot(termSymbols...)
break
}
}
+1 -1
View File
@@ -98,7 +98,7 @@ func (ast *Ast) insert(tree, node *Term) (root *Term, err error) {
root = node
tree.SetParent(node)
} else {
err = node.Errorf("two adjacent operators: %q and %q", tree, node)
err = node.Errorf("two adjacent operators: %q and %q", tree, node.Source())
}
return
}
-19
View File
@@ -188,25 +188,6 @@ func StringEndsWithOperator(s string) bool {
return endingOperator(s) != SymNone
}
// func endingOperator(s string) (sym Symbol) {
// var matchLength = 0
// sym = SymNone
// lower := strings.TrimRight(strings.ToLower(s), " \t")
// for symbol, spec := range symbolMap {
// if strings.HasSuffix(lower, spec.repr) {
// if len(spec.repr) > matchLength {
// matchLength = len(spec.repr)
// if spec.kind == symClassOperator && (spec.opType == PosInfix || spec.opType == PosPrefix) {
// sym = symbol
// } else {
// sym = SymNone
// }
// }
// }
// }
// return
// }
func endingOperator(s string) (sym Symbol) {
var matchLength = 0
var repr string
+4
View File
@@ -136,3 +136,7 @@ func (tk *Token) ErrorExpectedGotStringWithPrefix(prefix, symbol, got string) (e
err = fmt.Errorf("[%d:%d] %s %s, got `%s`", tk.row, tk.col, prefix, symbol, got)
return
}
func (tk *Token) ErrorExpectedOneOfGot(expected ...Symbol) (err error) {
return tk.ErrorExpectedGotStringWithPrefix("expected one of ", SymListToString(expected, true), SymToString(tk.Sym))
}
+8 -3
View File
@@ -39,11 +39,16 @@ func TestDictParser(t *testing.T) {
/* 16 */ {`D={1:"a"}; D.2`, nil, `[1:15] key 2 not found`},
/* 17 */ {`D={1:"a"}; D."2"`, nil, `[1:17] key "2" not found`},
/* 18 */ {`D={"a":1, "b":2}; D.a = 10; D.a`, int64(10), nil},
// /* 19 */ {`D={"a": {"one": 1}}; D.a."one" = 10; D["a"]["one"]`, int64(10), nil},
/* 19 */ {`k="a"; D={k: 10}`, kern.NewDict(map[any]any{"a": int64(10)}), nil},
/* 20 */ {`k=[<1,2>]; D={k: 10}`, nil, `[1:16] dict key can be integer or string, got lisked-list`},
/* 21 */ {`f=func(n){"x"+n}; d{f(5):10}`, nil, `[0:0] two adjacent operators: "d" and "{}"`},
/* 22 */ {`f=func(n){"x"+n}; d={f(5):10}`, kern.NewDict(map[any]any{"x5": int64(10)}), nil},
/* 23 */ {`f=func(n){"x"+n}; d={f(5); "z":10}`, nil, "[1:27] expected one of `:`, `}`, got `;`"},
/* 24 */ {`f=func(n){"x"+n}; d={f(5) but "z":10}`, kern.NewDict(map[any]any{"z": int64(10)}), nil},
}
// runTestSuiteSpec(t, section, inputs, 16)
runTestSuite(t, section, inputs)
runTestSuiteSpec(t, section, inputs, 1, 23, 24)
// runTestSuite(t, section, inputs)
}
func TestAccessSubFields(t *testing.T) {