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

@ -150,7 +150,8 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
if ctx.Debug { if ctx.Debug {
log.Println("Compiling parse tree") log.Println("Compiling parse tree")
} }
err = c.Compile(tree)
_, err = c.Compile(tree)
if err != nil { if err != nil {
var e core.FormatedError var e core.FormatedError
if errors.As(err, &e) { if errors.As(err, &e) {
@ -296,7 +297,7 @@ func (cmd *ReplCmd) Run(ctx *Context) error {
} }
c.SetSource(src) c.SetSource(src)
if err = c.Compile(prog); err != nil { if _, err = c.Compile(prog); err != nil {
var e core.FormatedError var e core.FormatedError
if errors.As(err, &e) { if errors.As(err, &e) {
log.Print(e.Format()) log.Print(e.Format())

View file

@ -173,28 +173,10 @@ func (c *Compiler) addConstant(value Value) {
c.add(Bytecode(len(chunk.Constants) - 1)) 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) c.fileStack.Push(p.Path)
for _, i := range p.Imports { return c.compile(p.Block)
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
} }
func EscapeString(in string) string { func EscapeString(in string) string {
@ -682,6 +664,9 @@ func (c *Compiler) compile(tree Node) (TypeSignature, error) {
return sig, nil return sig, nil
case IncludeNodeType:
return c.compileInclude(tree.(*IncludeNode))
case AccessNodeType: case AccessNodeType:
n := tree.(*AccessNode) n := tree.(*AccessNode)
ps, err := c.compile(n.source) ps, err := c.compile(n.source)
@ -837,20 +822,33 @@ func (c *Compiler) compileBinary(binary *BinaryNode) (TypeSignature, error) {
case BinarySubtraction: case BinarySubtraction:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionSubFloat) c.add(InstructionSubFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionSubInt) c.add(InstructionSubInt)
} else {
return nil, c.error(fmt.Sprintf("cannot subtract %s", tl), binary.operator)
} }
case BinaryMultiplication: case BinaryMultiplication:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionMulFloat) c.add(InstructionMulFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionMulInt) c.add(InstructionMulInt)
} else {
return nil, c.error(fmt.Sprintf("cannot multiply %s", tl), binary.operator)
} }
case BinaryDivision: case BinaryDivision:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionDivFloat) c.add(InstructionDivFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionDivInt) 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: case BinaryEquality:
c.add(InstructionEquals) c.add(InstructionEquals)
@ -862,36 +860,52 @@ func (c *Compiler) compileBinary(binary *BinaryNode) (TypeSignature, error) {
case BinaryLess: case BinaryLess:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionLessFloat) c.add(InstructionLessFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionLessInt) c.add(InstructionLessInt)
} else {
return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator)
} }
res = &BooleanSignature{} res = &BooleanSignature{}
case BinaryGreater: case BinaryGreater:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionGreaterFloat) c.add(InstructionGreaterFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionGreaterInt) c.add(InstructionGreaterInt)
} else {
return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator)
} }
res = &BooleanSignature{} res = &BooleanSignature{}
case BinaryLessEqual: case BinaryLessEqual:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionLessOrEqualFloat) c.add(InstructionLessOrEqualFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionLessOrEqualInt) c.add(InstructionLessOrEqualInt)
} else {
return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator)
} }
res = &BooleanSignature{} res = &BooleanSignature{}
case BinaryGreaterEqual: case BinaryGreaterEqual:
if tl.Type() == TypeFloat { if tl.Type() == TypeFloat {
c.add(InstructionGreaterOrEqualFloat) c.add(InstructionGreaterOrEqualFloat)
} else { } else if tl.Type() == TypeInteger {
c.add(InstructionGreaterOrEqualInt) c.add(InstructionGreaterOrEqualInt)
} else {
return nil, c.error(fmt.Sprintf("cannot compare ordering of %s", tl), binary.operator)
} }
res = &BooleanSignature{} res = &BooleanSignature{}
case BinaryBooleanAnd: 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) c.add(InstructionAnd)
res = &BooleanSignature{} res = &BooleanSignature{}
case BinaryBooleanOr: 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) c.add(InstructionOr)
res = &BooleanSignature{} res = &BooleanSignature{}
} }
@ -1071,49 +1085,50 @@ func (c *Compiler) warn(msg string, causer Node) {
c.Warnings = append(c.Warnings, c.error(msg, causer)) c.Warnings = append(c.Warnings, c.error(msg, causer))
} }
func (c *Compiler) resolveImport(imp Import) error { func (c *Compiler) compileInclude(include *IncludeNode) (TypeSignature, error) {
res, err := c.resolver.Resolve(c.fileStack.Peek(), imp.path) res, err := c.resolver.Resolve(c.fileStack.Peek(), include.path.value)
if err != nil { if err != nil {
return err return nil, err
} }
// if already imported and available // warn if already included
for _, i := range c.imports { for _, i := range c.imports {
if c.resolver.IsSame(res.Path, i) { 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-- { for i := c.fileStack.Current - 1; i >= 0; i-- {
if c.resolver.IsSame(res.Path, c.fileStack.items[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) l := NewLexer(res.Source)
tokens, err := l.Tokenize() tokens, err := l.Tokenize()
if err != nil { if err != nil {
return err return nil, err
} }
parser := NewParser(res.Source, append(c.fileStack.Slice(), res.Path), tokens) parser := NewParser(res.Source, append(c.fileStack.Slice(), res.Path), tokens)
p, err := parser.Parse(res.Path) p, err := parser.Parse(res.Path)
if err != nil { if err != nil {
return err return nil, err
} }
oldSrc := c.source oldSrc := c.source
// update source for more descriptive errors // update source for more descriptive errors
c.source = []rune(res.Source) c.source = []rune(res.Source)
if err := c.Compile(p); err != nil { t, err := c.Compile(p)
return err if err != nil {
return nil, err
} }
c.source = oldSrc c.source = oldSrc
return nil return t, nil
} }
func (c *Compiler) SetImportsResolver(resolver ImportsResolver) { func (c *Compiler) SetImportsResolver(resolver ImportsResolver) {

View file

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

View file

@ -43,6 +43,7 @@ const (
AccessNodeType AccessNodeType
AliasNodeType AliasNodeType
IndexNodeType IndexNodeType
IncludeNodeType
BreakpointNodeType BreakpointNodeType
) )
@ -294,6 +295,7 @@ const (
BinarySubtraction BinarySubtraction
BinaryMultiplication BinaryMultiplication
BinaryDivision BinaryDivision
BinaryModulo
BinaryBooleanAnd BinaryBooleanAnd
BinaryBooleanOr BinaryBooleanOr
@ -317,6 +319,8 @@ func (n BinaryOperation) Symbol() string {
return "*" return "*"
case BinaryDivision: case BinaryDivision:
return "/" return "/"
case BinaryModulo:
return "%"
case BinaryEquality: case BinaryEquality:
return "==" return "=="
case BinaryInequality: case BinaryInequality:
@ -690,6 +694,25 @@ func (n AliasNode) Bounds() (Pos, Pos) {
return n.start, n.end 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 { type IndexNode struct {
source Node source Node
index Node index Node

View file

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

View file

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

View file

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