package main import ( "bufio" "encoding/json" "errors" "flag" "fmt" "os" "path" "strings" "time" "usebox.net/micro-lang/interpreter" "usebox.net/micro-lang/parser" "usebox.net/micro-lang/tokenizer" ) const name = "micro" var Version = "" func perror(err error) { fmt.Fprintln(os.Stderr, err) first := err for { perr := err err = errors.Unwrap(err) if err == nil { if perr.Error() != first.Error() { fmt.Fprintf(os.Stderr, "%s\n", perr) } break } } } func fatal(errno int, err error) { perror(err) os.Exit(errno) } func fatals(typ string, errno int, err error) { fmt.Fprintf(os.Stderr, "%s: %s\n", name, typ) fatal(errno, err) } func repl() { scanner := bufio.NewScanner(os.Stdin) p := parser.NewParser(interpreter.BuiltInTypes()) i := interpreter.NewInterpreter() fmt.Printf("Welcome to %s %s\nType in expressions for evaluation, or :q to exit.\n\n", strings.Title(name), Version) repl := 0 for { fmt.Printf("%s> ", name) var line string multiline := false for { if multiline { fmt.Print(" | ") line += " " } if !scanner.Scan() { fmt.Println() return } r := scanner.Text() if r == ":q" { return } line += r if !multiline { if r == "" { continue } if r[len(r)-1] != ';' { multiline = true continue } break } if r == "" { break } } in := strings.NewReader(line) repl++ t := tokenizer.NewTokenizer(fmt.Sprintf("in%d", repl), in) ts, err := t.Scan() if err != nil { perror(err) continue } tree, err := p.Parse(ts) if err != nil { perror(err) continue } for _, expr := range tree { v, err := i.Interp(expr) if err != nil { perror(err) break } if v != nil { fmt.Println(v) } } } } func usage() { me := path.Base(os.Args[0]) fmt.Fprintf(os.Stderr, "Usage: %s [options] ... [file]\nOptions:\n", me) flag.PrintDefaults() fmt.Fprintln(os.Stderr) } func main() { fVersion := flag.Bool("V", false, "display version and exit") fParse := flag.Bool("parse", false, "parse reporting any errors and exit") fDebugTokens := flag.Bool("tokens", false, "dump to stderr tokens and exit") fDebugAst := flag.Bool("ast", false, "dump to stderr AST and exit") fTime := flag.Bool("time", false, "measure execution time") flag.Usage = usage flag.Parse() if *fVersion { fmt.Printf("%s %s\n", name, Version) os.Exit(0) } var input *os.File var filename = flag.Arg(0) if filename == "" { repl() os.Exit(0) } else { var err error input, err = os.Open(filename) if err != nil { fatals("error opening input file", 1, err) } } t := tokenizer.NewTokenizer(filename, input) ts, err := t.Scan() if err != nil { fatals("parsing error", 10, err) } if *fDebugTokens { out, err := json.MarshalIndent(ts, "", " ") if err != nil { fatals("encoding error", 11, err) } fmt.Fprintf(os.Stderr, "%s\n", string(out)) os.Exit(0) } p := parser.NewParser(interpreter.BuiltInTypes()) tree, err := p.Parse(ts) if *fParse { if err != nil { perror(err) os.Exit(1) } os.Exit(0) } if err != nil { fatal(20, err) } if *fDebugAst { out, err := json.MarshalIndent(tree, "", " ") if err != nil { fatals("encoding error", 21, err) } fmt.Fprintf(os.Stderr, "%s\n", string(out)) os.Exit(0) } start := time.Now() i := interpreter.NewInterpreter() for _, expr := range tree { if _, err := i.Interp(expr); err != nil { fatal(31, err) } } if *fTime { fmt.Fprintf(os.Stderr, "elapsed: %f\n", time.Since(start).Seconds()) } }