Browse Source

Implement chain syntax that also allows operators.
Add other various improvements and some more documentation.

Beoran 4 years ago
parent
commit
8bfea9bd95
12 changed files with 433 additions and 224 deletions
  1. 3 6
      ast.go
  2. 48 35
      builtin.go
  3. 41 16
      cmd/muesli/main.go
  4. 121 0
      console.go
  5. 16 0
      frame.go
  6. 1 1
      keyword.go
  7. 1 1
      lexer.go
  8. 4 0
      logger.go
  9. 77 67
      parser.go
  10. 2 2
      parser_test.go
  11. 112 0
      scope.go
  12. 7 96
      vm.go

+ 3 - 6
ast.go

@@ -13,7 +13,6 @@ type AstMetaKindProgram AstBasicMetaKind
 type AstMetaKindStatements AstBasicMetaKind
 type AstMetaKindStatement AstBasicMetaKind
 type AstMetaKindOperation AstBasicMetaKind
-type AstMetaKindClosed AstBasicMetaKind
 type AstMetaKindSet AstBasicMetaKind
 type AstMetaKindGet AstBasicMetaKind
 type AstMetaKindTarget AstBasicMetaKind
@@ -72,7 +71,6 @@ func (astkind AstMetaKindProgram) String() string     { return "AstProgram    "
 func (astkind AstMetaKindStatements) String() string  { return "AstStatements " }
 func (astkind AstMetaKindStatement) String() string   { return "AstStatement  " }
 func (astkind AstMetaKindOperation) String() string   { return "AstOperation  " }
-func (astkind AstMetaKindClosed) String() string      { return "AstClosed     " }
 func (astkind AstMetaKindSet) String() string         { return "AstSet        " }
 func (astkind AstMetaKindGet) String() string         { return "AstGet        " }
 func (astkind AstMetaKindTarget) String() string      { return "AstTarget     " }
@@ -81,7 +79,7 @@ func (astkind AstMetaKindArguments) String() string   { return "AstArguments  "
 func (astkind AstMetaKindArgument) String() string    { return "AstArgument   " }
 func (astkind AstMetaKindExpression) String() string  { return "AstExpression " }
 func (astkind AstMetaKindBlock) String() string       { return "AstBlock      " }
-func (astkind AstMetaKindParenthesis) String() string{ return "AstParenthesis"}
+func (astkind AstMetaKindParenthesis) String() string { return "AstParenthesis"}
 func (astkind AstMetaKindList) String() string        { return "AstList       " }
 func (astkind AstMetaKindCapture) String() string     { return "AstCapture    " }
 func (astkind AstMetaKindWordValue) String() string   { return "AstWordValue  " }
@@ -97,7 +95,6 @@ func (astkind AstMetaKindProgram) IsLeaf() bool     { return false }
 func (astkind AstMetaKindStatements) IsLeaf() bool  { return false }
 func (astkind AstMetaKindStatement) IsLeaf() bool   { return false }
 func (astkind AstMetaKindOperation) IsLeaf() bool   { return false }
-func (astkind AstMetaKindClosed) IsLeaf() bool      { return false }
 func (astkind AstMetaKindSet) IsLeaf() bool         { return false }
 func (astkind AstMetaKindGet) IsLeaf() bool         { return false }
 func (astkind AstMetaKindTarget) IsLeaf() bool      { return false }
@@ -155,7 +152,7 @@ func (astkind AstMetaKindOperation) Eval(vm *VM, ast Ast, val ...Value) []Value
     return res	
 }
 
-func (astkind AstMetaKindClosed) Eval(vm *VM, ast Ast, val ...Value) []Value {return ReturnEmpty() }
+// func (astkind AstMetaKindClosed) Eval(vm *VM, ast Ast, val ...Value) []Value {return ReturnEmpty() }
 func (astkind AstMetaKindSet) Eval(vm *VM, ast Ast, val ...Value) []Value {
 	values := val
 	if len(values) < 2 { 
@@ -182,7 +179,7 @@ func (astkind AstMetaKindGet) Eval(vm *VM, ast Ast, val ...Value) []Value {
 		if len(targets) < 2 {
 			return Fail(fmt.Errorf("indirect get needs results: received %v", targets)) 
 		}
-		target = targets[1].String()
+        target = targets[1].String()
 	}
 	// log.Printf("(astkind AstMetaKindGet) Run: target %s", target)
 	return Ok(vm.Lookup(target))

+ 48 - 35
builtin.go

@@ -1,7 +1,5 @@
 package muesli
 
-import "fmt"
-import "log"
 import "runtime"
 import "strings"
 import "sort"
@@ -11,7 +9,7 @@ type LogTracer struct {
 
 func (t LogTracer) Trace(vm VM, fmt string, args ... interface{}) bool {
 	args = append([]interface{}{vm.BackTrace()}, args...)	
-	log.Printf("%s: " + fmt,  args...)
+	vm.Log("TRACE", "builtin.go", 12, "%s: " + vm.BackTrace() + fmt,  args...)
 	return false
 }
 
@@ -21,7 +19,7 @@ type FmtTracer struct {
 
 func (t FmtTracer) Trace(vm VM, fm string, args ... interface{}) bool {
 	args = append([]interface{}{vm.BackTrace()}, args...)	
-	fmt.Printf("%s: " + fm + "\n",  args...)
+	vm.Printf("%s: " + fm + "\n",  args...)
 	return false
 }
 
@@ -32,7 +30,7 @@ func printf(vm *VM, args ...Value) []Value {
 		return Fail(err)
 	}
 	extra := ListFromList(rest)
-	fmt.Printf(format, extra...)
+	vm.Printf(format, extra...)
 	return None()
 }
 
@@ -42,29 +40,29 @@ func println(vm *VM, args ...Value) []Value {
 	if err != nil {
 		return Fail(err)
 	} else {
-		fmt.Println(msg)
+		vm.Println(msg)
 	}
 	return None()
 }
 
 func p(vm *VM, args ...Value) []Value {	
 	for _, arg := range args {
-		fmt.Printf("%v\n", arg)
+		vm.Printf("%v\n", arg)
 	}	
 	return None()
 }
 
 func trace(vm *VM, args ...Value) []Value {
 	var b bool = true
-	fmt.Printf("command trace: %v\n", args)
+	vm.Printf("command trace: %v\n", args)
 	_, err := ParseArgs(args, &b)
 	if err != nil {
-		fmt.Printf("Error: %s\n", err.Error())
+		vm.Printf("Error: %s\n", err.Error())
 		return Fail(err)
 	}
-	fmt.Printf("command trace: bool: %v\n", b)
+	vm.Printf("command trace: bool: %v\n", b)
 	if b {
-		vm.Tracer = FmtTracer{}
+		vm.Tracer = vm.Console // FmtTracer{}
 	} else {
 		vm.Tracer = nil
 	}
@@ -74,7 +72,7 @@ func trace(vm *VM, args ...Value) []Value {
 func sumi(vm *VM, args ...Value) []Value {
 	slice, err := ParseArgsToIntSlice(args)
 	if err != nil {
-		fmt.Printf("Error: %s\n", err.Error())
+		vm.Printf("Error: %s\n", err.Error())
 		return Fail(err)
 	}
 	res := int(0)
@@ -87,7 +85,7 @@ func sumi(vm *VM, args ...Value) []Value {
 func sumf(vm *VM, args ...Value) []Value {
 	slice, err := ParseArgsToFloat64Slice(args)
 	if err != nil {
-		fmt.Printf("Error: %s\n", err.Error())
+		vm.Printf("Error: %s\n", err.Error())
 		return Fail(err)
 	}
 	res := float64(0)
@@ -263,32 +261,42 @@ func types(vm *VM, args ...Value) []Value {
 
 func set(vm *VM, val ...Value) []Value {
 	if len(val) < 2 { 
-		return Fail(fmt.Errorf("set needs at least 2 arguments: received %v", val)) 
+		return Fail(vm.Errorf("set needs at least 2 arguments: received %v", val)) 
 	}
 	target := val[0].String() 
 	value := val[1]
 	if target == "(" || target == "$" {
 		if len(val) < 3 {
-			return Fail(fmt.Errorf("indirect get needs results: received %v", val)) 
+			return Fail(vm.Errorf("indirect get needs results: received %v", val)) 
 		}
 		target = val[1].String() 
 		value = val[2]
-	}	
-	vm.RegisterUp(target, value, 1)
+	}
+    level := 1
+    if len(val) > 2 {
+        upval := val[2]
+        if upval.Type() == IntType {
+            upval.Convert(&level)
+        } else if upval.String() == "top" {
+            vm.RegisterTop(target, value)
+            return Ok(value)
+        } 
+    }
+	vm.RegisterUp(target, value, level)
 	return Ok(value)
 }
 
 func get(vm *VM, val ...Value) []Value {
 	if len(val) < 1 {
-		return Fail(fmt.Errorf("get needs at least 1 argument")) 
+		return Fail(vm.Errorf("get needs at least 1 argument")) 
 	}
 	target := val[0].String() 
 	if target == "(" || target == "$" {
 		if len(val) < 2 {
-			return Fail(fmt.Errorf("indirect get needs results: received %v", val)) 
+			return Fail(vm.Errorf("indirect get needs results: received %v", val)) 
 		}
 		target = val[1].String()
-	}	
+	}
 	return Ok(vm.Lookup(target))
 }
 
@@ -313,7 +321,7 @@ func fetchl(vm *VM, args ...Value) []Value {
 		return Fail(err)
 	}
     if (index < 0) || (index >= len(list.List)) {
-        return Fail(fmt.Errorf("index out of range: %d<->%d", index, len(list.List)))
+        return Fail(vm.Errorf("index out of range: %d<->%d", index, len(list.List)))
     } 
 	return Ok(list.List[index])	
 }
@@ -326,10 +334,10 @@ func storel(vm *VM, args ...Value) []Value {
 		return Fail(err)
 	}
 	if len(rest) < 1 {
-		return Fail(fmt.Errorf("fetch: need 3 arguments")) 
+		return Fail(vm.Errorf("fetch: need 3 arguments")) 
 	}
     if (index < 0) || (index >= len(list.List)) {
-        return Fail(fmt.Errorf("index out of range: %d<->%d", index, len(list.List)))
+        return Fail(vm.Errorf("index out of range: %d<->%d", index, len(list.List)))
     }
 	list.List[index] = rest[0]
 	return Ok(list.List[index])	
@@ -354,7 +362,7 @@ func storem(vm *VM, args ...Value) []Value {
 		return Fail(err)
 	}
 	if len (rest) < 1 {
-		return Fail(fmt.Errorf("fetch: need 3 arguments")) 
+		return Fail(vm.Errorf("fetch: need 3 arguments")) 
 	}	
 	hmap.Map[index] = rest[0]
 	return Ok(hmap.Map[index])	
@@ -372,33 +380,38 @@ func newmap(vm *VM, args ...Value) []Value {
 
 func help(vm *VM, val ...Value) [] Value {
 	if len(val) < 1 {
-		fmt.Printf("help <callable> will display help on the callable\n\nThe following commands are available:\n")
+		vm.Printf("help <callable> will display help on the callable\n\nThe following commands are available:\n")
 		helpers := vm.DefinedHelpers()
 		sort.SliceStable(helpers, func(i, j int) bool {
 			return helpers[i].HelperName() < helpers[j].HelperName()
 		})
 		for _, helper := range helpers {
 			fl := strings.SplitN(helper.Help(),"\n", 2)
-			fmt.Printf("%s %s: \n", helper.HelperName(), fl[0])
+			vm.Printf("%s %s: \n", helper.HelperName(), fl[0])
 		}
 		return Ok()
 	}
 	targetName := val[0].String() 	
 	target := vm.Lookup(targetName)
 	if target == nil {
-		fmt.Printf("help: %s not found.\n", targetName)
+		vm.Printf("help: %s not found.\n", targetName)
+        return Ok()
 	}
 	
 	if helper, isHelper := target.(Helper) ; isHelper {
-		help := helper.Help()
+		if len(val) > 1 {
+            helper.SetHelp(val[1].String())
+        }
+        help := helper.Help()
 		if call, isCall := target.(Callable); isCall { 
-			fmt.Printf("%s %s: %s.\n", targetName, call.Signature().String(), help)
+			vm.Printf("%s %s: %s.\n", targetName, call.Signature().String(), help)
 		} else {
-			fmt.Printf("%s: %s.\n", targetName, help)
+            vm.Printf("%s: %s.\n", targetName, help)
 		}
 		return Ok(StringValue(help))
-	}
-	
+	} else {
+        vm.Printf("%s %s. No help available.\n", targetName, target.Type())
+    }
 	return Ok()
 }
 
@@ -427,10 +440,10 @@ func exit(vm *VM, val ...Value) [] Value {
 }
 
 func comma(vm * VM, val ...Value) []Value {
-    fmt.Printf("Comma arguments: %v\n", val)
+    vm.Printf("Comma arguments: %v\n", val)
     
     if len(val) < 2 {
-        return Fail(fmt.Errorf("Need at least 2 arguments"))
+        return Fail(vm.Errorf("Need at least 2 arguments"))
     }
     
     target := val[0]
@@ -443,7 +456,7 @@ func comma(vm * VM, val ...Value) []Value {
     } else if name, isName := command.(WordValue) ; isName {
         return vm.CallNamed(name.String(), val...)
     } else {
-        return Fail(fmt.Errorf("Not callable: %v", command))
+        return Fail(vm.Errorf("Not callable: %v", command))
     }
 }
 

+ 41 - 16
cmd/muesli/main.go

@@ -4,7 +4,9 @@ import (
 	"fmt"
 	"os"
     "io"
+    "sort"
 	"path/filepath"
+    "unicode"
 )
 
 import "gitlab.com/beoran/muesli"
@@ -12,7 +14,10 @@ import "github.com/peterh/liner"
 
 func runLine(vm *muesli.VM, in string) error {	
 	parser := muesli.NewParserFromString(in)
-	kw := vm.LoadKeywords() 
+    if val := vm.Lookup("__muesli_debug__"); val == muesli.TrueValue { 
+        parser.SetVmLogger(vm)
+	}
+    kw := vm.LoadKeywords() 
 	if kw != nil {
 		parser.AddKeywords(kw)
 	} else {
@@ -43,16 +48,16 @@ func runLines(vm *muesli.VM, line *liner.State) error {
 		if in, err := line.Prompt("> "); err == nil {
 			err = runLine(vm, in)
 			if err != nil { 
-				os.Stderr.WriteString(fmt.Sprintf("Error %s: \n", err))
+				vm.Errorf("Error %s: \n", err)
 			}
 			line.AppendHistory(in)
 		} else if err == liner.ErrPromptAborted {
-			os.Stderr.WriteString("Aborted\n")
+			vm.Errorf("Aborted\n")
 			return nil
 		} else if err == io.EOF { 
             return nil
         } else {
-			os.Stderr.WriteString(fmt.Sprintf("Error reading line: %s\n", err))
+			vm.Errorf("Error reading line: %s\n", err)
 		}
 	}
 	return nil
@@ -61,7 +66,7 @@ func runLines(vm *muesli.VM, line *liner.State) error {
 func runFile(vm *muesli.VM, name string) error {
 	parser, err := muesli.NewParserFromFilename(name)
 	if err != nil {
-		os.Stderr.WriteString(fmt.Sprintf("Error opening file %s: %s\n", name, err))
+        vm.Errorf("Error opening file %s: %s\n", name, err)
 		return err
 	}
 	ast := parser.Parse()
@@ -69,7 +74,7 @@ func runFile(vm *muesli.VM, name string) error {
     if err == io.EOF { 
         return nil
     } else if err != nil {
-		os.Stderr.WriteString(fmt.Sprintf("%s: execution error\n", err))
+        vm.Errorf("Error opening file %s: %s\n", name, err)
 		return err
 	}
 	vm.RunAst(*ast, muesli.NewListValue())
@@ -88,15 +93,6 @@ func main() {
 	home, _ := os.UserHomeDir()	
 	historyName := filepath.Join(home, ".muesli_history")
 
-	/* line.SetCompleter(func(line string) (c []string) {
-		for _, n := range names {
-			if strings.HasPrefix(n, strings.ToLower(line)) {
-				c = append(c, n)
-			}
-		}
-		return
-	})
-	*/
 
 	if f, err := os.Open(historyName); err == nil {
 		line.ReadHistory(f)
@@ -112,11 +108,40 @@ func main() {
 		}
         return 
 	}
+
+	line.SetWordCompleter(func(line string, pos int) (head string, c []string, tail string) {
+        end := pos 
+        // XXX unicode support!
+        for end < len(line) && unicode.IsLetter(rune(line[end])) {
+            end++
+        }
+        if end > len(line) {
+            end = len(line)
+        } 
+        tail = line[end:]
+        start := pos - 1
+        for start > 0 && unicode.IsLetter(rune(line[start-1])) {
+            start--
+        }
+        if start < 0 {
+            start = 0
+        }
+        fmt.Printf("%d %d %d\n", start, end, len(line))
+        
+        head = line[0:start]
+        word := line[start:end]
+        fmt.Printf(">%s<\n", word)
+        c = vm.DefinedNamesLike(word)
+        sort.Strings(c)
+        return head, c, tail
+	})
+
+
 	
 	runLines(vm, line)
 	
 	if f, err := os.Create(historyName); err != nil {
-		fmt.Print("Error writing history file: %s\n", err)
+		vm.Errorf("Error writing history file: %s\n", err)
 	} else {
 		line.WriteHistory(f)
 		f.Close()

+ 121 - 0
console.go

@@ -0,0 +1,121 @@
+package muesli
+
+import "io"
+import "fmt"
+import "os"
+
+// Consoler is an interface to a virtual console that the VM 's frames use for
+// stdin, stdout, stderr, and logging output.
+// This allows you to redirect the output of the VM and any built-in functions 
+// to whatever you like.
+type Consoler interface {
+    LogTo() io.Writer // logs should be written to this writer of not nil
+    Out() io.Writer // standard output goes to this writer of not nil.
+    Err() io.Writer // standard error go to this writer if not nil
+    In() io.Reader // can read standard input from this reader if not nil
+}
+
+
+// BasicConsole is a reusable virtual console struct. 
+// It implements the BasicConsoler interface
+type BasicConsole struct {
+    log io.Writer
+    out io.Writer
+    err io.Writer
+    in io.Reader
+} 
+
+
+func (c BasicConsole) LogTo() io.Writer {
+    return c.log
+}
+
+
+func (c BasicConsole) Out() io.Writer {
+    return c.out
+}
+
+
+func (c BasicConsole) Err() io.Writer {
+    return c.err
+}
+
+
+func (c BasicConsole) In() io.Reader {
+    return c.in
+}
+
+
+func NewBasicConsole(log, out, err io.Writer, in io.Reader) BasicConsole {
+    return BasicConsole { log, out, err, in }
+}
+
+var _ Consoler = BasicConsole{}
+
+
+// Console is a fully features wrapper BasicConsole, easier to use with Printf 
+// and Log, etc, and implements the Logger and Consoler interface.
+type Console struct {
+    LoggerWrapper
+    Consoler
+}
+
+func (c *Console) Fprintf(out io.Writer, format string, args ... interface{}) (int, error) {
+    if out != nil {
+        return fmt.Fprintf(out, format, args...)
+    }
+    return 0, nil
+}
+
+func (c *Console) Fprintln(out io.Writer, args ... interface{}) (int, error) {
+    if out != nil {
+        return fmt.Fprintln(out, args...)
+    }
+    return 0, nil
+}
+
+
+func (c *Console) Log(level string, file string, line int, format string, args ...interface{}) {
+    log := c.Consoler.LogTo()
+    c.Fprintf(log, "%s:%s:%d: ", level, file, line)
+    c.Fprintf(log, format, args...)
+}
+
+func (c *Console) Printf(format string, args ...interface{}) {
+    out := c.Out()
+    c.Fprintf(out, format, args...)
+}
+
+func (c *Console) Println(args ...interface{}) {
+    out := c.Out()
+    c.Fprintln(out, args...)
+}
+
+
+func (c *Console) Errf(format string, args ...interface{}) {
+    err := c.Err()
+    c.Fprintf(err, format, args...)
+}
+
+func NewConsole(log, out, err io.Writer, in io.Reader) *Console {
+    console := &Console{}
+    console.Consoler = NewBasicConsole(log, out, err, in)
+    return console
+}
+
+
+// also implement the tracer interface 
+func (c *Console) Trace(vm VM, format string, args ... interface{}) bool {
+	args = append([]interface{}{vm.BackTrace()}, args...)	
+    c.LogTrace("%s: " + format, args...)
+    return false
+}
+
+func NewStdConsole() *Console {
+    return NewConsole(os.Stdout, os.Stdout, os.Stderr, os.Stdin)
+}
+
+
+
+
+

+ 16 - 0
frame.go

@@ -0,0 +1,16 @@
+package muesli
+
+// Frame of execution of a function in the Muesli VM
+type Frame struct {	
+	parent    *Frame
+	arguments []Value
+	results   []Value
+	failed    bool
+	returned  bool
+	position  *Position
+}
+
+func NewFrame(parent *Frame, position *Position) *Frame {
+	return &Frame{parent, EmptyValueArray(), EmptyValueArray(), false, false, position}
+}
+

+ 1 - 1
keyword.go

@@ -112,7 +112,7 @@ func keywords(vm *VM, val ...Value) []Value {
 }
 
 
-const KeywordName = "muesli__keywords__"
+const KeywordName = "__muesli_keywords__"
 
 func (vm * VM) StoreKeywords(keywords []*Keyword) *ListValue {
     vm.Register("Keyword", KeywordType) 

+ 1 - 1
lexer.go

@@ -745,7 +745,7 @@ func (lexer *Lexer) lex() Token {
 	default:
 	}
 
-	if unicode.IsLetter(r) {
+	if unicode.IsLetter(r) || r == '_' {
 		if unicode.IsUpper(r) {
 			return lexer.LexType()
 		} else {

+ 4 - 0
logger.go

@@ -36,6 +36,10 @@ func (lw LoggerWrapper) WriteLog(depth int, level string, format string, args ..
 	WriteLog(lw.Logger, depth, level, format, args...)
 }
 
+func (lw LoggerWrapper) LogTrace(format string, args ...interface{}) {
+	lw.WriteLog(3, "TRACE:", format, args...)
+}
+
 func (lw LoggerWrapper) LogDebug(format string, args ...interface{}) {
 	lw.WriteLog(3, "DEBUG:", format, args...)
 }

+ 77 - 67
parser.go

@@ -18,59 +18,23 @@ import (
 /* Grammar:
 Desrired syntax (verified LL(1) on smlweb.cpsc.ucalgary.ca)
 
-PROGRAM -> STATEMENTS.
-CLOSED -> BLOCK | LIST | PARENTHESIS .
-STATEMENTS -> STATEMENT STATEMENTS | .
-STATEMENT -> CLOSED | EXPRESSION eos | eos .
-COMMAND -> word PARAMETERS.
-PARAMETERS -> PARAMETER PARAMETERS | .
-PARAMETER -> WORDVALUE | GETTER | SETTER | CLOSED .
-EXPRESSION -> COMMAND | GETTER | SETTER | VALUE.
-PARENTHESIS -> closeparen EXPRESSION openparen .
-BLOCK -> openblock STATEMENTS closeblock .
-LIST -> openlist PARAMETERS closelist .
-VALUE -> string | int | float | symbol | type .
-WORDVALUE -> word | VALUE.
-SETTER -> set TARGET . 
-TARGET -> word PARAMETER | GETTER PARAMETER .
-GETTER -> get ORIGIN .
-ORIGIN -> word | SETTER | GETTER .
-
-
-Or with simple operators variant.
-
-PROGRAM -> STATEMENTS.
-CLOSED -> BLOCK | LIST | PARENTHESIS .
-STATEMENTS -> STATEMENT STATEMENTS | .
-STATEMENT -> CLOSED | EXPRESSION eos | eos .
-COMMAND -> WORDVALUE DETAILS .
-DETAILS -> OPERATION | PARAMETERS .
-OPERATION -> operator COMMAND .
-PARAMETERS -> PARAMETER PARAMETERS | .
-PARAMETER -> WORDVALUE | GETTER | SETTER | CLOSED .
-EXPRESSION -> COMMAND | GETTER | SETTER.
-PARENTHESIS -> openparen EXPRESSION closeparen .
-BLOCK -> openblock STATEMENTS closeblock .
-LIST -> openlist PARAMETERS closelist .
-VALUE -> string | int | float | symbol | type .
-WORDVALUE -> word | VALUE.
-SETTER -> set TARGET . 
-TARGET -> word PARAMETER | GETTER PARAMETER .
-GETTER -> get ORIGIN .
-GETCALL -> comma COMMAND | .
-ORIGIN -> word | SETTER | GETTER .
+In Muesli, the program is first tokenized by a lexer. The lexer uses the first 
+character, and only the first character of every token to determine it's type.
 
-
-Or, new syntax with operators for command chains at the top level, 
-for easier shunting the operator into commands:
+Then, the tokens are parsed with a recursive descent parser with a structured 
+syntax, which has been verified to be LL(1) on smlweb.cpsc.ucalgary.ca. This 
+means the parser only needs one token of lookahead to be able to parse the 
+syntax. The syntax can be described as follows:
 
 PROGRAM -> STATEMENTS .
 STATEMENTS -> STATEMENT STATEMENTS | .
 STATEMENT -> BLOCK | CHAIN eos | eos .
-CHAIN -> EXPRESSION LINKS . 
-LINKS -> LINK | .
-LINK -> OPERATOR CHAIN .
-OPERATOR -> evaluator | redirect | blockopt .
+CHAIN -> EXPRESSION OPERATIONS . 
+OPERATIONS -> LINK | .
+LINK -> OPERATION | REDIRECT | METHOD .
+METHOD -> method EXPRESSION . 
+REDIRECT -> redirect EXPRESSION . 
+OPERATION -> operator EXPRESSION .
 EXPRESSION -> COMMAND | SUBSTITUTION |  LITERAL .
 COMMAND -> NAME PARAMETERS.
 PARAMETERS -> PARAMETER PARAMETERS | .
@@ -84,15 +48,38 @@ NAME -> word | symbol | type .
 SETTER -> set PARAMETER PARAMETER . 
 GETTER -> get PARAMETER .
 
-semantics: 
-
-
-- A muesli program consists of statements.
-- A statement is either a block, or a command chain terminated by eos, or just an eos .
-- A command chain consists of commands chained together with operators.
-- A command may be a direct command, an indirect command, or a literal value. 
-- A () parenthesis gets substituted inline anywhere it occurs,  also in the
-  beginning of a command.
+The semantics of a muesli program are based on this syntax and can be explained 
+as follows:
+ 
+- A muesli program consists of statements, which are executed from top to 
+  bottom.
+- A statement is either a block, or a command chain terminated by end of 
+  statement, eos,  or an empty statement terminated by eos. The period . and 
+  the unescaped newline characters are eos. 
+- A block consist of statements between braces { }. The statements of a block 
+  are not executed but compiled to a block which can be called to execute them
+  later.
+- A command chain consists of an expression optionally followed by links.
+  A command chain is executed by executing it's expression and applying the, 
+  operator, redirect and method links from left to right.
+- A link is either operator, redirect or method.
+- An operator begins with a token with one of the following characters: -+/*^%~
+  Next is an expression. The effect of an operator is to evaluate the 
+  links before it and after it as a substitution, and then look up the callable
+  with the name of the operator and execute that. 
+- A redirect starts with a token of the following characters: |&><@
+  Next is an expression. The effect of a redirect is to evaluate the 
+  links before it and after it as a block, and then look up the callable
+  with the name of the operator and execute that.
+- A method start with a token with one of the following characters: ,;
+  Next is an expression. The effect of a method is to evaluate the 
+  links before it and after it as a list of ast expressions, shuffle them 
+  so the operator comes first, but the rest is kept in order, and then look up 
+  the callable with the name of the operator and execute that.
+- A command may be a direct command, an indirect command, or a literal value.
+- A direct command  
+- A () parenthesis gets substituted inline by the value returned by executing it 
+  anywhere it occurs, also in the beginning of a command.
 - A bracketed list [ elem1 elem2 ... ]  is syntactic sugar for (list elem1 elem 2)
 - A dollar getter $varname is syntactic sugar for (get varname)
 - A equals setter =varname value is syntactic sugar for (set varname value)
@@ -137,6 +124,16 @@ func (parser *Parser) SetLogger(logger Logger) {
 	parser.LoggerWrapper = LoggerWrapper{logger}
 }
 
+func (parser *Parser) SetDefaultLogger() {
+    lg := &testLogger{}
+	parser.LoggerWrapper = LoggerWrapper{lg}
+}
+
+func (parser *Parser) SetVmLogger(vm * VM) {
+	parser.LoggerWrapper = vm.Console.LoggerWrapper
+}
+
+
 func (parser *Parser) Errorf(message string, args ...interface{}) ParserError {
 	err := fmt.Errorf(message, args...)
     pe := ParserError { Parser: parser, Token:&parser.current, Chain: err }
@@ -577,17 +574,25 @@ func (parser Parser) NextIsStatement() bool {
 	return parser.NextIsChain() || parser.NextIsBlock() || parser.NextIsEOX()
 }
 
-func newChain(oper Token, expr1, expr2 *Ast) * Ast {
+func (parser *Parser) newMethodChain(oper Token, expr1, expr2 *Ast) *Ast {
+    chain := NewAstWithToken(AstKindOperation, oper)
+    chain.AppendChildren(expr1)
+    chain.AppendChild(NewAstWithToken(AstKindForToken(expr2.Token()), expr2.Token()))
+    chain.AppendChildren(expr2.Children()...)
+    parser.LogDebug("New method chain: %v", chain)
+    return chain
+}
+
+func (parser *Parser) newChain(oper Token, expr1, expr2 *Ast) * Ast {
     var astkind AstKind = AstKindParenthesis
     
     if oper.TokenKind == TokenKindRedirect {
+        parser.LogDebug("New redirect chain.")
         astkind = AstKindStatements
     } else if oper.TokenKind == TokenKindMethod {
-        chain := NewAstWithToken(AstKindOperation, oper)
-        chain.AppendChildren(expr1, expr2)
-        return chain
+        return parser.newMethodChain(oper, expr1, expr2)
     }
-    
+
     subst1 := NewAstWithToken(astkind, oper)
     subst1.AppendChildren(expr1)
     subst2 := NewAstWithToken(astkind, oper)
@@ -597,14 +602,19 @@ func newChain(oper Token, expr1, expr2 *Ast) * Ast {
     return chain
 }
 
-func composeChain(oper Token, chain, nextExpr *Ast) * Ast {
+func (parser *Parser) composeChain(oper Token, chain, nextExpr *Ast) * Ast {
     var astkind AstKind = AstKindParenthesis
     
     if oper.TokenKind == TokenKindRedirect {
+        parser.LogDebug("Composed redirect chain: %v", chain)
         astkind = AstKindStatements
     } else if oper.TokenKind == TokenKindMethod {
-        chain.AppendChildren(nextExpr)
+        chain.AppendChild(NewAstWithToken(AstKindForToken(nextExpr.Token()), nextExpr.Token()))
+        chain.AppendChildren(nextExpr.Children()...)
+        parser.LogDebug("Composed method chain: %v", chain)
         return chain
+    } else {
+        parser.LogDebug("Composed normal chain: %v", chain)
     }
 
     subst := NewAstWithToken(astkind, oper)
@@ -634,16 +644,16 @@ func (parser *Parser) ParseChain() *Ast {
     // Now there are N expressions and N - 1 operators, iterate
     // for easy composition
     var chain *Ast
-    
+    parser.LogDebug("Working on operator chain: %v %v", operators, expressions)
     for i, j := 0, 0 ; i < len(expressions) && j < len(operators) ; i, j = i + 1 , j + 1 {
         expression := expressions[i]
         oper := operators[j]
         if chain == nil {
             expression2 := expressions[i+1]
-            chain = newChain(oper, expression, expression2) 
+            chain = parser.newChain(oper, expression, expression2) 
             i++
         } else {
-            chain = composeChain(oper, chain, expression)
+            chain = parser.composeChain(oper, chain, expression)
         }
     }
     return chain

+ 2 - 2
parser_test.go

@@ -66,13 +66,13 @@ func TestParenthesis(test *testing.T) {
 	// HelperParseAndFailOnErrors(`add 5 10 `, 0, (*Parser).ParseCommand, test)
 }
 
-func TestClosed(test *testing.T) {
+func TestSubstitute(test *testing.T) {
 	HelperParseAndFailOnErrors(`( add 5 10 ) `, 0, (*Parser).Parse, test)
 	// HelperParseAndFailOnErrors(`( add 5 10 ) `, 0, (*Parser).ParseParenthesis, test)
 	// HelperParseAndFailOnErrors(`add 5 10 `, 0, (*Parser).ParseCommand, test)
 }
 
-func TestClosedWithError(test *testing.T) {
+func TestSubstituteWithError(test *testing.T) {
 	HelperParseAndFailOnErrors(`( add 5 10 ) ) `, 1, (*Parser).Parse, test)
 	// HelperParseAndFailOnErrors(`( add 5 10 ) `, 0, (*Parser).ParseParenthesis, test)
 	// HelperParseAndFailOnErrors(`add 5 10 `, 0, (*Parser).ParseCommand, test)

+ 112 - 0
scope.go

@@ -0,0 +1,112 @@
+package muesli
+
+import "fmt"
+import "strings"
+
+// LookupFallback is a fallback function that can be set to be called when 
+// lookup in a scope fails
+type LookupFallback func (name string) Value
+
+
+// Scope of symbols defined in the VM, hierarchical
+type Scope struct {
+	parent   *Scope
+	children []*Scope
+	symbols  map[string]Value
+    fallback LookupFallback
+}
+
+func NewScope(parent *Scope) *Scope {
+	return &Scope{parent, make([]*Scope, 0), make(map[string]Value), nil}
+}
+
+// Sets a lookup fall back to be called before trying the parent scope.
+// Returns the previous lookup fallback, or nil if it was nil.
+func (scope * Scope) SetLookupFallback(fb LookupFallback) LookupFallback {
+    res := scope.fallback
+    scope.fallback = fb
+    return res
+} 
+
+// Lookup looks up a value of a name registered in the current scope,
+// and if not found there, recursively in the parent scope.
+// Returns NilValue if not found.
+func (scope *Scope) Lookup(name string) Value {
+	value, ok := scope.symbols[name]
+	if ok {
+		return value
+	}
+    if scope.fallback != nil {
+        return scope.fallback(name)
+    }
+	if scope.parent != nil {
+		return scope.parent.Lookup(name)
+	}
+	return NilValue
+}
+
+func (scope *Scope) Register(name string, value Value) Value {
+	scope.symbols[name] = value
+	return value
+}
+
+func (scope * Scope) Known(filter func(string, Value) bool) map[string]Value {
+	res := make(map[string]Value)
+	if scope.parent != nil {
+		res = scope.parent.Known(filter)
+	}
+	for k, v := range scope.symbols {
+		if (filter == nil) || filter(k, v) {
+			res[k] = v
+		}
+	}
+	return res
+}
+
+func (scope * Scope) ForEachDefined(do func(string, Value) (bool, error)) (bool, error)  {
+	var res bool = true
+	var err error
+	
+	if (do == nil) {
+		return false, fmt.Errorf("do may not be nil")
+	}
+			
+	if scope.parent != nil {
+		res, err = scope.parent.ForEachDefined(do)
+	}
+	if res == false || err != nil {
+		return res, err
+	}
+	for k, v := range scope.symbols {
+		res, err = do(k, v)
+		if res == false || err != nil {
+			return res, err
+		}		
+	}
+	return res, err
+}
+
+
+func (scope* Scope) DefinedHelpers() []Helper {
+	res := []Helper{}
+	scope.ForEachDefined(func (k string, v Value) (bool, error) {
+		helper, hok := v.(Helper)
+		if hok {
+			res = append(res, helper)
+		}
+		return true, nil
+	})
+	return res
+}
+
+func (scope* Scope) DefinedNamesLike(prefix string) []string {
+	res := []string{}
+	scope.ForEachDefined(func (k string, v Value) (bool, error) {
+        if strings.HasPrefix(k, prefix) {
+            res = append(res, k)
+        }
+        return true, nil
+	})
+	return res
+}
+

+ 7 - 96
vm.go

@@ -4,7 +4,6 @@ package muesli
 import (
 	"fmt"
 	"strings"
-    "io"
 )
 
 
@@ -269,6 +268,11 @@ func (cv * CoverValue) AddOverload(name string, callable Callable, tv ... TypeVa
 	return nil
 }
 
+func (vm * VM) Errorf(format string, args...interface{}) error {
+    return fmt.Errorf(format, args...)
+}
+
+
 func (vm * VM) AddOverloadCallable(from, target string, level int, callable Callable) error {
 	var cover *CoverValue
 	var ok bool	
@@ -365,98 +369,6 @@ func (vm * VM) SetHelp(target, help string) error {
 } 
 
 
-// Scope of symbols defined in the VM, hierarchical
-type Scope struct {
-	parent   *Scope
-	children []*Scope
-	symbols  map[string]Value
-}
-
-func NewScope(parent *Scope) *Scope {
-	return &Scope{parent, make([]*Scope, 0), make(map[string]Value)}
-}
-
-
-func (scope *Scope) Lookup(name string) Value {
-	value, ok := scope.symbols[name]
-	if ok {
-		return value
-	}
-	if scope.parent != nil {
-		return scope.parent.Lookup(name)
-	}
-	return NilValue
-}
-
-func (scope *Scope) Register(name string, value Value) Value {
-	scope.symbols[name] = value
-	return value
-}
-
-func (scope * Scope) Known(filter func(string, Value) bool) map[string]Value {
-	res := make(map[string]Value)
-	if scope.parent != nil {
-		res = scope.parent.Known(filter)
-	}
-	for k, v := range scope.symbols {
-		if (filter == nil) || filter(k, v) {
-			res[k] = v
-		}
-	}
-	return res
-}
-
-func (scope * Scope) ForEachDefined(do func(string, Value) (bool, error)) (bool, error)  {
-	var res bool = true
-	var err error
-	
-	if (do == nil) {
-		return false, fmt.Errorf("do may not be nil")
-	}
-			
-	if scope.parent != nil {
-		res, err = scope.parent.ForEachDefined(do)
-	}
-	if res == false || err != nil {
-		return res, err
-	}
-	for k, v := range scope.symbols {
-		res, err = do(k, v)
-		if res == false || err != nil {
-			return res, err
-		}		
-	}
-	return res, err
-}
-
-
-func (scope* Scope) DefinedHelpers() []Helper {
-	res := []Helper{}
-	scope.ForEachDefined(func (k string, v Value) (bool, error) {
-		helper, hok := v.(Helper)
-		if hok {
-			res = append(res, helper)
-		}
-		return true, nil
-	})
-	return res
-}
-
-
-// Frame of execution of a function
-type Frame struct {	
-	parent    *Frame
-	arguments []Value
-	results   []Value
-	failed    bool
-	returned  bool
-	position  *Position
-}
-
-func NewFrame(parent *Frame, position *Position) *Frame {
-	return &Frame{parent, EmptyValueArray(), EmptyValueArray(), false, false, position}
-}
-
 type Tracer interface {
 	Trace(vm VM, fmt string, args ... interface{}) bool
 }
@@ -468,13 +380,12 @@ type VM struct {
 	*Scope              // Current Scope
 	*Frame              // Current frame
 	Tracer			    // Tracer to emit tracing info to, could be used for logging or debugging
-    Output  io.Writer   // Writer to which command output should be written.
-    Input   io.Reader   // Reader from which command input should be read.
+    *Console            // every VM has it's own virtual console. 
 	ExitStatus int
 }
 
 func NewVM() *VM {
-	vm := &VM{NewScope(nil), NewFrame(nil, nil), nil, nil, nil, nil, nil, 0}
+	vm := &VM{NewScope(nil), NewFrame(nil, nil), nil, nil, nil, NewStdConsole(), 0}
 	vm.Scope = vm.TopScope
 	vm.Frame = vm.TopFrame
 	return vm