fix type-to-string conv. + fix binary type check + add modulo + rework import (now include)
Some checks failed
/ test (push) Failing after 41s

This commit is contained in:
Neemek 2026-07-13 23:34:19 +02:00
parent 91baf28fdf
commit 575fd8e37a
Signed by: neemek
GPG key ID: 84FFE4D7D40AB25E
7 changed files with 131 additions and 91 deletions

View file

@ -173,28 +173,10 @@ func (c *Compiler) addConstant(value Value) {
c.add(Bytecode(len(chunk.Constants) - 1))
}
func (c *Compiler) Compile(p *Program) error {
func (c *Compiler) Compile(p *Program) (TypeSignature, error) {
c.fileStack.Push(p.Path)
for _, i := range p.Imports {
if err := c.resolveImport(i); err != nil {
return err
}
}
for i, s := range p.Block.statements {
if _, err := c.compile(s); err != nil {
return err
}
if i != len(p.Block.statements)-1 {
c.add(InstructionPop)
}
}
c.fileStack.Pop()
return nil
return c.compile(p.Block)
}
func EscapeString(in string) string {
@ -682,6 +664,9 @@ func (c *Compiler) compile(tree Node) (TypeSignature, error) {
return sig, nil
case IncludeNodeType:
return c.compileInclude(tree.(*IncludeNode))
case AccessNodeType:
n := tree.(*AccessNode)
ps, err := c.compile(n.source)
@ -837,20 +822,33 @@ func (c *Compiler) compileBinary(binary *BinaryNode) (TypeSignature, error) {
case BinarySubtraction:
if tl.Type() == TypeFloat {
c.add(InstructionSubFloat)
} else {
} 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 {
} 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 {
} 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)
@ -862,36 +860,52 @@ func (c *Compiler) compileBinary(binary *BinaryNode) (TypeSignature, error) {
case BinaryLess:
if tl.Type() == TypeFloat {
c.add(InstructionLessFloat)
} else {
} 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 {
} 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 {
} 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 {
} 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{}
}
@ -1071,49 +1085,50 @@ func (c *Compiler) warn(msg string, causer Node) {
c.Warnings = append(c.Warnings, c.error(msg, causer))
}
func (c *Compiler) resolveImport(imp Import) error {
res, err := c.resolver.Resolve(c.fileStack.Peek(), imp.path)
func (c *Compiler) compileInclude(include *IncludeNode) (TypeSignature, error) {
res, err := c.resolver.Resolve(c.fileStack.Peek(), include.path.value)
if err != nil {
return err
return nil, err
}
// if already imported and available
// warn if already included
for _, i := range c.imports {
if c.resolver.IsSame(res.Path, i) {
return nil
c.warn("already included elsewhere", include)
}
}
// stop recursive imports
// stop recursive includes
for i := c.fileStack.Current - 1; i >= 0; i-- {
if c.resolver.IsSame(res.Path, c.fileStack.items[i]) {
return c.error("recursive import", imp)
return nil, c.error("recursive inclusion", include)
}
}
l := NewLexer(res.Source)
tokens, err := l.Tokenize()
if err != nil {
return err
return nil, err
}
parser := NewParser(res.Source, append(c.fileStack.Slice(), res.Path), tokens)
p, err := parser.Parse(res.Path)
if err != nil {
return err
return nil, err
}
oldSrc := c.source
// update source for more descriptive errors
c.source = []rune(res.Source)
if err := c.Compile(p); err != nil {
return err
t, err := c.Compile(p)
if err != nil {
return nil, err
}
c.source = oldSrc
return nil
return t, nil
}
func (c *Compiler) SetImportsResolver(resolver ImportsResolver) {

View file

@ -29,6 +29,7 @@ const (
TokenMinus
TokenStar
TokenSlash
TokenPercent
TokenBang
TokenSemicolon
@ -55,7 +56,7 @@ const (
TokenVar
TokenIf
TokenElse
TokenImport
TokenInclude
TokenType
TokenFor
TokenIn
@ -166,8 +167,8 @@ func (t TokenKind) String() string {
return "open bracket"
case TokenCloseBracket:
return "close bracket"
case TokenImport:
return "import"
case TokenInclude:
return "include"
case TokenColon:
return "colon"
case TokenPipe:
@ -195,7 +196,7 @@ var Keywords = map[string]TokenKind{
"nil": TokenNil,
"if": TokenIf,
"else": TokenElse,
"import": TokenImport,
"include": TokenInclude,
"var": TokenVar,
"fn": TokenFunc,
"return": TokenReturn,
@ -268,6 +269,8 @@ func (l *Lexer) NextToken() (Token, error) {
}
return l.makeToken(TokenSlash), nil
case '%':
return l.makeToken(TokenPercent), nil
case '(':
return l.makeToken(TokenOpenParenthesis), nil
case ')':

View file

@ -43,6 +43,7 @@ const (
AccessNodeType
AliasNodeType
IndexNodeType
IncludeNodeType
BreakpointNodeType
)
@ -294,6 +295,7 @@ const (
BinarySubtraction
BinaryMultiplication
BinaryDivision
BinaryModulo
BinaryBooleanAnd
BinaryBooleanOr
@ -317,6 +319,8 @@ func (n BinaryOperation) Symbol() string {
return "*"
case BinaryDivision:
return "/"
case BinaryModulo:
return "%"
case BinaryEquality:
return "=="
case BinaryInequality:
@ -690,6 +694,25 @@ func (n AliasNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type IncludeNode struct {
path *StringNode
start Pos
end Pos
}
func (n IncludeNode) Type() NodeType {
return IncludeNodeType
}
func (n IncludeNode) String() string {
return fmt.Sprintf("include \"%s\"", n.path)
}
func (n IncludeNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type IndexNode struct {
source Node
index Node

View file

@ -95,39 +95,21 @@ func NewParser(source string, trace []string, tokens []Token) *Parser {
}
type Program struct {
Imports []Import
Block *BlockNode
Path string
}
type Import struct {
path string
start Pos
end Pos
}
func (i Import) Bounds() (Pos, Pos) {
return i.start, i.end
Block *BlockNode
Path string
}
func (p *Program) String() string {
builder := strings.Builder{}
sb := strings.Builder{}
builder.WriteString("=== Imports ===\n")
for _, i := range p.Imports {
builder.WriteString(i.path)
builder.WriteString("\n")
}
builder.WriteString("===============\n")
sb.WriteString(fmt.Sprintf("=v= program %s =v=\n", p.Path))
sb.WriteString(p.Block.String())
sb.WriteString(fmt.Sprintf("=^= program %s =^=\n", p.Path))
builder.WriteString(p.Block.String())
return builder.String()
return sb.String()
}
func (p *Parser) Parse(path string) (*Program, error) {
imports := make([]Import, 0)
// top level statements
statements := make([]Node, 0)
@ -135,20 +117,6 @@ func (p *Parser) Parse(path string) (*Program, error) {
p.advance()
for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF {
if p.accept(TokenImport) {
start := p.prev.Start
if err := p.expect(TokenString, "import requires a path/name to import"); err != nil {
return nil, err
}
imports = append(imports, Import{
p.prev.Lexeme[1 : len(p.prev.Lexeme)-1],
start,
p.prev.End,
})
continue
}
for p.accept(TokenNewLine) {
}
@ -168,7 +136,6 @@ func (p *Parser) Parse(path string) (*Program, error) {
}
return &Program{
imports,
&BlockNode{
statements,
0,
@ -415,6 +382,25 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) {
p.prev.End,
}, nil
case TokenInclude:
p.advance()
start := p.prev.Start
if err := p.expect(TokenString, "import requires a path/name to include"); err != nil {
return nil, err
}
return &IncludeNode{
&StringNode{
p.prev.Lexeme[1 : len(p.prev.Lexeme)-1],
p.prev.Lexeme,
p.prev.Start,
p.prev.End,
},
start,
p.prev.End,
}, nil
default:
s, err := p.binary()
if err != nil {
@ -448,7 +434,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) {
func isBinaryOperator(tokenType TokenKind) bool {
switch tokenType {
case TokenPlus, TokenMinus, TokenStar, TokenSlash, TokenPipe, TokenDoubleAmpersand, TokenDoublePipe, TokenEquals, TokenBangEquals, TokenLessThan, TokenLessThanOrEqual, TokenGreaterThan, TokenGreaterThanOrEqual:
case TokenPlus, TokenMinus, TokenStar, TokenSlash, TokenPercent, TokenPipe, TokenDoubleAmpersand, TokenDoublePipe, TokenEquals, TokenBangEquals, TokenLessThan, TokenLessThanOrEqual, TokenGreaterThan, TokenGreaterThanOrEqual:
return true
default:
return false
@ -461,10 +447,12 @@ func binaryPrecedence(op TokenKind) int {
return 1
case TokenEquals, TokenBangEquals, TokenLessThan, TokenGreaterThan, TokenLessThanOrEqual, TokenGreaterThanOrEqual:
return 2
case TokenPlus, TokenMinus, TokenPipe:
case TokenPercent:
return 3
case TokenStar, TokenSlash:
case TokenPlus, TokenMinus, TokenPipe:
return 5
case TokenStar, TokenSlash:
return 10
default:
panic("unimplemented")
}
@ -480,6 +468,8 @@ func tokenToBinaryOperation(tokenType TokenKind) BinaryOperation {
return BinaryMultiplication
case TokenSlash:
return BinaryDivision
case TokenPercent:
return BinaryModulo
case TokenPipe:
panic("unimplemented bitwise ops")
case TokenDoubleAmpersand:

View file

@ -158,7 +158,7 @@ func (s *StringSignature) Equal(other TypeSignature) bool {
}
func (*StringSignature) String() string {
return "string"
return "str"
}
type FloatSignature struct{}
@ -407,7 +407,7 @@ func (s *FunctionSignature) Equal(other TypeSignature) bool {
func (s *FunctionSignature) String() string {
b := strings.Builder{}
b.WriteString("func(")
b.WriteString("fn(")
for i, t := range s.In {
if i > 0 {
@ -418,7 +418,7 @@ func (s *FunctionSignature) String() string {
b.WriteString(")")
if s.Out.Type() != TypeNil {
b.WriteString(" ")
b.WriteString(" -> ")
b.WriteString(s.Out.String())
}

View file

@ -41,6 +41,8 @@ const (
InstructionMulInt
// InstructionDivInt pop two ints and divide the second by the first
InstructionDivInt
// InstructionModInt pop two ints and compute the modulo of the first by the second
InstructionModInt
// InstructionNegateInt negate the int; if it was positive, make it negative, and vice versa.
InstructionNegateInt
@ -810,6 +812,12 @@ func (vm *VM) Next() bool {
vm.Stack.Push(&IntegerValue{new(big.Int).Div(l, r)})
case InstructionModInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Mod(l, r)})
case InstructionNegateInt:
v := vm.Stack.Pop().(*IntegerValue).Number