not done yet

This commit is contained in:
Neemek 2025-03-30 10:42:28 +02:00
parent b73af5f92f
commit 49c15f6fb6
15 changed files with 473 additions and 389 deletions

5
bad.ang Normal file
View file

@ -0,0 +1,5 @@
import "lib/list.ang"
write(str(map([1, 2, 3], func(n: number) number {
return n*n
})))

View file

@ -6,6 +6,7 @@ import (
"log"
"neemek.com/anglais/core"
"os"
"path"
"path/filepath"
)
@ -24,123 +25,132 @@ type WorkingDirectoryResolver struct {
workingDirectory string
}
func (r *WorkingDirectoryResolver) Resolve(path string) (core.Node, error) {
func (r *WorkingDirectoryResolver) Resolve(path string) (string, error) {
pth := filepath.Join(r.workingDirectory, path)
f, err := os.ReadFile(pth)
if err != nil {
return "", err
}
return string(f), nil
}
func (r *WorkingDirectoryResolver) IsSame(a, b string) bool {
apath := filepath.Clean(filepath.Join(r.workingDirectory, a))
bpath := filepath.Clean(filepath.Join(r.workingDirectory, b))
return apath == bpath
}
func makeChunk(ctx *Context, filepath string, ignoreWarnings bool) (*core.Chunk, error) {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(filepath)
if err != nil {
return nil, err
}
src := string(f)
if ctx.Debug {
log.Println("Initialized lexer")
}
l := core.NewLexer(src)
if ctx.Debug {
log.Println("Lexing all tokens")
}
tokens, err := l.Tokenize()
if err != nil {
return nil, err
}
p := core.NewParser(tokens)
if len(tokens) <= 1 {
return nil, errors.New("empty file")
}
if ctx.Debug {
log.Printf("Lexed %d tokens", len(tokens))
}
p := core.NewParser(src, tokens)
if ctx.Debug {
log.Println("Initialized parser")
}
tree, err := p.Parse()
if err != nil {
return nil, err
if ctx.Debug {
log.Printf("Parsed tree, meaning:\n%s", tree)
}
return tree, nil
// if there were parsing errors, print them out
if err != nil {
print(err.(core.ParsingError).Format())
log.Fatal("Parsing had errors")
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler([]rune(src))
if ctx.Debug {
log.Println("Setting imports resolver")
}
dir, _ := path.Split(filepath)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug {
log.Println("Compiling parse tree")
}
err = c.Compile(tree)
if err != nil {
var e core.FormatedError
if errors.As(err, &e) {
log.Fatal(e.Format())
}
log.Fatal(err)
}
// if there were non-critical warnings, report them
if !ignoreWarnings && len(c.Warnings) != 0 {
for _, warning := range c.Warnings {
log.Println(warning.Format())
}
log.Fatal("compiler reported warning(s) (ignore warnings with the --ignore-warnings option)")
}
return c.Chunk, nil
}
func (cmd *RunCmd) Run(ctx *Context) error {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
if err != nil {
return err
}
var chunk *core.Chunk
if !cmd.Bytecode {
src := string(f)
if ctx.Debug {
log.Println("Initialized lexer")
}
l := core.NewLexer(src)
if ctx.Debug {
log.Println("Lexing all tokens")
}
tokens, err := l.Tokenize()
c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
if err != nil {
log.Fatal(err)
return err
}
if len(tokens) <= 1 {
log.Fatal("Empty file")
}
if ctx.Debug {
log.Printf("Lexed %d tokens", len(tokens))
}
p := core.NewParser(tokens)
if ctx.Debug {
log.Println("Initialized parser")
}
tree, err := p.Parse()
if ctx.Debug {
log.Printf("Parsed tree, meaning:\n%s", tree)
}
// if there were parsing errors, print them out
if err != nil {
print(err.(core.ParsingError).Format([]rune(src)))
log.Fatal("Parsing had errors")
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler([]rune(src))
if ctx.Debug {
log.Println("Setting imports resolver")
}
dir, _ := filepath.Split(cmd.File)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug {
log.Println("Compiling parse tree")
}
err = c.Compile(tree)
if err != nil {
var e core.CompilerError
if errors.As(err, &e) {
log.Fatal(e.Format())
}
log.Fatal(err)
}
// if there were non-critical warnings, report them
if !cmd.IgnoreWarnings && len(c.Warnings) != 0 {
for _, warning := range c.Warnings {
log.Println(warning.Format())
}
log.Fatal("compiler reported warning(s) (ignore warnings with the --ignore-warnings option)")
}
chunk = c.Chunk
chunk = c
} else {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
if err != nil {
return err
}
if ctx.Debug {
log.Println("Registering GOB types")
}
@ -175,87 +185,17 @@ func (cmd *RunCmd) Run(ctx *Context) error {
}
type CompileCmd struct {
File string `arg:"" name:"file" help:"File to compile program from" type:"existingfile"`
Output string `arg:"" name:"output" help:"File path to output bytecode to" type:"path"`
File string `arg:"" name:"file" help:"File to compile program from" type:"existingfile"`
Output string `arg:"" name:"output" help:"File path to output bytecode to" type:"path"`
IgnoreWarnings bool `name:"ignore-warnings" help:"Ignore warning messages"`
}
func (cmd *CompileCmd) Run(ctx *Context) error {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
if err != nil {
return err
}
src := string(f)
if ctx.Debug {
log.Println("Initializing lexer")
}
l := core.NewLexer(src)
if ctx.Debug {
log.Println("Lexing all tokens")
}
tokens, err := l.Tokenize()
if err != nil {
log.Fatal(err)
}
if ctx.Debug {
log.Println("Initializing parser")
}
p := core.NewParser(tokens)
if ctx.Debug {
log.Println("Parsing tree")
}
tree, err := p.Parse()
if err != nil {
log.Fatal(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler([]rune(src))
if ctx.Debug {
log.Println("Setting import resolver")
}
dir, _ := filepath.Split(cmd.File)
c.SetImportsResolver(&WorkingDirectoryResolver{
dir,
})
if ctx.Debug {
log.Println("Compiling parse tree")
}
err = c.Compile(tree)
if err != nil {
var e core.CompilerError
if errors.As(err, &e) {
log.Fatal(e.Format())
}
log.Fatal(err)
}
// if there were non-critical warnings, report them
if len(c.Warnings) != 0 {
for _, warning := range c.Warnings {
log.Println(warning.Format())
}
}
if ctx.Debug {
log.Println("Registering GOB types")
}
@ -266,7 +206,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
log.Println("Serializing chunk")
}
serialized := c.Chunk.Serialize()
serialized := c.Serialize()
if ctx.Debug {
log.Println("Writing file")

View file

@ -69,6 +69,48 @@ func GetAllTestCases() map[string]AllTestCase {
},
},
},
"constant_list_concat": {
"a := [1, 2] + [3]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
"list_concat": {
"a := [1, 2]\nb := a + [3]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
0,
},
&VariableValue{
"b",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
}
}
@ -102,7 +144,7 @@ func TestAll(t *testing.T) {
c := NewCompiler([]rune(tc.src))
t.Log("Compiling parse tree")
err = c.Compile(tree)
err = c.compile(tree)
if err != nil {
t.Fatalf("Compiler had an error: %s", err)
}
@ -135,7 +177,7 @@ func BenchmarkAll(b *testing.B) {
tree, _ := p.Parse()
c := NewCompiler([]rune(tc.src))
_ = c.Compile(tree)
_ = c.compile(tree)
vm := NewVM(c.Chunk, 256, 256)

View file

@ -2,6 +2,7 @@ package core
import (
"fmt"
"log"
"strings"
)
@ -10,16 +11,18 @@ type Compiler struct {
ip Pos
scope Pos
imports map[string]Node
resolver ImportsResolver
source []rune
Warnings []CompilerError
imports []string
importStack *Stack[string]
resolver ImportsResolver
source []rune
Warnings []CompilerError
stack *Stack[LocalVariable]
}
type ImportsResolver interface {
Resolve(path string) (Node, error)
Resolve(path string) (string, error)
IsSame(a, b string) bool
}
type LocalVariable struct {
@ -106,7 +109,8 @@ func NewCompiler(source []rune) *Compiler {
NewChunk(make([]Bytecode, 0), make([]Value, 0)),
0,
0,
make(map[string]Node),
make([]string, 0),
NewStack[string](256),
nil,
source,
[]CompilerError{},
@ -141,7 +145,17 @@ func (c *Compiler) addConstant(value Value) {
c.add(Bytecode(len(chunk.Constants) - 1))
}
func (c *Compiler) Compile(tree Node) error {
func (c *Compiler) Compile(p *Program) error {
for _, i := range p.Imports {
if err := c.resolveImport(i); err != nil {
return err
}
}
return c.compile(p.Block)
}
func (c *Compiler) compile(tree Node) error {
if tree == nil {
panic("compile called with nil value")
}
@ -172,7 +186,7 @@ func (c *Compiler) Compile(tree Node) error {
c.addConstant(v)
} else {
for _, n := range l.items {
err := c.Compile(n)
err := c.compile(n)
if err != nil {
return err
}
@ -200,7 +214,7 @@ func (c *Compiler) Compile(tree Node) error {
c.add(InstructionConstant)
c.addConstant(v)
} else {
err := c.Compile(tree.(*UnaryNode).value)
err := c.compile(tree.(*UnaryNode).value)
if err != nil {
return err
}
@ -226,7 +240,7 @@ func (c *Compiler) Compile(tree Node) error {
case BlockNodeType:
c.addDescend()
for _, n := range tree.(*BlockNode).statements {
err := c.Compile(n)
err := c.compile(n)
if err != nil {
return err
}
@ -246,7 +260,7 @@ func (c *Compiler) Compile(tree Node) error {
}
// the stack should have whether the condition was truthful
err = c.Compile(n.condition)
err = c.compile(n.condition)
if err != nil {
return err
}
@ -259,7 +273,7 @@ func (c *Compiler) Compile(tree Node) error {
c.advance(2)
// this part would be executed if the value was true
err = c.Compile(n.do)
err = c.compile(n.do)
if err != nil {
return err
}
@ -277,7 +291,7 @@ func (c *Compiler) Compile(tree Node) error {
c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2))
if n.otherwise != nil {
err := c.Compile(n.otherwise)
err := c.compile(n.otherwise)
if err != nil {
return err
}
@ -297,7 +311,7 @@ func (c *Compiler) Compile(tree Node) error {
}
conditionPos := c.ip
err = c.Compile(n.condition)
err = c.compile(n.condition)
if err != nil {
return err
}
@ -306,7 +320,7 @@ func (c *Compiler) Compile(tree Node) error {
jumpValuePos := c.ip
c.advance(2)
err = c.Compile(n.do)
err = c.compile(n.do)
if err != nil {
return err
}
@ -322,13 +336,13 @@ func (c *Compiler) Compile(tree Node) error {
if n.name == "_" {
// allow non-ish statements
err := c.Compile(n.value)
err := c.compile(n.value)
if err != nil {
return err
}
c.add(InstructionPop)
} else {
if c.isVarDeclaredHere(n.name) {
if n.declare && c.isVarDeclaredHere(n.name) {
return c.error(fmt.Sprintf("%s is already declared in this scope", n.name), n)
}
@ -370,13 +384,13 @@ func (c *Compiler) Compile(tree Node) error {
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg)
}
err = c.Compile(arg)
err = c.compile(arg)
if err != nil {
return err
}
}
err = c.Compile(n.source)
err = c.compile(n.source)
if err != nil {
return err
}
@ -413,6 +427,7 @@ func (c *Compiler) Compile(tree Node) error {
// reset instruction pointer (ip)
c.ip = 0
c.descend()
for _, p := range n.parameters {
c.registerVar(p.Name, p.Signature)
}
@ -421,14 +436,11 @@ func (c *Compiler) Compile(tree Node) error {
return err
}
err = c.Compile(n.logic)
err = c.compile(n.logic)
if err != nil {
return err
}
if n.logic.Type() != BlockNodeType {
c.stack.Pop()
}
c.ascend()
mc.Constants[fi] = &FunctionValue{
n.name,
@ -444,7 +456,7 @@ func (c *Compiler) Compile(tree Node) error {
case AccessNodeType:
n := tree.(*AccessNode)
err := c.Compile(n.source)
err := c.compile(n.source)
if err != nil {
return err
}
@ -453,20 +465,8 @@ func (c *Compiler) Compile(tree Node) error {
n.property,
})
case ImportNodeType:
n := tree.(*ImportNode)
t := c.resolveImport(n.path).(*BlockNode)
for _, statement := range t.statements {
err := c.Compile(statement)
if err != nil {
return err
}
}
case ReturnNodeType:
err := c.Compile(tree.(*ReturnNode).value)
err := c.compile(tree.(*ReturnNode).value)
if err != nil {
return err
}
@ -494,11 +494,11 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
return nil
}
err := c.Compile(binary.Left)
err := c.compile(binary.Left)
if err != nil {
return err
}
err = c.Compile(binary.Right)
err = c.compile(binary.Right)
if err != nil {
return err
}
@ -512,6 +512,8 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
if res.Type() == TypeString {
c.add(InstructionStringConcatenation)
} else if res.Type() == TypeList {
c.add(InstructionConcatLists)
} else {
c.add(InstructionAdd)
}
@ -574,12 +576,16 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
if contents == nil {
contents = sig
} else {
} else if !contents.Matches(sig) {
contents = &AnySignature{}
break
}
}
if contents == nil {
return nil, c.error("can't deduce content type", n)
}
return &ListSignature{
contents,
}, nil
@ -594,7 +600,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
return nil, err
}
if l != r {
if !l.Matches(r) {
return nil, c.error(fmt.Sprintf("cannot perform binary %s on different types: %s and %s", n.BinaryOperation, l, r), n)
}
@ -611,6 +617,10 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
return &StringSignature{}, nil
case TypeNumber:
return &NumberSignature{}, nil
case TypeList:
return &ListSignature{
l.(*ListSignature).Contents,
}, nil
default:
return nil, c.error(fmt.Sprintf("cannot perform binary addition on type %s", l), n)
}
@ -774,6 +784,7 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
return err
}
log.Printf("try affirming %s matches %s", v, sig)
if !sig.Matches(v) {
return c.error(fmt.Sprintf("function cannot return a value with type %s. defined to be %s", v, sig), n.value)
}
@ -857,7 +868,7 @@ func (c *Compiler) getVar(name string) {
}
func (c *Compiler) setVar(name string, value Node, declare bool) error {
err := c.Compile(value)
err := c.compile(value)
if err != nil {
return err
}
@ -915,7 +926,7 @@ func (c *Compiler) isTreeConstant(tree Node) bool {
case BinaryNodeType:
return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right)
case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, CallNodeType, FunctionNodeType,
ReturnNodeType, AccessNodeType, BreakpointNodeType, ImportNodeType, ReferenceNodeType:
ReturnNodeType, AccessNodeType, BreakpointNodeType, ReferenceNodeType:
return false
default:
panic(fmt.Sprintf("unexpected node %s", tree))
@ -984,10 +995,42 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
return nil, err
}
if l.Type() != r.Type() {
return nil, c.error(fmt.Sprintf("cannot perform binary %s on different types %s and %s", n.BinaryOperation, l.Type(), r.Type()), n)
}
// perform type check
switch n.BinaryOperation {
case BinaryAddition:
if l.Type() != StringValueType && l.Type() != NumberValueType && l.Type() != ListValueType {
return nil, c.error(fmt.Sprintf("cannot add values of type %s", l.Type()), n)
}
case BinarySubtraction, BinaryMultiplication, BinaryDivision, BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual:
if l.Type() != NumberValueType {
return nil, c.error(fmt.Sprintf("cannot do binary %s on non-number type %s", n.BinaryOperation, l.Type()), n)
}
case BinaryAnd, BinaryOr:
if l.Type() != BoolValueType {
return nil, c.error(fmt.Sprintf("cannot do binary %s on non-boolean type %s", n.BinaryOperation, l.Type()), n)
}
case BinaryEquality, BinaryInequality:
// can compare all types with themselves
default:
}
var v interface{}
switch n.BinaryOperation {
case BinaryAddition:
v = l.(*NumberValue).Number + r.(*NumberValue).Number
switch l.Type() {
case NumberValueType:
v = l.(*NumberValue).Number + r.(*NumberValue).Number
case StringValueType:
v = l.(*StringValue).Text + r.(*StringValue).Text
case ListValueType:
v = append(l.(*ListValue).Items, r.(*ListValue).Items...)
default:
return nil, c.error(fmt.Sprintf("cannot perform binary add on type %s", l.Type()), n)
}
case BinarySubtraction:
v = l.(*NumberValue).Number - r.(*NumberValue).Number
case BinaryMultiplication:
@ -1058,20 +1101,47 @@ func (c *Compiler) warn(msg string, causer Node) {
c.Warnings = append(c.Warnings, c.error(msg, causer))
}
func (c *Compiler) resolveImport(path string) Node {
if chunk, ok := c.imports[path]; ok {
return chunk
func (c *Compiler) resolveImport(path string) error {
// if already imported and available
for _, i := range c.imports {
if c.resolver.IsSame(path, i) {
return nil
}
}
// find tree
tree, err := c.resolver.Resolve(path)
src, err := c.resolver.Resolve(path)
if err != nil {
panic(err)
return err
}
c.imports[path] = tree
l := NewLexer(src)
tokens, err := l.Tokenize()
if err != nil {
return err
}
return tree
parser := NewParser(src, tokens)
p, err := parser.Parse()
if err != nil {
return err
}
oldChunk := c.Chunk
oldSrc := c.source
c.Chunk = NewChunk([]Bytecode{}, []Value{})
c.source = []rune(src)
if err := c.Compile(p); err != nil {
return err
}
c.imports[path] = c.Chunk
c.Chunk = oldChunk
c.source = oldSrc
return nil
}
func (c *Compiler) SetImportsResolver(resolver ImportsResolver) {
@ -1087,7 +1157,7 @@ func (c *Compiler) addU16(v uint16) {
c.add(Bytecode(v & 0xff)) // last 8 bits
}
// putU16 put a unsigned 16-bit value at an arbitrary position.
// 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

View file

@ -502,7 +502,7 @@ func TestCompile(t *testing.T) {
c := NewCompiler([]rune(testCase.tree.String()))
t.Log("Compiling node tree")
err := c.Compile(testCase.tree)
err := c.compile(testCase.tree)
if err != nil {
t.Fatalf("Compiling failed: %v", err)
}
@ -529,7 +529,7 @@ func BenchmarkCompile(b *testing.B) {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
c := NewCompiler([]rune{})
_ = c.Compile(testCase.tree)
_ = c.compile(testCase.tree)
}
})
}
@ -573,7 +573,7 @@ func TestCompiler_CleanStack(t *testing.T) {
t.Run(name, func(t *testing.T) {
c := NewCompiler([]rune(tc.tree.String()))
err := c.Compile(tc.tree)
err := c.compile(tc.tree)
if err != nil {
t.Fatalf("Compiling failed: %v", err)
}

View file

@ -32,7 +32,6 @@ const (
FunctionNodeType
ReturnNodeType
AccessNodeType
ImportNodeType
BreakpointNodeType
)
@ -70,8 +69,6 @@ func (n NodeType) String() string {
return "Access"
case BreakpointNodeType:
return "Breakpoint"
case ImportNodeType:
return "Import"
case UnaryNodeType:
return "Unary"
}
@ -405,25 +402,6 @@ func (n BlockNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type ImportNode struct {
path string
start Pos
end Pos
}
func (n ImportNode) Type() NodeType {
return ImportNodeType
}
func (n ImportNode) String() string {
return fmt.Sprintf("import %s", n.path)
}
func (n ImportNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ConditionalNode conditionals (if statements)
type ConditionalNode struct {
condition Node

View file

@ -8,9 +8,15 @@ import (
"strings"
)
type FormatedError interface {
Error() string
Format() string
}
type ParsingError struct {
Description string
Causer *Token
Source string
}
func (p ParsingError) Error() string {
@ -18,7 +24,8 @@ func (p ParsingError) Error() string {
}
// Format Print a rich and informative error
func (p ParsingError) Format(src []rune) string {
func (p ParsingError) Format() string {
src := []rune(p.Source)
builder := strings.Builder{}
lineNumber := 1
@ -62,20 +69,44 @@ func (p ParsingError) Format(src []rune) string {
}
type Parser struct {
source string
tokens []Token
prev *Token
curr *Token
pos Pos
}
func NewParser(tokens []Token) *Parser {
func NewParser(source string, tokens []Token) *Parser {
return &Parser{
source: source,
tokens: tokens,
pos: 0,
}
}
func (p *Parser) Parse() (Node, error) {
type Program struct {
Imports []string
Block *BlockNode
}
func (p *Program) String() string {
builder := strings.Builder{}
builder.WriteString("=== Imports ===\n")
for _, i := range p.Imports {
builder.WriteString(i)
builder.WriteString("\n")
}
builder.WriteString("===============\n")
builder.WriteString(p.Block.String())
return builder.String()
}
func (p *Parser) Parse() (*Program, error) {
imports := make([]string, 0)
// top level statements
statements := make([]Node, 0)
@ -83,6 +114,14 @@ func (p *Parser) Parse() (Node, error) {
p.advance()
for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF {
if p.accept(TokenImport) {
if err := p.expect(TokenString); err != nil {
return nil, err
}
imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1])
}
b, err := p.block(true)
if err != nil {
@ -92,8 +131,13 @@ func (p *Parser) Parse() (Node, error) {
statements = append(statements, b)
}
return &BlockNode{
statements: statements,
return &Program{
imports,
&BlockNode{
statements,
0,
p.curr.Start + p.curr.Length,
},
}, nil
}
@ -141,6 +185,7 @@ func (p *Parser) error(error string, causer *Token) error {
return ParsingError{
Description: error,
Causer: causer,
Source: p.source,
}
}
@ -643,22 +688,6 @@ func (p *Parser) statement() (Node, error) {
return p.condition()
}
case TokenImport:
p.advance()
start := p.prev.Start
if err := p.expect(TokenString); err != nil {
return nil, err
}
path := p.prev.Lexeme[1 : len(p.prev.Lexeme)-1]
return &ImportNode{
path,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenFunc:
p.advance()
@ -867,13 +896,19 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
var s TypeSignature
if p.accept(TokenFunc) {
if err := p.expect(TokenCloseParenthesis); err != nil {
if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err
}
var in []TypeSignature
for !p.accept(TokenCloseParenthesis) && (len(in) == 0 || p.accept(TokenComma)) {
for !p.accept(TokenCloseParenthesis) {
if len(in) > 0 {
if err := p.expect(TokenComma); err != nil {
return nil, err
}
}
sig, err := p.parseSignature()
if err != nil {
@ -883,10 +918,6 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
in = append(in, sig)
}
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
out, err := p.parseSignature()
if err != nil {
return nil, err
@ -896,40 +927,43 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
in,
out,
}
}
if err := p.expect(TokenName); err != nil {
return nil, err
}
name := (*p.prev).Lexeme
switch name {
case "string":
s = &StringSignature{}
case "number":
s = &NumberSignature{}
case "boolean":
s = &BooleanSignature{}
case "list":
if err := p.expect(TokenOpenBracket); err != nil {
} else {
if err := p.expect(TokenName); err != nil {
return nil, err
}
name := (*p.prev).Lexeme
contents, err := p.parseSignature()
if err != nil {
return nil, err
switch name {
case "string":
s = &StringSignature{}
case "number":
s = &NumberSignature{}
case "boolean":
s = &BooleanSignature{}
case "list":
if err := p.expect(TokenOpenBracket); err != nil {
return nil, err
}
contents, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseBracket); err != nil {
return nil, err
}
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
default:
return nil, p.error("unsupported type: "+name, p.prev)
}
if err := p.expect(TokenCloseBracket); err != nil {
return nil, err
}
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
}
if p.accept(TokenPipe) {
@ -943,9 +977,5 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
}, nil
}
if s == nil {
return nil, p.error("unsupported type: "+name, p.prev)
}
return s, nil
}

View file

@ -68,15 +68,6 @@ func GoToValue(gov interface{}) Value {
return &StringValue{
v,
}
case []interface{}:
values := make([]Value, len(v))
for i, value := range v {
values[i] = GoToValue(value)
}
return &ListValue{
values,
}
case map[string]interface{}:
values := map[string]Value{}
for key, value := range v {
@ -86,9 +77,17 @@ func GoToValue(gov interface{}) Value {
return &ObjectValue{
values,
}
case Value:
return v
default:
if reflect.TypeOf(v).Kind() == reflect.Slice {
return &ListValue{
v.([]Value),
}
}
}
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov).Name()))
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov)))
}
type Value interface {
@ -319,24 +318,26 @@ var StringPrototype = map[string]*BuiltinFunctionValue{
"split",
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NilSignature{},
&ListSignature{
&StringSignature{},
},
},
func(vm *VM, this Value, v []Value) (Value, error) {
str := this.(*StringValue).String()
sep := v[0].(*StringValue).String()
var out []string
var out []Value
tmp := strings.Builder{}
for i := 0; i < len(str)-len(sep); i++ {
tmp.WriteRune([]rune(str)[i])
if str[i:i+len(sep)] == sep {
out = append(out, tmp.String())
out = append(out, &StringValue{tmp.String()})
tmp.Reset()
}
}
return GoToValue(out), nil
return &ListValue{out}, nil
},
nil,
true,
@ -460,47 +461,6 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
nil,
false,
},
"map": {
"map",
&FunctionSignature{
[]TypeSignature{
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
},
&AnySignature{},
},
},
&ListSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
list := value.(*ListValue)
v := m[0]
var f Value
switch a := v.(type) {
case *FunctionValue, *BuiltinFunctionValue:
f = a
default:
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
}
for i, item := range list.Items {
var err error
list.Items[i], err = vm.Call(f, []Value{
item,
})
if err != nil {
return nil, err
}
}
return list, nil
},
nil,
false,
},
"reduce": {
"reduce",
&FunctionSignature{

View file

@ -102,6 +102,8 @@ const (
// items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.) The order is reversed compared
// to on the stack; the top value on the stack is the last in the list.
InstructionFormList
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
InstructionConcatLists
// InstructionBreakpoint for debugging purposes
InstructionBreakpoint
@ -187,6 +189,8 @@ func (b Bytecode) String() string {
return "APPEND"
case InstructionAccessProperty:
return "ACCESS_PROPERTY"
case InstructionConcatLists:
return "CONCAT_LISTS"
}
return "UNDEFINED"
}
@ -377,6 +381,21 @@ var DefaultGlobals = map[string]Value{
nil,
true,
},
"byte": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
s := args[0].(*StringValue).Text
b := []byte(s)[0]
return &NumberValue{float64(b)}, nil
},
nil,
true,
},
"assertEq": &BuiltinFunctionValue{
"assertEq",
&FunctionSignature{
@ -710,6 +729,14 @@ func (vm *VM) Next() bool {
list.Items = append(list.Items, value)
vm.stack.Push(list)
case InstructionConcatLists:
r := vm.stack.Pop().(*ListValue)
l := vm.stack.Pop().(*ListValue)
vm.stack.Push(&ListValue{
append(l.Items, r.Items...),
})
case InstructionDescend:
vm.descend()

View file

@ -560,6 +560,37 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{5},
},
},
"list_concat": {
NewChunk(
[]Bytecode{
InstructionConstant, 0,
InstructionConstant, 1,
InstructionConcatLists,
},
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
&ListValue{
[]Value{
&NumberValue{3},
},
},
},
),
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
},
},
}
}

12
lib/list.ang Normal file
View file

@ -0,0 +1,12 @@
func map(list: list[any], f: func(any)any) list[any] {
out := []
i := 0
while i < list.length() {
out.append(f(list.at(i)))
i = i + 1
}
return out
}

View file

@ -1,4 +1,4 @@
#!/bin/zsh
#!/bin/bash
echo '=== Building CLI ==='
cd cli || exit 1

View file

@ -22,3 +22,6 @@ while x <= 100 {
x = x + 1
}
assertEq([1, 2] + [3], [1, 2, 3])
assertEq(["Eny", "meanie"] + ["minie", "moe"], ["Eny", "meanie", "minie", "moe"])

View file

@ -19,8 +19,8 @@ while n < fibonacci_numbers.length() {
n = n + 1
}
# return to start of line
print(format("%[%D", [char(0x1B), n]))
# return to start of line (with carriage return \r)
print(char(0x0D))
x := 0
while x < fibonacci_numbers.length() {

View file

@ -13,32 +13,18 @@ type JsResolver struct {
jsResolver js.Value
}
func (r *JsResolver) Resolve(name string) (core.Node, error) {
func (r *JsResolver) Resolve(name string) (string, error) {
jsv := r.jsResolver.Invoke(name)
if jsv.Type() == js.TypeUndefined {
return nil, errors.New("cannot find import with name " + name)
return "", errors.New("cannot find import with name " + name)
}
if jsv.Type() != js.TypeString {
return nil, errors.New("invalid value for source: " + jsv.String())
return "", errors.New("invalid value for source: " + jsv.String())
}
source := jsv.String()
l := core.NewLexer(source)
tokens, err := l.Tokenize()
if err != nil {
return nil, err
}
p := core.NewParser(tokens)
tree, err := p.Parse()
if err != nil {
return nil, err
}
return tree, nil
return jsv.String(), nil
}
func jsError(err error) interface{} {
@ -68,14 +54,14 @@ func run(_ js.Value, args []js.Value) interface{} {
log.Printf("got tokens: %v", tokens)
parser := core.NewParser(tokens)
parser := core.NewParser(source, tokens)
tree, err := parser.Parse()
if err != nil {
var e core.ParsingError
var e core.FormatedError
if errors.As(err, &e) {
errorHandler.Invoke(e.Format([]rune(source)))
errorHandler.Invoke(e.Format())
return nil
}
errorHandler.Invoke(err.Error())
@ -86,7 +72,7 @@ func run(_ js.Value, args []js.Value) interface{} {
compiler := core.NewCompiler([]rune(source))
log.Printf("Set imports resolver", tree.String())
log.Println("Set imports resolver")
compiler.SetImportsResolver(&JsResolver{
resolver,
@ -118,8 +104,8 @@ func run(_ js.Value, args []js.Value) interface{} {
vm.SetGlobal("write", &core.BuiltinFunctionValue{
Name: "write",
Signature: &core.FunctionSignature{
[]core.TypeSignature{&core.StringSignature{}},
&core.NilSignature{},
In: []core.TypeSignature{&core.StringSignature{}},
Out: &core.NilSignature{},
},
F: func(vm *core.VM, this core.Value, args []core.Value) (core.Value, error) {
s := args[0].String()