summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2025-12-03 15:28:18 +0100
committerMarc Vertes <mvertes@free.fr>2025-12-03 15:28:18 +0100
commit90284c5bedc5ab7bb442b34ef470744578dcd266 (patch)
tree09f28c2092ed28b0bbcedb15fccd6fb0846e64d2
parent3d64f909cfb55d8886ac4b4839a98f8f6cdc98e7 (diff)
feat: support literal struct expressions with keyed elements
-rw-r--r--comp/compiler.go24
-rw-r--r--interp/interpreter_test.go2
-rw-r--r--parser/expr.go36
-rw-r--r--vm/type.go10
4 files changed, 67 insertions, 5 deletions
diff --git a/comp/compiler.go b/comp/compiler.go
index 7b12086..7c21d46 100644
--- a/comp/compiler.go
+++ b/comp/compiler.go
@@ -48,6 +48,7 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
log.Println("Codegen tokens:", tokens)
fixList := parser.Tokens{} // list of tokens to fix after all necessary information is gathered
stack := []*parser.Symbol{} // for symbolic evaluation, type checking, etc
+ keyList := []string{}
emit := func(t scanner.Token, op vm.Op, arg ...int) {
_, file, line, _ := runtime.Caller(1)
@@ -152,14 +153,31 @@ func (c *Compiler) Generate(tokens parser.Tokens) (err error) {
}
emit(t, vm.CallX, t.Beg)
+ case lang.Colon:
+ // Keyed element in a literal composite expression:
+ // If the key is an ident (field name), then push it on the keystack,
+ // to be computed at compile time in Composite handling.
+ // Or generate instructions so key will be computed at runtime.
+ if tokens[i-1].Tok == lang.Ident {
+ keyList = append(keyList, tokens[i-1].Str)
+ }
+
case lang.Composite:
d := top() // let the type symbol on the stack, may be required for assignment
switch d.Type.Rtype.Kind() {
case reflect.Struct:
emit(t, vm.Fnew, d.Index)
- nf := d.Type.Rtype.NumField()
- for i := 0; i < nf; i++ {
- emit(t, vm.FieldSet, i)
+ if len(keyList) == 0 {
+ nf := d.Type.Rtype.NumField()
+ for i := 0; i < nf; i++ {
+ emit(t, vm.FieldSet, i)
+ }
+ } else {
+ for _, fname := range keyList {
+ i := d.Type.FieldNameIndex(fname)
+ emit(t, vm.FieldSet, i...)
+ }
+ keyList = []string{}
}
default:
return fmt.Errorf("composite kind not supported yet: %v", d.Type.Rtype.Kind())
diff --git a/interp/interpreter_test.go b/interp/interpreter_test.go
index 42cb7e2..4f5690d 100644
--- a/interp/interpreter_test.go
+++ b/interp/interpreter_test.go
@@ -260,5 +260,7 @@ func TestComposite(t *testing.T) {
{src: "t := struct{}{}; t", res: "{}"},
{src: `type T struct {}; var t T; t = T{}; t`, res: "{}"},
{src: `type T struct{N int; S string}; var t T; t = T{2, "foo"}; t`, res: `{2 foo}`},
+ {src: `type T struct{N int; S string}; t := T{2, "foo"}; t`, res: `{2 foo}`},
+ {src: `type T struct{N int; S string}; t := T{S: "foo"}; t`, res: `{0 foo}`},
})
}
diff --git a/parser/expr.go b/parser/expr.go
index f2dbce3..1732b8d 100644
--- a/parser/expr.go
+++ b/parser/expr.go
@@ -15,6 +15,7 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
var ops, selectors Tokens
var vl int
var selectorIndex string
+
//
// Process tokens from last to first, the goal is to reorder the tokens in
// a stack machine processing order, so it can be directly interpreted.
@@ -30,6 +31,7 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
out = append(out, fid)
return out, err
}
+
for i := len(in) - 1; i >= 0; i-- {
t := in[i]
// temporary assumptions: binary operators, returning 1 value
@@ -48,14 +50,22 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
}
out = append(out, t)
vl++
+
+ case lang.Colon:
+ // Make ':' a key-value operator for literal composite.
+ ops = append(ops, t)
+
case lang.Period:
t.Str += selectorIndex
selectors = append(Tokens{t}, selectors...)
continue
+
case lang.Int, lang.String:
out = append(out, t)
vl++
- case lang.Define, lang.Add, lang.Sub, lang.Assign, lang.Equal, lang.Greater, lang.Less, lang.Mul, lang.Land, lang.Lor, lang.Shl, lang.Shr, lang.Not, lang.And:
+
+ case lang.Define, lang.Add, lang.Sub, lang.Assign, lang.Equal, lang.Greater, lang.Less,
+ lang.Mul, lang.Land, lang.Lor, lang.Shl, lang.Shr, lang.Not, lang.And:
if i == 0 || in[i-1].Tok.IsOperator() {
// An operator preceded by an operator or no token is unary.
t.Tok = lang.UnaryOp[t.Tok]
@@ -71,6 +81,7 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
if vl < 2 {
ops = append(ops, t)
}
+
case lang.ParenBlock:
// If the previous token is an arithmetic, logic or assign operator then
// this parenthesis block is an enclosed expr, otherwise a call expr.
@@ -85,9 +96,11 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
out = append(out, t)
vl++
ops = append(ops, scanner.Token{Tok: lang.Call, Pos: t.Pos, Beg: p.numItems(t.Block(), lang.Comma)})
+
case lang.BraceBlock:
// the block can be a func body or a composite type content.
- // In both cases it is preceded by a type definition. We must determine the starting token of type def,
+ // In both cases it is preceded by a type definition.
+ // We must determine the starting token of type def,
// parse the type def, and substitute the type def by a single ident.
// TODO: handle implicit type in composite expression.
ti := p.typeStartIndex(in[:len(in)-1])
@@ -103,19 +116,24 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
i = ti
vl += 2
ops = append(ops, scanner.Token{Tok: lang.Composite, Pos: t.Pos})
+
case lang.BracketBlock:
out = append(out, t)
vl++
ops = append(ops, scanner.Token{Tok: lang.Index, Pos: t.Pos})
+
case lang.Comment:
return out, nil
+
default:
return nil, fmt.Errorf("invalid expression: %v: %q", t.Tok, t.Str)
}
+
if len(selectors) > 0 {
out = append(out, selectors...)
selectors = nil
}
+
if lops, lout := len(ops), len(out); lops > 0 && vl > lops {
op := ops[lops-1]
ops = ops[:lops-1]
@@ -137,6 +155,7 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
if out, err = p.parseLogical(out); err != nil {
return out, err
}
+
if l := len(out) - 1; l >= 0 && (out[l].Tok == lang.Define || out[l].Tok == lang.Assign) {
// Handle the assignment of a logical expression.
s1 := p.subExprLen(out[:l])
@@ -166,6 +185,7 @@ func (p *Parser) parseExpr(in Tokens) (out Tokens, err error) {
out2 = append(out2, toks...)
out = append(out2, out[i+1:]...)
}
+
log.Println("Final out:", out)
return out, err
}
@@ -174,6 +194,7 @@ func (p *Parser) parseExprStr(s string) (tokens Tokens, err error) {
if tokens, err = p.Scan(s, false); err != nil {
return tokens, err
}
+
var result Tokens
for _, sub := range tokens.Split(lang.Comma) {
toks, err := p.parseExpr(sub)
@@ -182,6 +203,7 @@ func (p *Parser) parseExprStr(s string) (tokens Tokens, err error) {
}
result = append(toks, result...)
}
+
return result, err
}
@@ -194,23 +216,28 @@ func (p *Parser) parseLogical(in Tokens) (out Tokens, err error) {
if l < 0 || !in[l].Tok.IsLogicalOp() {
return in, nil
}
+
xp := strconv.Itoa(p.labelCount[p.scope])
p.labelCount[p.scope]++
rhsIndex := p.subExprLen(in[:l])
+
lhs, err := p.parseLogical(in[l-rhsIndex : l])
if err != nil {
return out, err
}
+
rhs, err := p.parseLogical(in[:l-rhsIndex])
if err != nil {
return out, err
}
out = append(out, lhs...)
+
if in[l].Tok == lang.Lor {
out = append(out, scanner.Token{Tok: lang.JumpSetTrue, Str: p.scope + "x" + xp})
} else {
out = append(out, scanner.Token{Tok: lang.JumpSetFalse, Str: p.scope + "x" + xp})
}
+
out = append(out, rhs...)
out = append(out, scanner.Token{Tok: lang.Label, Str: p.scope + "x" + xp})
return out, err
@@ -220,20 +247,25 @@ func (p *Parser) parseLogical(in Tokens) (out Tokens, err error) {
func (p *Parser) subExprLen(in Tokens) int {
l := len(in) - 1
last := in[l]
+
switch last.Tok {
case lang.Int, lang.Float, lang.String, lang.Char, lang.Ident, lang.ParenBlock, lang.BracketBlock:
return 1
+
case lang.Call:
s1 := p.subExprLen(in[:l])
return 1 + s1 + p.subExprLen(in[:l-s1])
// TODO: add selector and index operators when ready
}
+
if last.Tok.IsBinaryOp() {
s1 := p.subExprLen(in[:l])
return 1 + s1 + p.subExprLen(in[:l-s1])
}
+
if last.Tok.IsUnaryOp() {
return 1 + p.subExprLen(in[:l])
}
+
return 0 // should not occur. TODO: diplay some error here.
}
diff --git a/vm/type.go b/vm/type.go
index 5f91eee..782aa28 100644
--- a/vm/type.go
+++ b/vm/type.go
@@ -94,3 +94,13 @@ func StructOf(fields []*Type) *Type {
}
return &Type{Rtype: reflect.StructOf(rf)}
}
+
+// FieldNameIndex returns the index of struct field name.
+func (t *Type) FieldNameIndex(name string) []int {
+ for _, f := range reflect.VisibleFields(t.Rtype) {
+ if f.Name == name {
+ return f.Index
+ }
+ }
+ return nil
+}