package types import ( "git.smesh.lol/moxie/pkg/mxutil" "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/token" ) func (c *Checker) checkConstGroup(decls []*syntax.ConstDecl) { var prevType syntax.Expr var prevValues syntax.Expr for i, d := range decls { typ := d.Type vals := d.Values if vals == nil { typ = prevType vals = prevValues } else { prevType = typ prevValues = vals } c.checkConstDeclAt(d, typ, vals, int64(i)) } } func (c *Checker) checkConstDeclAt(d *syntax.ConstDecl, typExpr syntax.Expr, valExpr syntax.Expr, iotaVal int64) { var typ Type if typExpr != nil { typ = c.resolveTypeExpr(typExpr) } var vals []ConstVal if valExpr != nil { vals = c.evalConstExprList(valExpr, iotaVal) } // When no explicit type and the value is a type conversion (CallExpr // with a type name as Fun), infer the type from the conversion target. // e.g. const EOF = errors.Const("EOF") -> type is errors.Const var inferredTypes []Type if typ == nil && valExpr != nil { inferredTypes = c.inferConstTypeList(valExpr) } for i, name := range d.NameList { if name.Value == "_" { continue } obj, ok := c.pkg.Scope.Lookup(name.Value).(*TCConst) if !ok { continue } var cv ConstVal if i < len(vals) { cv = vals[i] } obj.Val = cv if typ != nil { obj.Typ = typ } else if i < len(inferredTypes) && inferredTypes[i] != nil { obj.Typ = inferredTypes[i] } else if cv != nil { obj.Typ = UntypedTypeOfCV(cv) } if c.info != nil { c.info.Defs[name] = obj } } } func (c *Checker) inferConstTypeList(e syntax.Expr) (ts []Type) { if l, ok := e.(*syntax.ListExpr); ok { var out []Type for _, el := range l.ElemList { push(out, c.inferConstType(el)) } return out } return []Type{c.inferConstType(e)} } func (c *Checker) inferConstType(e syntax.Expr) (t Type) { if e == nil { return nil } ce, ok := e.(*syntax.CallExpr) if !ok { return nil } if len(ce.ArgList) != 1 { return nil } return c.resolveTypeExpr(ce.Fun) } func (c *Checker) evalConstExprList(e syntax.Expr, iotaVal int64) (cs []ConstVal) { if l, ok := e.(*syntax.ListExpr); ok { var out []ConstVal for _, el := range l.ElemList { cv := c.evalConst(el, iotaVal) push(out, cv) } return out } v := c.evalConst(e, iotaVal) return []ConstVal{v} } func (c *Checker) evalConst(e syntax.Expr, iotaVal int64) (cv ConstVal) { if e == nil { return nil } switch ex := e.(type) { case *syntax.BasicLit: return EvalBasicLitLocal(ex) case *syntax.Name: if ex.Value == "iota" { return &ConstInt{V:iotaVal} } var obj Object if c.localScope != nil { _, obj = c.localScope.LookupParent(ex.Value) } if obj == nil { _, obj = c.lookup(ex.Value, c.pkg.Scope) } if obj == nil { mxutil.WriteStr(2, " CONST-LOOKUP-FAIL name=" | ex.Value | " pkg=" | c.pkg.Path | "\n") return nil } if k, matched := obj.(*TCConst); matched { if k.Val == nil { mxutil.WriteStr(2, " CONST-REF-NIL name=" | ex.Value | " pkg=" | c.pkg.Path | "\n") } return k.Val } return nil case *syntax.SelectorExpr: if pkgName, matched := ex.X.(*syntax.Name); matched { if _, imp := c.lookupType(pkgName.Value); imp != nil { if pn, ok2 := imp.(*PkgName); ok2 && pn.Imported != nil { if obj := pn.Imported.Scope.Lookup(ex.Sel.Value); obj != nil { if k, ok3 := obj.(*TCConst); ok3 { return k.Val } } } } } return nil case *syntax.Operation: return c.evalConstOp(ex, iotaVal) case *syntax.ParenExpr: return c.evalConst(ex.X, iotaVal) case *syntax.CallExpr: if id, matched := ex.Fun.(*syntax.Name); matched && id.Value == "len" && len(ex.ArgList) == 1 { if lit, matched2 := ex.ArgList[0].(*syntax.BasicLit); matched2 && lit.Kind == token.StringLit { lv := EvalBasicLitLocal(lit) if cs2, matched3 := lv.(*ConstStr); matched3 { return &ConstInt{V:int64(len(cs2.S))} } } lenArg := c.evalConst(ex.ArgList[0], iotaVal) if cs2, matched2 := lenArg.(*ConstStr); matched2 { return &ConstInt{V:int64(len(cs2.S))} } } if sel, matched := ex.Fun.(*syntax.SelectorExpr); matched { if pkg, matched2 := sel.X.(*syntax.Name); matched2 && pkg.Value == "unsafe" { switch sel.Sel.Value { case "Sizeof", "Alignof", "Offsetof": return &ConstInt{V:8} } } } if len(ex.ArgList) != 1 { return nil } arg := c.evalConst(ex.ArgList[0], iotaVal) if arg == nil { return nil } targetType := c.resolveTypeExpr(ex.Fun) if targetType == nil { funName := "?" if id, ok2 := ex.Fun.(*syntax.Name); ok2 { funName = id.Value } mxutil.WriteStr(2, " CONST-CONV-FAIL fun=" | funName | " pkg=" | c.pkg.Path | "\n") return nil } return ConvertConstLocal(arg, targetType) } return nil } func EvalBasicLitLocal(e *syntax.BasicLit) (c ConstVal) { switch e.Kind { case token.IntLit: n := ParseIntLocal(e.Value) return &ConstInt{V:n} case token.FloatLit: f := parseFloatLocal(e.Value) return &ConstFloat{V:f, Lit:e.Value} case token.StringLit: s := interpStringLocal(e.Value) return &ConstStr{S:s} case token.RuneLit: s := interpStringLocal(e.Value) if len(s) > 0 { return &ConstInt{V:int64(decodeFirstRune(s))} } return &ConstInt{V:0} } return nil } func ParseIntLocal(s string) (n int64) { if len(s) == 0 { return 0 } neg := false i := 0 if s[0] == '-' { neg = true i = 1 } else if s[0] == '+' { i = 1 } base := int64(10) if i < len(s)-1 && s[i] == '0' { c := s[i+1] if c == 'x' || c == 'X' { base = 16 i += 2 } else if c == 'o' || c == 'O' { base = 8 i += 2 } else if c == 'b' || c == 'B' { base = 2 i += 2 } else if c >= '0' && c <= '7' { base = 8 i += 1 } } for ; i < len(s); i++ { ch := s[i] if ch == '_' { continue } var d int64 if ch >= '0' && ch <= '9' { d = int64(ch - '0') } else if ch >= 'a' && ch <= 'f' { d = int64(ch-'a') + 10 } else if ch >= 'A' && ch <= 'F' { d = int64(ch-'A') + 10 } else { break } n = n*base + d } if neg { return -n } return n } func parseFloatLocal(s string) (f float64) { if len(s) == 0 { return 0.0 } neg := false i := 0 if s[0] == '-' { neg = true i = 1 } else if s[0] == '+' { i = 1 } var intPart float64 for ; i < len(s); i++ { ch := s[i] if ch == '_' { continue } if ch < '0' || ch > '9' { break } intPart = intPart*10.0 + float64(ch-'0') } var fracPart float64 if i < len(s) && s[i] == '.' { i++ divisor := 10.0 for ; i < len(s); i++ { ch := s[i] if ch == '_' { continue } if ch < '0' || ch > '9' { break } fracPart = fracPart + float64(ch-'0')/divisor divisor = divisor * 10.0 } } result := intPart + fracPart if i < len(s) && (s[i] == 'e' || s[i] == 'E') { i++ expNeg := false if i < len(s) && s[i] == '-' { expNeg = true i++ } else if i < len(s) && s[i] == '+' { i++ } var exp int32 for ; i < len(s); i++ { ch := s[i] if ch < '0' || ch > '9' { break } exp = exp*10 + int32(ch-'0') } mul := 1.0 for j := 0; j < exp; j++ { mul = mul * 10.0 } if expNeg { result = result / mul } else { result = result * mul } } if neg { return -result } return result } func interpStringLocal(s string) (sv string) { if len(s) < 2 { return s } if s[0] == '`' { return s[1 : len(s)-1] } quote := s[0] if quote != '"' && quote != '\'' { return s } s = s[1 : len(s)-1] hasEsc := false for i := 0; i < len(s); i++ { if s[i] == '\\' { hasEsc = true break } } if !hasEsc { return s } var buf []byte for i := 0; i < len(s); i++ { if s[i] != '\\' { push(buf, s[i]) continue } i++ if i >= len(s) { break } switch s[i] { case 'n': push(buf, '\n') case 't': push(buf, '\t') case 'r': push(buf, '\r') case '\\': push(buf, '\\') case '\'': push(buf, '\'') case '"': push(buf, '"') case '0': push(buf, 0) case 'a': push(buf, '\a') case 'b': push(buf, '\b') case 'f': push(buf, '\f') case 'v': push(buf, '\v') case 'x': if i+2 < len(s) { h := hexDigit(s[i+1])*16 + hexDigit(s[i+2]) push(buf, byte(h)) i += 2 } case 'u': if i+4 < len(s) { r := rune(hexDigit(s[i+1]))*4096 + rune(hexDigit(s[i+2]))*256 + rune(hexDigit(s[i+3]))*16 + rune(hexDigit(s[i+4])) buf = appendRuneUTF8(buf, r) i += 4 } case 'U': if i+8 < len(s) { var r rune for j := 1; j <= 8; j++ { r = r*16 + rune(hexDigit(s[i+j])) } buf = appendRuneUTF8(buf, r) i += 8 } default: if s[i] >= '0' && s[i] <= '7' { v := int32(s[i] - '0') if i+1 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' { v = v*8 + int32(s[i+1]-'0') i++ if i+1 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' { v = v*8 + int32(s[i+1]-'0') i++ } } push(buf, byte(v)) } else { push(buf, s[i]) } } } return string(buf) } func hexDigit(c byte) (n int32) { if c >= '0' && c <= '9' { return int32(c - '0') } if c >= 'a' && c <= 'f' { return int32(c-'a') + 10 } if c >= 'A' && c <= 'F' { return int32(c-'A') + 10 } return 0 } func appendRuneUTF8(buf []byte, r rune) (buf2 []byte) { if r < 0x80 { push(buf, byte(r)) return buf } if r < 0x800 { push(buf, byte(0xC0|(r>>6)), byte(0x80|(r&0x3F))) return buf } if r < 0x10000 { push(buf, byte(0xE0|(r>>12)), byte(0x80|((r>>6)&0x3F)), byte(0x80|(r&0x3F))) return buf } push(buf, byte(0xF0|(r>>18)), byte(0x80|((r>>12)&0x3F)), byte(0x80|((r>>6)&0x3F)), byte(0x80|(r&0x3F))) return buf } func (c *Checker) evalConstOp(e *syntax.Operation, iotaVal int64) (cv ConstVal) { if e.Y == nil { xu := c.evalConst(e.X, iotaVal) if xu == nil { mxutil.WriteStr(2, " CONSTOP-UNARY-NIL-X pkg=" | c.pkg.Path | "\n") return nil } r := EvalUnaryLocal(e.Op, xu) if r == nil { mxutil.WriteStr(2, " CONSTOP-UNARY-NIL-R pkg=" | c.pkg.Path | " x=" | xu.String() | "\n") } // ^x on a typed operand must be masked to the operand's width: the // arbitrary-precision result (-x-1) is only correct for untyped // constants. Without this, uint64(^someUint32Const) sign-extends // the stored negative value to 0xffffffffXXXXXXXX. if r != nil && e.Op == token.Xor { if b := c.constExprBasic(e.X); b != nil { r = MaskConstToBasic(r, b) } } return r } x := c.evalConst(e.X, iotaVal) y := c.evalConst(e.Y, iotaVal) if x == nil || y == nil { return nil } return EvalBinaryLocal(e.Op, x, y) } func EvalUnaryLocal(op token.Operator, x ConstVal) (c ConstVal) { switch op { case token.Add: return x case token.Sub: if ci, ok := x.(*ConstInt); ok { return &ConstInt{V:-ci.V} } if cf, ok := x.(*ConstFloat); ok { neg := "" if cf.Lit != "" { neg = "-" | cf.Lit } return &ConstFloat{V:-cf.V, Lit:neg} } case token.Xor: if ci, ok := x.(*ConstInt); ok { return &ConstInt{V:^ci.V} } case token.Not: if cb, ok := x.(*ConstBool); ok { return &ConstBool{V:!cb.V} } } return nil } func EvalBinaryLocal(op token.Operator, x ConstVal, y ConstVal) (c ConstVal) { switch op { case token.Add: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V + cvInt(y)} } if xf, ok := x.(*ConstFloat); ok { return &ConstFloat{V:xf.V + cvFloat(y)} } if xs, ok := x.(*ConstStr); ok { return &ConstStr{S:xs.S | cvStr(y)} } case token.Sub: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V - cvInt(y)} } if xf, ok := x.(*ConstFloat); ok { return &ConstFloat{V:xf.V - cvFloat(y)} } case token.Mul: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V * cvInt(y)} } if xf, ok := x.(*ConstFloat); ok { return &ConstFloat{V:xf.V * cvFloat(y)} } case token.Div: if xi, ok := x.(*ConstInt); ok { d := cvInt(y) if d == 0 { return &ConstInt{V:0} } return &ConstInt{V:xi.V / d} } if xf, ok := x.(*ConstFloat); ok { d := cvFloat(y) if d == 0.0 { return &ConstFloat{V:0.0} } return &ConstFloat{V:xf.V / d} } case token.Rem: if xi, ok := x.(*ConstInt); ok { d := cvInt(y) if d == 0 { return &ConstInt{V:0} } return &ConstInt{V:xi.V % d} } case token.And: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V & cvInt(y)} } case token.Or: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V | cvInt(y)} } if xs, ok := x.(*ConstStr); ok { return &ConstStr{S:xs.S | cvStr(y)} } case token.Xor: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V ^ cvInt(y)} } case token.Shl: if xi, ok := x.(*ConstInt); ok { shift := uint32(cvInt(y)) return &ConstInt{V:xi.V << shift} } case token.Shr: if xi, ok := x.(*ConstInt); ok { shift := uint32(cvInt(y)) return &ConstInt{V:xi.V >> shift} } case token.AndNot: if xi, ok := x.(*ConstInt); ok { return &ConstInt{V:xi.V &^ cvInt(y)} } case token.Eql: return &ConstBool{V:constEqual(x, y)} case token.Neq: return &ConstBool{V:!constEqual(x, y)} case token.Lss: return &ConstBool{V:constLess(x, y)} case token.Leq: return &ConstBool{V:constLess(x, y) || constEqual(x, y)} case token.Gtr: return &ConstBool{V:constLess(y, x)} case token.Geq: return &ConstBool{V:constLess(y, x) || constEqual(x, y)} case token.OrOr: xb, xok := x.(*ConstBool) yb, yok := y.(*ConstBool) if xok && yok { return &ConstBool{V:xb.V || yb.V} } case token.AndAnd: xb, xok := x.(*ConstBool) yb, yok := y.(*ConstBool) if xok && yok { return &ConstBool{V:xb.V && yb.V} } } return nil } func constEqual(x ConstVal, y ConstVal) (eq bool) { if xi, matched := x.(*ConstInt); matched { return xi.V == cvInt(y) } if xf, matched := x.(*ConstFloat); matched { return xf.V == cvFloat(y) } if xs, matched := x.(*ConstStr); matched { return xs.S == cvStr(y) } if xb, matched := x.(*ConstBool); matched { if yb, matched2 := y.(*ConstBool); matched2 { return xb.V == yb.V } } return false } func constLess(x ConstVal, y ConstVal) (lt bool) { if xi, matched := x.(*ConstInt); matched { return xi.V < cvInt(y) } if xf, matched := x.(*ConstFloat); matched { return xf.V < cvFloat(y) } if xs, matched := x.(*ConstStr); matched { return xs.S < cvStr(y) } return false } func ConvertConstLocal(v ConstVal, target Type) (c ConstVal) { b, isBasic := SafeUnderlying(target).(*Basic) if !isBasic { return v } switch { case b.Info&IsInteger != 0: iv := cvInt(v) return MaskConstToBasic(&ConstInt{V:iv}, b) case b.Info&IsFloat != 0: fv := cvFloat(v) if cf, matched := v.(*ConstFloat); matched { return &ConstFloat{V:fv, Lit:cf.Lit} } return &ConstFloat{V:fv} case b.Info&IsString != 0: if cvKind(v) == 3 { n := cvInt(v) return &ConstStr{S:runeToStr(rune(n))} } return v } return v } // MaskConstToBasic truncates a ConstInt to the width and signedness of the // given sized basic integer type. Untyped and non-integer types pass // through unchanged. func MaskConstToBasic(v ConstVal, b *Basic) (c ConstVal) { ci, ok := v.(*ConstInt) if !ok || b == nil || b.Info&IsInteger == 0 || b.Info&IsUntyped != 0 { return v } n := ci.V switch b.Kind { case Int8: n = int64(int8(n)) case Int16: n = int64(int16(n)) case Int32: n = int64(int32(n)) case Uint8: n = int64(uint8(n)) case Uint16: n = int64(uint16(n)) case Uint32: n = int64(uint32(n)) case Int64, Uint64: // 64-bit: int64 bit pattern is already exact. default: return v } return &ConstInt{V:n} } // constExprBasic resolves the basic type of a constant expression, used to // apply width masking to typed constant operations. Returns nil when the // type cannot be determined (untyped literals and untracked forms). func (c *Checker) constExprBasic(e syntax.Expr) (b *Basic) { switch ex := e.(type) { case *syntax.Name: var obj Object if c.localScope != nil { _, obj = c.localScope.LookupParent(ex.Value) } if obj == nil { _, obj = c.lookup(ex.Value, c.pkg.Scope) } if k, matched := obj.(*TCConst); matched && k.Typ != nil { if bb, matched2 := SafeUnderlying(k.Typ).(*Basic); matched2 { return bb } } case *syntax.SelectorExpr: if pkgName, matched := ex.X.(*syntax.Name); matched { if _, imp := c.lookupType(pkgName.Value); imp != nil { if pn, matched2 := imp.(*PkgName); matched2 && pn.Imported != nil { if obj := pn.Imported.Scope.Lookup(ex.Sel.Value); obj != nil { if k, matched3 := obj.(*TCConst); matched3 && k.Typ != nil { if bb, matched4 := SafeUnderlying(k.Typ).(*Basic); matched4 { return bb } } } } } } case *syntax.ParenExpr: return c.constExprBasic(ex.X) case *syntax.CallExpr: if t := c.resolveTypeExpr(ex.Fun); t != nil { if bb, matched := SafeUnderlying(t).(*Basic); matched { return bb } } case *syntax.Operation: if bb := c.constExprBasic(ex.X); bb != nil && bb.Info&IsUntyped == 0 { return bb } if ex.Y != nil { return c.constExprBasic(ex.Y) } } return nil } func UntypedTypeOfCV(v ConstVal) (t Type) { switch cvKind(v) { case 1: return Typ[UntypedBool] case 3: return Typ[UntypedInt] case 4: return Typ[UntypedFloat] case 2: return Typ[UntypedString] } return nil } func cvKind(val ConstVal) (n int32) { switch val.(type) { case *ConstBool: return 1 case *ConstStr: return 2 case *ConstInt: return 3 case *ConstFloat: return 4 case *ConstNil: return 0 } return 0 } func constInt64CV(v ConstVal) (n int64, valid bool) { if v == nil || cvKind(v) != 3 { return 0, false } if ci, matched := v.(*ConstInt); matched { return ci.V, true } return 0, false } func (c *Checker) IsConstExpr(e syntax.Expr) (ok bool) { return c.evalConst(e, 0) != nil } func (c *Checker) evalArrayLen(e syntax.Expr) (n int64) { v := c.evalConst(e, 0) if v == nil { return 0 } n, _ = constInt64CV(v) return n } func cvInt(v ConstVal) (n int64) { if ci, ok := v.(*ConstInt); ok { return ci.V } if cf, ok := v.(*ConstFloat); ok { return int64(cf.V) } return 0 } func cvFloat(v ConstVal) (f float64) { if cf, ok := v.(*ConstFloat); ok { return cf.V } if ci, ok := v.(*ConstInt); ok { return float64(ci.V) } return 0 } func cvStr(v ConstVal) (s string) { if cs, ok := v.(*ConstStr); ok { return cs.S } return "" } func decodeFirstRune(s string) (n int32) { b := s[0] if b < 0x80 { return int32(b) } if b < 0xC0 { return int32(b) } if b < 0xE0 && len(s) >= 2 { return int32(b&0x1F)<<6 | int32(s[1]&0x3F) } if b < 0xF0 && len(s) >= 3 { return int32(b&0x0F)<<12 | int32(s[1]&0x3F)<<6 | int32(s[2]&0x3F) } if len(s) >= 4 { return int32(b&0x07)<<18 | int32(s[1]&0x3F)<<12 | int32(s[2]&0x3F)<<6 | int32(s[3]&0x3F) } return int32(b) } func runeToStr(r rune) (s string) { if r < 0 || r > 0x10FFFF { r = 0xFFFD } var out []byte if r <= 0x7F { push(out, byte(r)) } else if r <= 0x7FF { push(out, byte(0xC0|(r>>6)), byte(0x80|(r&0x3F))) } else if r <= 0xFFFF { push(out, byte(0xE0|(r>>12)), byte(0x80|((r>>6)&0x3F)), byte(0x80|(r&0x3F))) } else { push(out, byte(0xF0|(r>>18)), byte(0x80|((r>>12)&0x3F)), byte(0x80|((r>>6)&0x3F)), byte(0x80|(r&0x3F))) } return string(out) }