diff options
| author | Marc Vertes <mvertes@free.fr> | 2023-11-15 11:59:15 +0100 |
|---|---|---|
| committer | Marc Vertes <mvertes@free.fr> | 2023-11-15 11:59:15 +0100 |
| commit | a4d7fb2da6a8390b818dae8d07391c7d76e365e9 (patch) | |
| tree | 166d1ed4bb07337ead19fd4f7ebc8d79885e2cfd /parser/decl.go | |
| parent | 2eab5877e1c634db872b595dd2414f4031ae4eb5 (diff) | |
parser: hande const declarations
Only symbols are produced, no bytecode is emitted. The constant
expressions are evaluated at compile time using the stdlib package
go/constant. The parser handles implicit repetition of the last
non-empty expression list. The iota symbol is reset to 0 and
incremented for each line of a const block.
To be done in a next commit: type conversions.
Diffstat (limited to 'parser/decl.go')
| -rw-r--r-- | parser/decl.go | 180 |
1 files changed, 178 insertions, 2 deletions
diff --git a/parser/decl.go b/parser/decl.go index 6c10363..ba68da0 100644 --- a/parser/decl.go +++ b/parser/decl.go @@ -2,13 +2,190 @@ package parser import ( "errors" - "log" + "go/constant" + "go/token" "strings" "github.com/gnolang/parscan/lang" "github.com/gnolang/parscan/scanner" ) +func (p *Parser) ParseConst(in Tokens) (out Tokens, err error) { + if len(in) < 2 { + return out, errors.New("missing expression") + } + if in[1].Id != lang.ParenBlock { + return p.parseConstLine(in[1:]) + } + if in, err = p.Scan(in[1].Block(), false); err != nil { + return out, err + } + var cnt int64 + p.symbols["iota"].cval = constant.Make(cnt) + var prev Tokens + for i, lt := range in.Split(lang.Semicolon) { + if i > 0 && len(lt) == 1 { + lt = append(Tokens{lt[0]}, prev...) // Handle implicit repetition of the previous expression. + } + ot, err := p.parseConstLine(lt) + if err != nil { + return out, err + } + out = append(out, ot...) + prev = lt[1:] + cnt++ + p.symbols["iota"].cval = constant.Make(cnt) + } + return out, err +} + +func (p *Parser) parseConstLine(in Tokens) (out Tokens, err error) { + decl := in + var assign Tokens + if i := decl.Index(lang.Assign); i >= 0 { + assign = decl[i+1:] + decl = decl[:i] + } + var vars []string + if _, vars, err = p.parseParamTypes(decl, parseTypeVar); err != nil { + if errors.Is(err, missingTypeError) { + for _, lt := range decl.Split(lang.Comma) { + vars = append(vars, lt[0].Str) + // TODO: compute type from rhs + p.addSym(unsetAddr, strings.TrimPrefix(p.scope+"/"+lt[0].Str, "/"), nil, symConst, nil, false) + } + } else { + return out, err + } + } + values := assign.Split(lang.Comma) + if len(values) == 1 && len(values[0]) == 0 { + values = nil + } + for i, v := range values { + if v, err = p.ParseExpr(v); err != nil { + return out, err + } + cval, _, err := p.evalConstExpr(v) + if err != nil { + return out, err + } + name := strings.TrimPrefix(p.scope+"/"+vars[i], "/") + p.symbols[name] = &symbol{ + kind: symConst, + index: unsetAddr, + cval: cval, + value: constValue(cval), + local: p.funcScope != "", + used: true, + } + // TODO: type conversion when applicable. + } + return out, err +} + +func (p *Parser) evalConstExpr(in Tokens) (cval constant.Value, length int, err error) { + l := len(in) - 1 + if l < 0 { + return nil, 0, errors.New("missing argument") + } + t := in[l] + id := t.Id + switch { + case id.IsBinaryOp(): + op1, l1, err := p.evalConstExpr(in[:l]) + if err != nil { + return nil, 0, err + } + op2, l2, err := p.evalConstExpr(in[:l-l1]) + if err != nil { + return nil, 0, err + } + length = 1 + l1 + l2 + tok := gotok[id] + if id.IsBoolOp() { + return constant.MakeBool(constant.Compare(op1, tok, op2)), length, err + } + if id == lang.Shl || id == lang.Shr { + s, ok := constant.Uint64Val(op2) + if !ok { + return nil, 0, errors.New("invalid shift parameter") + } + return constant.Shift(op1, tok, uint(s)), length, err + } + if tok == token.QUO && op1.Kind() == constant.Int && op2.Kind() == constant.Int { + tok = token.QUO_ASSIGN // Force int result, see https://pkg.go.dev/go/constant#BinaryOp + } + return constant.BinaryOp(op1, tok, op2), length, err + case id.IsUnaryOp(): + op1, l1, err := p.evalConstExpr(in[:l]) + if err != nil { + return nil, 0, err + } + return constant.UnaryOp(gotok[id], op1, 0), 1 + l1, err + case id.IsLiteral(): + return constant.MakeFromLiteral(t.Str, gotok[id], 0), 1, err + case id == lang.Ident: + s, _, ok := p.getSym(t.Str, p.scope) + if !ok { + return nil, 0, errors.New("symbol not found") + } + if s.kind != symConst { + return nil, 0, errors.New("symbol is not a constant") + } + return s.cval, 1, err + case id == lang.Call: + // TODO: implement support for type conversions and builtin calls. + panic("not implemented yet") + default: + return nil, 0, errors.New("invalid constant expression") + } +} + +func constValue(c constant.Value) any { + switch c.Kind() { + case constant.Bool: + return constant.BoolVal(c) + case constant.String: + return constant.StringVal(c) + case constant.Int: + v, _ := constant.Int64Val(c) + return int(v) + case constant.Float: + v, _ := constant.Float64Val(c) + return v + } + return nil +} + +var gotok = map[lang.TokenId]token.Token{ + lang.Char: token.CHAR, + lang.Imag: token.IMAG, + lang.Int: token.INT, + lang.Float: token.FLOAT, + lang.Add: token.ADD, + lang.Sub: token.SUB, + lang.Mul: token.MUL, + lang.Quo: token.QUO, + lang.Rem: token.REM, + lang.And: token.AND, + lang.Or: token.OR, + lang.Xor: token.XOR, + lang.Shl: token.SHL, + lang.Shr: token.SHR, + lang.AndNot: token.AND_NOT, + lang.Equal: token.EQL, + lang.Greater: token.GTR, + lang.Less: token.LSS, + lang.GreaterEqual: token.GEQ, + lang.LessEqual: token.LEQ, + lang.NotEqual: token.NEQ, + lang.Plus: token.ADD, + lang.Minus: token.SUB, + lang.BitComp: token.XOR, + lang.Not: token.NOT, +} + func (p *Parser) ParseType(in Tokens) (out Tokens, err error) { if len(in) < 2 { return out, missingTypeError @@ -91,7 +268,6 @@ func (p *Parser) parseVarLine(in Tokens) (out Tokens, err error) { if len(values) == 1 && len(values[0]) == 0 { values = nil } - log.Println("ParseVar:", vars, values, len(values)) for i, v := range values { if v, err = p.ParseExpr(v); err != nil { return out, err |
