Merge branch 'types' into main
All checks were successful
/ test (push) Successful in 46s

This commit is contained in:
Neemek 2025-09-16 17:38:46 +00:00
commit f53ab30dbe
42 changed files with 3635 additions and 904 deletions

View file

@ -1,10 +1,13 @@
package main
import (
"bufio"
"errors"
"github.com/alecthomas/kong"
"log"
"neemek.com/anglais/core"
"os"
"path"
"path/filepath"
)
@ -13,8 +16,9 @@ type Context struct {
}
type RunCmd struct {
Bytecode bool `name:"bytecode" short:"c" help:"Run file as if it's bytecode"`
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
IgnoreWarnings bool `name:"ignore-warnings" help:"Ignore warning messages"`
Bytecode bool `name:"bytecode" short:"c" help:"Run file as if it's bytecode"`
File string `arg:"" name:"file" help:"File to read program from" type:"existingfile"`
}
// WorkingDirectoryResolver resolves imports relative to the working directory
@ -22,107 +26,137 @@ 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 nil, err
return "", err
}
str := string(f)
l := core.NewLexer(str)
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 string(f), nil
}
func (cmd *RunCmd) Run(ctx *Context) error {
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, fpath string, ignoreWarnings bool) (*core.Chunk, error) {
if ctx.Debug {
log.Println("Reading file")
}
f, err := os.ReadFile(cmd.File)
f, err := os.ReadFile(fpath)
if err != nil {
return err
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
}
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")
}
pathat, err := filepath.Abs(fpath)
if err != nil {
return nil, err
}
tree, err := p.Parse(pathat)
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())
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(fpath)
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 {
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")
}
chunk = c
} else {
if ctx.Debug {
log.Printf("Lexed %d tokens", len(tokens))
}
p := core.NewParser(tokens)
if ctx.Debug {
log.Println("Initialized parser")
log.Println("Reading file")
}
tree, err := p.Parse()
f, err := os.ReadFile(cmd.File)
// 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()
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 {
return err
}
chunk = c.Chunk
} else {
if ctx.Debug {
log.Println("Registering GOB types")
}
@ -157,72 +191,13 @@ 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)
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 {
print(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug {
log.Println("Initialized compiler")
}
c := core.NewCompiler()
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)
c, err := makeChunk(ctx, cmd.File, cmd.IgnoreWarnings)
if err != nil {
return err
}
@ -237,7 +212,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")
@ -252,11 +227,59 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
return nil
}
type ReplCmd struct {
}
func (cmd *ReplCmd) Run(ctx *Context) error {
c := core.NewCompiler([]rune(""))
vm := core.NewVM(core.NewChunk([]core.Bytecode{}, []core.Value{}), 256, 256)
reader := bufio.NewReader(os.Stdin)
for {
print("> ")
src, err := reader.ReadString('\n')
if err != nil {
return err
}
l := core.NewLexer(src)
tokens, err := l.Tokenize()
if err != nil {
log.Println(err)
continue
}
p := core.NewParser(src, tokens)
prog, err := p.Parse("REPL")
if err != nil {
var e core.FormatedError
if errors.As(err, &e) {
log.Print(e.Format())
}
continue
}
c.SetSource(src)
if err = c.Compile(prog); err != nil {
var e core.FormatedError
if errors.As(err, &e) {
log.Print(e.Format())
}
continue
}
vm.SetChunk(c.Chunk)
for vm.Next() {
}
}
}
var cli struct {
Debug bool `short:"D" name:"debug" help:"Enable debug mode."`
Run RunCmd `cmd:"" name:"run" help:"Run program."`
CompileCmd CompileCmd `cmd:"" name:"compile" help:"Compile program to bytecode."`
Run RunCmd `cmd:"" name:"run" help:"Run program."`
Compile CompileCmd `cmd:"" name:"compile" help:"Compile program to bytecode."`
Repl ReplCmd `cmd:"" name:"repl" help:"Start a REPL loop."`
}
func main() {