package semantic
import (
"fmt"
"git.sr.ht/~mna/snow/pkg/token"
)
const (
SelfVarName = "self"
MainFnName = "main"
ExternAttrName = "extern"
TrueLitName = "true"
FalseLitName = "false"
)
// Node is the common interface for the abstract semantic graph. Unlike the
// AST, the semantic graph doesn't care about staying close to the source
// code, it cares about making static, semantic analysis as easy as
// possible, recording as many facts as possible to reason about the
// semantics of the program. As such, things like comments have no
// place here.
//
// Once transformed to this graph, the AST isn't used anymore for the rest
// of the compilation. The only thing kept from the AST (other than the
// details of the program, of course) is the starting position of each
// node for error reporting.
type Node interface {
Pos() token.Pos
Scope() *Scope
}
type commonNode struct {
pos token.Pos
scope *Scope
}
func (c commonNode) Pos() token.Pos { return c.pos }
func (c commonNode) Scope() *Scope { return c.scope }
type Stmt interface {
Node
stmt()
}
type commonStmt struct {
commonNode
}
func (c commonStmt) stmt() {}
type Typed interface {
Node
Type() Type
TypeContext() TypeContext
}
type Expr interface {
Typed
expr()
}
type commonExpr struct {
commonNode
typ Type
ctx TypeContext
}
func (c commonExpr) Type() Type { return c.typ }
func (c commonExpr) TypeContext() TypeContext { return c.ctx }
func (c commonExpr) expr() {}
type Decl interface {
Typed
Ident() string
decl()
}
type commonDecl struct {
commonStmt
ident string
typ Type
}
func (c commonDecl) Ident() string { return c.ident }
func (c commonDecl) Type() Type { return c.typ }
func (c commonDecl) decl() {}
func AsFnDecl(n Node) *Fn {
if fn, ok := n.(*Fn); ok {
return fn
}
return nil
}
func AsVarDecl(n Node) *Var {
if v, ok := n.(*Var); ok {
return v
}
return nil
}
type Unit struct {
Files []*File
FileSet *token.FileSet // set by Run after the translation pass
Main *Fn // set during typecheck pass, only one main fn per compilation unit is allowed
ValueUses map[*Ident]Decl // set during analysis pass, RHS uses of declarations
commonNode
}
type File struct {
Vars []*Var
Fns []*Fn
Structs []*Struct
commonNode
}
// ************** DECLARATIONS *****************
type Fn struct {
Attrs []*Call // set during translation pass
Params []*Var // for Fn, Var.Ctx == immutable and Var.Value == nil
ReturnExpr Expr // possibly nil, not a resolved Type, but the Expr of the return
Body *Block
MethodOf *Struct // set during translation pass
IsRef bool // set during translation pass
commonDecl
}
func (fn *Fn) TypeContext() TypeContext { return Immutable }
type Var struct {
Ctx TypeContext // only mutable or immutable for Var, set during translation pass
TypeExpr Expr // possibly nil, not a resolved Type, but the Expr assigned to var in `var x: int`
Value Expr // possibly nil
PropOf *Struct // set during translation pass if the Var is a Struct property
ParamOf *Fn // set during translation pass if the Var is a function parameter
commonDecl
}
func (v *Var) TypeContext() TypeContext { return v.Ctx }
// Struct is also used to declare the pre-defined attributes (currently just
// @extern), even though just Vars can be declared on those. This is TBD if
// it stays this way, but the current thinking is that attributes will be
// structs with a special trait.
type Struct struct {
Vars []*Var
Fns []*Fn
Structs []*Struct
// BodyScope is the scope for the content of the struct (unlike Struct.Scope
// which is the scope where the struct is defined).
BodyScope *Scope
commonDecl
}
func (s *Struct) TypeContext() TypeContext { return Typ }
// ************** STATEMENTS *****************
type Block struct {
Stmts []Stmt
commonStmt
}
type Return struct {
Value Expr
commonStmt
}
type Assign struct {
Left Expr
Op token.Token
Right Expr
commonStmt
}
type ExprStmt struct {
Value Expr
commonStmt
}
type If struct {
Conds []Expr
Body *Block
Else Stmt // *If or *Block
commonStmt
}
type Guard struct {
Conds []Expr
Else *Block
commonStmt
}
// ************** EXPRESSIONS *****************
type FnTypeExpr struct {
Params []Expr
Return Expr
commonExpr
}
type TupleTypeExpr struct {
Fields []Expr
commonExpr
}
type TupleVal struct {
Values []Expr
commonExpr
}
type Binary struct {
Left Expr
Op token.Token
Right Expr
commonExpr
}
type Unary struct {
Op token.Token
Right Expr
commonExpr
}
type Paren struct {
Value Expr
commonExpr
}
// Call represents any call-like expression: function call (Fun is an
// expression of function type), struct initializer (Fun is the struct
// identifier) and attribute (Fun is the attribute identifier).
type Call struct {
Fun Expr
Labels []string
Args []Expr
InitOf *Struct // set during typeassign pass if this is a struct init call (including attributes)
IsAttr bool // set during translation pass if this is an attribute
AttrOf *Fn // set during translation pass if attribute on a function
commonExpr
}
// Values returns the argument values of a Call keyed by their label.
// It should only be called for attributes (Call with IsAttr==true),
// as those have compile-time values as arguments and require labels.
func (c *Call) Values() map[string]interface{} {
vals := make(map[string]interface{}, len(c.Args))
for i, arg := range c.Args {
lbl := c.Labels[i]
var val interface{}
switch arg := arg.(type) {
case *LitInt:
val = arg.Value
case *LitString:
val = arg.Value
}
vals[lbl] = val
}
return vals
}
type Selector struct {
Left Expr
Sel *Ident
commonExpr
}
type LitString struct {
Repr string
Value string // filled in the translation pass, the actual string value
commonExpr
}
type LitInt struct {
Repr string
Value int // filled in the translation pass, the actual integer value
commonExpr
}
type Ident struct {
Name string
Index int // filled in the translation pass, -1 for non-tuple identifiers
Ref Decl // filled in the typeassign pass, can still be nil for Tuple fields
commonExpr
}
// added in the typecheck pass where implicit conversions take place, the Value
// is the expression being converted, and the Type of this node is the target
// type.
type ImplicitConv struct {
Value Expr
commonExpr
}
// TypeIdentString returns a string representation of n where the type is
// printed as well as the identifier if it is a Decl or an *Ident.
func TypeIdentString(n Node) string {
var lbl string
switch n := n.(type) {
case Decl:
lbl = n.Ident()
case *Ident:
lbl = n.Name
case *Selector:
lbl = "_." + n.Sel.Name
}
if lbl == "" {
return fmt.Sprintf("%T", n)
}
return fmt.Sprintf("%T %s", n, lbl)
}