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:
joeduffy 2017-01-25 18:38:53 -08:00
parent 2ec7452bf7
commit 281805311c
9 changed files with 136 additions and 36 deletions

View file

@ -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
}
}
}

View file

@ -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"`
}

View file

@ -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)
}

View file

@ -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.

View file

@ -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")
)

View file

@ -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.

View file

@ -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(

View file

@ -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.
)

View file

@ -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).
}