From 1055569dd6e95d2593e2833fb587c1071b043a5c Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Wed, 20 May 2026 05:14:22 +0200 Subject: [PATCH] Dict literal now accepts expressions as keys. Key values are computed at evalutetion time and still must be integer or string --- operand-dict.go | 9 ++++++- parser.go | 61 +++++++++++++++++++++++++++++----------------- scan/ast.go | 2 +- scan/symbol-map.go | 19 --------------- scan/token.go | 4 +++ t_dict_test.go | 11 ++++++--- 6 files changed, 60 insertions(+), 46 deletions(-) diff --git a/operand-dict.go b/operand-dict.go index 1977d9d..103d91a 100644 --- a/operand-dict.go +++ b/operand-dict.go @@ -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 diff --git a/parser.go b/parser.go index ce28a55..1a80594 100644 --- a/parser.go +++ b/parser.go @@ -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 } } diff --git a/scan/ast.go b/scan/ast.go index 1077059..20dd117 100644 --- a/scan/ast.go +++ b/scan/ast.go @@ -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 } diff --git a/scan/symbol-map.go b/scan/symbol-map.go index 760669a..e8a22ed 100644 --- a/scan/symbol-map.go +++ b/scan/symbol-map.go @@ -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 diff --git a/scan/token.go b/scan/token.go index fa6c855..628b5c6 100644 --- a/scan/token.go +++ b/scan/token.go @@ -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)) +} diff --git a/t_dict_test.go b/t_dict_test.go index 644bbac..9be5b74 100644 --- a/t_dict_test.go +++ b/t_dict_test.go @@ -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) {