// Package monkey provides a virtual machine for executing Monkey programs. package monkey import ( "errors" "fmt" "io" "log" "os" "path/filepath" "runtime" "strings" "monkey/internal/compiler" "monkey/internal/parser" "monkey/internal/vm" ) // MonkeyVersion is the version number for the monkey language virtual machine. var MonkeyVersion = "v2.0.1" func fileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { return false } return !info.IsDir() } func decodeCompiledFile(fn string) (*compiler.Bytecode, error) { b, err := os.ReadFile(fn) if err != nil { return nil, err } return compiler.Decode(b), nil } func compileFile(fn string, debug bool) (*compiler.Bytecode, error) { input, err := os.ReadFile(fn) if err != nil { return nil, err } res, errs := parser.Parse(fn, string(input)) if len(errs) > 0 { var buf strings.Builder for _, e := range errs { buf.WriteString(e) buf.WriteByte('\n') } return nil, errors.New(buf.String()) } if debug { log.Printf("AST:\n%s\n", res) } c := compiler.New() c.Debug = debug c.SetFileInfo("", string(input)) if err := c.Compile(res); err != nil { return nil, err } return c.Bytecode(), nil } func compileString(input string, debug bool) (bc *compiler.Bytecode, err error) { res, errs := parser.Parse("", input) if len(errs) > 0 { var buf strings.Builder for _, e := range errs { buf.WriteString(e) buf.WriteByte('\n') } return nil, errors.New(buf.String()) } if debug { log.Printf("AST:\n%s\n", res) } c := compiler.New() c.Debug = debug c.SetFileInfo("", input) if err := c.Compile(res); err != nil { return nil, err } return c.Bytecode(), nil } // ExecFile executes a Monkey program from a file on disk. func ExecFile(fn string, opts *Options) error { var ( bytecode *compiler.Bytecode err error ) if opts == nil { opts = &Options{} } ext := filepath.Ext(fn) mc := fn[:len(fn)-len(ext)] + ".mc" if ext == ".mc" { bytecode, err = decodeCompiledFile(fn) } else if ext == ".m" && fileExists(mc) { bytecode, err = decodeCompiledFile(mc) } else { input, err := os.ReadFile(fn) if err != nil { return err } bytecode, err = compileString(string(input), opts.Debug) } if err != nil { return err } if opts.Debug { log.Printf("Bytecode:\n%s\n", bytecode) } if ext == ".m" && !fileExists(mc) { data, err := bytecode.Encode() if err == nil { os.WriteFile(mc, data, os.FileMode(0644)) } } mvm := vm.New(fn, bytecode, opts.ToVMOptions()...) if err := mvm.Run(); err != nil { return err } return nil } // ExecString executes a Monkey program from a string. func ExecString(input string, opts *Options) error { if opts == nil { opts = &Options{} } bytecode, err := compileString(input, opts.Debug) if err != nil { return err } if opts.Debug { log.Printf("Bytecode:\n%s\n", bytecode) } mvm := vm.New("", bytecode, opts.ToVMOptions()...) if err = mvm.Run(); err != nil { return err } return nil } // CompileFiles compiles multiple Monkey files and returns any errors encountered during compilation. func CompileFiles(fns []string, opts *Options) error { if opts == nil { opts = &Options{} } for _, fn := range fns { ext := filepath.Ext(fn) mc := fn[:len(fn)-len(ext)] + ".mc" input, err := os.ReadFile(fn) if err != nil { return err } res, errs := parser.Parse(fn, string(input)) if len(errs) > 0 { var buf strings.Builder for _, e := range errs { buf.WriteString(e) buf.WriteByte('\n') } return errors.New(buf.String()) } if opts.Debug { log.Printf("AST:\n%s\n", res) } c := compiler.New() c.Debug = opts.Debug if err := c.Compile(res); err != nil { return err } if opts.Debug { log.Printf("Bytecode:\n%s\n", c.Bytecode()) } data, err := c.Bytecode().Encode() if err != nil { return err } if err := os.WriteFile(mc, data, os.FileMode(0644)); err != nil { return err } } return nil } // PrintVersionInfo prints information about the current version of the monkey virtual machine to the given writer. func PrintVersionInfo(w io.Writer) { fmt.Fprintf(w, "Monkey %s on %s\n", MonkeyVersion, runtime.GOOS) }