211 lines
4.1 KiB
Go
211 lines
4.1 KiB
Go
// 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("<stdin>", 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("<stdin>", 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("<stdin>", 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
|
|
)
|
|
|
|
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, vm.WithDebug(opts.Debug), vm.WithTrace(opts.Trace))
|
|
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 {
|
|
bytecode, err := compileString(input, opts.Debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.Debug {
|
|
log.Printf("Bytecode:\n%s\n", bytecode)
|
|
}
|
|
|
|
mvm := vm.New("<stdin>", 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 {
|
|
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)
|
|
}
|