2024-03-10 22:48:57 +01:00
|
|
|
// The compiler
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-03-29 15:43:55 +01:00
|
|
|
"bytes"
|
2024-03-29 15:12:37 +01:00
|
|
|
"embed"
|
2024-03-29 15:43:55 +01:00
|
|
|
"flag"
|
2024-03-10 22:48:57 +01:00
|
|
|
"log"
|
|
|
|
"os"
|
2024-03-29 15:43:55 +01:00
|
|
|
"os/exec"
|
2024-03-21 20:37:21 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2024-03-10 22:48:57 +01:00
|
|
|
)
|
|
|
|
|
2024-03-24 21:36:34 +01:00
|
|
|
const ERROR_LOG_LINES = 5
|
2024-03-21 20:37:21 +01:00
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
//go:embed stdlib/*
|
|
|
|
var stdlib embed.FS
|
|
|
|
|
2024-03-21 20:37:21 +01:00
|
|
|
func countTabs(line string) int {
|
|
|
|
tabs := 0
|
|
|
|
for _, rune := range line {
|
|
|
|
if rune == '\t' {
|
|
|
|
tabs++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tabs
|
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
func printCompilerError(sources map[string]string, err CompilerError) {
|
|
|
|
source, ok := sources[err.Position.SourceFile]
|
|
|
|
if !ok {
|
|
|
|
log.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-21 20:37:21 +01:00
|
|
|
sourceRunes := []rune(source)
|
|
|
|
lines := strings.Split(source, "\n")
|
|
|
|
line := 0
|
|
|
|
col := 0
|
|
|
|
var i uint64
|
2024-03-29 15:12:37 +01:00
|
|
|
for i = 0; i < err.Position.Position; i++ {
|
2024-03-21 20:37:21 +01:00
|
|
|
col++
|
|
|
|
if sourceRunes[i] == '\n' {
|
|
|
|
line++
|
|
|
|
col = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
log.Println("Failed to compile: " + err.Message + " (at " + err.Position.SourceFile + ":" + strconv.Itoa(line+1) + ":" + strconv.Itoa(col+1) + ")")
|
2024-03-21 20:37:21 +01:00
|
|
|
|
|
|
|
linesStart := max(0, line-ERROR_LOG_LINES)
|
|
|
|
linesEnd := min(len(lines), line+ERROR_LOG_LINES+1)
|
|
|
|
|
|
|
|
for _, line := range lines[linesStart:line] {
|
|
|
|
println(strings.Replace(line, "\t", " ", -1))
|
|
|
|
}
|
|
|
|
|
|
|
|
tabs := countTabs(lines[line])
|
|
|
|
println(strings.Repeat(" ", col+tabs*3) + "v--- error occurs here ---")
|
|
|
|
|
|
|
|
for _, line := range lines[line:linesEnd] {
|
|
|
|
println(strings.Replace(line, "\t", " ", -1))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
func readEmbedDir(name string, files map[string]string) {
|
|
|
|
entries, err := stdlib.ReadDir(name)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
2024-03-10 22:48:57 +01:00
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
for _, entry := range entries {
|
|
|
|
fullName := name + "/" + entry.Name()
|
|
|
|
if entry.IsDir() {
|
|
|
|
readEmbedDir(fullName, files)
|
|
|
|
} else {
|
|
|
|
bytes, err := stdlib.ReadFile(fullName)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
files[fullName] = string(bytes)
|
|
|
|
}
|
2024-03-10 22:48:57 +01:00
|
|
|
}
|
2024-03-29 15:12:37 +01:00
|
|
|
}
|
2024-03-10 22:48:57 +01:00
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
func main() {
|
2024-03-29 15:43:55 +01:00
|
|
|
outputFile := flag.String("o", "a.out", "Output file")
|
|
|
|
generateWAT := flag.Bool("wat", false, "Generate WAT instead of WASM")
|
2024-03-30 21:57:38 +01:00
|
|
|
wasm64 := flag.Bool("wasm64", false, "Use 64-bit memory (may not be supported in all browsers)")
|
2024-03-29 15:43:55 +01:00
|
|
|
includeStdlib := flag.Bool("stdlib", true, "Include the standard library")
|
|
|
|
flag.Parse()
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
if len(os.Args) < 2 {
|
|
|
|
log.Fatalln("Usage: " + os.Args[0] + " <files...>")
|
|
|
|
}
|
2024-03-21 20:37:21 +01:00
|
|
|
|
2024-03-29 15:43:55 +01:00
|
|
|
files := flag.Args()
|
2024-03-29 15:12:37 +01:00
|
|
|
|
|
|
|
fileSources := make(map[string]string)
|
|
|
|
for _, file := range files {
|
|
|
|
content, err := os.ReadFile(file)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalln("Cannot open input file.", err)
|
2024-03-14 16:42:22 +01:00
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
fileSources[file] = string(content)
|
2024-03-10 22:48:57 +01:00
|
|
|
}
|
|
|
|
|
2024-03-29 15:43:55 +01:00
|
|
|
if *includeStdlib {
|
|
|
|
stdlibFiles := make(map[string]string)
|
|
|
|
readEmbedDir("stdlib", stdlibFiles)
|
|
|
|
for path, file := range stdlibFiles {
|
|
|
|
fileSources["[embedded]/"+path] = file
|
|
|
|
}
|
2024-03-29 15:12:37 +01:00
|
|
|
}
|
2024-03-11 22:05:36 +01:00
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
fileTokens := make(map[string][]LexToken)
|
|
|
|
for file, source := range fileSources {
|
|
|
|
tokens, err := lexer(file, source)
|
|
|
|
if err != nil {
|
|
|
|
if c, ok := err.(CompilerError); ok {
|
|
|
|
printCompilerError(fileSources, c)
|
|
|
|
} else {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2024-03-13 23:26:20 +01:00
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
// log.Printf("Tokens:\n%+#v\n\n", tokens)
|
|
|
|
fileTokens[file] = tokens
|
2024-03-11 22:05:36 +01:00
|
|
|
}
|
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
var parsedFiles []*ParsedFile
|
|
|
|
for _, tokens := range fileTokens {
|
|
|
|
parser := Parser{Tokens: tokens}
|
|
|
|
parsed, err := parser.parseFile()
|
|
|
|
if err != nil {
|
|
|
|
if c, ok := err.(CompilerError); ok {
|
|
|
|
printCompilerError(fileSources, c)
|
|
|
|
} else {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-29 15:43:55 +01:00
|
|
|
// log.Printf("Parsed:\n%+#v\n\n", parsed)
|
2024-03-29 15:12:37 +01:00
|
|
|
parsedFiles = append(parsedFiles, parsed)
|
|
|
|
}
|
2024-03-16 20:12:00 +01:00
|
|
|
|
2024-03-30 21:57:38 +01:00
|
|
|
validator := Validator{Files: parsedFiles}
|
2024-03-20 19:26:48 +01:00
|
|
|
errors := validator.validate()
|
2024-03-16 20:12:00 +01:00
|
|
|
if len(errors) != 0 {
|
2024-03-29 15:12:37 +01:00
|
|
|
for _, err := range errors {
|
2024-03-16 20:12:00 +01:00
|
|
|
if c, ok := err.(CompilerError); ok {
|
2024-03-29 15:12:37 +01:00
|
|
|
printCompilerError(fileSources, c)
|
2024-03-21 20:37:21 +01:00
|
|
|
} else {
|
|
|
|
log.Println(err)
|
2024-03-16 20:12:00 +01:00
|
|
|
}
|
2024-03-21 20:37:21 +01:00
|
|
|
}
|
2024-03-16 20:12:00 +01:00
|
|
|
|
2024-03-21 20:37:21 +01:00
|
|
|
if len(errors) != 0 {
|
|
|
|
return
|
2024-03-16 20:12:00 +01:00
|
|
|
}
|
|
|
|
}
|
2024-03-17 19:55:28 +01:00
|
|
|
|
2024-03-29 15:12:37 +01:00
|
|
|
// log.Printf("Validated:\n%+#v\n\n", parsedFiles)
|
2024-03-18 21:14:28 +01:00
|
|
|
|
2024-03-30 21:57:38 +01:00
|
|
|
compiler := Compiler{Files: parsedFiles, Wasm64: *wasm64}
|
|
|
|
wat, err := compiler.compile()
|
2024-03-18 21:14:28 +01:00
|
|
|
if err != nil {
|
|
|
|
if c, ok := err.(CompilerError); ok {
|
2024-03-29 15:12:37 +01:00
|
|
|
printCompilerError(fileSources, c)
|
2024-03-21 20:37:21 +01:00
|
|
|
} else {
|
|
|
|
log.Println(err)
|
2024-03-18 21:14:28 +01:00
|
|
|
}
|
|
|
|
|
2024-03-21 20:37:21 +01:00
|
|
|
return
|
2024-03-18 21:14:28 +01:00
|
|
|
}
|
|
|
|
|
2024-03-29 15:43:55 +01:00
|
|
|
// log.Println("WAT: " + wat)
|
|
|
|
|
|
|
|
if *generateWAT {
|
|
|
|
os.WriteFile(*outputFile, []byte(wat), 0o644)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command("wat2wasm", "-o", *outputFile, "-")
|
|
|
|
|
|
|
|
var input bytes.Buffer
|
|
|
|
input.Write([]byte(wat))
|
|
|
|
|
|
|
|
cmd.Stdin = &input
|
|
|
|
|
2024-03-30 21:57:38 +01:00
|
|
|
output, err := cmd.CombinedOutput()
|
2024-03-29 15:43:55 +01:00
|
|
|
if err != nil {
|
2024-03-30 21:57:38 +01:00
|
|
|
log.Fatalln(err, string(output))
|
2024-03-29 15:43:55 +01:00
|
|
|
}
|
2024-03-10 22:48:57 +01:00
|
|
|
}
|