add file trace to parser, refactor, fix inner type

This commit is contained in:
Neemek 2025-09-30 14:09:05 +02:00
parent bcf0978b68
commit c8660a6471
Signed by: neemek
GPG key ID: 84FFE4D7D40AB25E
10 changed files with 131 additions and 51 deletions

View file

@ -9,6 +9,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings"
) )
type Context struct { type Context struct {
@ -21,24 +22,28 @@ type RunCmd struct {
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"` File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
} }
// WorkingDirectoryResolver resolves imports relative to the working directory // DirectoryResolver resolves imports from several directories
type WorkingDirectoryResolver struct { type DirectoryResolver struct {
workingDirectory string // directories to search through, in decreasing priority.
directories []string
} }
func (r *WorkingDirectoryResolver) Resolve(path string) (string, error) { func (r *DirectoryResolver) Resolve(path string) (string, error) {
pth := filepath.Join(r.workingDirectory, path) for _, d := range r.directories {
pth := filepath.Join(d, path)
f, err := os.ReadFile(pth) f, err := os.ReadFile(pth)
if err != nil {
return "", err if err == nil {
return string(f), nil
}
} }
return string(f), nil return "", errors.New("couldn't resolve import")
} }
func (r *WorkingDirectoryResolver) IsSame(a, b string) bool { func (r *DirectoryResolver) IsSame(a, b string) bool {
apath := filepath.Clean(filepath.Join(r.workingDirectory, a)) apath := filepath.Clean(a)
bpath := filepath.Clean(filepath.Join(r.workingDirectory, b)) bpath := filepath.Clean(b)
return apath == bpath return apath == bpath
} }
@ -57,7 +62,7 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
src := string(f) src := string(f)
if ctx.Debug { if ctx.Debug {
log.Println("Initialized lexer") log.Println("Initializing lexer")
} }
l := core.NewLexer(src) l := core.NewLexer(src)
@ -78,7 +83,7 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
log.Printf("Lexed %d tokens", len(tokens)) log.Printf("Lexed %d tokens", len(tokens))
} }
p := core.NewParser(src, tokens) p := core.NewParser(src, []string{fpath}, tokens)
if ctx.Debug { if ctx.Debug {
log.Println("Initialized parser") log.Println("Initialized parser")
@ -110,9 +115,14 @@ func makeChunk(ctx *Context, fpath string, ignoreWarnings bool) (*core.Chunk, er
log.Println("Setting imports resolver") log.Println("Setting imports resolver")
} }
lib := os.Getenv("ANGLAIS_PATH")
dirs := strings.Split(lib, ":")
dir, _ := path.Split(fpath) dir, _ := path.Split(fpath)
c.SetImportsResolver(&WorkingDirectoryResolver{ c.SetImportsResolver(&DirectoryResolver{
append([]string{
dir, dir,
}, dirs...),
}) })
if ctx.Debug { if ctx.Debug {
@ -231,7 +241,7 @@ type ReplCmd struct {
} }
func (cmd *ReplCmd) Run(ctx *Context) error { func (cmd *ReplCmd) Run(ctx *Context) error {
c := core.NewCompiler([]rune("")) c := core.NewCompiler([]rune{})
vm := core.NewVM(core.NewChunk([]core.Bytecode{}, []core.Value{}), 256, 256) vm := core.NewVM(core.NewChunk([]core.Bytecode{}, []core.Value{}), 256, 256)
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
@ -249,7 +259,7 @@ func (cmd *ReplCmd) Run(ctx *Context) error {
continue continue
} }
p := core.NewParser(src, tokens) p := core.NewParser(src, []string{}, tokens)
prog, err := p.Parse("REPL") prog, err := p.Parse("REPL")
if err != nil { if err != nil {
var e core.FormatedError var e core.FormatedError

View file

@ -130,7 +130,7 @@ func TestAll(t *testing.T) {
} }
t.Log("Initializing parser") t.Log("Initializing parser")
p := NewParser(tc.src, tokens) p := NewParser(tc.src, []string{}, tokens)
t.Log("Parsing tokens") t.Log("Parsing tokens")
tree, err := p.Parse(tc.src) tree, err := p.Parse(tc.src)
@ -173,7 +173,7 @@ func BenchmarkAll(b *testing.B) {
l := NewLexer(tc.src) l := NewLexer(tc.src)
tokens, _ := l.Tokenize() tokens, _ := l.Tokenize()
p := NewParser(tc.src, tokens) p := NewParser(tc.src, []string{}, tokens)
tree, _ := p.Parse(tc.src) tree, _ := p.Parse(tc.src)
c := NewCompiler([]rune(tc.src)) c := NewCompiler([]rune(tc.src))

View file

@ -393,6 +393,18 @@ func (c *Compiler) compile(tree Node) error {
return c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n) return c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n)
} }
var innerType TypeSignature
if a, ok := n.source.(*AccessNode); ok {
isig, err := c.deduceSignature(a.source)
if err != nil {
return err
}
if isig.Type() == TypeList {
innerType = isig.(*ListSignature).Contents
}
}
for i, arg := range n.args { for i, arg := range n.args {
sig, err := c.deduceSignature(arg) sig, err := c.deduceSignature(arg)
if err != nil { if err != nil {
@ -400,7 +412,11 @@ func (c *Compiler) compile(tree Node) error {
} }
// check that arg type is as required // check that arg type is as required
if !f.In[i].Matches(sig) { fin := f.In[i]
if fin.Type() == TypeInner && innerType != nil {
fin = innerType
}
if !fin.Matches(sig) {
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg) return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg)
} }
@ -598,7 +614,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
n := tree.(*ListNode) n := tree.(*ListNode)
contents := n.content contents := n.content
// check for contents type // check for content type
for _, v := range n.items { for _, v := range n.items {
sig, err := c.deduceSignature(v) sig, err := c.deduceSignature(v)
if err != nil { if err != nil {
@ -761,7 +777,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
if f.Out.Type() == TypeInner { if f.Out.Type() == TypeInner {
if innerType == nil { if innerType == nil {
return nil, c.error(fmt.Sprintf("function source (%s) has no inner type", sig), n.source) return nil, c.error(fmt.Sprintf("function source (%s) has no inner type (@ output)", sig), n.source)
} }
return innerType, nil return innerType, nil
@ -1204,7 +1220,7 @@ func (c *Compiler) error(msg string, causer Node) CompilerError {
msg, msg,
causer, causer,
c.source, c.source,
c.fileStack.items[0:c.fileStack.Current], c.fileStack.Slice(),
} }
} }
@ -1223,7 +1239,7 @@ func (c *Compiler) resolveImport(path string) error {
// stop recursive imports // stop recursive imports
for i := c.fileStack.Current - 1; i >= 0; i-- { for i := c.fileStack.Current - 1; i >= 0; i-- {
if c.resolver.IsSame(path, c.fileStack.items[i]) { if c.resolver.IsSame(path, c.fileStack.items[i]) {
return errors.New("recursive imports") return errors.New("recursive import")
} }
} }
@ -1238,7 +1254,7 @@ func (c *Compiler) resolveImport(path string) error {
return err return err
} }
parser := NewParser(src, tokens) parser := NewParser(src, append(c.fileStack.Slice(), path), tokens)
p, err := parser.Parse(path) p, err := parser.Parse(path)
if err != nil { if err != nil {
return err return err

View file

@ -164,7 +164,7 @@ func (t TokenType) String() string {
return "hexadecimal" return "hexadecimal"
} }
return "UNDEFINED TOKENTYPE STRING CONVERSION" panic("UNDEFINED TOKENTYPE STRING CONVERSION")
} }
type Lexer struct { type Lexer struct {
@ -320,9 +320,9 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenString), nil return l.makeToken(TokenString), nil
default: default:
if unicode.IsLetter(c) || c == '_' { if l.isAlpha(c) {
// assemble variable // assemble variable
for l.isAlpha(l.peek()) { for l.isAlphaNumeric(l.peek()) {
l.advance() l.advance()
} }
@ -435,7 +435,11 @@ func (l *Lexer) accept(c rune) bool {
} }
func (l *Lexer) isAlpha(c rune) bool { func (l *Lexer) isAlpha(c rune) bool {
return unicode.IsLetter(c) || unicode.IsDigit(c) || c == '_' return unicode.IsLetter(c) || c == '_'
}
func (l *Lexer) isAlphaNumeric(c rune) bool {
return l.isAlpha(c) || unicode.IsDigit(c)
} }
func (l *Lexer) advance() { func (l *Lexer) advance() {

View file

@ -17,6 +17,7 @@ type ParsingError struct {
Description string Description string
Causer *Token Causer *Token
Source string Source string
Trace []string
} }
func (p ParsingError) Error() string { func (p ParsingError) Error() string {
@ -26,7 +27,7 @@ func (p ParsingError) Error() string {
// Format Print a rich and informative error // Format Print a rich and informative error
func (p ParsingError) Format() string { func (p ParsingError) Format() string {
src := []rune(p.Source) src := []rune(p.Source)
builder := strings.Builder{} b := strings.Builder{}
lineNumber := 1 lineNumber := 1
lineBeginning := 0 lineBeginning := 0
@ -46,39 +47,46 @@ func (p ParsingError) Format() string {
} }
descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1) descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1)
builder.WriteString(p.Description) b.WriteString(p.Description)
builder.WriteRune('\n') b.WriteRune('\n')
builder.WriteString(descriptor) b.WriteString(descriptor)
builder.WriteString(" | ") b.WriteString(" | ")
builder.WriteString(string(src[lineBeginning:lineEnd])) b.WriteString(string(src[lineBeginning:lineEnd]))
builder.WriteString("\n") b.WriteString("\n")
builder.WriteString(strings.Repeat(" ", len(descriptor))) b.WriteString(strings.Repeat(" ", len(descriptor)))
builder.WriteString(" ") b.WriteString(" ")
for i := lineBeginning; i <= int(p.Causer.Start); i++ { for i := lineBeginning; i <= int(p.Causer.Start); i++ {
builder.WriteRune(' ') b.WriteRune(' ')
} }
for i := 0; i < int(p.Causer.Length); i++ { for i := 0; i < int(p.Causer.Length); i++ {
builder.WriteRune('^') b.WriteRune('^')
} }
builder.WriteRune('\n') b.WriteRune('\n')
b.WriteRune('\n')
return builder.String() for i := len(p.Trace) - 1; i >= 0; i-- {
b.WriteString(fmt.Sprintf("[%d] %s\n", i, p.Trace[i]))
}
return b.String()
} }
type Parser struct { type Parser struct {
source string source string
trace []string
tokens []Token tokens []Token
prev *Token prev *Token
curr *Token curr *Token
pos Pos pos Pos
} }
func NewParser(source string, tokens []Token) *Parser { func NewParser(source string, trace []string, tokens []Token) *Parser {
return &Parser{ return &Parser{
source: source, source: source,
trace: trace,
tokens: tokens, tokens: tokens,
pos: 0, pos: 0,
} }
@ -121,6 +129,7 @@ func (p *Parser) Parse(path string) (*Program, error) {
} }
imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1]) imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1])
continue
} }
b, err := p.block(true) b, err := p.block(true)
@ -190,6 +199,7 @@ func (p *Parser) error(error string, causer *Token) error {
Description: error, Description: error,
Causer: causer, Causer: causer,
Source: p.source, Source: p.source,
Trace: p.trace,
} }
} }

View file

@ -10,7 +10,7 @@ import (
func TestNewParser(t *testing.T) { func TestNewParser(t *testing.T) {
tokens := make([]Token, 0) tokens := make([]Token, 0)
p := NewParser("", tokens) p := NewParser("", []string{}, tokens)
if p == nil { if p == nil {
t.Fatal("parser should not be nil") t.Fatal("parser should not be nil")
@ -34,7 +34,7 @@ func TestNewParser(t *testing.T) {
func BenchmarkNewParser(b *testing.B) { func BenchmarkNewParser(b *testing.B) {
tokens := make([]Token, 0) tokens := make([]Token, 0)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = NewParser("", tokens) _ = NewParser("", []string{}, tokens)
} }
} }
@ -910,7 +910,7 @@ func TestParser_Parse(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Logf("Initializing parser") t.Logf("Initializing parser")
p := NewParser("", data.tokens) p := NewParser("", []string{}, data.tokens)
t.Logf("Parsing main") t.Logf("Parsing main")
tree, err := p.Parse("") tree, err := p.Parse("")
@ -931,7 +931,7 @@ func BenchmarkParser_Parse(b *testing.B) {
for name, data := range tokenData { for name, data := range tokenData {
b.Run(name, func(b *testing.B) { b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
p := NewParser("", data.tokens) p := NewParser("", []string{}, data.tokens)
_, _ = p.Parse("") _, _ = p.Parse("")
} }

View file

@ -56,3 +56,8 @@ func (s *Stack[T]) check() {
panic("stack underflow") panic("stack underflow")
} }
} }
// Slice gets a slice of the current items in use
func (s *Stack[T]) Slice() []T {
return s.items[:s.Current]
}

View file

@ -467,6 +467,31 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
nil, nil,
false, false,
}, },
"put": {
"put",
&FunctionSignature{
[]TypeSignature{
&NumberSignature{}, &InnerSignature{},
},
&NilSignature{},
},
func(_ *VM, this Value, args []Value) (Value, error) {
l := this.(*ListValue)
i := int(args[0].(*NumberValue).Number)
v := args[1]
// bounds check
if i < 0 || i >= len(l.Items) {
return nil, errors.New(fmt.Sprintf("index %x out of range", i))
}
l.Items[i] = v
return &NilValue{}, nil
},
nil,
false,
},
"length": { "length": {
"length", "length",
&FunctionSignature{ &FunctionSignature{

View file

@ -1,9 +1,19 @@
#!/bin/bash #!/bin/bash
echo '=== Building WASM lib ==='
cd wasm || exit 1
if ! GOOS=js GOARCH=wasm go build .; then
echo "=x= Had error building WASM lib =x="
exit 1
else
echo "=+= Successfully built WASM lib =+="
fi
cd ..
echo '=== Building CLI ===' echo '=== Building CLI ==='
cd cli || exit 1 cd cli || exit 1
if ! go build .; then if ! go build .; then
echo "=== Had error building CLI ===" echo "=x= Had error building CLI =x="
exit 1 exit 1
else else
echo "=+= Successfully built CLI =+=" echo "=+= Successfully built CLI =+="

View file

@ -1,4 +1,4 @@
//go:build wasm && go1.23 //go:build wasm && go1.24
package main package main
@ -58,7 +58,7 @@ func run(_ js.Value, args []js.Value) interface{} {
log.Printf("got tokens: %v", tokens) log.Printf("got tokens: %v", tokens)
parser := core.NewParser(source, tokens) parser := core.NewParser(source, []string{}, tokens)
tree, err := parser.Parse(source) tree, err := parser.Parse(source)