package core import ( "errors" "fmt" "strings" ) type Compiler struct { Chunk *Chunk ip Pos scope Pos imports []string fileStack *Stack[string] resolver ImportsResolver source []rune Warnings []CompilerError // optimize Whether to attempt some optimization of the emitted bytecode optimize bool stack *Stack[LocalVariable] typeAliases map[string]TypeSignature expectedReturn TypeSignature } type ImportResult struct { Source string Path string } type ImportsResolver interface { Resolve(from, path string) (*ImportResult, error) IsSame(a, b string) bool } type LocalVariable struct { name string signature TypeSignature scope int } type CompilerError struct { Description string Boundary Bounded Source []rune Trace []string } func (e CompilerError) Error() string { return e.Description } func (e CompilerError) Format() string { b := strings.Builder{} src := e.Source b.WriteString(e.Description) // highlight offending area start, end := e.Boundary.Bounds() lineEnd := 0 lineStart := 0 line := 1 pos := 0 for i := Pos(0); i <= start; i++ { pos++ if src[i] == '\n' { line++ lineStart = int(i) + 1 pos = 0 } } for lineEnd < int(end) { b.WriteString("\n") lineEnd = lineStart for lineEnd < len(src) { if src[lineEnd] == '\n' { lineEnd++ break } lineEnd++ } begin := max(0, int(start)-lineStart) length := int(min(end, Pos(lineEnd)) - max(start, Pos(lineStart))) lineDescriptor := fmt.Sprintf("%d:%d~%d", line, begin, begin+length, ) b.WriteString(lineDescriptor) b.WriteString(" | ") b.WriteString(string(src[lineStart : lineEnd-1])) b.WriteString("\n") b.WriteString(strings.Repeat(" ", len(lineDescriptor))) b.WriteString(" ") b.WriteString(strings.Repeat(" ", max(int(start)-lineStart, 0))) b.WriteString(strings.Repeat("^", length)) lineStart = lineEnd line++ } b.WriteString("\nsource trace:") // print import stack trace for i := len(e.Trace) - 1; i >= 0; i-- { p := e.Trace[i] b.WriteString(fmt.Sprintf("\n[%d] %s", i, p)) } return b.String() } func NewCompiler(source []rune) *Compiler { c := &Compiler{ NewChunk(make([]Bytecode, 0), make([]Value, 0)), 0, 0, make([]string, 0), NewStack[string](256), nil, source, []CompilerError{}, false, NewStack[LocalVariable](256), map[string]TypeSignature{}, nil, } return c } func (c *Compiler) add(instruction Bytecode) { for len(c.Chunk.Bytecode) <= int(c.ip) { c.Chunk.Bytecode = append(c.Chunk.Bytecode, 0) } c.Chunk.Bytecode[c.ip] = instruction c.advance(1) } // addConstant add both a constant (if it is not already defined), and add the index of it to the bytecode func (c *Compiler) addConstant(value Value) { chunk := c.Chunk for i := 0; i < len(chunk.Constants); i++ { if chunk.Constants[i].Equals(value) { c.add(Bytecode(i)) return } } chunk.Constants = append(chunk.Constants, value) if len(chunk.Constants) > 256 { panic("too many constants (>256)") } c.add(Bytecode(len(chunk.Constants) - 1)) } func (c *Compiler) Compile(p *Program) (TypeSignature, error) { c.fileStack.Push(p.Path) return c.compile(p.Block) } func EscapeString(in string) string { out := "" escaped := false for _, ch := range in { if escaped { switch ch { case 'n': out += "\n" case 't': out += "\t" case 'r': out += "\r" default: out += string(ch) } escaped = false continue } switch ch { case '\\': escaped = true default: out += string(ch) } } return out } func (c *Compiler) resolveType(value TypeSignature) TypeSignature { if value.Type() == TypeNamed { return c.typeAliases[value.(*NamedSignature).Name] } return value } func (c *Compiler) typeMatches(value, template TypeSignature) bool { value = c.resolveType(value) template = c.resolveType(template) return template.Contains(value) } func (c *Compiler) compile(tree Node) (TypeSignature, error) { if tree == nil { panic("compile called with nil value") } switch tree.Type() { case StringNodeType: c.add(InstructionConstant) c.addConstant(&StringValue{ EscapeString(tree.(*StringNode).value), }) return &StringSignature{}, nil case FloatNodeType: c.add(InstructionConstant) c.addConstant(&FloatValue{tree.(*FloatNode).value}) return &FloatSignature{}, nil case IntegerNodeType: c.add(InstructionConstant) c.addConstant(&IntegerValue{tree.(*IntegerNode).value}) return &IntegerSignature{}, nil case TupleNodeType: n := tree.(*TupleNode) var contents []TypeSignature for _, n := range n.items { t, err := c.compile(n) if err != nil { return nil, err } contents = append(contents, t) } c.add(InstructionFormTuple) c.addU16(uint16(len(n.items))) return &TupleSignature{contents}, nil case RecordNodeType: n := tree.(*RecordNode) c.add(InstructionNewRecord) contents := map[string]TypeSignature{} for k, v := range n.entries { t, err := c.compile(v) if err != nil { return nil, err } c.add(InstructionSetRecordItem) c.addConstant(&StringValue{ k, }) contents[k] = t } return &RecordSignature{ contents, }, nil case ListNodeType: l := tree.(*ListNode) contents := l.content for _, n := range l.items { t, err := c.compile(n) if err != nil { return nil, err } if contents == nil { contents = t } else if !c.typeMatches(t, contents) { contents = &CompositeSignature{ contents, t, } } } c.add(InstructionFormList) c.addU16(uint16(len(l.items))) return &ListSignature{contents}, nil case ReferenceNodeType: return c.addGetVar(tree.(*ReferenceNode).name, tree) case BinaryNodeType: return c.compileBinary(tree.(*BinaryNode)) case UnaryNodeType: vt, err := c.compile(tree.(*UnaryNode).value) if err != nil { return nil, err } switch tree.(*UnaryNode).UnaryOperation { case UnaryNegate: if vt.Type() == TypeInteger { c.add(InstructionNegateInt) } else { c.add(InstructionNegateFloat) } return vt, nil case UnaryNot: c.add(InstructionNot) return &BooleanSignature{}, nil } return nil, c.error("unimplemented unary operation", tree.(*UnaryNode).operator) case BooleanNodeType: if tree.(*BooleanNode).Boolean { c.add(InstructionTrue) } else { c.add(InstructionFalse) } return &BooleanSignature{}, nil case NilNodeType: c.add(InstructionNil) return &NilSignature{}, nil case BlockNodeType: if len(tree.(*BlockNode).statements) == 0 { c.add(InstructionNil) return &NilSignature{}, nil } c.addDescend() var last TypeSignature var err error for i, n := range tree.(*BlockNode).statements { last, err = c.compile(n) if err != nil { return nil, err } if i != len(tree.(*BlockNode).statements)-1 { c.add(InstructionPop) } } c.addAscend() return last, nil case ConditionalNodeType: n := tree.(*ConditionalNode) if v, ok := n.condition.(*BooleanNode); ok { if v.Boolean { c.warn("condition is always true", n.condition) return c.compile(n.do) } c.warn("condition is always false", n.condition) if n.otherwise != nil { return c.compile(n.otherwise) } c.add(InstructionNil) return &NilSignature{}, nil } // the stack should have whether the condition was truthful sig, err := c.compile(n.condition) if err != nil { return nil, err } // make sure condition is boolean if sig.Type() != TypeBoolean { return nil, c.error(fmt.Sprintf("condition must be boolean (is non-boolean type %s)", sig), n.condition) } // if the condition equated to true, we should jump over the body c.add(InstructionJumpFalse) // we save where uint16 jump by value is stored, and update it when // we know the size of this condition (in bytecode) jumpByPos := c.ip c.advance(2) // this part would be executed if the value was true dot, err := c.compile(n.do) if err != nil { return nil, err } // we store the position of the jump over the else code here // this would jump over the else/otherwise block in the code c.add(InstructionJump) jumpOverElse := c.ip c.advance(2) // put the u16 of where to jump if the condition was false c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2)) var ot TypeSignature if n.otherwise != nil { ot, err = c.compile(n.otherwise) if err != nil { return nil, err } } else { c.add(InstructionNil) ot = &NilSignature{} } c.putU16(jumpOverElse, uint16(c.ip-jumpOverElse-2)) if dot.Contains(ot) { return dot, nil } else if ot.Contains(dot) { return ot, nil } return &CompositeSignature{ dot, ot, }, nil case LoopNodeType: n := tree.(*LoopNode) c.add(InstructionNil) conditionPos := c.ip jumpValuePos := Pos(0) alwaysLoop := false if v, ok := n.condition.(*BooleanNode); ok { if !v.Boolean { c.warn("while-loop condition is always false", n.condition) return &NilSignature{}, nil } c.warn("while-loop condition is always true", n.condition) alwaysLoop = true } else { sig, err := c.compile(n.condition) if err != nil { return nil, err } // make sure condition is boolean if sig.Type() != TypeBoolean { return nil, c.error(fmt.Sprintf("cannot loop depending on value of type %s; requires boolean", sig), n.condition) } c.add(InstructionJumpFalse) jumpValuePos = c.ip c.advance(2) } c.add(InstructionPop) dt, err := c.compile(n.do) if err != nil { return nil, err } c.add(InstructionLoop) // condition pos < ip c.addU16(uint16(c.ip - conditionPos + 2)) if !alwaysLoop { c.putU16(jumpValuePos, uint16(c.ip-jumpValuePos-2)) } return &CompositeSignature{dt, &NilSignature{}}, nil case ForNodeType: n := tree.(*ForNode) is, err := c.compile(n.iterator) if err != nil { return nil, err } iteratorSignature := &FunctionSignature{ []TypeSignature{}, &TupleSignature{ []TypeSignature{ &AnySignature{}, &BooleanSignature{}, }, }, } if !iteratorSignature.Contains(is) { return nil, c.error(fmt.Sprintf("cannot iterate with non-iterator %s (must be %s)", is, iteratorSignature), n.iterator) } outputSig := is.(*FunctionSignature).Out.(*TupleSignature).Contents[0] ipos := c.ip c.addDescend() c.add(InstructionDuplicate) c.add(InstructionCall) c.add(InstructionDestructureTuple) // if no more items; jump to end c.add(InstructionJumpFalse) jmpValuePos := c.ip c.advance(2) if n.counter.Type() != ReferenceNodeType { return nil, c.error("cannot use non-variable as a counter", n.counter) } name := n.counter.(*ReferenceNode).name c.add(InstructionDeclareLocal) c.addConstant(&StringValue{ name, }) c.add(InstructionPop) c.registerVar(name, outputSig) _, err = c.compile(n.logic) if err != nil { return nil, err } c.add(InstructionPop) c.addAscend() c.add(InstructionLoop) c.addU16(uint16(c.ip - ipos + 2)) // end of loop c.putU16(jmpValuePos, uint16(c.ip-jmpValuePos-2)) c.add(InstructionPop) c.add(InstructionPop) c.add(InstructionNil) return &NilSignature{}, nil case AssignNodeType: n := tree.(*AssignNode) t, err := c.compile(n.value) if err != nil { return nil, err } return c.compileAssignFromStack(n.dest, t, n.declare) case InvokeNodeType: n := tree.(*InvokeNode) var argSigs []TypeSignature var err error for _, arg := range n.args { sig, err := c.compile(arg) if err != nil { return nil, err } argSigs = append(argSigs, sig) } s, err := c.compile(n.source) if err != nil { return nil, err } f, ok := s.(*FunctionSignature) if !ok { return nil, c.error(fmt.Sprintf("cannot call non-function value of type %s", s), n) } if len(n.args) != len(f.In) { return nil, c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n) } for i, sig := range argSigs { // check that arg type is as required fin := f.In[i] if !c.typeMatches(sig, fin) { return nil, c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), n.args[i]) } } c.add(InstructionCall) return f.Out, nil case FunctionNodeType: n := tree.(*FunctionNode) fi := len(c.Chunk.Constants) c.Chunk.Constants = append(c.Chunk.Constants, nil) c.add(InstructionConstant) c.add(Bytecode(fi)) // allow self-referencing sig := n.Signature() c.descend() c.registerVar(n.name, sig) // keep track of main chunk mc := c.Chunk // and ip mip := c.ip // assign a new empty chunk c.Chunk = NewChunk(make([]Bytecode, 0), make([]Value, 0)) // reset instruction pointer (ip) c.ip = 0 c.descend() for _, p := range n.parameters { c.registerVar(p.Name, p.Signature) } parentExpectedReturn := c.expectedReturn c.expectedReturn = sig.Out yield, err := c.compile(n.logic) if err != nil { return nil, err } if c.expectedReturn.Type() == TypeNil && yield.Type() != TypeNil { c.add(InstructionPop) c.add(InstructionNil) } else if !c.typeMatches(yield, c.expectedReturn) { var causer Node = n if v, ok := n.logic.(*BlockNode); ok && len(v.statements) > 0 { causer = v.statements[len(v.statements)-1] } return nil, c.error(fmt.Sprintf("yield does not match expected return; got %s, expected %s", yield, c.expectedReturn), causer) } c.ascend() c.ascend() mc.Constants[fi] = &FunctionValue{ n.name, n.parameters, n.yield, c.Chunk, nil, nil, } // restore old chunk and ip c.Chunk = mc c.ip = mip c.expectedReturn = parentExpectedReturn return sig, nil case IncludeNodeType: return c.compileInclude(tree.(*IncludeNode)) case AccessNodeType: n := tree.(*AccessNode) ps, err := c.compile(n.source) if err != nil { return nil, err } c.add(InstructionAccessProperty) c.addConstant(&StringValue{ n.property.Lexeme, }) s, err := c.getPropertySignature(ps, n.property.Lexeme) if err != nil { return nil, c.error(err.Error(), n.property) } return s, nil case ReturnNodeType: if c.expectedReturn == nil { return nil, c.error("cannot return", tree) } t, err := c.compile(tree.(*ReturnNode).value) if err != nil { return nil, err } if !c.typeMatches(t, c.expectedReturn) { return nil, c.error(fmt.Sprintf("cannot return %s; must be %s", t, c.expectedReturn), tree.(*ReturnNode).value) } c.add(InstructionReturn) return t, nil case AliasNodeType: n := tree.(*AliasNode) if _, ok := c.typeAliases[n.name.Lexeme]; ok { return nil, c.error(fmt.Sprintf("%s is already a declared type alias", n.name.Lexeme), n.name) } c.typeAliases[n.name.Lexeme] = n.signature c.add(InstructionNil) return &NilSignature{}, nil case IndexNodeType: n := tree.(*IndexNode) st, err := c.compile(n.source) if err != nil { return nil, err } st = c.resolveType(st) it, err := c.compile(n.index) if err != nil { return nil, err } if it.Type() != TypeInteger { return nil, c.error(fmt.Sprintf("can only index with integers (got %s, nice try)", it), n.index) } var output TypeSignature switch st.Type() { case TypeList: // list stuff output = st.(*ListSignature).Contents c.add(InstructionIndexList) case TypeTuple: // Tuple stuff sig := st.(*TupleSignature) if len(sig.Contents) == 0 { return nil, c.error("cannot index into empty tuple", n.source) } if n.index.Type() == IntegerNodeType { in := n.index.(*IntegerNode) i := in.value.Int64() if i < 0 || int64(len(sig.Contents)) <= i { return nil, c.error(fmt.Sprintf("cannot index outside of tuple (%d items)", len(sig.Contents)), n.source) } output = sig.Contents[i] } else { output = sig.Contents[0] for _, is := range sig.Contents[1:] { if c.typeMatches(is, output) { continue } output = &CompositeSignature{ output, is, } } } c.add(InstructionIndexTuple) case TypeString: output = &StringSignature{} c.add(InstructionIndexString) default: return nil, c.error(fmt.Sprintf("cannot index into value of type %s", st), n.source) } return output, nil case BreakpointNodeType: c.add(InstructionBreakpoint) c.add(InstructionNil) return &NilSignature{}, nil default: panic(fmt.Sprintf("unimplemented compiling of %s", tree.Type())) } } func (c *Compiler) compileBinary(binary *BinaryNode) (TypeSignature, error) { tl, err := c.compile(binary.Left) if err != nil { return nil, err } tr, err := c.compile(binary.Right) if err != nil { return nil, err } if tl.Type() != tr.Type() { return nil, c.error(fmt.Sprintf("cannot %s values of different types (%s and %s)", binary.BinaryOperation, tl, tr), binary.operator) } res := tl switch binary.BinaryOperation { case BinaryAddition: if tl.Type() == TypeString { c.add(InstructionConcatStrings) } else if tl.Type() == TypeList { c.add(InstructionConcatLists) } else if tl.Type() == TypeFloat { c.add(InstructionAddFloat) } else if tl.Type() == TypeInteger { c.add(InstructionAddInt) } else { return nil, c.error("unimplemented binary compilation", binary) } case BinarySubtraction: if tl.Type() == TypeFloat { c.add(InstructionSubFloat) } else if tl.Type() == TypeInteger { c.add(InstructionSubInt) } else { return nil, c.error(fmt.Sprintf("cannot subtract %s", tl), binary.operator) } case BinaryMultiplication: if tl.Type() == TypeFloat { c.add(InstructionMulFloat) } else if tl.Type() == TypeInteger { c.add(InstructionMulInt) } else { return nil, c.error(fmt.Sprintf("cannot multiply %s", tl), binary.operator) } case BinaryDivision: if tl.Type() == TypeFloat { c.add(InstructionDivFloat) } else if tl.Type() == TypeInteger { c.add(InstructionDivInt) } else { return nil, c.error(fmt.Sprintf("cannot divide %s", tl), binary.operator) } case BinaryModulo: if tl.Type() == TypeInteger { c.add(InstructionModInt) } else { return nil, c.error(fmt.Sprintf("cannot compute modulo of %s", tl), binary.operator) } case BinaryEquality: c.add(InstructionEquals) res = &BooleanSignature{} case BinaryInequality: c.add(InstructionNotEqual) res = &BooleanSignature{} case BinaryLess: if tl.Type() == TypeFloat { c.add(InstructionLessFloat) } else if tl.Type() == TypeInteger { c.add(InstructionLessInt) } else { return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator) } res = &BooleanSignature{} case BinaryGreater: if tl.Type() == TypeFloat { c.add(InstructionGreaterFloat) } else if tl.Type() == TypeInteger { c.add(InstructionGreaterInt) } else { return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator) } res = &BooleanSignature{} case BinaryLessEqual: if tl.Type() == TypeFloat { c.add(InstructionLessOrEqualFloat) } else if tl.Type() == TypeInteger { c.add(InstructionLessOrEqualInt) } else { return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator) } res = &BooleanSignature{} case BinaryGreaterEqual: if tl.Type() == TypeFloat { c.add(InstructionGreaterOrEqualFloat) } else if tl.Type() == TypeInteger { c.add(InstructionGreaterOrEqualInt) } else { return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator) } res = &BooleanSignature{} case BinaryBooleanAnd: if tl.Type() != TypeBoolean { return nil, c.error(fmt.Sprintf("cannot boolean-and of non-boolean %s", tl), binary.operator) } c.add(InstructionAnd) res = &BooleanSignature{} case BinaryBooleanOr: if tl.Type() != TypeBoolean { return nil, c.error(fmt.Sprintf("cannot boolean-or of non-boolean %s", tl), binary.operator) } c.add(InstructionOr) res = &BooleanSignature{} } return res, nil } // compileAssignFromStack assign the value of type sig which is expected to be on top of the stack. func (c *Compiler) compileAssignFromStack(to Node, sig TypeSignature, declare bool) (TypeSignature, error) { switch to.Type() { case ReferenceNodeType: d := to.(*ReferenceNode) if d.name == "_" { return sig, nil } if declare && c.isVarDeclaredHere(d.name) { return nil, c.error(fmt.Sprintf("%s is already declared in this scope", d.name), to) } return c.addSetVar(d.name, sig, declare, to) case TupleNodeType: t := to.(*TupleNode) tsig, ok := sig.(*TupleSignature) if !ok { return nil, c.error(fmt.Sprintf("cannot destructure non-tuple %s", sig), to) } if len(t.items) != len(tsig.Contents) { return nil, c.error(fmt.Sprintf("not same amount of items; must be %d", len(tsig.Contents)), to) } c.add(InstructionDuplicate) c.add(InstructionDestructureTuple) // iterate from top to bottom for i := len(t.items) - 1; i >= 0; i-- { _, err := c.compileAssignFromStack(t.items[i], tsig.Contents[i], declare) if err != nil { return nil, err } c.add(InstructionPop) } return sig, nil case IndexNodeType: if declare { return nil, c.error(fmt.Sprintf("cannot declare an indexed item"), to) } n := to.(*IndexNode) ssig, err := c.compile(n.source) if err != nil { return nil, err } isig, err := c.compile(n.index) if err != nil { return nil, err } if isig.Type() != TypeInteger { return nil, c.error(fmt.Sprintf("cannot index into list with non-integer (%s)", isig), n.index) } switch ssig.Type() { case TypeList: lsig := ssig.(*ListSignature) if !c.typeMatches(sig, lsig.Contents) { return nil, c.error(fmt.Sprintf("cannot assign value of type %s to list with items of type %s", sig, lsig.Contents), to) } c.add(InstructionSetIndexList) default: return nil, c.error(fmt.Sprintf("cannot set index of %s", ssig.Type()), n.source) } return sig, nil default: return nil, c.error(fmt.Sprintf("cannot assign to %s", to.Type()), to) } } func (c *Compiler) getVarSignature(name string, causer Node) (TypeSignature, error) { if c.isGlobal(name) { return SignatureOf(DefaultGlobals[name]), nil } for i := c.stack.Current - 1; i >= 0; i-- { v := c.stack.items[i] if v.name == name { return v.signature, nil } } return nil, c.error(fmt.Sprintf("variable %s not defined", name), causer) } // isVarDeclaredHere check whether a variable is declared in the current scope func (c *Compiler) isVarDeclaredHere(name string) bool { for i := c.stack.Current - 1; i >= 0 && c.stack.items[i].scope == int(c.scope); i-- { v := c.stack.items[i] if v.name == name { return true } } return false } func (c *Compiler) addGetVar(name string, causer Node) (TypeSignature, error) { if c.isGlobal(name) { c.add(InstructionGetGlobal) c.addConstant(&StringValue{ name, }) } else { c.add(InstructionGetLocal) c.addConstant(&StringValue{ name, }) } return c.getVarSignature(name, causer) } // addSetVar add instructions for setting a variable of specified type which is ON TOP OF THE STACK // does also register the variable with the correct type. func (c *Compiler) addSetVar(name string, t TypeSignature, declare bool, causer Node) (TypeSignature, error) { if declare { c.add(InstructionDeclareLocal) c.registerVar(name, t) } else { vt, err := c.getVarSignature(name, causer) // it needs someone to blame >:3 if err != nil { return nil, err } if !vt.Contains(t) { return nil, c.error(fmt.Sprintf("cannot assign value of type %s to variable %s of type %s", t, name, vt), causer) } c.add(InstructionSetLocal) } c.addConstant(&StringValue{ name, }) return t, nil } // keep track that a variable is declared but doesn't necessarily have a deducible type func (c *Compiler) registerVar(name string, t TypeSignature) { c.stack.Push(LocalVariable{ name, t, int(c.scope), }) } // isLocal whether a variable of with the name provided is declared within the local scope func (c *Compiler) isLocal(name string) bool { for i := c.stack.Current - 1; i >= 0; i-- { if c.stack.items[i].name == name { return true } } return false } // isGlobal whether a variable is defined in the standard global environment func (c *Compiler) isGlobal(name string) bool { return DefaultGlobals[name] != nil } func (c *Compiler) ascend() { c.scope-- for ; c.stack.Current > 0 && c.stack.Peek().scope > int(c.scope); c.stack.Pop() { } } func (c *Compiler) addAscend() { c.ascend() c.add(InstructionAscend) } func (c *Compiler) descend() { c.scope++ } func (c *Compiler) addDescend() { c.descend() c.add(InstructionDescend) } func (c *Compiler) error(msg string, causer Bounded) CompilerError { return CompilerError{ msg, causer, c.source, c.fileStack.Slice(), } } func (c *Compiler) warn(msg string, causer Node) { c.Warnings = append(c.Warnings, c.error(msg, causer)) } func (c *Compiler) compileInclude(include *IncludeNode) (TypeSignature, error) { res, err := c.resolver.Resolve(c.fileStack.Peek(), include.path.value) if err != nil { return nil, err } // warn if already included for _, i := range c.imports { if c.resolver.IsSame(res.Path, i) { c.warn("already included elsewhere", include) } } // stop recursive includes for i := c.fileStack.Current - 1; i >= 0; i-- { if c.resolver.IsSame(res.Path, c.fileStack.items[i]) { return nil, c.error("recursive inclusion", include) } } l := NewLexer(res.Source) tokens, err := l.Tokenize() if err != nil { return nil, err } parser := NewParser(res.Source, append(c.fileStack.Slice(), res.Path), tokens) p, err := parser.Parse(res.Path) if err != nil { return nil, err } oldSrc := c.source // update source for more descriptive errors c.source = []rune(res.Source) t, err := c.Compile(p) if err != nil { return nil, err } c.source = oldSrc return t, nil } func (c *Compiler) SetImportsResolver(resolver ImportsResolver) { c.resolver = resolver } func (c *Compiler) SetSource(src string) { c.source = []rune(src) } func (c *Compiler) advance(amount Pos) { c.ip += amount } func (c *Compiler) addU16(v uint16) { c.add(Bytecode(v >> 8)) // first 8 bits c.add(Bytecode(v & 0xff)) // last 8 bits } // putU16 put an unsigned 16-bit value at an arbitrary position. // p is the position before the value func (c *Compiler) putU16(p Pos, v uint16) { // save original position start := c.ip // move to position c.ip = p // set values of the next 2 bytes to the u16 c.addU16(v) // restore position c.ip = start } var TypePrototypes = map[Type]*map[string]*BuiltinFunctionValue{ TypeString: &StringPrototype, TypeTuple: &TuplePrototype, TypeList: &ListPrototype, TypeObject: &ObjectPrototype, } func (c *Compiler) getPropertySignature(source TypeSignature, property string) (TypeSignature, error) { sig := c.resolveType(source) switch sig.Type() { case TypeAny: return nil, errors.New("cannot deduce properties of type any") case TypeComposite: csig := sig.(*CompositeSignature) at, err := c.getPropertySignature(csig.A, property) if err != nil { return nil, err } bt, err := c.getPropertySignature(csig.B, property) if err != nil { return nil, err } if at.Equal(bt) { return at, nil } return &CompositeSignature{ at, bt, }, nil case TypeRecord: prop, ok := sig.(*RecordSignature).Entries[property] if !ok { return nil, errors.New(fmt.Sprintf("cannot record has no property \"%s\"", property)) } return prop, nil default: } if prot, pOk := TypePrototypes[sig.Type()]; pOk { v, ok := (*prot)[property] if !ok { return nil, errors.New(fmt.Sprintf("list has no property %s", property)) } return SignatureOf(v), nil } return nil, errors.New(fmt.Sprintf("%s has no properties", sig.Type())) }