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/core"
|
||||
"github.com/marapongo/mu/pkg/graph"
|
||||
"github.com/marapongo/mu/pkg/tokens"
|
||||
"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
|
||||
// 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.
|
||||
func dashdashArgsToMap(args []string) map[string]interface{} {
|
||||
mapped := make(map[string]interface{})
|
||||
func dashdashArgsToMap(args []string) core.Args {
|
||||
mapped := make(core.Args)
|
||||
|
||||
for i := 0; i < len(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.
|
||||
if eq := strings.IndexByte(arg, '='); eq != -1 {
|
||||
// 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 {
|
||||
if i+1 < len(args) && args[i+1][0] != '-' {
|
||||
// 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++
|
||||
} else if arg[0:3] == "no-" {
|
||||
// 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 {
|
||||
// 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.
|
||||
type Module struct {
|
||||
DefinitionNode
|
||||
Default bool `json:"default,omitempty"`
|
||||
Imports *[]*ModuleToken `json:"imports,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.
|
||||
|
||||
b := binder.New(c.w, c.ctx, c.reader)
|
||||
b.BindPackage(pkg)
|
||||
pkgsym := b.BindPackage(pkg)
|
||||
if !c.Diag().Success() {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := eval.New(b.Ctx())
|
||||
return e.EvaluatePackage(pkg)
|
||||
return e.EvaluatePackage(pkgsym, c.ctx.Opts.Args)
|
||||
}
|
||||
|
|
|
@ -4,12 +4,16 @@ package core
|
|||
|
||||
import (
|
||||
"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.
|
||||
type Options struct {
|
||||
Diag diag.Sink // a sink to use for all diagnostics.
|
||||
Args map[string]interface{} // optional blueprint arguments passed at the CLI.
|
||||
Diag diag.Sink // a sink to use for all diagnostics.
|
||||
Args Args // optional blueprint arguments passed at the CLI.
|
||||
}
|
||||
|
||||
// DefaultOptions returns the default set of compiler options.
|
||||
|
|
|
@ -4,5 +4,11 @@ package errors
|
|||
|
||||
// Eval errors are in the [1000,2000) range.
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/marapongo/mu/pkg/compiler/ast"
|
||||
"github.com/marapongo/mu/pkg/compiler/binder"
|
||||
"github.com/marapongo/mu/pkg/compiler/core"
|
||||
|
@ -11,7 +13,6 @@ import (
|
|||
"github.com/marapongo/mu/pkg/compiler/types"
|
||||
"github.com/marapongo/mu/pkg/diag"
|
||||
"github.com/marapongo/mu/pkg/graph"
|
||||
"github.com/marapongo/mu/pkg/pack"
|
||||
"github.com/marapongo/mu/pkg/tokens"
|
||||
"github.com/marapongo/mu/pkg/util/contract"
|
||||
)
|
||||
|
@ -22,10 +23,12 @@ type Interpreter interface {
|
|||
|
||||
Ctx() *binder.Context // the binding context object.
|
||||
|
||||
// EvaluatePackage performs evaluation on the given blueprint package, starting in its entrypoint.
|
||||
EvaluatePackage(pkg *pack.Package) graph.Graph
|
||||
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||
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(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.
|
||||
|
@ -52,32 +55,100 @@ var _ Interpreter = (*evaluator)(nil)
|
|||
func (e *evaluator) Ctx() *binder.Context { return e.ctx }
|
||||
func (e *evaluator) Diag() diag.Sink { return e.ctx.Diag }
|
||||
|
||||
// EvaluatePackage performs evaluation on the given blueprint package, starting in its entrypoint.
|
||||
func (e *evaluator) EvaluatePackage(pkg *pack.Package) graph.Graph {
|
||||
// TODO: find the entrypoint.
|
||||
// TODO: pair up the ctx args, if any, with the entrypoint's parameters.
|
||||
// TODO: visit the function.
|
||||
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||
func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) graph.Graph {
|
||||
glog.Infof("Evaluating package '%v'", pkg.Name)
|
||||
if glog.V(2) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// First, turn the arguments into real runtime *Objects.
|
||||
argObjs := make([]*Object, len(args))
|
||||
for i, arg := range args {
|
||||
argObjs[i] = NewConstantObject(arg)
|
||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, args core.Args) graph.Graph {
|
||||
glog.Infof("Evaluating function '%v'", fnc.Token())
|
||||
if glog.V(2) {
|
||||
defer glog.V(2).Infof("Evaluation of function '%v' completed w/ %v warnings and %v errors",
|
||||
fnc.Token(), e.Diag().Warnings(), e.Diag().Errors())
|
||||
}
|
||||
|
||||
// Next, make the call.
|
||||
_, uw := e.evalCall(fnc, argObjs...)
|
||||
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))
|
||||
// First, validate any arguments, and turn them into real runtime *Objects.
|
||||
var argos []*Object
|
||||
params := fnc.FuncNode().GetParameters()
|
||||
if params == nil {
|
||||
if len(args) != 0 {
|
||||
e.Diag().Errorf(errors.ErrorFunctionArgMismatch.At(fnc.Tree()), 0, len(args))
|
||||
}
|
||||
} else {
|
||||
if len(*params) != len(args) {
|
||||
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.
|
||||
|
|
|
@ -19,6 +19,7 @@ type Module struct {
|
|||
var _ Symbol = (*Module)(nil)
|
||||
|
||||
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) Token() tokens.Token {
|
||||
return tokens.Token(
|
||||
|
|
|
@ -2,16 +2,31 @@
|
|||
|
||||
package tokens
|
||||
|
||||
// Accessibility modifiers.
|
||||
// Module accessibility.
|
||||
type Accessibility string // accessibility modifiers common to all.
|
||||
const (
|
||||
PublicAccessibility Accessibility = "public"
|
||||
PrivateAccessibility Accessibility = "private"
|
||||
)
|
||||
|
||||
// Class member accessibility.
|
||||
type ClassMemberAccessibility Accessibility // accessibility modifiers for class members.
|
||||
const (
|
||||
PublicClassAccessibility ClassMemberAccessibility = "public"
|
||||
PrivateClassAccessibility ClassMemberAccessibility = "private"
|
||||
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.
|
||||
export interface Module extends Definition {
|
||||
kind: ModuleKind;
|
||||
default?: boolean; // true if this is the package entrypoint (just one).
|
||||
imports?: ModuleToken[]; // an ordered list of import modules to initialize.
|
||||
members?: ModuleMembers; // a list of members (both private and public, exported ones).
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue