1278 lines
27 KiB
Go
1278 lines
27 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"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, p := range slices.Backward(e.Trace) {
|
|
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 {
|
|
var out strings.Builder
|
|
escaped := false
|
|
|
|
for _, ch := range in {
|
|
if escaped {
|
|
switch ch {
|
|
case 'n':
|
|
out.WriteRune('\n')
|
|
case 't':
|
|
out.WriteRune('\t')
|
|
case 'r':
|
|
out.WriteRune('\r')
|
|
default:
|
|
out.WriteString(string(ch))
|
|
}
|
|
escaped = false
|
|
continue
|
|
}
|
|
|
|
switch ch {
|
|
case '\\':
|
|
escaped = true
|
|
default:
|
|
out.WriteString(string(ch))
|
|
}
|
|
}
|
|
|
|
return out.String()
|
|
}
|
|
|
|
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, v := range slices.Backward(t.items) {
|
|
_, err := c.compileAssignFromStack(v, 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()))
|
|
}
|