anglais/core/compiler.go
neemek 42fa039daf
All checks were successful
/ test (push) Successful in 50s
basic records support
2026-08-17 17:08:24 +02:00

1243 lines
27 KiB
Go

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
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()))
}