Perform package and module evaluation
This change looks up the main module from a package, and that module's entrypoint, when performing evaluation. In any case, the arguments are validated and bound to the resulting function's parameters.
This commit is contained in:
parent
2ec7452bf7
commit
281805311c
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/marapongo/mu/pkg/compiler"
|
"github.com/marapongo/mu/pkg/compiler"
|
||||||
"github.com/marapongo/mu/pkg/compiler/core"
|
"github.com/marapongo/mu/pkg/compiler/core"
|
||||||
"github.com/marapongo/mu/pkg/graph"
|
"github.com/marapongo/mu/pkg/graph"
|
||||||
|
"github.com/marapongo/mu/pkg/tokens"
|
||||||
"github.com/marapongo/mu/pkg/util/contract"
|
"github.com/marapongo/mu/pkg/util/contract"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,8 +82,8 @@ func newCompileCmd() *cobra.Command {
|
||||||
// dashdashArgsToMap is a simple args parser that places incoming key/value pairs into a map. These are then used
|
// dashdashArgsToMap is a simple args parser that places incoming key/value pairs into a map. These are then used
|
||||||
// during MuPackage compilation as inputs to the main entrypoint function.
|
// during MuPackage compilation as inputs to the main entrypoint function.
|
||||||
// TODO: this is fairly rudimentary; we eventually want to support arrays, maps, and complex types.
|
// TODO: this is fairly rudimentary; we eventually want to support arrays, maps, and complex types.
|
||||||
func dashdashArgsToMap(args []string) map[string]interface{} {
|
func dashdashArgsToMap(args []string) core.Args {
|
||||||
mapped := make(map[string]interface{})
|
mapped := make(core.Args)
|
||||||
|
|
||||||
for i := 0; i < len(args); i++ {
|
for i := 0; i < len(args); i++ {
|
||||||
arg := args[i]
|
arg := args[i]
|
||||||
|
@ -98,18 +99,18 @@ func dashdashArgsToMap(args []string) map[string]interface{} {
|
||||||
// Now find a k=v, and split the k/v part.
|
// Now find a k=v, and split the k/v part.
|
||||||
if eq := strings.IndexByte(arg, '='); eq != -1 {
|
if eq := strings.IndexByte(arg, '='); eq != -1 {
|
||||||
// For --k=v, simply store v underneath k's entry.
|
// For --k=v, simply store v underneath k's entry.
|
||||||
mapped[arg[:eq]] = arg[eq+1:]
|
mapped[tokens.Name(arg[:eq])] = arg[eq+1:]
|
||||||
} else {
|
} else {
|
||||||
if i+1 < len(args) && args[i+1][0] != '-' {
|
if i+1 < len(args) && args[i+1][0] != '-' {
|
||||||
// If the next arg doesn't start with '-' (i.e., another flag) use its value.
|
// If the next arg doesn't start with '-' (i.e., another flag) use its value.
|
||||||
mapped[arg] = args[i+1]
|
mapped[tokens.Name(arg)] = args[i+1]
|
||||||
i++
|
i++
|
||||||
} else if arg[0:3] == "no-" {
|
} else if arg[0:3] == "no-" {
|
||||||
// For --no-k style args, strip off the no- prefix and store false underneath k.
|
// For --no-k style args, strip off the no- prefix and store false underneath k.
|
||||||
mapped[arg[3:]] = false
|
mapped[tokens.Name(arg[3:])] = false
|
||||||
} else {
|
} else {
|
||||||
// For all other --k args, assume this is a boolean flag, and set the value of k to true.
|
// For all other --k args, assume this is a boolean flag, and set the value of k to true.
|
||||||
mapped[arg] = true
|
mapped[tokens.Name(arg)] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ func (node *DefinitionNode) GetDescription() *string { return node.Description }
|
||||||
// Module contains members, including variables, functions, and/or classes.
|
// Module contains members, including variables, functions, and/or classes.
|
||||||
type Module struct {
|
type Module struct {
|
||||||
DefinitionNode
|
DefinitionNode
|
||||||
|
Default bool `json:"default,omitempty"`
|
||||||
Imports *[]*ModuleToken `json:"imports,omitempty"`
|
Imports *[]*ModuleToken `json:"imports,omitempty"`
|
||||||
Members *ModuleMembers `json:"members,omitempty"`
|
Members *ModuleMembers `json:"members,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,11 +149,11 @@ func (c *compiler) CompilePackage(pkg *pack.Package) graph.Graph {
|
||||||
// Afterwards, we can safely evaluate the MuIL entrypoint, using our MuIL AST interpreter.
|
// Afterwards, we can safely evaluate the MuIL entrypoint, using our MuIL AST interpreter.
|
||||||
|
|
||||||
b := binder.New(c.w, c.ctx, c.reader)
|
b := binder.New(c.w, c.ctx, c.reader)
|
||||||
b.BindPackage(pkg)
|
pkgsym := b.BindPackage(pkg)
|
||||||
if !c.Diag().Success() {
|
if !c.Diag().Success() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
e := eval.New(b.Ctx())
|
e := eval.New(b.Ctx())
|
||||||
return e.EvaluatePackage(pkg)
|
return e.EvaluatePackage(pkgsym, c.ctx.Opts.Args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,16 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/marapongo/mu/pkg/diag"
|
"github.com/marapongo/mu/pkg/diag"
|
||||||
|
"github.com/marapongo/mu/pkg/tokens"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Args are a set of command line arguments supplied to a blueprint.
|
||||||
|
type Args map[tokens.Name]interface{}
|
||||||
|
|
||||||
// Options contains all of the settings a user can use to control the compiler's behavior.
|
// Options contains all of the settings a user can use to control the compiler's behavior.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Diag diag.Sink // a sink to use for all diagnostics.
|
Diag diag.Sink // a sink to use for all diagnostics.
|
||||||
Args map[string]interface{} // optional blueprint arguments passed at the CLI.
|
Args Args // optional blueprint arguments passed at the CLI.
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultOptions returns the default set of compiler options.
|
// DefaultOptions returns the default set of compiler options.
|
||||||
|
|
|
@ -4,5 +4,11 @@ package errors
|
||||||
|
|
||||||
// Eval errors are in the [1000,2000) range.
|
// Eval errors are in the [1000,2000) range.
|
||||||
var (
|
var (
|
||||||
ErrorUnhandledException = newError(1000, "An unhandled exception terminated the program: %v")
|
ErrorUnhandledException = newError(1000, "An unhandled exception terminated the program: %v")
|
||||||
|
ErrorPackageHasNoDefaultModule = newError(1001, "Package '%v' is missing a default module")
|
||||||
|
ErrorModuleHasNoEntryPoint = newError(1002, "Module '%v' is missing an entrypoint function")
|
||||||
|
ErrorFunctionArgMismatch = newError(1003, "Function expected %v arguments, but only got %v")
|
||||||
|
ErrorFunctionArgIncorrectType = newError(1004, "Function argument has an incorrect type; expected %v, got %v")
|
||||||
|
ErrorFunctionArgNotFound = newError(1005, "Function argument '%v' was not supplied")
|
||||||
|
ErrorFunctionArgUnknown = newError(1006, "Function argument '%v' was not recognized")
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
package eval
|
package eval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"github.com/marapongo/mu/pkg/compiler/ast"
|
"github.com/marapongo/mu/pkg/compiler/ast"
|
||||||
"github.com/marapongo/mu/pkg/compiler/binder"
|
"github.com/marapongo/mu/pkg/compiler/binder"
|
||||||
"github.com/marapongo/mu/pkg/compiler/core"
|
"github.com/marapongo/mu/pkg/compiler/core"
|
||||||
|
@ -11,7 +13,6 @@ import (
|
||||||
"github.com/marapongo/mu/pkg/compiler/types"
|
"github.com/marapongo/mu/pkg/compiler/types"
|
||||||
"github.com/marapongo/mu/pkg/diag"
|
"github.com/marapongo/mu/pkg/diag"
|
||||||
"github.com/marapongo/mu/pkg/graph"
|
"github.com/marapongo/mu/pkg/graph"
|
||||||
"github.com/marapongo/mu/pkg/pack"
|
|
||||||
"github.com/marapongo/mu/pkg/tokens"
|
"github.com/marapongo/mu/pkg/tokens"
|
||||||
"github.com/marapongo/mu/pkg/util/contract"
|
"github.com/marapongo/mu/pkg/util/contract"
|
||||||
)
|
)
|
||||||
|
@ -22,10 +23,12 @@ type Interpreter interface {
|
||||||
|
|
||||||
Ctx() *binder.Context // the binding context object.
|
Ctx() *binder.Context // the binding context object.
|
||||||
|
|
||||||
// EvaluatePackage performs evaluation on the given blueprint package, starting in its entrypoint.
|
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||||
EvaluatePackage(pkg *pack.Package) graph.Graph
|
EvaluatePackage(pkg *symbols.Package, args core.Args) graph.Graph
|
||||||
|
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||||
|
EvaluateModule(mod *symbols.Module, args core.Args) graph.Graph
|
||||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
||||||
EvaluateFunction(fnc symbols.Function, args ...interface{}) graph.Graph
|
EvaluateFunction(fnc symbols.Function, args core.Args) graph.Graph
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an interpreter that can be used to evaluate MuPackages.
|
// New creates an interpreter that can be used to evaluate MuPackages.
|
||||||
|
@ -52,32 +55,100 @@ var _ Interpreter = (*evaluator)(nil)
|
||||||
func (e *evaluator) Ctx() *binder.Context { return e.ctx }
|
func (e *evaluator) Ctx() *binder.Context { return e.ctx }
|
||||||
func (e *evaluator) Diag() diag.Sink { return e.ctx.Diag }
|
func (e *evaluator) Diag() diag.Sink { return e.ctx.Diag }
|
||||||
|
|
||||||
// EvaluatePackage performs evaluation on the given blueprint package, starting in its entrypoint.
|
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||||
func (e *evaluator) EvaluatePackage(pkg *pack.Package) graph.Graph {
|
func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) graph.Graph {
|
||||||
// TODO: find the entrypoint.
|
glog.Infof("Evaluating package '%v'", pkg.Name)
|
||||||
// TODO: pair up the ctx args, if any, with the entrypoint's parameters.
|
if glog.V(2) {
|
||||||
// TODO: visit the function.
|
defer glog.V(2).Infof("Evaluation of package '%v' completed w/ %v warnings and %v errors",
|
||||||
|
pkg.Name, e.Diag().Warnings(), e.Diag().Errors())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the package for a default module "index" to evaluate.
|
||||||
|
for _, mod := range pkg.Modules {
|
||||||
|
if mod.Default() {
|
||||||
|
return e.EvaluateModule(mod, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Diag().Errorf(errors.ErrorPackageHasNoDefaultModule.At(pkg.Tree()), pkg.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||||
|
func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) graph.Graph {
|
||||||
|
glog.Infof("Evaluating module '%v'", mod.Token())
|
||||||
|
if glog.V(2) {
|
||||||
|
defer glog.V(2).Infof("Evaluation of module '%v' completed w/ %v warnings and %v errors",
|
||||||
|
mod.Token(), e.Diag().Warnings(), e.Diag().Errors())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the module's entrypoint function, erroring out if it doesn't have one.
|
||||||
|
if ep, has := mod.Members[tokens.EntryPointFunction]; has {
|
||||||
|
if epfnc, ok := ep.(symbols.Function); ok {
|
||||||
|
return e.EvaluateFunction(epfnc, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Diag().Errorf(errors.ErrorModuleHasNoEntryPoint.At(mod.Tree()), mod.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
||||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, args ...interface{}) graph.Graph {
|
func (e *evaluator) EvaluateFunction(fnc symbols.Function, args core.Args) graph.Graph {
|
||||||
// First, turn the arguments into real runtime *Objects.
|
glog.Infof("Evaluating function '%v'", fnc.Token())
|
||||||
argObjs := make([]*Object, len(args))
|
if glog.V(2) {
|
||||||
for i, arg := range args {
|
defer glog.V(2).Infof("Evaluation of function '%v' completed w/ %v warnings and %v errors",
|
||||||
argObjs[i] = NewConstantObject(arg)
|
fnc.Token(), e.Diag().Warnings(), e.Diag().Errors())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, make the call.
|
// First, validate any arguments, and turn them into real runtime *Objects.
|
||||||
_, uw := e.evalCall(fnc, argObjs...)
|
var argos []*Object
|
||||||
if uw != nil {
|
params := fnc.FuncNode().GetParameters()
|
||||||
// If the call had an unwind out of it, then presumably we have an unhandled exception.
|
if params == nil {
|
||||||
contract.Assert(!uw.Break)
|
if len(args) != 0 {
|
||||||
contract.Assert(!uw.Continue)
|
e.Diag().Errorf(errors.ErrorFunctionArgMismatch.At(fnc.Tree()), 0, len(args))
|
||||||
contract.Assert(!uw.Return)
|
}
|
||||||
contract.Assert(uw.Throw)
|
} else {
|
||||||
// TODO: ideally we would have a stack trace to show here.
|
if len(*params) != len(args) {
|
||||||
e.Diag().Errorf(errors.ErrorUnhandledException, uw.Thrown.Data.(string))
|
e.Diag().Errorf(errors.ErrorFunctionArgMismatch.At(fnc.Tree()), 0, len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
ptys := fnc.FuncType().Parameters
|
||||||
|
found := make(map[tokens.Name]bool)
|
||||||
|
for i, param := range *params {
|
||||||
|
pname := param.Name.Ident
|
||||||
|
if arg, has := args[pname]; has {
|
||||||
|
found[pname] = true
|
||||||
|
argo := NewConstantObject(arg)
|
||||||
|
if types.CanConvert(argo.Type, ptys[i]) {
|
||||||
|
argos = append(argos, argo)
|
||||||
|
} else {
|
||||||
|
e.Diag().Errorf(errors.ErrorFunctionArgIncorrectType.At(fnc.Tree()), ptys[i], argo.Type)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.Diag().Errorf(errors.ErrorFunctionArgNotFound.At(fnc.Tree()), param.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for arg := range args {
|
||||||
|
if !found[arg] {
|
||||||
|
e.Diag().Errorf(errors.ErrorFunctionArgUnknown.At(fnc.Tree()), arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Diag().Success() {
|
||||||
|
// If the arguments bound correctly, make the call.
|
||||||
|
_, uw := e.evalCall(fnc, argos...)
|
||||||
|
if uw != nil {
|
||||||
|
// If the call had an unwind out of it, then presumably we have an unhandled exception.
|
||||||
|
contract.Assert(!uw.Break)
|
||||||
|
contract.Assert(!uw.Continue)
|
||||||
|
contract.Assert(!uw.Return)
|
||||||
|
contract.Assert(uw.Throw)
|
||||||
|
// TODO: ideally we would have a stack trace to show here.
|
||||||
|
e.Diag().Errorf(errors.ErrorUnhandledException, uw.Thrown.Data.(string))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: turn the returned object into a graph.
|
// TODO: turn the returned object into a graph.
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Module struct {
|
||||||
var _ Symbol = (*Module)(nil)
|
var _ Symbol = (*Module)(nil)
|
||||||
|
|
||||||
func (node *Module) symbol() {}
|
func (node *Module) symbol() {}
|
||||||
|
func (node *Module) Default() bool { return node.Node.Default }
|
||||||
func (node *Module) Name() tokens.Name { return node.Node.Name.Ident }
|
func (node *Module) Name() tokens.Name { return node.Node.Name.Ident }
|
||||||
func (node *Module) Token() tokens.Token {
|
func (node *Module) Token() tokens.Token {
|
||||||
return tokens.Token(
|
return tokens.Token(
|
||||||
|
|
|
@ -2,16 +2,31 @@
|
||||||
|
|
||||||
package tokens
|
package tokens
|
||||||
|
|
||||||
// Accessibility modifiers.
|
// Module accessibility.
|
||||||
type Accessibility string // accessibility modifiers common to all.
|
type Accessibility string // accessibility modifiers common to all.
|
||||||
const (
|
const (
|
||||||
PublicAccessibility Accessibility = "public"
|
PublicAccessibility Accessibility = "public"
|
||||||
PrivateAccessibility Accessibility = "private"
|
PrivateAccessibility Accessibility = "private"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Class member accessibility.
|
||||||
type ClassMemberAccessibility Accessibility // accessibility modifiers for class members.
|
type ClassMemberAccessibility Accessibility // accessibility modifiers for class members.
|
||||||
const (
|
const (
|
||||||
PublicClassAccessibility ClassMemberAccessibility = "public"
|
PublicClassAccessibility ClassMemberAccessibility = "public"
|
||||||
PrivateClassAccessibility ClassMemberAccessibility = "private"
|
PrivateClassAccessibility ClassMemberAccessibility = "private"
|
||||||
ProtectedClassAccessibility ClassMemberAccessibility = "protected"
|
ProtectedClassAccessibility ClassMemberAccessibility = "protected"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Special variable tokens.
|
||||||
|
const (
|
||||||
|
ThisVariable Name = ".this" // the current object (for class methods).
|
||||||
|
SuperVariable Name = ".super" // the parent class object (for class methods).
|
||||||
|
)
|
||||||
|
|
||||||
|
// Special function tokens.
|
||||||
|
const (
|
||||||
|
EntryPointFunction ModuleMemberName = ".main" // the special package entrypoint function.
|
||||||
|
ModuleInitializerFunction ModuleMemberName = ".init" // the special module initialization function.
|
||||||
|
ClassConstructorFunction ClassMemberName = ".ctor" // the special class instance constructor function.
|
||||||
|
ClassInitializerFunction ClassMemberName = ".init" // the special class initialization function.
|
||||||
|
)
|
||||||
|
|
|
@ -19,6 +19,7 @@ export interface Definition extends Node {
|
||||||
// A module contains members, including variables, functions, and/or classes.
|
// A module contains members, including variables, functions, and/or classes.
|
||||||
export interface Module extends Definition {
|
export interface Module extends Definition {
|
||||||
kind: ModuleKind;
|
kind: ModuleKind;
|
||||||
|
default?: boolean; // true if this is the package entrypoint (just one).
|
||||||
imports?: ModuleToken[]; // an ordered list of import modules to initialize.
|
imports?: ModuleToken[]; // an ordered list of import modules to initialize.
|
||||||
members?: ModuleMembers; // a list of members (both private and public, exported ones).
|
members?: ModuleMembers; // a list of members (both private and public, exported ones).
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue