Browse Source

chore: Cleanup code

Matthias Ladkau 3 years ago
parent
commit
0207b5c065
44 changed files with 2739 additions and 1207 deletions
  1. 14 5
      cli/tool/debug.go
  2. 400 0
      cli/tool/debug_test.go
  3. 23 17
      cli/tool/format.go
  4. 122 0
      cli/tool/format_test.go
  5. 17 0
      cli/tool/helper.go
  6. 123 0
      cli/tool/helper_test.go
  7. 61 40
      cli/tool/interpret.go
  8. 355 0
      cli/tool/interpret_test.go
  9. 47 43
      cli/tool/pack.go
  10. 278 0
      cli/tool/pack_test.go
  11. 1 1
      ecal-support/README.md
  12. 1 1
      ecal-support/src/ecalDebugAdapter.ts
  13. 1 1
      ecal-support/src/extension.ts
  14. 18 13
      engine/processor_test.go
  15. 71 62
      engine/pubsub/eventpump_test.go
  16. 21 22
      engine/taskqueue_test.go
  17. 8 2
      examples/embedding/main.go
  18. 18 18
      interpreter/debug.go
  19. 10 10
      interpreter/debug_cmd.go
  20. 86 122
      interpreter/debug_test.go
  21. 2 2
      interpreter/func_provider.go
  22. 29 14
      interpreter/func_provider_test.go
  23. 4 1
      interpreter/rt_arithmetic_test.go
  24. 9 9
      interpreter/rt_assign_test.go
  25. 9 2
      interpreter/rt_boolean_test.go
  26. 90 65
      interpreter/rt_identifier.go
  27. 124 111
      interpreter/rt_sink.go
  28. 184 156
      interpreter/rt_statements.go
  29. 18 7
      interpreter/rt_statements_test.go
  30. 14 36
      parser/helper.go
  31. 47 14
      parser/helper_test.go
  32. 9 2
      parser/lexer.go
  33. 46 56
      parser/parser.go
  34. 184 148
      parser/prettyprinter.go
  35. 54 51
      scope/varsscope.go
  36. 17 10
      scope/varsscope_test.go
  37. 52 36
      stdlib/adapter.go
  38. 31 20
      stdlib/adapter_test.go
  39. 117 93
      stdlib/generate/generate.go
  40. 1 1
      stdlib/stdlib.go
  41. 1 1
      util/error.go
  42. 12 8
      util/import_test.go
  43. 4 1
      util/logging.go
  44. 6 6
      util/types.go

+ 14 - 5
cli/tool/debug.go

@@ -21,6 +21,7 @@ import (
 	"net"
 	"net"
 	"os"
 	"os"
 	"strings"
 	"strings"
+	"sync"
 	"time"
 	"time"
 
 
 	"devt.de/krotik/common/errorutil"
 	"devt.de/krotik/common/errorutil"
@@ -113,8 +114,12 @@ func (i *CLIDebugInterpreter) Interpret() error {
 
 
 			debugServer := &debugTelnetServer{*i.DebugServerAddr, "ECALDebugServer: ",
 			debugServer := &debugTelnetServer{*i.DebugServerAddr, "ECALDebugServer: ",
 				nil, true, *i.EchoDebugServer, i, i.RuntimeProvider.Logger}
 				nil, true, *i.EchoDebugServer, i, i.RuntimeProvider.Logger}
-			go debugServer.Run()
-			time.Sleep(500 * time.Millisecond) // Too lazy to do proper signalling
+
+			wg := &sync.WaitGroup{}
+			wg.Add(1)
+			go debugServer.Run(wg)
+			wg.Wait()
+
 			defer func() {
 			defer func() {
 				if debugServer.listener != nil {
 				if debugServer.listener != nil {
 					debugServer.listen = false
 					debugServer.listen = false
@@ -211,12 +216,16 @@ type debugTelnetServer struct {
 /*
 /*
 Run runs the debug server.
 Run runs the debug server.
 */
 */
-func (s *debugTelnetServer) Run() {
+func (s *debugTelnetServer) Run(wg *sync.WaitGroup) {
 	tcpaddr, err := net.ResolveTCPAddr("tcp", s.address)
 	tcpaddr, err := net.ResolveTCPAddr("tcp", s.address)
 
 
 	if err == nil {
 	if err == nil {
 
 
-		if s.listener, err = net.ListenTCP("tcp", tcpaddr); err == nil {
+		s.listener, err = net.ListenTCP("tcp", tcpaddr)
+
+		if err == nil {
+
+			wg.Done()
 
 
 			s.logger.LogInfo(s.logPrefix,
 			s.logger.LogInfo(s.logPrefix,
 				"Running Debug Server on ", tcpaddr.String())
 				"Running Debug Server on ", tcpaddr.String())
@@ -225,7 +234,6 @@ func (s *debugTelnetServer) Run() {
 				var conn net.Conn
 				var conn net.Conn
 
 
 				if conn, err = s.listener.Accept(); err == nil {
 				if conn, err = s.listener.Accept(); err == nil {
-
 					go s.HandleConnection(conn)
 					go s.HandleConnection(conn)
 
 
 				} else if s.listen {
 				} else if s.listen {
@@ -238,6 +246,7 @@ func (s *debugTelnetServer) Run() {
 
 
 	if s.listen && err != nil {
 	if s.listen && err != nil {
 		s.logger.LogError(s.logPrefix, "Could not start debug server - ", err)
 		s.logger.LogError(s.logPrefix, "Could not start debug server - ", err)
+		wg.Done()
 	}
 	}
 }
 }
 
 

+ 400 - 0
cli/tool/debug_test.go

@@ -0,0 +1,400 @@
+/*
+ * ECAL
+ *
+ * Copyright 2020 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the MIT
+ * License, If a copy of the MIT License was not distributed with this
+ * file, You can obtain one at https://opensource.org/licenses/MIT.
+ */
+
+package tool
+
+import (
+	"bufio"
+	"bytes"
+	"flag"
+	"fmt"
+	"net"
+	"os"
+	"reflect"
+	"strconv"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"devt.de/krotik/common/errorutil"
+	"devt.de/krotik/ecal/config"
+	"devt.de/krotik/ecal/interpreter"
+	"devt.de/krotik/ecal/stdlib"
+	"devt.de/krotik/ecal/util"
+)
+
+var testDebugLogOut *bytes.Buffer
+
+func newTestDebugWithConfig() *CLIDebugInterpreter {
+	tdin := NewCLIDebugInterpreter(newTestInterpreterWithConfig())
+
+	testDebugLogOut = &bytes.Buffer{}
+	tdin.LogOut = testDebugLogOut
+
+	return tdin
+}
+
+func TestDebugBasicFunctions(t *testing.T) {
+	tdin := newTestDebugWithConfig()
+	defer tearDown()
+
+	// Test help output
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
+
+	osArgs = []string{"foo", "bar", "-help"}
+	defer func() { osArgs = []string{} }()
+
+	flag.CommandLine.SetOutput(&testTerm.out)
+
+	if stop := tdin.ParseArgs(); !stop {
+		t.Error("Asking for help should request to stop the program")
+		return
+	}
+
+	if !strings.Contains(testTerm.out.String(), "Root directory for ECAL interpreter") {
+		t.Error("Helptext does not contain expected string - output:", testTerm.out.String())
+		return
+	}
+
+	if stop := tdin.ParseArgs(); stop {
+		t.Error("Asking again should be caught by the short circuit")
+		return
+	}
+
+	tdin = newTestDebugWithConfig()
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
+
+	flag.CommandLine.SetOutput(&testTerm.out)
+
+	if err := tdin.Interpret(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+}
+
+func TestDebugInterpret(t *testing.T) {
+	tdin := newTestDebugWithConfig()
+	defer tearDown()
+
+	if stop := tdin.ParseArgs(); stop {
+		t.Error("Setting default args should be fine")
+		return
+	}
+
+	if err := tdin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	tdin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
+	tdin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{
+		Files: map[string]string{
+			"foo": "a := 1",
+		},
+	}
+
+	l1 := ""
+	tdin.LogFile = &l1
+	l2 := ""
+	tdin.LogLevel = &l2
+	l3 := true
+	tdin.Interactive = &l3
+	tdin.RunDebugServer = &l3
+
+	testTerm.in = []string{"xxx := 1", "q"}
+
+	// The interpret call takes quite long because the debug server is
+	// closed by the defer call when the call returns
+
+	if err := tdin.Interpret(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if testLogOut.String() != `ECAL `+config.ProductVersion+`
+Log level: info - Root directory: /home/ml/data/krotik/ecal/cli/tool
+Running in debug mode - with debug server on localhost:33274 - prefix debug commands with ##
+Type 'q' or 'quit' to exit the shell and '?' to get help
+` {
+		t.Error("Unexpected result:", testLogOut.String())
+		return
+	}
+
+	if tdin.GlobalVS.String() != `GlobalScope {
+    xxx (float64) : 1
+}` {
+		t.Error("Unexpected scope:", tdin.GlobalVS)
+		return
+	}
+}
+
+func TestDebugHandleInput(t *testing.T) {
+	tdin := newTestDebugWithConfig()
+	defer tearDown()
+
+	if stop := tdin.ParseArgs(); stop {
+		t.Error("Setting default args should be fine")
+		return
+	}
+
+	if err := tdin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	stdlib.AddStdlibPkg("foo", "bar")
+	stdlib.AddStdlibFunc("foo", "Println",
+		stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Println), "xxx"))
+	stdlib.AddStdlibFunc("foo", "Atoi",
+		stdlib.NewECALFunctionAdapter(reflect.ValueOf(strconv.Atoi), "xxx"))
+
+	tdin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
+	tdin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
+	tdin.CustomHelpString = "123"
+
+	l1 := ""
+	tdin.LogFile = &l1
+	l2 := ""
+	tdin.LogLevel = &l2
+	l3 := true
+	tdin.Interactive = &l3
+	l4 := false
+	tdin.RunDebugServer = &l4
+
+	testTerm.in = []string{"?", "@dbg", "##status", "##foo", "q"}
+
+	if err := tdin.Interpret(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	// Just check for a simple string no need for the whole thing
+	time.Sleep(1 * time.Second)
+
+	if !strings.Contains(testTerm.out.String(), "Set a breakpoint specifying <source>:<line>") {
+		t.Error("Unexpected result:", testTerm.out.String())
+		return
+	}
+
+	if !strings.Contains(testTerm.out.String(), "Unknown command: foo") {
+		t.Error("Unexpected result:", testTerm.out.String())
+		return
+	}
+
+	testTerm.out.Reset()
+
+	testTerm.in = []string{"@dbg status", "q"}
+
+	if err := tdin.Interpret(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if testTerm.out.String() != `╒══════════════╤═════════════════════════════════════════╕
+│Debug command │Description                              │
+╞══════════════╪═════════════════════════════════════════╡
+│status        │Shows breakpoints and suspended threads. │
+│              │                                         │
+╘══════════════╧═════════════════════════════════════════╛
+
+
+` {
+		t.Error("Unexpected result:", "#"+testTerm.out.String()+"#")
+		return
+	}
+
+	testTerm.out.Reset()
+
+	testTerm.in = []string{"1", "raise(123)", "q"}
+
+	if err := tdin.Interpret(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if testTerm.out.String() != `1
+ECAL error in foo: 123 () (Line:1 Pos:1)
+` {
+		t.Error("Unexpected result:", testTerm.out.String())
+		return
+	}
+}
+
+func TestDebugTelnetServer(t *testing.T) {
+	tdin := newTestDebugWithConfig()
+	defer tearDown()
+
+	if err := tdin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	tdin.RuntimeProvider.Logger = util.NewMemoryLogger(10)
+	tdin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
+	tdin.RuntimeProvider.Debugger = interpreter.NewECALDebugger(tdin.GlobalVS)
+	tdin.RuntimeProvider.Debugger.BreakOnError(false)
+	tdin.CustomHandler = tdin
+
+	addr := "localhost:33274"
+	mlog := util.NewMemoryLogger(10)
+
+	srv := &debugTelnetServer{
+		address:     addr,
+		logPrefix:   "testdebugserver",
+		listener:    nil,
+		listen:      true,
+		echo:        true,
+		interpreter: tdin,
+		logger:      mlog,
+	}
+	defer func() {
+		srv.listen = false
+		srv.listener.Close() // Attempt to cleanup
+	}()
+
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+	go srv.Run(wg)
+	wg.Wait()
+
+	conn, err := net.Dial("tcp", addr)
+	errorutil.AssertOk(err)
+	reader := bufio.NewReader(conn)
+
+	fmt.Fprintf(conn, "a:= 1; a\n")
+
+	line, err := reader.ReadString('}')
+	errorutil.AssertOk(err)
+
+	if line != `{
+  "EncodedOutput": "MQo="
+}` {
+		t.Error("Unexpected output:", line)
+		return
+	}
+
+	if tdin.GlobalVS.String() != `GlobalScope {
+    a (float64) : 1
+}` {
+		t.Error("Unexpected result:", tdin.GlobalVS)
+		return
+	}
+
+	fmt.Fprintf(conn, "##status\n")
+
+	line, err = reader.ReadString('}')
+	errorutil.AssertOk(err)
+	l, err := reader.ReadString('}')
+	errorutil.AssertOk(err)
+	line += l
+	l, err = reader.ReadString('}')
+	errorutil.AssertOk(err)
+	line += l
+	line = strings.TrimSpace(line)
+
+	if line != `{
+  "breakonstart": false,
+  "breakpoints": {},
+  "sources": [
+    "console input"
+  ],
+  "threads": {}
+}` {
+		t.Error("Unexpected output:", line)
+		return
+	}
+
+	fmt.Fprintf(conn, "@sym\n")
+
+	line, err = reader.ReadString('}')
+	errorutil.AssertOk(err)
+
+	if !strings.Contains(line, "KioqKioqKioqKioqKioqKioq") {
+		t.Error("Unexpected output:", line)
+		return
+	}
+
+	fmt.Fprintf(conn, "raise(123);1\n")
+
+	line, err = reader.ReadString('}')
+	errorutil.AssertOk(err)
+	line = strings.TrimSpace(line)
+
+	if line != `{
+  "EncodedOutput": "RUNBTCBlcnJvciBpbiBmb286IDEyMyAoKSAoTGluZToxIFBvczoxKQo="
+}` {
+		t.Error("Unexpected output:", line)
+		return
+	}
+
+	testDebugLogOut.Reset()
+
+	errorutil.AssertOk(conn.Close())
+
+	time.Sleep(10 * time.Millisecond)
+
+	if !strings.Contains(testDebugLogOut.String(), "Disconnected") {
+		t.Error("Unexpected output:", testDebugLogOut)
+		return
+	}
+
+	testDebugLogOut.Reset()
+
+	conn, err = net.Dial("tcp", addr)
+	errorutil.AssertOk(err)
+
+	if _, err := fmt.Fprintf(conn, "q\n"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	// Make sure we can't start a second server on the same port
+
+	mlog2 := util.NewMemoryLogger(10)
+
+	srv2 := &debugTelnetServer{
+		address:     addr,
+		logPrefix:   "testdebugserver",
+		listener:    nil,
+		listen:      true,
+		echo:        true,
+		interpreter: tdin,
+		logger:      mlog2,
+	}
+	defer func() {
+		srv2.listen = false
+		srv2.listener.Close() // Attempt to cleanup
+	}()
+
+	mlog2.Reset()
+
+	wg = &sync.WaitGroup{}
+	wg.Add(1)
+	go srv2.Run(wg)
+	wg.Wait()
+
+	if !strings.Contains(mlog2.String(), "address already in use") {
+		t.Error("Unexpected output:", mlog2.String())
+		return
+	}
+
+	mlog.Reset()
+
+	srv.listener.Close()
+
+	time.Sleep(5 * time.Millisecond)
+
+	if !strings.Contains(mlog.String(), "use of closed network connection") {
+		t.Error("Unexpected output:", mlog.String())
+		return
+	}
+}

+ 23 - 17
cli/tool/format.go

@@ -16,10 +16,14 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"strings"
 
 
 	"devt.de/krotik/ecal/parser"
 	"devt.de/krotik/ecal/parser"
 )
 )
 
 
+/*
+Format formats a given set of ECAL files.
+*/
 func Format() error {
 func Format() error {
 	var err error
 	var err error
 
 
@@ -30,17 +34,17 @@ func Format() error {
 	showHelp := flag.Bool("help", false, "Show this help message")
 	showHelp := flag.Bool("help", false, "Show this help message")
 
 
 	flag.Usage = func() {
 	flag.Usage = func() {
-		fmt.Println()
-		fmt.Println(fmt.Sprintf("Usage of %s format [options]", os.Args[0]))
-		fmt.Println()
+		fmt.Fprintln(flag.CommandLine.Output())
+		fmt.Fprintln(flag.CommandLine.Output(), fmt.Sprintf("Usage of %s format [options]", os.Args[0]))
+		fmt.Fprintln(flag.CommandLine.Output())
 		flag.PrintDefaults()
 		flag.PrintDefaults()
-		fmt.Println()
-		fmt.Println("This tool will format all ECAL files in a directory structure.")
-		fmt.Println()
+		fmt.Fprintln(flag.CommandLine.Output())
+		fmt.Fprintln(flag.CommandLine.Output(), "This tool will format all ECAL files in a directory structure.")
+		fmt.Fprintln(flag.CommandLine.Output())
 	}
 	}
 
 
 	if len(os.Args) >= 2 {
 	if len(os.Args) >= 2 {
-		flag.CommandLine.Parse(os.Args[2:])
+		flag.CommandLine.Parse(osArgs[2:])
 
 
 		if *showHelp {
 		if *showHelp {
 			flag.Usage()
 			flag.Usage()
@@ -48,26 +52,28 @@ func Format() error {
 		}
 		}
 	}
 	}
 
 
-	fmt.Println(fmt.Sprintf("Formatting all %v files in %v", *ext, *dir))
+	fmt.Fprintln(flag.CommandLine.Output(), fmt.Sprintf("Formatting all %v files in %v", *ext, *dir))
 
 
-	err = filepath.Walk(".",
+	err = filepath.Walk(*dir,
 		func(path string, i os.FileInfo, err error) error {
 		func(path string, i os.FileInfo, err error) error {
 			if err == nil && !i.IsDir() {
 			if err == nil && !i.IsDir() {
 				var data []byte
 				var data []byte
 				var ast *parser.ASTNode
 				var ast *parser.ASTNode
 				var srcFormatted string
 				var srcFormatted string
 
 
-				if data, err = ioutil.ReadFile(path); err == nil {
-					var ferr error
+				if strings.HasSuffix(path, *ext) {
+					if data, err = ioutil.ReadFile(path); err == nil {
+						var ferr error
 
 
-					if ast, ferr = parser.Parse(path, string(data)); ferr == nil {
-						if srcFormatted, ferr = parser.PrettyPrint(ast); ferr == nil {
-							ioutil.WriteFile(path, []byte(srcFormatted), i.Mode())
+						if ast, ferr = parser.Parse(path, string(data)); ferr == nil {
+							if srcFormatted, ferr = parser.PrettyPrint(ast); ferr == nil {
+								ioutil.WriteFile(path, []byte(srcFormatted), i.Mode())
+							}
 						}
 						}
-					}
 
 
-					if ferr != nil {
-						fmt.Fprintln(os.Stderr, fmt.Sprintf("Could not format %v: %v", path, ferr))
+						if ferr != nil {
+							fmt.Fprintln(flag.CommandLine.Output(), fmt.Sprintf("Could not format %v: %v", path, ferr))
+						}
 					}
 					}
 				}
 				}
 			}
 			}

+ 122 - 0
cli/tool/format_test.go

@@ -0,0 +1,122 @@
+/*
+ * ECAL
+ *
+ * Copyright 2020 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the MIT
+ * License, If a copy of the MIT License was not distributed with this
+ * file, You can obtain one at https://opensource.org/licenses/MIT.
+ */
+
+package tool
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"devt.de/krotik/common/errorutil"
+	"devt.de/krotik/common/fileutil"
+)
+
+const formatTestDir = "formattest"
+
+func setupFormatTestDir() {
+
+	if res, _ := fileutil.PathExists(formatTestDir); res {
+		os.RemoveAll(formatTestDir)
+	}
+
+	err := os.Mkdir(formatTestDir, 0770)
+	if err != nil {
+		fmt.Print("Could not create test directory:", err.Error())
+		os.Exit(1)
+	}
+}
+
+func tearDownFormatTestDir() {
+	err := os.RemoveAll(formatTestDir)
+	if err != nil {
+		fmt.Print("Could not remove test directory:", err.Error())
+	}
+}
+
+func TestFormat(t *testing.T) {
+	setupFormatTestDir()
+	defer tearDownFormatTestDir()
+
+	out := bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{"foo", "bar", "-help"}
+
+	if err := Format(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if !strings.Contains(out.String(), "Root directory for ECAL files") {
+		t.Error("Unexpected output:", out.String())
+		return
+	}
+
+	myfile := filepath.Join(formatTestDir, "myfile.ecal")
+	myfile2 := filepath.Join(formatTestDir, "myfile.eca")
+	myfile3 := filepath.Join(formatTestDir, "myinvalidfile.ecal")
+
+	originalContent := "if a == 1 { b := 1 }"
+
+	err := ioutil.WriteFile(myfile, []byte(originalContent), 0777)
+	errorutil.AssertOk(err)
+
+	err = ioutil.WriteFile(myfile2, []byte(originalContent), 0777)
+	errorutil.AssertOk(err)
+
+	err = ioutil.WriteFile(myfile3, []byte(originalContent[5:]), 0777)
+	errorutil.AssertOk(err)
+
+	out = bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{"foo", "bar", "-dir", formatTestDir}
+
+	if err := Format(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if out.String() != `Formatting all .ecal files in formattest
+Could not format formattest/myinvalidfile.ecal: Parse error in formattest/myinvalidfile.ecal: Term cannot start an expression (==) (Line:1 Pos:1)
+` {
+		t.Error("Unexpected output:", out.String())
+		return
+	}
+
+	myfileContent, err := ioutil.ReadFile(myfile)
+	errorutil.AssertOk(err)
+
+	if string(myfileContent) != `if a == 1 {
+    b := 1
+}
+` {
+		t.Error("Unexpected result:", string(myfileContent))
+		return
+	}
+
+	myfileContent, err = ioutil.ReadFile(myfile2)
+	errorutil.AssertOk(err)
+
+	if string(myfileContent) != originalContent {
+		t.Error("Unexpected result:", string(myfileContent))
+		return
+	}
+}

+ 17 - 0
cli/tool/helper.go

@@ -12,12 +12,29 @@ package tool
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"io"
+	"os"
 	"regexp"
 	"regexp"
 	"strings"
 	"strings"
 
 
 	"devt.de/krotik/common/stringutil"
 	"devt.de/krotik/common/stringutil"
 )
 )
 
 
+/*
+osArgs is a local copy of os.Args (used for unit tests)
+*/
+var osArgs = os.Args
+
+/*
+osStderr is a local copy of os.Stderr (used for unit tests)
+*/
+var osStderr io.Writer = os.Stderr
+
+/*
+osExit is a local variable pointing to os.Exit (used for unit tests)
+*/
+var osExit func(int) = os.Exit
+
 /*
 /*
 CLIInputHandler is a handler object for CLI input.
 CLIInputHandler is a handler object for CLI input.
 */
 */

+ 123 - 0
cli/tool/helper_test.go

@@ -0,0 +1,123 @@
+/*
+ * ECAL
+ *
+ * Copyright 2020 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the MIT
+ * License, If a copy of the MIT License was not distributed with this
+ * file, You can obtain one at https://opensource.org/licenses/MIT.
+ */
+
+package tool
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+	"testing"
+
+	"devt.de/krotik/common/stringutil"
+	"devt.de/krotik/common/termutil"
+)
+
+type testConsoleLineTerminal struct {
+	in  []string
+	out bytes.Buffer
+}
+
+func (t *testConsoleLineTerminal) StartTerm() error {
+	return nil
+}
+
+func (t *testConsoleLineTerminal) AddKeyHandler(handler termutil.KeyHandler) {
+}
+
+func (t *testConsoleLineTerminal) NextLine() (string, error) {
+	var err error
+	var ret string
+
+	if len(t.in) > 0 {
+		ret = t.in[0]
+		t.in = t.in[1:]
+	} else {
+		err = fmt.Errorf("Input is empty in testConsoleLineTerminal")
+	}
+	return ret, err
+}
+
+func (t *testConsoleLineTerminal) NextLinePrompt(prompt string, echo rune) (string, error) {
+	return t.NextLine()
+}
+
+func (t *testConsoleLineTerminal) WriteString(s string) {
+	t.out.WriteString(s)
+}
+
+func (t *testConsoleLineTerminal) Write(p []byte) (n int, err error) {
+	return t.out.Write(p)
+}
+
+func (t *testConsoleLineTerminal) StopTerm() {
+}
+
+type testCustomHandler struct {
+}
+
+func (t *testCustomHandler) CanHandle(s string) bool {
+	return s == "@cus"
+}
+
+func (t *testCustomHandler) Handle(ot OutputTerminal, input string) {}
+
+func (t *testCustomHandler) LoadInitialFile(tid uint64) error {
+	return nil
+}
+
+type testOutputTerminal struct {
+	b bytes.Buffer
+}
+
+func (t *testOutputTerminal) WriteString(s string) {
+	t.b.WriteString(s)
+}
+
+func TestMatchesFulltextSearch(t *testing.T) {
+	ot := &testOutputTerminal{}
+
+	ok := matchesFulltextSearch(ot, "abc", "s[")
+
+	if !ok && strings.HasPrefix(ot.b.String(), "Invalid search expression") {
+		t.Error("Unexpected result:", ot.b.String(), ok)
+		return
+	}
+
+	ot.b = bytes.Buffer{}
+
+	ok = matchesFulltextSearch(ot, "abc", "a*")
+
+	if !ok || ot.b.String() != "" {
+		t.Error("Unexpected result:", ot.b.String(), ok)
+		return
+	}
+
+	ok = matchesFulltextSearch(ot, "abc", "ac*")
+
+	if ok || ot.b.String() != "" {
+		t.Error("Unexpected result:", ot.b.String(), ok)
+		return
+	}
+}
+
+func TestFillTableRow(t *testing.T) {
+
+	res := fillTableRow([]string{}, "test", stringutil.GenerateRollingString("123 ", 100))
+
+	b, _ := json.Marshal(&res)
+
+	if string(b) != `["test","123 123 123 123 123 123 123 123 123 123 123 123 `+
+		`123 123 123 123 123 123 123 123","","123 123 123 123 123","",""]` {
+		t.Error("Unexpected result:", string(b))
+		return
+	}
+}

+ 61 - 40
cli/tool/interpret.go

@@ -62,6 +62,10 @@ type CLIInterpreter struct {
 	LogFile  *string // Logfile (blank for stdout)
 	LogFile  *string // Logfile (blank for stdout)
 	LogLevel *string // Log level string (Debug, Info, Error)
 	LogLevel *string // Log level string (Debug, Info, Error)
 
 
+	// User terminal
+
+	Term termutil.ConsoleLineTerminal
+
 	// Log output
 	// Log output
 
 
 	LogOut io.Writer
 	LogOut io.Writer
@@ -71,7 +75,7 @@ type CLIInterpreter struct {
 NewCLIInterpreter creates a new commandline interpreter for ECAL.
 NewCLIInterpreter creates a new commandline interpreter for ECAL.
 */
 */
 func NewCLIInterpreter() *CLIInterpreter {
 func NewCLIInterpreter() *CLIInterpreter {
-	return &CLIInterpreter{scope.NewScope(scope.GlobalScope), nil, nil, "", "", "", nil, nil, nil, os.Stdout}
+	return &CLIInterpreter{scope.NewScope(scope.GlobalScope), nil, nil, "", "", "", nil, nil, nil, nil, os.Stdout}
 }
 }
 
 
 /*
 /*
@@ -92,15 +96,15 @@ func (i *CLIInterpreter) ParseArgs() bool {
 	showHelp := flag.Bool("help", false, "Show this help message")
 	showHelp := flag.Bool("help", false, "Show this help message")
 
 
 	flag.Usage = func() {
 	flag.Usage = func() {
-		fmt.Println()
-		fmt.Println(fmt.Sprintf("Usage of %s run [options] [file]", os.Args[0]))
-		fmt.Println()
+		fmt.Fprintln(flag.CommandLine.Output())
+		fmt.Fprintln(flag.CommandLine.Output(), fmt.Sprintf("Usage of %s run [options] [file]", osArgs[0]))
+		fmt.Fprintln(flag.CommandLine.Output())
 		flag.PrintDefaults()
 		flag.PrintDefaults()
-		fmt.Println()
+		fmt.Fprintln(flag.CommandLine.Output())
 	}
 	}
 
 
-	if len(os.Args) >= 2 {
-		flag.CommandLine.Parse(os.Args[2:])
+	if len(osArgs) >= 2 {
+		flag.CommandLine.Parse(osArgs[2:])
 
 
 		if cargs := flag.Args(); len(cargs) > 0 {
 		if cargs := flag.Args(); len(cargs) > 0 {
 			i.EntryFile = flag.Arg(0)
 			i.EntryFile = flag.Arg(0)
@@ -115,7 +119,7 @@ func (i *CLIInterpreter) ParseArgs() bool {
 }
 }
 
 
 /*
 /*
-Create the runtime provider of this interpreter. This function expects Dir,
+CreateRuntimeProvider creates the runtime provider of this interpreter. This function expects Dir,
 LogFile and LogLevel to be set.
 LogFile and LogLevel to be set.
 */
 */
 func (i *CLIInterpreter) CreateRuntimeProvider(name string) error {
 func (i *CLIInterpreter) CreateRuntimeProvider(name string) error {
@@ -130,6 +134,7 @@ func (i *CLIInterpreter) CreateRuntimeProvider(name string) error {
 
 
 	if i.LogFile != nil && *i.LogFile != "" {
 	if i.LogFile != nil && *i.LogFile != "" {
 		var logWriter io.Writer
 		var logWriter io.Writer
+
 		logFileRollover := fileutil.SizeBasedRolloverCondition(1000000) // Each file can be up to a megabyte
 		logFileRollover := fileutil.SizeBasedRolloverCondition(1000000) // Each file can be up to a megabyte
 		logWriter, err = fileutil.NewMultiFileBuffer(*i.LogFile, fileutil.ConsecutiveNumberIterator(10), logFileRollover)
 		logWriter, err = fileutil.NewMultiFileBuffer(*i.LogFile, fileutil.ConsecutiveNumberIterator(10), logFileRollover)
 		logger = util.NewBufferLogger(logWriter)
 		logger = util.NewBufferLogger(logWriter)
@@ -180,21 +185,36 @@ func (i *CLIInterpreter) LoadInitialFile(tid uint64) error {
 
 
 		initFile, err = ioutil.ReadFile(i.EntryFile)
 		initFile, err = ioutil.ReadFile(i.EntryFile)
 
 
-		if ast, err = parser.ParseWithRuntime(i.EntryFile, string(initFile), i.RuntimeProvider); err == nil {
-			if err = ast.Runtime.Validate(); err == nil {
-				_, err = ast.Runtime.Eval(i.GlobalVS, make(map[string]interface{}), tid)
-			}
-			defer func() {
-				if i.RuntimeProvider.Debugger != nil {
-					i.RuntimeProvider.Debugger.RecordThreadFinished(tid)
+		if err == nil {
+			if ast, err = parser.ParseWithRuntime(i.EntryFile, string(initFile), i.RuntimeProvider); err == nil {
+				if err = ast.Runtime.Validate(); err == nil {
+					_, err = ast.Runtime.Eval(i.GlobalVS, make(map[string]interface{}), tid)
 				}
 				}
-			}()
+				defer func() {
+					if i.RuntimeProvider.Debugger != nil {
+						i.RuntimeProvider.Debugger.RecordThreadFinished(tid)
+					}
+				}()
+			}
 		}
 		}
 	}
 	}
 
 
 	return err
 	return err
 }
 }
 
 
+/*
+CreateTerm creates a new console terminal for stdout.
+*/
+func (i *CLIInterpreter) CreateTerm() error {
+	var err error
+
+	if i.Term == nil {
+		i.Term, err = termutil.NewConsoleLineTerminal(os.Stdout)
+	}
+
+	return err
+}
+
 /*
 /*
 Interpret starts the ECAL code interpreter. Starts an interactive console in
 Interpret starts the ECAL code interpreter. Starts an interactive console in
 the current tty if the interactive flag is set.
 the current tty if the interactive flag is set.
@@ -205,7 +225,7 @@ func (i *CLIInterpreter) Interpret(interactive bool) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	clt, err := termutil.NewConsoleLineTerminal(os.Stdout)
+	err := i.CreateTerm()
 
 
 	if interactive {
 	if interactive {
 		fmt.Fprintln(i.LogOut, fmt.Sprintf("ECAL %v", config.ProductVersion))
 		fmt.Fprintln(i.LogOut, fmt.Sprintf("ECAL %v", config.ProductVersion))
@@ -235,39 +255,33 @@ func (i *CLIInterpreter) Interpret(interactive bool) error {
 
 
 			if err = i.LoadInitialFile(tid); err == nil {
 			if err = i.LoadInitialFile(tid); err == nil {
 
 
-				if interactive {
+				// Drop into interactive shell
 
 
-					// Drop into interactive shell
-
-					if err == nil {
-						isExitLine := func(s string) bool {
-							return s == "exit" || s == "q" || s == "quit" || s == "bye" || s == "\x04"
-						}
+				if interactive {
 
 
-						// Add history functionality without file persistence
+					// Add history functionality without file persistence
 
 
-						clt, err = termutil.AddHistoryMixin(clt, "",
-							func(s string) bool {
-								return isExitLine(s)
-							})
+					i.Term, err = termutil.AddHistoryMixin(i.Term, "",
+						func(s string) bool {
+							return i.isExitLine(s)
+						})
 
 
-						if err == nil {
+					if err == nil {
 
 
-							if err = clt.StartTerm(); err == nil {
-								var line string
+						if err = i.Term.StartTerm(); err == nil {
+							var line string
 
 
-								defer clt.StopTerm()
+							defer i.Term.StopTerm()
 
 
-								fmt.Fprintln(i.LogOut, "Type 'q' or 'quit' to exit the shell and '?' to get help")
+							fmt.Fprintln(i.LogOut, "Type 'q' or 'quit' to exit the shell and '?' to get help")
 
 
-								line, err = clt.NextLine()
-								for err == nil && !isExitLine(line) {
-									trimmedLine := strings.TrimSpace(line)
+							line, err = i.Term.NextLine()
+							for err == nil && !i.isExitLine(line) {
+								trimmedLine := strings.TrimSpace(line)
 
 
-									i.HandleInput(clt, trimmedLine, tid)
+								i.HandleInput(i.Term, trimmedLine, tid)
 
 
-									line, err = clt.NextLine()
-								}
+								line, err = i.Term.NextLine()
 							}
 							}
 						}
 						}
 					}
 					}
@@ -279,6 +293,13 @@ func (i *CLIInterpreter) Interpret(interactive bool) error {
 	return err
 	return err
 }
 }
 
 
+/*
+isExitLine returns if a given input line should exit the interpreter.
+*/
+func (i *CLIInterpreter) isExitLine(s string) bool {
+	return s == "exit" || s == "q" || s == "quit" || s == "bye" || s == "\x04"
+}
+
 /*
 /*
 HandleInput handles input to this interpreter. It parses a given input line
 HandleInput handles input to this interpreter. It parses a given input line
 and outputs on the given output terminal. Requires a thread ID of the executing
 and outputs on the given output terminal. Requires a thread ID of the executing

+ 355 - 0
cli/tool/interpret_test.go

@@ -0,0 +1,355 @@
+/*
+ * ECAL
+ *
+ * Copyright 2020 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the MIT
+ * License, If a copy of the MIT License was not distributed with this
+ * file, You can obtain one at https://opensource.org/licenses/MIT.
+ */
+
+package tool
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strconv"
+	"strings"
+	"testing"
+
+	"devt.de/krotik/common/errorutil"
+	"devt.de/krotik/common/fileutil"
+	"devt.de/krotik/ecal/config"
+	"devt.de/krotik/ecal/interpreter"
+	"devt.de/krotik/ecal/stdlib"
+	"devt.de/krotik/ecal/util"
+)
+
+const testDir = "tooltest"
+
+var testLogOut *bytes.Buffer
+var testTerm *testConsoleLineTerminal
+
+func newTestInterpreter() *CLIInterpreter {
+	tin := NewCLIInterpreter()
+
+	// Redirect I/O bits into internal buffers
+
+	testTerm = &testConsoleLineTerminal{nil, bytes.Buffer{}}
+	tin.Term = testTerm
+
+	testLogOut = &bytes.Buffer{}
+	tin.LogOut = testLogOut
+
+	return tin
+}
+
+func newTestInterpreterWithConfig() *CLIInterpreter {
+	tin := newTestInterpreter()
+
+	// Setup
+	if res, _ := fileutil.PathExists(testDir); res {
+		os.RemoveAll(testDir)
+	}
+
+	err := os.Mkdir(testDir, 0770)
+	if err != nil {
+		fmt.Print("Could not create test directory:", err.Error())
+		os.Exit(1)
+	}
+
+	l := testDir
+	tin.Dir = &l
+
+	tin.CustomWelcomeMessage = "123"
+
+	// Teardown
+
+	return tin
+}
+
+func tearDown() {
+	err := os.RemoveAll(testDir)
+	if err != nil {
+		fmt.Print("Could not remove test directory:", err.Error())
+	}
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
+}
+
+func TestInterpretBasicFunctions(t *testing.T) {
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
+
+	// Test normal initialisation
+
+	tin := NewCLIInterpreter()
+	if err := tin.CreateTerm(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	tin = newTestInterpreter()
+
+	// Test help output
+
+	osArgs = []string{"foo", "bar", "-help"}
+
+	flag.CommandLine.SetOutput(&testTerm.out)
+
+	if stop := tin.ParseArgs(); !stop {
+		t.Error("Asking for help should request to stop the program")
+		return
+	}
+
+	if !strings.Contains(testTerm.out.String(), "Root directory for ECAL interpreter") {
+		t.Error("Helptext does not contain expected string - output:", testTerm.out.String())
+		return
+	}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
+
+	// Test interpret
+
+	tin = newTestInterpreter()
+
+	osArgs = []string{"foo", "bar", "-help"}
+
+	flag.CommandLine.SetOutput(&testTerm.out)
+
+	errorutil.AssertOk(tin.Interpret(true))
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
+
+	// Test entry file parsing
+
+	tin = NewCLIInterpreter()
+
+	osArgs = []string{"foo", "bar", "myfile"}
+
+	if stop := tin.ParseArgs(); stop {
+		t.Error("Giving an entry file should not stop the program")
+		return
+	}
+
+	if stop := tin.ParseArgs(); stop {
+		t.Error("Giving an entry file should not stop the program")
+		return
+	}
+
+	if tin.EntryFile != "myfile" {
+		t.Error("Unexpected entryfile:", tin.EntryFile)
+		return
+	}
+}
+
+func TestCreateRuntimeProvider(t *testing.T) {
+	tin := newTestInterpreterWithConfig()
+	defer tearDown()
+
+	l := filepath.Join(testDir, "test.log")
+	tin.LogFile = &l
+
+	if err := tin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if _, ok := tin.RuntimeProvider.Logger.(*util.BufferLogger); !ok {
+		t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
+		return
+	}
+
+	tin = newTestInterpreterWithConfig()
+	defer tearDown()
+
+	l = "error"
+	tin.LogLevel = &l
+
+	if err := tin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if _, ok := tin.RuntimeProvider.Logger.(*util.LogLevelLogger); !ok {
+		t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
+		return
+	}
+
+	if err := tin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if _, ok := tin.RuntimeProvider.Logger.(*util.LogLevelLogger); !ok {
+		t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
+		return
+	}
+}
+
+func TestLoadInitialFile(t *testing.T) {
+	tin := NewCLIDebugInterpreter(newTestInterpreterWithConfig())
+	defer tearDown()
+
+	if err := tin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	tin.RuntimeProvider.Debugger = interpreter.NewECALDebugger(tin.GlobalVS)
+	tin.RuntimeProvider.Logger = util.NewMemoryLogger(10)
+	tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
+
+	tin.EntryFile = filepath.Join(testDir, "foo.ecal")
+
+	ioutil.WriteFile(tin.EntryFile, []byte("a := 1"), 0777)
+
+	if err := tin.CLIInterpreter.LoadInitialFile(1); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if tin.GlobalVS.String() != `GlobalScope {
+    a (float64) : 1
+}` {
+		t.Error("Unexpected scope:", tin.GlobalVS)
+		return
+	}
+}
+
+func TestInterpret(t *testing.T) {
+	tin := newTestInterpreterWithConfig()
+	defer tearDown()
+
+	if err := tin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	tin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
+	tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{
+		Files: map[string]string{
+			"foo": "a := 1",
+		},
+	}
+
+	l1 := ""
+	tin.LogFile = &l1
+	l2 := ""
+	tin.LogLevel = &l2
+
+	testTerm.in = []string{"xxx := 1", "q"}
+
+	if err := tin.Interpret(true); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if testLogOut.String() != `ECAL `+config.ProductVersion+`
+Log level: info - Root directory: tooltest
+123
+Type 'q' or 'quit' to exit the shell and '?' to get help
+` {
+		t.Error("Unexpected result:", testLogOut.String())
+		return
+	}
+
+	if tin.GlobalVS.String() != `GlobalScope {
+    xxx (float64) : 1
+}` {
+		t.Error("Unexpected scope:", tin.GlobalVS)
+		return
+	}
+}
+
+func TestHandleInput(t *testing.T) {
+	tin := newTestInterpreterWithConfig()
+	defer tearDown()
+
+	tin.CustomHandler = &testCustomHandler{}
+
+	if err := tin.CreateRuntimeProvider("foo"); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	stdlib.AddStdlibPkg("foo", "bar")
+	stdlib.AddStdlibFunc("foo", "Println",
+		stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Println), "xxx"))
+	stdlib.AddStdlibFunc("foo", "Atoi",
+		stdlib.NewECALFunctionAdapter(reflect.ValueOf(strconv.Atoi), "xxx"))
+
+	tin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
+	tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
+	tin.CustomHelpString = "123"
+
+	l1 := ""
+	tin.LogFile = &l1
+	l2 := ""
+	tin.LogLevel = &l2
+
+	testTerm.in = []string{"?", "@reload", "@sym", "@std", "@cus", "q"}
+
+	if err := tin.Interpret(true); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	// Just check for a simple string no need for the whole thing
+
+	if !strings.Contains(testTerm.out.String(), "New creates a new object instance.") {
+		t.Error("Unexpected result:", testTerm.out.String())
+		return
+	}
+
+	testTerm.out.Reset()
+
+	testTerm.in = []string{"@sym raise", "@std math.Phi", "@std foo Print", "q"}
+
+	if err := tin.Interpret(true); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if testTerm.out.String() != `╒═════════════════╤═══════════════════════════════╕
+│Inbuild function │Description                    │
+╞═════════════════╪═══════════════════════════════╡
+│raise            │Raise returns an error object. │
+│                 │                               │
+╘═════════════════╧═══════════════════════════════╛
+╒═════════╤══════════════════╕
+│Constant │Value             │
+╞═════════╪══════════════════╡
+│math.Phi │1.618033988749895 │
+│         │                  │
+╘═════════╧══════════════════╛
+╒════════════╤════════════╕
+│Function    │Description │
+╞════════════╪════════════╡
+│foo.Println │xxx         │
+│            │            │
+╘════════════╧════════════╛
+` {
+		t.Error("Unexpected result:", testTerm.out.String())
+		return
+	}
+
+	testTerm.out.Reset()
+
+	testTerm.in = []string{"1", "raise(123)", "q"}
+
+	if err := tin.Interpret(true); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if testTerm.out.String() != `1
+ECAL error in foo: 123 () (Line:1 Pos:1)
+` {
+		t.Error("Unexpected result:", testTerm.out.String())
+		return
+	}
+}

+ 47 - 43
cli/tool/pack.go

@@ -47,6 +47,9 @@ type CLIPacker struct {
 	LogOut io.Writer
 	LogOut io.Writer
 }
 }
 
 
+var packmarkerend = "####"
+var packmarker = fmt.Sprintf("\n%v%v%v\n", packmarkerend, "ECALSRC", packmarkerend)
+
 /*
 /*
 NewCLIPacker creates a new commandline packer.
 NewCLIPacker creates a new commandline packer.
 */
 */
@@ -63,7 +66,7 @@ func (p *CLIPacker) ParseArgs() bool {
 		return false
 		return false
 	}
 	}
 
 
-	binname, err := filepath.Abs(os.Args[0])
+	binname, err := filepath.Abs(osArgs[0])
 	errorutil.AssertOk(err)
 	errorutil.AssertOk(err)
 
 
 	wd, _ := os.Getwd()
 	wd, _ := os.Getwd()
@@ -74,18 +77,18 @@ func (p *CLIPacker) ParseArgs() bool {
 	showHelp := flag.Bool("help", false, "Show this help message")
 	showHelp := flag.Bool("help", false, "Show this help message")
 
 
 	flag.Usage = func() {
 	flag.Usage = func() {
-		fmt.Println()
-		fmt.Println(fmt.Sprintf("Usage of %s pack [options] [entry file]", os.Args[0]))
-		fmt.Println()
+		fmt.Fprintln(flag.CommandLine.Output())
+		fmt.Fprintln(flag.CommandLine.Output(), fmt.Sprintf("Usage of %s pack [options] [entry file]", os.Args[0]))
+		fmt.Fprintln(flag.CommandLine.Output())
 		flag.PrintDefaults()
 		flag.PrintDefaults()
-		fmt.Println()
-		fmt.Println("This tool will collect all files in the root directory and " +
+		fmt.Fprintln(flag.CommandLine.Output())
+		fmt.Fprintln(flag.CommandLine.Output(), "This tool will collect all files in the root directory and "+
 			"build a standalone executable from the given source binary and the collected files.")
 			"build a standalone executable from the given source binary and the collected files.")
-		fmt.Println()
+		fmt.Fprintln(flag.CommandLine.Output())
 	}
 	}
 
 
 	if len(os.Args) >= 2 {
 	if len(os.Args) >= 2 {
-		flag.CommandLine.Parse(os.Args[2:])
+		flag.CommandLine.Parse(osArgs[2:])
 
 
 		if cargs := flag.Args(); len(cargs) > 0 {
 		if cargs := flag.Args(); len(cargs) > 0 {
 			p.EntryFile = flag.Arg(0)
 			p.EntryFile = flag.Arg(0)
@@ -128,10 +131,7 @@ func (p *CLIPacker) Pack() error {
 				fmt.Fprintln(p.LogOut, fmt.Sprintf("Copied %v bytes for interpreter.", bytes))
 				fmt.Fprintln(p.LogOut, fmt.Sprintf("Copied %v bytes for interpreter.", bytes))
 				var bytes int
 				var bytes int
 
 
-				end := "####"
-				marker := fmt.Sprintf("\n%v%v%v\n", end, "ECALSRC", end)
-
-				if bytes, err = dest.WriteString(marker); err == nil {
+				if bytes, err = dest.WriteString(packmarker); err == nil {
 					var data []byte
 					var data []byte
 					fmt.Fprintln(p.LogOut, fmt.Sprintf("Writing marker %v bytes for source archive.", bytes))
 					fmt.Fprintln(p.LogOut, fmt.Sprintf("Writing marker %v bytes for source archive.", bytes))
 
 
@@ -147,11 +147,12 @@ func (p *CLIPacker) Pack() error {
 
 
 								// Add files to the archive
 								// Add files to the archive
 
 
-								if err = p.packFiles(w, *p.Dir, ""); err == nil {
-									err = w.Close()
-
+								defer func() {
+									w.Close()
 									os.Chmod(*p.TargetBinary, 0775) // Try a chmod but don't care about any errors
 									os.Chmod(*p.TargetBinary, 0775) // Try a chmod but don't care about any errors
-								}
+								}()
+
+								err = p.packFiles(w, *p.Dir, "")
 							}
 							}
 						}
 						}
 					}
 					}
@@ -193,6 +194,16 @@ func (p *CLIPacker) packFiles(w *zip.Writer, filePath string, zipPath string) er
 	return err
 	return err
 }
 }
 
 
+var ( // Internal reading buffers
+	b1 = 4096
+	b2 = len(packmarker) + 11
+)
+
+/*
+handleError is the error handling function for runtime errors in packed binaries.
+*/
+var handleError func(error) = errorutil.AssertOk
+
 /*
 /*
 RunPackedBinary runs ECAL code is it has been attached to the currently running binary.
 RunPackedBinary runs ECAL code is it has been attached to the currently running binary.
 Exits if attached ECAL code has been executed.
 Exits if attached ECAL code has been executed.
@@ -201,12 +212,9 @@ func RunPackedBinary() {
 	var retCode = 0
 	var retCode = 0
 	var result bool
 	var result bool
 
 
-	exename, err := filepath.Abs(os.Args[0])
+	exename, err := filepath.Abs(osArgs[0])
 	errorutil.AssertOk(err)
 	errorutil.AssertOk(err)
 
 
-	end := "####"
-	marker := fmt.Sprintf("\n%v%v%v\n", end, "ECALSRC", end)
-
 	if ok, _ := fileutil.PathExists(exename); !ok {
 	if ok, _ := fileutil.PathExists(exename); !ok {
 
 
 		// Try an optional .exe suffix which might work on Windows
 		// Try an optional .exe suffix which might work on Windows
@@ -224,8 +232,8 @@ func RunPackedBinary() {
 			defer f.Close()
 			defer f.Close()
 
 
 			found := false
 			found := false
-			buf := make([]byte, 4096)
-			buf2 := make([]byte, len(marker)+11)
+			buf := make([]byte, b1)
+			buf2 := make([]byte, b2)
 
 
 			// Look for the marker which marks the beginning of the attached zip file
 			// Look for the marker which marks the beginning of the attached zip file
 
 
@@ -240,12 +248,12 @@ func RunPackedBinary() {
 					if i2, err := f.Read(buf2); err == nil || err == io.EOF {
 					if i2, err := f.Read(buf2); err == nil || err == io.EOF {
 						candidateString := string(append(buf, buf2...))
 						candidateString := string(append(buf, buf2...))
 
 
-						// Now determine the position if the zip file
+						// Now determine the position of the zip file
 
 
-						markerIndex := strings.Index(candidateString, marker)
+						markerIndex := strings.Index(candidateString, packmarker)
 
 
 						if found = markerIndex >= 0; found {
 						if found = markerIndex >= 0; found {
-							start := int64(markerIndex + len(marker))
+							start := int64(markerIndex + len(packmarker))
 							for unicode.IsSpace(rune(candidateString[start])) || unicode.IsControl(rune(candidateString[start])) {
 							for unicode.IsSpace(rune(candidateString[start])) || unicode.IsControl(rune(candidateString[start])) {
 								start++ // Skip final control characters \n or \r\n
 								start++ // Skip final control characters \n or \r\n
 							}
 							}
@@ -271,9 +279,8 @@ func RunPackedBinary() {
 
 
 					ret, err = runInterpreter(io.NewSectionReader(f, pos, zipLen), zipLen)
 					ret, err = runInterpreter(io.NewSectionReader(f, pos, zipLen), zipLen)
 
 
-					if retNum, ok := ret.(float64); ok {
-						retCode = int(retNum)
-					}
+					retNum, _ := ret.(float64)
+					retCode = int(retNum)
 
 
 					result = err == nil
 					result = err == nil
 				}
 				}
@@ -281,10 +288,10 @@ func RunPackedBinary() {
 		}
 		}
 	}
 	}
 
 
-	errorutil.AssertOk(err)
+	handleError(err)
 
 
 	if result {
 	if result {
-		os.Exit(retCode)
+		osExit(retCode)
 	}
 	}
 }
 }
 
 
@@ -299,27 +306,24 @@ func runInterpreter(reader io.ReaderAt, size int64) (interface{}, error) {
 	if err == nil {
 	if err == nil {
 
 
 		for _, f := range r.File {
 		for _, f := range r.File {
+			if err == nil {
+				if rc, err = f.Open(); err == nil {
+					var data []byte
 
 
-			if rc, err = f.Open(); err == nil {
-				var data []byte
-
-				defer rc.Close()
+					defer rc.Close()
 
 
-				if data, err = ioutil.ReadAll(rc); err == nil {
-					il.Files[f.Name] = string(data)
+					if data, err = ioutil.ReadAll(rc); err == nil {
+						il.Files[f.Name] = string(data)
+					}
 				}
 				}
 			}
 			}
-
-			if err != nil {
-				break
-			}
 		}
 		}
 	}
 	}
 
 
 	if err == nil {
 	if err == nil {
 		var ast *parser.ASTNode
 		var ast *parser.ASTNode
 
 
-		erp := interpreter.NewECALRuntimeProvider(os.Args[0], il, util.NewStdOutLogger())
+		erp := interpreter.NewECALRuntimeProvider(osArgs[0], il, util.NewStdOutLogger())
 
 
 		if ast, err = parser.ParseWithRuntime(os.Args[0], il.Files[".ecalsrc-entry"], erp); err == nil {
 		if ast, err = parser.ParseWithRuntime(os.Args[0], il.Files[".ecalsrc-entry"], erp); err == nil {
 			if err = ast.Runtime.Validate(); err == nil {
 			if err = ast.Runtime.Validate(); err == nil {
@@ -334,10 +338,10 @@ func runInterpreter(reader io.ReaderAt, size int64) (interface{}, error) {
 				res, err = ast.Runtime.Eval(vs, make(map[string]interface{}), erp.NewThreadID())
 				res, err = ast.Runtime.Eval(vs, make(map[string]interface{}), erp.NewThreadID())
 
 
 				if err != nil {
 				if err != nil {
-					fmt.Fprintln(os.Stderr, err.Error())
+					fmt.Fprintln(osStderr, err.Error())
 
 
 					if terr, ok := err.(util.TraceableRuntimeError); ok {
 					if terr, ok := err.(util.TraceableRuntimeError); ok {
-						fmt.Fprintln(os.Stderr, fmt.Sprint("  ", strings.Join(terr.GetTraceString(), fmt.Sprint(fmt.Sprintln(), "  "))))
+						fmt.Fprintln(osStderr, fmt.Sprint("  ", strings.Join(terr.GetTraceString(), fmt.Sprint(fmt.Sprintln(), "  "))))
 					}
 					}
 
 
 					err = nil
 					err = nil

+ 278 - 0
cli/tool/pack_test.go

@@ -0,0 +1,278 @@
+/*
+ * ECAL
+ *
+ * Copyright 2020 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the MIT
+ * License, If a copy of the MIT License was not distributed with this
+ * file, You can obtain one at https://opensource.org/licenses/MIT.
+ */
+
+package tool
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"devt.de/krotik/common/errorutil"
+	"devt.de/krotik/common/fileutil"
+	"devt.de/krotik/common/stringutil"
+)
+
+const packTestDir = "packtest"
+
+var testPackOut *bytes.Buffer
+
+var lastReturnCode = 0
+var lastRuntimeError error
+
+func setupPackTestDir() {
+	if res, _ := fileutil.PathExists(packTestDir); res {
+		os.RemoveAll(packTestDir)
+	}
+
+	err := os.Mkdir(packTestDir, 0770)
+	if err != nil {
+		fmt.Print("Could not create test directory:", err.Error())
+		os.Exit(1)
+	}
+
+	err = os.Mkdir(filepath.Join(packTestDir, "sub"), 0770)
+	if err != nil {
+		fmt.Print("Could not create test directory:", err.Error())
+		os.Exit(1)
+	}
+
+	osExit = func(code int) {
+		lastReturnCode = code
+	}
+
+	handleError = func(err error) {
+		lastRuntimeError = err
+	}
+}
+
+func tearDownPackTestDir() {
+	err := os.RemoveAll(packTestDir)
+	if err != nil {
+		fmt.Print("Could not remove test directory:", err.Error())
+	}
+}
+
+func newTestCLIPacker() *CLIPacker {
+	clip := NewCLIPacker()
+
+	testPackOut = &bytes.Buffer{}
+	clip.LogOut = testPackOut
+
+	return clip
+}
+
+func TestPackParseArgs(t *testing.T) {
+	setupPackTestDir()
+	defer tearDownPackTestDir()
+
+	clip := newTestCLIPacker()
+
+	packTestSrcBin := filepath.Join(packTestDir, "source.bin")
+	out := bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{packTestSrcBin, "foo", "-help"}
+
+	if ok := clip.ParseArgs(); !ok {
+		t.Error("Asking for help should ask to finish the program")
+		return
+	}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{packTestSrcBin, "foo", "-help"}
+
+	if err := clip.Pack(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if !strings.Contains(out.String(), "Root directory for ECAL interpreter") {
+		t.Error("Unexpected output:", out.String())
+		return
+	}
+
+	out = bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{packTestSrcBin, "foo", "myentryfile"}
+
+	if ok := clip.ParseArgs(); ok {
+		t.Error("Only asking for help should finish the program")
+		return
+	}
+
+	if ok := clip.ParseArgs(); ok {
+		t.Error("Only asking for help should finish the program")
+		return
+	}
+
+	if clip.EntryFile != "myentryfile" {
+		t.Error("Unexpected output:", clip.EntryFile)
+		return
+	}
+}
+
+func TestPackPacking(t *testing.T) {
+	setupPackTestDir()
+	defer tearDownPackTestDir()
+
+	clip := newTestCLIPacker()
+
+	packTestSrcBin := filepath.Join(packTestDir, "source.bin")
+	packTestEntry := filepath.Join(packTestDir, "myentry.ecal")
+	packAnotherFile := filepath.Join(packTestDir, "sub", "anotherfile.ecal")
+	packTestDestBin := filepath.Join(packTestDir, "dest.exe")
+
+	b1 = 5
+	b2 = len(packmarker) + 11
+
+	err := ioutil.WriteFile(packTestSrcBin, []byte("mybinaryfilecontent#somemorecontent"+
+		stringutil.GenerateRollingString("123", 30)), 0777)
+	errorutil.AssertOk(err)
+
+	err = ioutil.WriteFile(packTestEntry, []byte("myvar := 1; 5"), 0777)
+	errorutil.AssertOk(err)
+
+	err = ioutil.WriteFile(packAnotherFile, []byte("func f() { raise(123) };f()"), 0777)
+	errorutil.AssertOk(err)
+
+	out := bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	// Write a binary with return code
+
+	osArgs = []string{packTestSrcBin, "foo", "-dir", packTestDir, "-target",
+		packTestDestBin, packTestEntry}
+
+	// Simulate that whitespaces are added around the pack marker
+
+	oldpackmarker := packmarker
+	packmarker = fmt.Sprintf("\n\n\n%v\n\n\n", packmarker)
+
+	if err := clip.Pack(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	packmarker = oldpackmarker
+
+	if !strings.Contains(testPackOut.String(), "bytes for intro") {
+		t.Error("Unexpected output:", testPackOut.String())
+		return
+	}
+
+	// Write a binary with which errors
+
+	clip = newTestCLIPacker()
+
+	out = bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{packTestSrcBin, "foo", "-dir", packTestDir, "-target",
+		packTestDestBin + ".error", packAnotherFile}
+
+	if err := clip.Pack(); err != nil {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if !strings.Contains(testPackOut.String(), "bytes for intro") {
+		t.Error("Unexpected output:", testPackOut.String())
+		return
+	}
+
+	// Write also a corrupted binary
+
+	err = ioutil.WriteFile(packTestDestBin+".corrupted", []byte(
+		"mybinaryfilecontent#somemorecontent"+
+			stringutil.GenerateRollingString("123", 30)+
+			"\n"+
+			packmarker+
+			"\n"+
+			stringutil.GenerateRollingString("123", 30)), 0777)
+
+	errorutil.AssertOk(err)
+
+	testRunningPackedBinary(t)
+}
+
+func testRunningPackedBinary(t *testing.T) {
+	packTestDestBin := filepath.Join(packTestDir, "dest") // Suffix .exe should be appended
+
+	out := bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{packTestDestBin + ".exe.corrupted"}
+
+	RunPackedBinary()
+
+	if lastRuntimeError == nil || lastRuntimeError.Error() != "zip: not a valid zip file" {
+		t.Error("Unexpected result:", lastRuntimeError)
+		return
+	}
+
+	out = bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+
+	osArgs = []string{packTestDestBin}
+
+	RunPackedBinary()
+
+	if lastRuntimeError != nil {
+		t.Error("Unexpected result:", lastRuntimeError)
+		return
+	}
+
+	if lastReturnCode != 5 {
+		t.Error("Unexpected result:", lastReturnCode)
+		return
+	}
+
+	out = bytes.Buffer{}
+
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) // Reset CLI parsing
+	flag.CommandLine.SetOutput(&out)
+	osStderr = &out
+
+	osArgs = []string{packTestDestBin + ".exe.error"}
+
+	RunPackedBinary()
+
+	if lastRuntimeError != nil {
+		t.Error("Unexpected result:", lastRuntimeError)
+		return
+	}
+
+	if !strings.HasPrefix(out.String(), "ECAL error in packtest/dest.exe.error: 123 () (Line:1 Pos:12)") ||
+		!strings.Contains(out.String(), "raise(123)") {
+		t.Error("Unexpected result:", out.String())
+		return
+	}
+}

+ 1 - 1
ecal-support/README.md

@@ -16,7 +16,7 @@ The extension supports the following features:
 
 
 ## Install the extension
 ## Install the extension
 
 
-The extention can be installed using a precompiled VSIX file which can be downloaded from here:
+The extension can be installed using a precompiled VSIX file which can be downloaded from here:
 
 
 https://devt.de/krotik/ecal/releases
 https://devt.de/krotik/ecal/releases
 
 

+ 1 - 1
ecal-support/src/ecalDebugAdapter.ts

@@ -28,7 +28,7 @@ import * as path from "path";
 
 
 /**
 /**
  * ECALDebugArguments are the arguments which VSCode can pass to the debug adapter.
  * ECALDebugArguments are the arguments which VSCode can pass to the debug adapter.
- * This defines the parameter which a VSCode instance using the ECAL extention can pass to the
+ * This defines the parameter which a VSCode instance using the ECAL extension can pass to the
  * debug adapter from a lauch configuration ('.vscode/launch.json') in a project folder.
  * debug adapter from a lauch configuration ('.vscode/launch.json') in a project folder.
  */
  */
 interface ECALDebugArguments extends DebugProtocol.LaunchRequestArguments {
 interface ECALDebugArguments extends DebugProtocol.LaunchRequestArguments {

+ 1 - 1
ecal-support/src/extension.ts

@@ -18,7 +18,7 @@ class InlineDebugAdapterFactory
   createDebugAdapterDescriptor(
   createDebugAdapterDescriptor(
     _session: vscode.DebugSession
     _session: vscode.DebugSession
   ): ProviderResult<vscode.DebugAdapterDescriptor> {
   ): ProviderResult<vscode.DebugAdapterDescriptor> {
-    // Declare the ECALDebugSession as an DebugAdapterInlineImplementation so extention and adapter can
+    // Declare the ECALDebugSession as an DebugAdapterInlineImplementation so extension and adapter can
     // run in-process (making it possible to easily debug the adapter)
     // run in-process (making it possible to easily debug the adapter)
     return new vscode.DebugAdapterInlineImplementation(new ECALDebugSession());
     return new vscode.DebugAdapterInlineImplementation(new ECALDebugSession());
   }
   }

+ 18 - 13
engine/processor_test.go

@@ -142,7 +142,7 @@ func TestProcessorSimpleCascade(t *testing.T) {
 	rootm.SetFinishHandler(func(p Processor) {
 	rootm.SetFinishHandler(func(p Processor) {
 		log.WriteString("finished!")
 		log.WriteString("finished!")
 	})
 	})
-	proc.AddEvent(e, rootm)
+	proc.AddEventAndWait(e, rootm)
 
 
 	if err := proc.AddRule(rule3); err.Error() != "Cannot add rule if the processor has not stopped" {
 	if err := proc.AddRule(rule3); err.Error() != "Cannot add rule if the processor has not stopped" {
 		t.Error("Unexpected error:", err)
 		t.Error("Unexpected error:", err)
@@ -187,7 +187,7 @@ finished!` {
 
 
 	// Push a root event
 	// Push a root event
 
 
-	proc.AddEvent(&Event{
+	proc.AddEventAndWait(&Event{
 		"InitialEvent",
 		"InitialEvent",
 		[]string{"core", "main", "event1"},
 		[]string{"core", "main", "event1"},
 		nil,
 		nil,
@@ -700,7 +700,7 @@ func TestProcessorSimpleErrorHandling(t *testing.T) {
 
 
 	// Push a root event
 	// Push a root event
 
 
-	mon, err := proc.AddEvent(&Event{
+	mon, err := proc.AddEventAndWait(&Event{
 		"InitialEvent",
 		"InitialEvent",
 		[]string{"core", "main", "event1"},
 		[]string{"core", "main", "event1"},
 		map[interface{}]interface{}{"name": "foo", "test": "123"},
 		map[interface{}]interface{}{"name": "foo", "test": "123"},
@@ -720,7 +720,7 @@ func TestProcessorSimpleErrorHandling(t *testing.T) {
 	}
 	}
 
 
 	_, err = proc.AddEvent(&Event{}, nil)
 	_, err = proc.AddEvent(&Event{}, nil)
-	if err.Error() != "Cannot add event if the processor is not running" {
+	if err.Error() != "Cannot add event if the processor is stopping or not running" {
 		t.Error("Unexpected error", err)
 		t.Error("Unexpected error", err)
 		return
 		return
 	}
 	}
@@ -749,25 +749,30 @@ InitialEvent -> event2 -> event3 -> TestRule3 : testerror2]` {
 		return
 		return
 	}
 	}
 
 
+	testProcessorAdvancedErrorHandling(t, proc, &recordedErrors)
+}
+
+func testProcessorAdvancedErrorHandling(t *testing.T, proc Processor, recordedErrorsPtr *int) {
+
 	// Second test will fail on the first failed rule in an event trigger sequence
 	// Second test will fail on the first failed rule in an event trigger sequence
 
 
 	proc.SetFailOnFirstErrorInTriggerSequence(true)
 	proc.SetFailOnFirstErrorInTriggerSequence(true)
 
 
 	proc.Start()
 	proc.Start()
 
 
-	mon, err = proc.AddEvent(&Event{
+	mon, err := proc.AddEventAndWait(&Event{
 		"InitialEvent",
 		"InitialEvent",
 		[]string{"core", "main", "event1"},
 		[]string{"core", "main", "event1"},
 		map[interface{}]interface{}{"name": "foo", "test": "123"},
 		map[interface{}]interface{}{"name": "foo", "test": "123"},
 	}, nil)
 	}, nil)
-	rmon, ok = mon.(*RootMonitor)
+	rmon, ok := mon.(*RootMonitor)
 	if !ok {
 	if !ok {
 		t.Error("Root monitor expected:", mon, err)
 		t.Error("Root monitor expected:", mon, err)
 		return
 		return
 	}
 	}
 	proc.Finish()
 	proc.Finish()
 
 
-	errs = rmon.AllErrors()
+	errs := rmon.AllErrors()
 
 
 	if len(errs) != 2 {
 	if len(errs) != 2 {
 		t.Error("Unexpected number of errors:", len(errs))
 		t.Error("Unexpected number of errors:", len(errs))
@@ -781,8 +786,8 @@ InitialEvent -> event2 -> event3 -> TestRule3 : testerror2]` {
 		return
 		return
 	}
 	}
 
 
-	if recordedErrors != 1 {
-		t.Error("Unexpected number of recorded errors:", recordedErrors)
+	if *recordedErrorsPtr != 1 {
+		t.Error("Unexpected number of recorded errors:", *recordedErrorsPtr)
 		return
 		return
 	}
 	}
 
 
@@ -797,8 +802,8 @@ InitialEvent -> event2 -> event3 -> TestRule3 : testerror2]` {
 		map[interface{}]interface{}{"name": "foo", "test": "123"},
 		map[interface{}]interface{}{"name": "foo", "test": "123"},
 	}, nil)
 	}, nil)
 
 
-	if mon != nil {
-		t.Error("Nothing should have triggered")
+	if mon != nil || err != nil {
+		t.Error("Nothing should have triggered: ", err)
 		return
 		return
 	}
 	}
 
 
@@ -833,8 +838,8 @@ InitialEvent -> event2 -> event3 -> TestRule3 : testerror2]` {
 		return
 		return
 	}
 	}
 
 
-	if recordedErrors != 3 {
-		t.Error("Unexpected number of recorded errors:", recordedErrors)
+	if *recordedErrorsPtr != 3 {
+		t.Error("Unexpected number of recorded errors:", *recordedErrorsPtr)
 		return
 		return
 	}
 	}
 
 

+ 71 - 62
engine/pubsub/eventpump_test.go

@@ -18,49 +18,46 @@ import (
 	"testing"
 	"testing"
 )
 )
 
 
-func TestEventPump(t *testing.T) {
-	var res []string
-
-	source1 := &bytes.Buffer{}
-	source2 := errors.New("TEST")
+var res []string
+var source1 = &bytes.Buffer{}
+var errSource2 error = fmt.Errorf("TEST")
+var ep = NewEventPump()
 
 
-	ep := NewEventPump()
+func addObservers2(t *testing.T) {
 
 
-	// Add observer 1
+	// Add observer 4
 
 
-	ep.AddObserver("event1", source1, func(event string, eventSource interface{}) {
+	ep.AddObserver("", source1, func(event string, eventSource interface{}) {
 		if eventSource != source1 {
 		if eventSource != source1 {
 			t.Error("Unexpected event source:", eventSource)
 			t.Error("Unexpected event source:", eventSource)
 			return
 			return
 		}
 		}
-		res = append(res, "1")
+		res = append(res, "4")
 		sort.Strings(res)
 		sort.Strings(res)
-
 	})
 	})
 
 
-	// Add observer 2
+	// Add observer 5
 
 
-	ep.AddObserver("event2", source2, func(event string, eventSource interface{}) {
-		if eventSource != source2 {
-			t.Error("Unexpected event source:", eventSource)
-			return
-		}
-		res = append(res, "2")
+	ep.AddObserver("", nil, func(event string, eventSource interface{}) {
+		res = append(res, "5")
 		sort.Strings(res)
 		sort.Strings(res)
-
 	})
 	})
 
 
-	// Add observer 3
+	// Add observer 6
 
 
-	ep.AddObserver("event2", source2, func(event string, eventSource interface{}) {
-		if eventSource != source2 {
+	ep.AddObserver("", errSource2, func(event string, eventSource interface{}) {
+		if eventSource != errSource2 {
 			t.Error("Unexpected event source:", eventSource)
 			t.Error("Unexpected event source:", eventSource)
 			return
 			return
 		}
 		}
-		res = append(res, "3")
+		res = append(res, "6")
 		sort.Strings(res)
 		sort.Strings(res)
-
 	})
 	})
+}
+
+func TestEventPump(t *testing.T) {
+
+	addObservers1(t)
 
 
 	// Run the tests
 	// Run the tests
 
 
@@ -75,7 +72,7 @@ func TestEventPump(t *testing.T) {
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event2", source2)
+	ep.PostEvent("event2", errSource2)
 
 
 	if fmt.Sprint(res) != "[2 3]" {
 	if fmt.Sprint(res) != "[2 3]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
@@ -84,45 +81,18 @@ func TestEventPump(t *testing.T) {
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event1", source2)
+	ep.PostEvent("event1", errSource2)
 
 
 	if fmt.Sprint(res) != "[]" {
 	if fmt.Sprint(res) != "[]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
 		return
 		return
 	}
 	}
 
 
-	// Add observer 4
-
-	ep.AddObserver("", source1, func(event string, eventSource interface{}) {
-		if eventSource != source1 {
-			t.Error("Unexpected event source:", eventSource)
-			return
-		}
-		res = append(res, "4")
-		sort.Strings(res)
-	})
-
-	// Add observer 5
-
-	ep.AddObserver("", nil, func(event string, eventSource interface{}) {
-		res = append(res, "5")
-		sort.Strings(res)
-	})
-
-	// Add observer 6
-
-	ep.AddObserver("", source2, func(event string, eventSource interface{}) {
-		if eventSource != source2 {
-			t.Error("Unexpected event source:", eventSource)
-			return
-		}
-		res = append(res, "6")
-		sort.Strings(res)
-	})
+	addObservers2(t)
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event1", source2)
+	ep.PostEvent("event1", errSource2)
 
 
 	if fmt.Sprint(res) != "[5 6]" {
 	if fmt.Sprint(res) != "[5 6]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
@@ -131,7 +101,7 @@ func TestEventPump(t *testing.T) {
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event3", source2)
+	ep.PostEvent("event3", errSource2)
 
 
 	if fmt.Sprint(res) != "[5 6]" {
 	if fmt.Sprint(res) != "[5 6]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
@@ -160,28 +130,28 @@ func TestEventPump(t *testing.T) {
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event2", source2)
+	ep.PostEvent("event2", errSource2)
 
 
 	if fmt.Sprint(res) != "[2 3 5 6]" {
 	if fmt.Sprint(res) != "[2 3 5 6]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
 		return
 		return
 	}
 	}
-	ep.RemoveObservers("event2", source2)
+	ep.RemoveObservers("event2", errSource2)
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event2", source2)
+	ep.PostEvent("event2", errSource2)
 
 
 	if fmt.Sprint(res) != "[5 6]" {
 	if fmt.Sprint(res) != "[5 6]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
 		return
 		return
 	}
 	}
 
 
-	ep.RemoveObservers("", source2) // Remove all handlers specific to source 2
+	ep.RemoveObservers("", errSource2) // Remove all handlers specific to source 2
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event2", source2)
+	ep.PostEvent("event2", errSource2)
 
 
 	if fmt.Sprint(res) != "[5]" {
 	if fmt.Sprint(res) != "[5]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
@@ -199,7 +169,7 @@ func TestEventPump(t *testing.T) {
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event2", source2)
+	ep.PostEvent("event2", errSource2)
 
 
 	if fmt.Sprint(res) != "[5]" {
 	if fmt.Sprint(res) != "[5]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
@@ -210,7 +180,7 @@ func TestEventPump(t *testing.T) {
 
 
 	res = make([]string, 0) // Reset res
 	res = make([]string, 0) // Reset res
 
 
-	ep.PostEvent("event2", source2)
+	ep.PostEvent("event2", errSource2)
 
 
 	if fmt.Sprint(res) != "[]" {
 	if fmt.Sprint(res) != "[]" {
 		t.Error("Unexpected result:", res)
 		t.Error("Unexpected result:", res)
@@ -227,6 +197,45 @@ func TestEventPump(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func addObservers1(t *testing.T) {
+
+	// Add observer 1
+
+	ep.AddObserver("event1", source1, func(event string, eventSource interface{}) {
+		if eventSource != source1 {
+			t.Error("Unexpected event source:", eventSource)
+			return
+		}
+		res = append(res, "1")
+		sort.Strings(res)
+
+	})
+
+	// Add observer 2
+
+	ep.AddObserver("event2", errSource2, func(event string, eventSource interface{}) {
+		if eventSource != errSource2 {
+			t.Error("Unexpected event source:", eventSource)
+			return
+		}
+		res = append(res, "2")
+		sort.Strings(res)
+
+	})
+
+	// Add observer 3
+
+	ep.AddObserver("event2", errSource2, func(event string, eventSource interface{}) {
+		if eventSource != errSource2 {
+			t.Error("Unexpected event source:", eventSource)
+			return
+		}
+		res = append(res, "3")
+		sort.Strings(res)
+
+	})
+}
+
 func TestWrongPostEvent(t *testing.T) {
 func TestWrongPostEvent(t *testing.T) {
 	defer func() {
 	defer func() {
 		if r := recover(); r == nil {
 		if r := recover(); r == nil {

+ 21 - 22
engine/taskqueue_test.go

@@ -33,16 +33,10 @@ func TestTaskQueue(t *testing.T) {
 	// Create different root monitors with different IDs
 	// Create different root monitors with different IDs
 
 
 	m1 := newRootMonitor(nil, NewRuleScope(map[string]bool{"": true}), proc.(*eventProcessor).messageQueue)
 	m1 := newRootMonitor(nil, NewRuleScope(map[string]bool{"": true}), proc.(*eventProcessor).messageQueue)
-	m2 := newRootMonitor(nil, NewRuleScope(map[string]bool{"": true}), proc.(*eventProcessor).messageQueue)
-	m3 := newRootMonitor(nil, NewRuleScope(map[string]bool{"": true}), proc.(*eventProcessor).messageQueue)
 
 
 	// Create now different tasks which come from the different monitors
 	// Create now different tasks which come from the different monitors
 
 
 	t1 := &Task{proc, m1, event}
 	t1 := &Task{proc, m1, event}
-	t2 := &Task{proc, m2, event}
-	t3 := &Task{proc, m3, event}
-	t4 := &Task{proc, m2.NewChildMonitor(5), event}
-	t5 := &Task{proc, m2.NewChildMonitor(10), event}
 
 
 	tq := NewTaskQueue(proc.(*eventProcessor).messageQueue)
 	tq := NewTaskQueue(proc.(*eventProcessor).messageQueue)
 
 
@@ -70,6 +64,19 @@ func TestTaskQueue(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
+	testTaskQueuePushPop(t, tq, proc, event, t1)
+}
+
+func testTaskQueuePushPop(t *testing.T, tq *TaskQueue, proc Processor, event *Event, t1 *Task) {
+
+	m2 := newRootMonitor(nil, NewRuleScope(map[string]bool{"": true}), proc.(*eventProcessor).messageQueue)
+	m3 := newRootMonitor(nil, NewRuleScope(map[string]bool{"": true}), proc.(*eventProcessor).messageQueue)
+
+	t2 := &Task{proc, m2, event}
+	t3 := &Task{proc, m3, event}
+	t4 := &Task{proc, m2.NewChildMonitor(5), event}
+	t5 := &Task{proc, m2.NewChildMonitor(10), event}
+
 	tq.Push(t1)
 	tq.Push(t1)
 	tq.Push(t2)
 	tq.Push(t2)
 	tq.Push(t3)
 	tq.Push(t3)
@@ -101,35 +108,23 @@ func TestTaskQueue(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
-	if e := tq.Pop(); e != t1 && e != t2 && e != t3 && e != t4 && e != t5 {
-		t.Error("Unexpected event:", e)
-		return
-	}
+	tq.Pop()
 
 
 	if res := len(tq.queues); res != 3 && res != 2 {
 	if res := len(tq.queues); res != 3 && res != 2 {
 		t.Error("Unexpected size:", res)
 		t.Error("Unexpected size:", res)
 		return
 		return
 	}
 	}
 
 
-	if e := tq.Pop(); e != t1 && e != t2 && e != t3 && e != t4 && e != t5 {
-		t.Error("Unexpected event:", e)
-		return
-	}
+	tq.Pop()
 
 
 	if s := tq.Size(); s != 2 {
 	if s := tq.Size(); s != 2 {
 		t.Error("Unexpected result:", s)
 		t.Error("Unexpected result:", s)
 		return
 		return
 	}
 	}
 
 
-	if e := tq.Pop(); e != t1 && e != t2 && e != t3 && e != t4 && e != t5 {
-		t.Error("Unexpected event:", e)
-		return
-	}
+	tq.Pop()
 
 
-	if e := tq.Pop(); e != t1 && e != t2 && e != t3 && e != t4 && e != t5 {
-		t.Error("Unexpected event:", e)
-		return
-	}
+	tq.Pop()
 
 
 	if s := tq.Size(); s != 0 {
 	if s := tq.Size(); s != 0 {
 		t.Error("Unexpected result:", s)
 		t.Error("Unexpected result:", s)
@@ -141,6 +136,10 @@ func TestTaskQueue(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
+	testTaskQueueMisc(t, tq, t5)
+}
+
+func testTaskQueueMisc(t *testing.T, tq *TaskQueue, t5 *Task) {
 	tq.Push(t5)
 	tq.Push(t5)
 
 
 	if fmt.Sprint(tq.queues) != "map[2:[ Task: RumbleProcessor 1 (workers:1) Monitor 5 (parent: Monitor 2 (parent: <nil> priority: 0 activated: false finished: false) priority: 10 activated: false finished: false) Event: DummyEvent main {} (10) ]]" {
 	if fmt.Sprint(tq.queues) != "map[2:[ Task: RumbleProcessor 1 (workers:1) Monitor 5 (parent: Monitor 2 (parent: <nil> priority: 0 activated: false finished: false) priority: 10 activated: false finished: false) Event: DummyEvent main {} (10) ]]" {

+ 8 - 2
examples/embedding/main.go

@@ -83,11 +83,11 @@ mystuff.add(compute(5), 1)
 
 
 	// Each thread which evaluates the Runtime of an AST should get a unique thread ID
 	// Each thread which evaluates the Runtime of an AST should get a unique thread ID
 
 
-	var threadId uint64 = 1
+	var threadID uint64 = 1
 
 
 	// Evaluate the Runtime of an AST with a variable scope
 	// Evaluate the Runtime of an AST with a variable scope
 
 
-	res, err := ast.Runtime.Eval(vs, make(map[string]interface{}), threadId)
+	res, err := ast.Runtime.Eval(vs, make(map[string]interface{}), threadID)
 	if err != nil {
 	if err != nil {
 		log.Fatal(err)
 		log.Fatal(err)
 	}
 	}
@@ -123,6 +123,9 @@ AddFunc is a simple add function which calculates the sum of two numbers.
 type AddFunc struct {
 type AddFunc struct {
 }
 }
 
 
+/*
+Run executes the add function
+*/
 func (f *AddFunc) Run(instanceID string, vs parser.Scope, is map[string]interface{}, tid uint64, args []interface{}) (interface{}, error) {
 func (f *AddFunc) Run(instanceID string, vs parser.Scope, is map[string]interface{}, tid uint64, args []interface{}) (interface{}, error) {
 
 
 	// This should have some proper error checking
 	// This should have some proper error checking
@@ -133,6 +136,9 @@ func (f *AddFunc) Run(instanceID string, vs parser.Scope, is map[string]interfac
 	return args[0].(float64) + args[1].(float64), nil
 	return args[0].(float64) + args[1].(float64), nil
 }
 }
 
 
+/*
+DocString returns the doc string for the add function.
+*/
 func (f *AddFunc) DocString() (string, error) {
 func (f *AddFunc) DocString() (string, error) {
 	return "Sum up two numbers", nil
 	return "Sum up two numbers", nil
 }
 }

+ 18 - 18
interpreter/debug.go

@@ -90,7 +90,7 @@ func newInterrogationState(node *parser.ASTNode, vs parser.Scope) *interrogation
 }
 }
 
 
 /*
 /*
-NewDebugger returns a new debugger object.
+NewECALDebugger returns a new debugger object.
 */
 */
 func NewECALDebugger(globalVS parser.Scope) util.ECALDebugger {
 func NewECALDebugger(globalVS parser.Scope) util.ECALDebugger {
 	return &ecalDebugger{
 	return &ecalDebugger{
@@ -135,7 +135,7 @@ func (ed *ecalDebugger) HandleInput(input string) (interface{}, error) {
 /*
 /*
 StopThreads will continue all suspended threads and set them to be killed.
 StopThreads will continue all suspended threads and set them to be killed.
 Returns true if a waiting thread was resumed. Can wait for threads to end
 Returns true if a waiting thread was resumed. Can wait for threads to end
-by ensuring that for at least d time no state change occured.
+by ensuring that for at least d time no state change occurred.
 */
 */
 func (ed *ecalDebugger) StopThreads(d time.Duration) bool {
 func (ed *ecalDebugger) StopThreads(d time.Duration) bool {
 	var ret = false
 	var ret = false
@@ -445,17 +445,17 @@ func (ed *ecalDebugger) RemoveBreakPoint(source string, line int) {
 ExtractValue copies a value from a suspended thread into the
 ExtractValue copies a value from a suspended thread into the
 global variable scope.
 global variable scope.
 */
 */
-func (ed *ecalDebugger) ExtractValue(threadId uint64, varName string, destVarName string) error {
+func (ed *ecalDebugger) ExtractValue(threadID uint64, varName string, destVarName string) error {
 	if ed.globalScope == nil {
 	if ed.globalScope == nil {
 		return fmt.Errorf("Cannot access global scope")
 		return fmt.Errorf("Cannot access global scope")
 	}
 	}
 
 
-	err := fmt.Errorf("Cannot find suspended thread %v", threadId)
+	err := fmt.Errorf("Cannot find suspended thread %v", threadID)
 
 
 	ed.lock.Lock()
 	ed.lock.Lock()
 	defer ed.lock.Unlock()
 	defer ed.lock.Unlock()
 
 
-	is, ok := ed.interrogationStates[threadId]
+	is, ok := ed.interrogationStates[threadID]
 
 
 	if ok && !is.running {
 	if ok && !is.running {
 		var val interface{}
 		var val interface{}
@@ -475,17 +475,17 @@ func (ed *ecalDebugger) ExtractValue(threadId uint64, varName string, destVarNam
 InjectValue copies a value from an expression (using the global variable scope) into
 InjectValue copies a value from an expression (using the global variable scope) into
 a suspended thread.
 a suspended thread.
 */
 */
-func (ed *ecalDebugger) InjectValue(threadId uint64, varName string, expression string) error {
+func (ed *ecalDebugger) InjectValue(threadID uint64, varName string, expression string) error {
 	if ed.globalScope == nil {
 	if ed.globalScope == nil {
 		return fmt.Errorf("Cannot access global scope")
 		return fmt.Errorf("Cannot access global scope")
 	}
 	}
 
 
-	err := fmt.Errorf("Cannot find suspended thread %v", threadId)
+	err := fmt.Errorf("Cannot find suspended thread %v", threadID)
 
 
 	ed.lock.Lock()
 	ed.lock.Lock()
 	defer ed.lock.Unlock()
 	defer ed.lock.Unlock()
 
 
-	is, ok := ed.interrogationStates[threadId]
+	is, ok := ed.interrogationStates[threadID]
 
 
 	if ok && !is.running {
 	if ok && !is.running {
 		var ast *parser.ASTNode
 		var ast *parser.ASTNode
@@ -515,11 +515,11 @@ func (ed *ecalDebugger) InjectValue(threadId uint64, varName string, expression
 /*
 /*
 Continue will continue a suspended thread.
 Continue will continue a suspended thread.
 */
 */
-func (ed *ecalDebugger) Continue(threadId uint64, contType util.ContType) {
+func (ed *ecalDebugger) Continue(threadID uint64, contType util.ContType) {
 	ed.lock.RLock()
 	ed.lock.RLock()
 	defer ed.lock.RUnlock()
 	defer ed.lock.RUnlock()
 
 
-	if is, ok := ed.interrogationStates[threadId]; ok && !is.running {
+	if is, ok := ed.interrogationStates[threadID]; ok && !is.running {
 
 
 		switch contType {
 		switch contType {
 		case util.Resume:
 		case util.Resume:
@@ -530,7 +530,7 @@ func (ed *ecalDebugger) Continue(threadId uint64, contType util.ContType) {
 			is.cmd = StepOver
 			is.cmd = StepOver
 		case util.StepOut:
 		case util.StepOut:
 			is.cmd = StepOut
 			is.cmd = StepOut
-			stack := ed.callStacks[threadId]
+			stack := ed.callStacks[threadID]
 			is.stepOutStack = stack[:len(stack)-1]
 			is.stepOutStack = stack[:len(stack)-1]
 		}
 		}
 
 
@@ -581,17 +581,17 @@ func (ed *ecalDebugger) Status() interface{} {
 }
 }
 
 
 /*
 /*
-Describe decribes a thread currently observed by the debugger.
+Describe describes a thread currently observed by the debugger.
 */
 */
-func (ed *ecalDebugger) Describe(threadId uint64) interface{} {
+func (ed *ecalDebugger) Describe(threadID uint64) interface{} {
 	ed.lock.RLock()
 	ed.lock.RLock()
 	defer ed.lock.RUnlock()
 	defer ed.lock.RUnlock()
 
 
 	var res map[string]interface{}
 	var res map[string]interface{}
 
 
-	threadCallStack, ok1 := ed.callStacks[threadId]
+	threadCallStack, ok1 := ed.callStacks[threadID]
 
 
-	if is, ok2 := ed.interrogationStates[threadId]; ok1 && ok2 {
+	if is, ok2 := ed.interrogationStates[threadID]; ok1 && ok2 {
 		callStackNode := make([]map[string]interface{}, 0)
 		callStackNode := make([]map[string]interface{}, 0)
 
 
 		for _, sn := range threadCallStack {
 		for _, sn := range threadCallStack {
@@ -603,8 +603,8 @@ func (ed *ecalDebugger) Describe(threadId uint64) interface{} {
 			"error":                     is.err,
 			"error":                     is.err,
 			"callStack":                 ed.prettyPrintCallStack(threadCallStack),
 			"callStack":                 ed.prettyPrintCallStack(threadCallStack),
 			"callStackNode":             callStackNode,
 			"callStackNode":             callStackNode,
-			"callStackVsSnapshot":       ed.callStackVsSnapshots[threadId],
-			"callStackVsSnapshotGlobal": ed.callStackGlobalVsSnapshots[threadId],
+			"callStackVsSnapshot":       ed.callStackVsSnapshots[threadID],
+			"callStackVsSnapshotGlobal": ed.callStackGlobalVsSnapshots[threadID],
 		}
 		}
 
 
 		if !is.running {
 		if !is.running {
@@ -672,7 +672,7 @@ func (ed *ecalDebugger) MergeMaps(maps ...map[string]interface{}) map[string]int
 }
 }
 
 
 /*
 /*
-Describe decribes a thread currently observed by the debugger.
+Describe describes a thread currently observed by the debugger.
 */
 */
 func (ed *ecalDebugger) prettyPrintCallStack(threadCallStack []*parser.ASTNode) []string {
 func (ed *ecalDebugger) prettyPrintCallStack(threadCallStack []*parser.ASTNode) []string {
 	cs := []string{}
 	cs := []string{}

+ 10 - 10
interpreter/debug_cmd.go

@@ -23,7 +23,7 @@ import (
 )
 )
 
 
 /*
 /*
-InbuildDebugCommandsMap contains the mapping of inbuild debug commands.
+DebugCommandsMap contains the mapping of inbuild debug commands.
 */
 */
 var DebugCommandsMap = map[string]util.DebugCommand{
 var DebugCommandsMap = map[string]util.DebugCommand{
 	"breakonstart": &breakOnStartCommand{&inbuildDebugCommand{}},
 	"breakonstart": &breakOnStartCommand{&inbuildDebugCommand{}},
@@ -231,7 +231,7 @@ func (c *contCommand) Run(debugger util.ECALDebugger, args []string) (interface{
 		return nil, fmt.Errorf("Need a thread ID and a command Resume, StepIn, StepOver or StepOut")
 		return nil, fmt.Errorf("Need a thread ID and a command Resume, StepIn, StepOver or StepOut")
 	}
 	}
 
 
-	threadId, err := c.AssertNumParam(1, args[0])
+	threadID, err := c.AssertNumParam(1, args[0])
 
 
 	if err == nil {
 	if err == nil {
 		cmdString := strings.ToLower(args[1])
 		cmdString := strings.ToLower(args[1])
@@ -248,7 +248,7 @@ func (c *contCommand) Run(debugger util.ECALDebugger, args []string) (interface{
 			return nil, fmt.Errorf("Invalid command %v - must be resume, stepin, stepover or stepout", cmdString)
 			return nil, fmt.Errorf("Invalid command %v - must be resume, stepin, stepover or stepout", cmdString)
 		}
 		}
 
 
-		debugger.Continue(threadId, cmd)
+		debugger.Continue(threadID, cmd)
 	}
 	}
 
 
 	return nil, err
 	return nil, err
@@ -258,7 +258,7 @@ func (c *contCommand) Run(debugger util.ECALDebugger, args []string) (interface{
 DocString returns a descriptive text about this command.
 DocString returns a descriptive text about this command.
 */
 */
 func (c *contCommand) DocString() string {
 func (c *contCommand) DocString() string {
-	return "Continues a suspended thread. Specify <threadId> <Resume | StepIn | StepOver | StepOut>"
+	return "Continues a suspended thread. Specify <threadID> <Resume | StepIn | StepOver | StepOut>"
 }
 }
 
 
 // describe
 // describe
@@ -282,11 +282,11 @@ func (c *describeCommand) Run(debugger util.ECALDebugger, args []string) (interf
 		return nil, fmt.Errorf("Need a thread ID")
 		return nil, fmt.Errorf("Need a thread ID")
 	}
 	}
 
 
-	threadId, err := c.AssertNumParam(1, args[0])
+	threadID, err := c.AssertNumParam(1, args[0])
 
 
 	if err == nil {
 	if err == nil {
 
 
-		res = debugger.Describe(threadId)
+		res = debugger.Describe(threadID)
 	}
 	}
 
 
 	return res, err
 	return res, err
@@ -344,7 +344,7 @@ func (c *extractCommand) Run(debugger util.ECALDebugger, args []string) (interfa
 		return nil, fmt.Errorf("Need a thread ID, a variable name and a destination variable name")
 		return nil, fmt.Errorf("Need a thread ID, a variable name and a destination variable name")
 	}
 	}
 
 
-	threadId, err := c.AssertNumParam(1, args[0])
+	threadID, err := c.AssertNumParam(1, args[0])
 
 
 	if err == nil {
 	if err == nil {
 		if !parser.NamePattern.MatchString(args[1]) || !parser.NamePattern.MatchString(args[2]) {
 		if !parser.NamePattern.MatchString(args[1]) || !parser.NamePattern.MatchString(args[2]) {
@@ -352,7 +352,7 @@ func (c *extractCommand) Run(debugger util.ECALDebugger, args []string) (interfa
 		}
 		}
 
 
 		if err == nil {
 		if err == nil {
-			err = debugger.ExtractValue(threadId, args[1], args[2])
+			err = debugger.ExtractValue(threadID, args[1], args[2])
 		}
 		}
 	}
 	}
 
 
@@ -386,13 +386,13 @@ func (c *injectCommand) Run(debugger util.ECALDebugger, args []string) (interfac
 		return nil, fmt.Errorf("Need a thread ID, a variable name and an expression")
 		return nil, fmt.Errorf("Need a thread ID, a variable name and an expression")
 	}
 	}
 
 
-	threadId, err := c.AssertNumParam(1, args[0])
+	threadID, err := c.AssertNumParam(1, args[0])
 
 
 	if err == nil {
 	if err == nil {
 		varName := args[1]
 		varName := args[1]
 		expression := strings.Join(args[2:], " ")
 		expression := strings.Join(args[2:], " ")
 
 
-		err = debugger.InjectValue(threadId, varName, expression)
+		err = debugger.InjectValue(threadID, varName, expression)
 	}
 	}
 
 
 	return nil, err
 	return nil, err

+ 86 - 122
interpreter/debug_test.go

@@ -33,18 +33,12 @@ func TestSimpleDebugging(t *testing.T) {
 
 
 	testDebugger = NewECALDebugger(nil)
 	testDebugger = NewECALDebugger(nil)
 
 
-	if _, err = testDebugger.HandleInput("break ECALEvalTest:3"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
-	if _, err = testDebugger.HandleInput("break ECALEvalTest:4"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
-	if _, err = testDebugger.HandleInput("disablebreak ECALEvalTest:4"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("break ECALEvalTest:3")
+	errorutil.AssertOk(err)
+	_, err = testDebugger.HandleInput("break ECALEvalTest:4")
+	errorutil.AssertOk(err)
+	_, err = testDebugger.HandleInput("disablebreak ECALEvalTest:4")
+	errorutil.AssertOk(err)
 
 
 	wg := &sync.WaitGroup{}
 	wg := &sync.WaitGroup{}
 	wg.Add(1)
 	wg.Add(1)
@@ -145,10 +139,8 @@ log("test3")
 
 
 	// Continue until the end
 	// Continue until the end
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v Resume", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v Resume", tid))
+	errorutil.AssertOk(err)
 
 
 	wg.Wait()
 	wg.Wait()
 
 
@@ -160,10 +152,8 @@ test3`[1:] {
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("rmbreak ECALEvalTest:4"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("rmbreak ECALEvalTest:4")
+	errorutil.AssertOk(err)
 
 
 	out, err = testDebugger.HandleInput(fmt.Sprintf("status"))
 	out, err = testDebugger.HandleInput(fmt.Sprintf("status"))
 
 
@@ -184,15 +174,11 @@ test3`[1:] {
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("break ECALEvalTest:4"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("break ECALEvalTest:4")
+	errorutil.AssertOk(err)
 
 
-	if _, err = testDebugger.HandleInput("rmbreak ECALEvalTest"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("rmbreak ECALEvalTest")
+	errorutil.AssertOk(err)
 
 
 	out, err = testDebugger.HandleInput(fmt.Sprintf("status"))
 	out, err = testDebugger.HandleInput(fmt.Sprintf("status"))
 
 
@@ -552,17 +538,17 @@ log("test4")
 func waitForThreadSuspension(t *testing.T) uint64 {
 func waitForThreadSuspension(t *testing.T) uint64 {
 	var tid uint64
 	var tid uint64
 
 
-	for i := 0; i < 100; i += 1 {
+	for i := 0; i < 100; i++ {
 		state, err := testDebugger.HandleInput("status")
 		state, err := testDebugger.HandleInput("status")
 		errorutil.AssertOk(err)
 		errorutil.AssertOk(err)
 
 
 		threads := state.(map[string]interface{})["threads"].(map[string]map[string]interface{})
 		threads := state.(map[string]interface{})["threads"].(map[string]map[string]interface{})
 		if len(threads) > 0 {
 		if len(threads) > 0 {
-			for threadId, status := range threads {
+			for threadID, status := range threads {
 
 
 				if r, ok := status["threadRunning"]; ok && !r.(bool) {
 				if r, ok := status["threadRunning"]; ok && !r.(bool) {
-					threadIdNum, _ := strconv.ParseInt(threadId, 10, 0)
-					tid = uint64(threadIdNum)
+					threadIDNum, _ := strconv.ParseInt(threadID, 10, 0)
+					tid = uint64(threadIDNum)
 					return tid
 					return tid
 				}
 				}
 			}
 			}
@@ -577,7 +563,7 @@ func waitForThreadSuspension(t *testing.T) uint64 {
 func waitForAllThreadSuspension(t *testing.T) uint64 {
 func waitForAllThreadSuspension(t *testing.T) uint64 {
 	var tid uint64
 	var tid uint64
 
 
-	for i := 0; i < 100; i += 1 {
+	for i := 0; i < 100; i++ {
 		state, err := testDebugger.HandleInput("status")
 		state, err := testDebugger.HandleInput("status")
 		errorutil.AssertOk(err)
 		errorutil.AssertOk(err)
 
 
@@ -640,15 +626,11 @@ d(d())
 log("finish")
 log("finish")
 `
 `
 
 
-	if _, err = testDebugger.HandleInput("break ECALEvalTest:10"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("break ECALEvalTest:10")
+	errorutil.AssertOk(err)
 
 
-	if _, err = testDebugger.HandleInput("breakonstart true"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("breakonstart true")
+	errorutil.AssertOk(err)
 
 
 	wg := &sync.WaitGroup{}
 	wg := &sync.WaitGroup{}
 	wg.Add(1)
 	wg.Add(1)
@@ -681,10 +663,8 @@ log("finish")
 		return
 		return
 	}
 	}
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v resume", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v resume", tid))
+	errorutil.AssertOk(err)
 
 
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
@@ -713,10 +693,8 @@ log("finish")
 
 
 	// Step in without a function
 	// Step in without a function
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepin", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepin", tid))
+	errorutil.AssertOk(err)
 
 
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
@@ -746,10 +724,8 @@ log("finish")
 
 
 	// Normal step over
 	// Normal step over
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepover", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepover", tid))
+	errorutil.AssertOk(err)
 
 
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
@@ -779,10 +755,8 @@ log("finish")
 
 
 	// Normal step in
 	// Normal step in
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepin", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepin", tid))
+	errorutil.AssertOk(err)
 
 
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
@@ -810,10 +784,8 @@ log("finish")
 
 
 	// Normal step out
 	// Normal step out
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepout", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepout", tid))
+	errorutil.AssertOk(err)
 
 
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
@@ -843,12 +815,15 @@ log("finish")
 
 
 	// Step in and step out - we should end up on the same line as before
 	// Step in and step out - we should end up on the same line as before
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepin", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepin", tid))
+	errorutil.AssertOk(err)
 
 
-	tid = waitForThreadSuspension(t)
+	testStepDebugging2(t, testDebugger, wg)
+}
+
+func testStepDebugging2(t *testing.T, testDebugger util.ECALDebugger, wg *sync.WaitGroup) {
+
+	tid := waitForThreadSuspension(t)
 
 
 	if state := getDebuggerState(tid, t); state != `{
 	if state := getDebuggerState(tid, t); state != `{
   "breakpoints": {
   "breakpoints": {
@@ -872,10 +847,9 @@ log("finish")
 		return
 		return
 	}
 	}
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepout", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepout", tid))
+	errorutil.AssertOk(err)
+
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
 	if state := getDebuggerState(tid, t); state != `{
 	if state := getDebuggerState(tid, t); state != `{
@@ -904,10 +878,9 @@ log("finish")
 
 
 	// Normal step out
 	// Normal step out
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepout", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepout", tid))
+	errorutil.AssertOk(err)
+
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
 	if state := getDebuggerState(tid, t); state != `{
 	if state := getDebuggerState(tid, t); state != `{
@@ -935,15 +908,11 @@ log("finish")
 
 
 	// Set a new breakpoint
 	// Set a new breakpoint
 
 
-	if _, err = testDebugger.HandleInput("break ECALEvalTest:28"); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput("break ECALEvalTest:28")
+	errorutil.AssertOk(err)
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v Resume", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v Resume", tid))
+	errorutil.AssertOk(err)
 
 
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
@@ -974,10 +943,9 @@ log("finish")
 
 
 	// Normal step over
 	// Normal step over
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v stepover", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v stepover", tid))
+	errorutil.AssertOk(err)
+
 	tid = waitForThreadSuspension(t)
 	tid = waitForThreadSuspension(t)
 
 
 	if state := getDebuggerState(tid, t); state != `{
 	if state := getDebuggerState(tid, t); state != `{
@@ -1038,10 +1006,8 @@ log("finish")
 
 
 	// Continue until the end
 	// Continue until the end
 
 
-	if _, err := testDebugger.HandleInput(fmt.Sprintf("cont %v Resume", tid)); err != nil {
-		t.Error("Unexpected result:", err)
-		return
-	}
+	_, err = testDebugger.HandleInput(fmt.Sprintf("cont %v Resume", tid))
+	errorutil.AssertOk(err)
 
 
 	wg.Wait()
 	wg.Wait()
 
 
@@ -1463,38 +1429,32 @@ func TestDebuggingErrorInput(t *testing.T) {
 
 
 	testDebugger = NewECALDebugger(vs)
 	testDebugger = NewECALDebugger(vs)
 
 
-	if _, err = testDebugger.HandleInput("uuu"); err == nil ||
-		err.Error() != `Unknown command: uuu` {
+	if _, err = testDebugger.HandleInput("uuu"); err.Error() != `Unknown command: uuu` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("break"); err == nil ||
-		err.Error() != `Need a break target (<source>:<line>) as first parameter` {
+	if _, err = testDebugger.HandleInput("break"); err.Error() != `Need a break target (<source>:<line>) as first parameter` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("break foo"); err == nil ||
-		err.Error() != `Invalid break target - should be <source>:<line>` {
+	if _, err = testDebugger.HandleInput("break foo"); err.Error() != `Invalid break target - should be <source>:<line>` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("rmbreak"); err == nil ||
-		err.Error() != `Need a break target (<source>[:<line>]) as first parameter` {
+	if _, err = testDebugger.HandleInput("rmbreak"); err.Error() != `Need a break target (<source>[:<line>]) as first parameter` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("disablebreak"); err == nil ||
-		err.Error() != `Need a break target (<source>:<line>) as first parameter` {
+	if _, err = testDebugger.HandleInput("disablebreak"); err.Error() != `Need a break target (<source>:<line>) as first parameter` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("disablebreak foo"); err == nil ||
-		err.Error() != `Invalid break target - should be <source>:<line>` {
+	if _, err = testDebugger.HandleInput("disablebreak foo"); err.Error() != `Invalid break target - should be <source>:<line>` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
@@ -1547,64 +1507,68 @@ log("test3")
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("cont foo"); err == nil ||
-		err.Error() != `Need a thread ID and a command Resume, StepIn, StepOver or StepOut` {
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v foo foo", tid)); err.Error() != `No such value foo` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
+}
+
+func TestDebuggingErrorInput2(t *testing.T) {
+	var err error
+
+	tid := 1
+
+	defer func() {
+		testDebugger = nil
+	}()
+
+	vs := scope.NewScope(scope.GlobalScope)
+
+	testDebugger = NewECALDebugger(vs)
 
 
-	if _, err = testDebugger.HandleInput("cont foo bar"); err == nil ||
-		err.Error() != `Parameter 1 should be a number` {
+	if _, err = testDebugger.HandleInput("cont foo"); err.Error() != `Need a thread ID and a command Resume, StepIn, StepOver or StepOut` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("cont 99 bar"); err == nil ||
-		err.Error() != `Invalid command bar - must be resume, stepin, stepover or stepout` {
+	if _, err = testDebugger.HandleInput("cont foo bar"); err.Error() != `Parameter 1 should be a number` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput("describe"); err == nil ||
-		err.Error() != `Need a thread ID` {
+	if _, err = testDebugger.HandleInput("cont 99 bar"); err.Error() != `Invalid command bar - must be resume, stepin, stepover or stepout` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v foo", tid)); err == nil ||
-		err.Error() != `Need a thread ID, a variable name and a destination variable name` {
+	if _, err = testDebugger.HandleInput("describe"); err.Error() != `Need a thread ID` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v _foo foo", tid)); err == nil ||
-		err.Error() != `Variable names may only contain [a-zA-Z] and [a-zA-Z0-9] from the second character` {
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v foo", tid)); err.Error() != `Need a thread ID, a variable name and a destination variable name` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v foo foo", tid)); err == nil ||
-		err.Error() != `No such value foo` {
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v _foo foo", tid)); err.Error() != `Variable names may only contain [a-zA-Z] and [a-zA-Z0-9] from the second character` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput(fmt.Sprintf("inject %v", tid)); err == nil ||
-		err.Error() != `Need a thread ID, a variable name and an expression` {
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("inject %v", tid)); err.Error() != `Need a thread ID, a variable name and an expression` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
 	testDebugger.(*ecalDebugger).globalScope = nil
 	testDebugger.(*ecalDebugger).globalScope = nil
 
 
-	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v foo foo", tid)); err == nil ||
-		err.Error() != `Cannot access global scope` {
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("extract %v foo foo", tid)); err.Error() != `Cannot access global scope` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
 
 
-	if _, err = testDebugger.HandleInput(fmt.Sprintf("inject %v foo foo", tid)); err == nil ||
-		err.Error() != `Cannot access global scope` {
+	if _, err = testDebugger.HandleInput(fmt.Sprintf("inject %v foo foo", tid)); err.Error() != `Cannot access global scope` {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}

+ 2 - 2
interpreter/func_provider.go

@@ -824,7 +824,7 @@ func (ct *setCronTrigger) Run(instanceID string, vs parser.Scope, is map[string]
 			tick := 0
 			tick := 0
 
 
 			erp.Cron.RegisterSpec(cs, func() {
 			erp.Cron.RegisterSpec(cs, func() {
-				tick += 1
+				tick++
 				now := erp.Cron.NowFunc()
 				now := erp.Cron.NowFunc()
 				event := engine.NewEvent(eventname, eventkind, map[interface{}]interface{}{
 				event := engine.NewEvent(eventname, eventkind, map[interface{}]interface{}{
 					"time":      now,
 					"time":      now,
@@ -894,7 +894,7 @@ func (pt *setPulseTrigger) Run(instanceID string, vs parser.Scope, is map[string
 				for {
 				for {
 					time.Sleep(time.Duration(micros) * time.Microsecond)
 					time.Sleep(time.Duration(micros) * time.Microsecond)
 
 
-					tick += 1
+					tick++
 					now := time.Now()
 					now := time.Now()
 					micros := now.UnixNano() / int64(time.Microsecond)
 					micros := now.UnixNano() / int64(time.Microsecond)
 					event := engine.NewEvent(eventname, eventkind, map[interface{}]interface{}{
 					event := engine.NewEvent(eventname, eventkind, map[interface{}]interface{}{

+ 29 - 14
interpreter/func_provider_test.go

@@ -17,6 +17,7 @@ import (
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
+	"devt.de/krotik/common/errorutil"
 	"devt.de/krotik/ecal/stdlib"
 	"devt.de/krotik/ecal/stdlib"
 )
 )
 
 
@@ -90,8 +91,9 @@ identifier: len
       number: 2
       number: 2
       number: 3
       number: 3
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || res != 3. {
+	if res != 3. {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -109,8 +111,9 @@ identifier: len
         number: 2
         number: 2
         string: 'b'
         string: 'b'
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || res != 2. {
+	if res != 2. {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -126,8 +129,9 @@ identifier: del
       number: 3
       number: 3
     number: 1
     number: 1
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != "[1 3]" {
+	if fmt.Sprint(res) != "[1 3]" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -153,8 +157,9 @@ identifier: del
         number: 3
         number: 3
     string: 'b'
     string: 'b'
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != "map[a:1 c:3]" {
+	if fmt.Sprint(res) != "map[a:1 c:3]" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -188,8 +193,9 @@ identifier: add
     number: 4
     number: 4
     number: 0
     number: 0
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != "[4 1 2 3]" {
+	if fmt.Sprint(res) != "[4 1 2 3]" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -206,13 +212,17 @@ identifier: add
     number: 4
     number: 4
     number: 1
     number: 1
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != "[1 4 2 3]" {
+	if fmt.Sprint(res) != "[1 4 2 3]" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
+}
 
 
-	res, err = UnitTestEvalAndAST(
+func TestSimpleFunctions2(t *testing.T) {
+
+	res, err := UnitTestEvalAndAST(
 		`concat([1,2,3], [4,5,6], [7,8,9])`, nil,
 		`concat([1,2,3], [4,5,6], [7,8,9])`, nil,
 		`
 		`
 identifier: concat
 identifier: concat
@@ -230,8 +240,9 @@ identifier: concat
       number: 8
       number: 8
       number: 9
       number: 9
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != "[1 2 3 4 5 6 7 8 9]" {
+	if fmt.Sprint(res) != "[1 2 3 4 5 6 7 8 9]" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -242,8 +253,9 @@ identifier: concat
 identifier: dumpenv
 identifier: dumpenv
   funccall
   funccall
 `[1:])
 `[1:])
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != `GlobalScope {
+	if fmt.Sprint(res) != `GlobalScope {
 }` {
 }` {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
@@ -255,16 +267,18 @@ func foo() {
 	log("hello")
 	log("hello")
 }
 }
 doc(foo)`, nil)
 doc(foo)`, nil)
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != `Declared function: foo (Line 2, Pos 1)` {
+	if fmt.Sprint(res) != `Declared function: foo (Line 2, Pos 1)` {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
 
 
 	res, err = UnitTestEval(
 	res, err = UnitTestEval(
 		`doc(len)`, nil)
 		`doc(len)`, nil)
+	errorutil.AssertOk(err)
 
 
-	if err != nil || fmt.Sprint(res) != `Len returns the size of a list or map.` {
+	if fmt.Sprint(res) != `Len returns the size of a list or map.` {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -275,8 +289,9 @@ doc(foo)`, nil)
 
 
 	res, err = UnitTestEval(
 	res, err = UnitTestEval(
 		`doc(fmt.Println)`, nil)
 		`doc(fmt.Println)`, nil)
+	errorutil.AssertOk(err)
 
 
-	if err != nil || res != "foo" {
+	if res != "foo" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -335,7 +350,7 @@ func TestCronTrigger(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
-	res, err = UnitTestEval(
+	_, err = UnitTestEval(
 		`
 		`
 sink test
 sink test
   kindmatch [ "foo.*" ],
   kindmatch [ "foo.*" ],
@@ -399,7 +414,7 @@ func TestPulseTrigger(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
-	res, err = UnitTestEval(
+	_, err = UnitTestEval(
 		`
 		`
 sink test
 sink test
   kindmatch [ "foo.*" ],
   kindmatch [ "foo.*" ],

+ 4 - 1
interpreter/rt_arithmetic_test.go

@@ -71,8 +71,11 @@ minus
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
+}
 
 
-	res, err = UnitTestEvalAndAST(
+func TestSimpleArithmetics2(t *testing.T) {
+
+	res, err := UnitTestEvalAndAST(
 		`-5.2 - 2.2`, nil,
 		`-5.2 - 2.2`, nil,
 		`
 		`
 minus
 minus

+ 9 - 9
interpreter/rt_assign_test.go

@@ -29,7 +29,7 @@ func TestSimpleAssignments(t *testing.T) {
   number: 42
   number: 42
 `[1:])
 `[1:])
 
 
-	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
+	if vsRes := vs.String(); vsRes != `GlobalScope {
     a (float64) : 42
     a (float64) : 42
 }` {
 }` {
 		t.Error("Unexpected result: ", vsRes, res, err)
 		t.Error("Unexpected result: ", vsRes, res, err)
@@ -44,7 +44,7 @@ func TestSimpleAssignments(t *testing.T) {
   string: 'test'
   string: 'test'
 `[1:])
 `[1:])
 
 
-	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
+	if vsRes := vs.String(); vsRes != `GlobalScope {
     a (string) : test
     a (string) : test
 }` {
 }` {
 		t.Error("Unexpected result: ", vsRes, res, err)
 		t.Error("Unexpected result: ", vsRes, res, err)
@@ -62,7 +62,7 @@ func TestSimpleAssignments(t *testing.T) {
     number: 3
     number: 3
 `[1:])
 `[1:])
 
 
-	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
+	if vsRes := vs.String(); vsRes != `GlobalScope {
     a ([]interface {}) : [1,2,3]
     a ([]interface {}) : [1,2,3]
 }` {
 }` {
 		t.Error("Unexpected result: ", vsRes, res, err)
 		t.Error("Unexpected result: ", vsRes, res, err)
@@ -94,7 +94,7 @@ func TestSimpleAssignments(t *testing.T) {
       number: 4
       number: 4
 `[1:])
 `[1:])
 
 
-	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
+	if vsRes := vs.String(); vsRes != `GlobalScope {
     a (map[interface {}]interface {}) : {"1":"foo","2":"bar","foobar":3,"null":4}
     a (map[interface {}]interface {}) : {"1":"foo","2":"bar","foobar":3,"null":4}
 }` {
 }` {
 		t.Error("Unexpected result: ", vsRes, res, err)
 		t.Error("Unexpected result: ", vsRes, res, err)
@@ -104,7 +104,7 @@ func TestSimpleAssignments(t *testing.T) {
 	_, err = UnitTestEval(
 	_, err = UnitTestEval(
 		`1 := [1, 2]`, vs)
 		`1 := [1, 2]`, vs)
 
 
-	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a variable or list of variables on the left side of the assignment) (Line:1 Pos:3)" {
+	if err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a variable or list of variables on the left side of the assignment) (Line:1 Pos:3)" {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
@@ -112,7 +112,7 @@ func TestSimpleAssignments(t *testing.T) {
 	_, err = UnitTestEval(
 	_, err = UnitTestEval(
 		`[1] := [1, 2]`, vs)
 		`[1] := [1, 2]`, vs)
 
 
-	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a list of variables on the left side of the assignment) (Line:1 Pos:5)" {
+	if err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a list of variables on the left side of the assignment) (Line:1 Pos:5)" {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
@@ -120,7 +120,7 @@ func TestSimpleAssignments(t *testing.T) {
 	_, err = UnitTestEval(
 	_, err = UnitTestEval(
 		`[a, b] := [1, 2, 3]`, vs)
 		`[a, b] := [1, 2, 3]`, vs)
 
 
-	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Assigned number of variables is different to number of values (2 variables vs 3 values)) (Line:1 Pos:8)" {
+	if err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Assigned number of variables is different to number of values (2 variables vs 3 values)) (Line:1 Pos:8)" {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
@@ -128,7 +128,7 @@ func TestSimpleAssignments(t *testing.T) {
 	_, err = UnitTestEval(
 	_, err = UnitTestEval(
 		`[a, b] := 1`, vs)
 		`[a, b] := 1`, vs)
 
 
-	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Result is not a list (value is 1)) (Line:1 Pos:8)" {
+	if err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Result is not a list (value is 1)) (Line:1 Pos:8)" {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
@@ -136,7 +136,7 @@ func TestSimpleAssignments(t *testing.T) {
 	_, err = UnitTestEval(
 	_, err = UnitTestEval(
 		`[a, b.c, c] := [1, 2, 3]`, vs)
 		`[a, b.c, c] := [1, 2, 3]`, vs)
 
 
-	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Variable b is not a container) (Line:1 Pos:13)" {
+	if err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Variable b is not a container) (Line:1 Pos:13)" {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}

+ 9 - 2
interpreter/rt_boolean_test.go

@@ -94,8 +94,11 @@ func TestSimpleBoolean(t *testing.T) {
 		t.Error(res, err)
 		t.Error(res, err)
 		return
 		return
 	}
 	}
+}
 
 
-	res, err = UnitTestEvalAndAST(
+func TestSimpleBoolean2(t *testing.T) {
+
+	res, err := UnitTestEvalAndAST(
 		`2 < 3`, nil,
 		`2 < 3`, nil,
 		`
 		`
 <
 <
@@ -177,7 +180,11 @@ func TestSimpleBoolean(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
-	res, err = UnitTestEvalAndAST(
+}
+
+func TestSimpleBoolean3(t *testing.T) {
+
+	res, err := UnitTestEvalAndAST(
 		`null == null`, nil,
 		`null == null`, nil,
 		`
 		`
 ==
 ==

+ 90 - 65
interpreter/rt_identifier.go

@@ -137,28 +137,8 @@ func (rt *identifierRuntime) resolveFunction(astring string, vs parser.Scope, is
 	for _, funccall := range node.Children {
 	for _, funccall := range node.Children {
 
 
 		if funccall.Name == parser.NodeFUNCCALL {
 		if funccall.Name == parser.NodeFUNCCALL {
-			var funcObj util.ECALFunction
 
 
-			ok := astring == "log" || astring == "error" || astring == "debug"
-
-			if !ok {
-
-				funcObj, ok = result.(util.ECALFunction)
-
-				if !ok {
-
-					// Check for stdlib function
-
-					funcObj, ok = stdlib.GetStdlibFunc(astring)
-
-					if !ok {
-
-						// Check for inbuild function
-
-						funcObj, ok = InbuildFuncMap[astring]
-					}
-				}
-			}
+			funcObj, ok := rt.resolveFunctionObject(astring, result)
 
 
 			if ok {
 			if ok {
 				var args []interface{}
 				var args []interface{}
@@ -175,72 +155,117 @@ func (rt *identifierRuntime) resolveFunction(astring string, vs parser.Scope, is
 				}
 				}
 
 
 				if err == nil {
 				if err == nil {
+					result, err = rt.executeFunction(astring, funcObj, args, vs, is, tid, node)
+				}
 
 
-					if astring == "log" || astring == "error" || astring == "debug" {
+			} else {
 
 
-						// Convert non-string structures
+				err = rt.erp.NewRuntimeError(util.ErrUnknownConstruct,
+					fmt.Sprintf("Unknown function: %v", node.Token.Val), node)
+			}
 
 
-						for i, a := range args {
-							if _, ok := a.(string); !ok {
-								args[i] = stringutil.ConvertToPrettyString(a)
-							}
-						}
+			break
+		}
+	}
 
 
-						if astring == "log" {
-							rt.erp.Logger.LogInfo(args...)
-						} else if astring == "error" {
-							rt.erp.Logger.LogError(args...)
-						} else if astring == "debug" {
-							rt.erp.Logger.LogDebug(args...)
-						}
+	return result, err
+}
 
 
-					} else {
+/*
+resolveFunctionObject will resolve a given string or object into a concrete ECAL function.
+*/
+func (rt *identifierRuntime) resolveFunctionObject(astring string, result interface{}) (util.ECALFunction, bool) {
+	var funcObj util.ECALFunction
 
 
-						if rt.erp.Debugger != nil {
-							rt.erp.Debugger.VisitStepInState(node, vs, tid)
-						}
+	ok := astring == "log" || astring == "error" || astring == "debug"
 
 
-						// Execute the function
+	if !ok {
 
 
-						result, err = funcObj.Run(rt.instanceID, vs, is, tid, args)
+		funcObj, ok = result.(util.ECALFunction)
 
 
-						if rt.erp.Debugger != nil {
-							rt.erp.Debugger.VisitStepOutState(node, vs, tid, err)
-						}
+		if !ok {
 
 
-						_, ok1 := err.(*util.RuntimeError)
-						_, ok2 := err.(*util.RuntimeErrorWithDetail)
+			// Check for stdlib function
 
 
-						if err != nil && !ok1 && !ok2 {
+			funcObj, ok = stdlib.GetStdlibFunc(astring)
 
 
-							// Convert into a proper runtime error if necessary
+			if !ok {
 
 
-							rerr := rt.erp.NewRuntimeError(util.ErrRuntimeError,
-								err.Error(), node).(*util.RuntimeError)
+				// Check for inbuild function
 
 
-							if err == util.ErrIsIterator || err == util.ErrEndOfIteration || err == util.ErrContinueIteration {
-								rerr.Type = err
-							}
+				funcObj, ok = InbuildFuncMap[astring]
+			}
+		}
+	}
 
 
-							err = rerr
-						}
+	return funcObj, ok
+}
 
 
-						if tr, ok := err.(util.TraceableRuntimeError); ok {
+/*
+executeFunction executes a function call with a given list of arguments and return the result.
+*/
+func (rt *identifierRuntime) executeFunction(astring string, funcObj util.ECALFunction, args []interface{},
+	vs parser.Scope, is map[string]interface{}, tid uint64, node *parser.ASTNode) (interface{}, error) {
 
 
-							// Add tracing information to the error
+	var result interface{}
+	var err error
 
 
-							tr.AddTrace(rt.node)
-						}
-					}
-				}
+	if stringutil.IndexOf(astring, []string{"log", "error", "debug"}) != -1 {
 
 
-			} else {
+		// Convert non-string structures
 
 
-				err = rt.erp.NewRuntimeError(util.ErrUnknownConstruct,
-					fmt.Sprintf("Unknown function: %v", node.Token.Val), node)
+		for i, a := range args {
+			if _, ok := a.(string); !ok {
+				args[i] = stringutil.ConvertToPrettyString(a)
 			}
 			}
+		}
 
 
-			break
+		if astring == "log" {
+			rt.erp.Logger.LogInfo(args...)
+		} else if astring == "error" {
+			rt.erp.Logger.LogError(args...)
+		} else if astring == "debug" {
+			rt.erp.Logger.LogDebug(args...)
+		}
+
+	} else {
+
+		if rt.erp.Debugger != nil {
+			rt.erp.Debugger.VisitStepInState(node, vs, tid)
+		}
+
+		// Execute the function
+
+		result, err = funcObj.Run(rt.instanceID, vs, is, tid, args)
+
+		if rt.erp.Debugger != nil {
+			rt.erp.Debugger.VisitStepOutState(node, vs, tid, err)
+		}
+
+		_, ok1 := err.(*util.RuntimeError)
+		_, ok2 := err.(*util.RuntimeErrorWithDetail)
+
+		if err != nil && !ok1 && !ok2 {
+
+			// Convert into a proper runtime error if necessary
+
+			rerr := rt.erp.NewRuntimeError(util.ErrRuntimeError,
+				err.Error(), node).(*util.RuntimeError)
+
+			if stringutil.IndexOf(err.Error(), []string{util.ErrIsIterator.Error(),
+				util.ErrEndOfIteration.Error(), util.ErrContinueIteration.Error()}) != -1 {
+
+				rerr.Type = err
+			}
+
+			err = rerr
+		}
+
+		if tr, ok := err.(util.TraceableRuntimeError); ok {
+
+			// Add tracing information to the error
+
+			tr.AddTrace(rt.node)
 		}
 		}
 	}
 	}
 
 

+ 124 - 111
interpreter/rt_sink.go

@@ -73,164 +73,177 @@ func (rt *sinkRuntime) Validate() error {
 Eval evaluate this runtime component.
 Eval evaluate this runtime component.
 */
 */
 func (rt *sinkRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint64) (interface{}, error) {
 func (rt *sinkRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint64) (interface{}, error) {
-	var kindMatch, scopeMatch, suppresses []string
-	var stateMatch map[string]interface{}
-	var priority int
-	var statements *parser.ASTNode
-
 	_, err := rt.baseRuntime.Eval(vs, is, tid)
 	_, err := rt.baseRuntime.Eval(vs, is, tid)
 
 
 	if err == nil {
 	if err == nil {
-		// Create default scope
+		var rule *engine.Rule
+		var statements *parser.ASTNode
 
 
-		scopeMatch = []string{}
+		rule, statements, err = rt.createRule(vs, is, tid)
 
 
-		// Get the name of the sink
+		if err == nil && statements != nil {
+
+			if len(rt.node.Meta) > 0 &&
+				(rt.node.Meta[0].Type() == parser.MetaDataPreComment ||
+					rt.node.Meta[0].Type() == parser.MetaDataPostComment) {
+				rule.Desc = strings.TrimSpace(rt.node.Meta[0].Value())
+			}
 
 
-		name := rt.node.Children[0].Token.Val
+			rule.Action = func(p engine.Processor, m engine.Monitor, e *engine.Event, tid uint64) error { // Action of the rule
 
 
-		// Create helper function
+				// Create a new root variable scope
 
 
-		makeStringList := func(child *parser.ASTNode) ([]string, error) {
-			var ret []string
+				sinkVS := scope.NewScope(fmt.Sprintf("sink: %v", rule.Name))
 
 
-			val, err := child.Runtime.Eval(vs, is, tid)
+				// Create a new instance state with the monitor - everything called
+				// by the rule will have access to the current monitor.
 
 
-			if err == nil {
-				for _, v := range val.([]interface{}) {
-					ret = append(ret, fmt.Sprint(v))
+				sinkIs := map[string]interface{}{
+					"monitor": m,
 				}
 				}
-			}
 
 
-			return ret, err
-		}
+				err = sinkVS.SetValue("event", map[interface{}]interface{}{
+					"name":  e.Name(),
+					"kind":  strings.Join(e.Kind(), engine.RuleKindSeparator),
+					"state": e.State(),
+				})
 
 
-		// Collect values from children
+				if err == nil {
+					scope.SetParentOfScope(sinkVS, vs)
 
 
-		for _, child := range rt.node.Children[1:] {
+					if _, err = statements.Runtime.Eval(sinkVS, sinkIs, tid); err != nil {
 
 
-			switch child.Name {
+						if sre, ok := err.(*util.RuntimeErrorWithDetail); ok {
+							sre.Environment = sinkVS
 
 
-			case parser.NodeKINDMATCH:
-				kindMatch, err = makeStringList(child)
-				break
+						} else {
+							var data interface{}
+							rerr := rt.erp.NewRuntimeError(util.ErrSink, err.Error(), rt.node).(*util.RuntimeError)
 
 
-			case parser.NodeSCOPEMATCH:
-				scopeMatch, err = makeStringList(child)
-				break
+							if e, ok := err.(*util.RuntimeError); ok {
+								rerr = e
+							} else if r, ok := err.(*returnValue); ok {
+								rerr = r.RuntimeError
+								data = r.returnValue
+							}
 
 
-			case parser.NodeSTATEMATCH:
-				var val interface{}
-				stateMatch = make(map[string]interface{})
+							// Provide additional information for unexpected errors
 
 
-				if val, err = child.Runtime.Eval(vs, is, tid); err == nil {
-					for k, v := range val.(map[interface{}]interface{}) {
-						stateMatch[fmt.Sprint(k)] = v
+							err = &util.RuntimeErrorWithDetail{
+								RuntimeError: rerr,
+								Environment:  sinkVS,
+								Data:         data,
+							}
+						}
 					}
 					}
 				}
 				}
-				break
-
-			case parser.NodePRIORITY:
-				var val interface{}
-
-				if val, err = child.Runtime.Eval(vs, is, tid); err == nil {
-					priority = int(math.Floor(val.(float64)))
-				}
-				break
 
 
-			case parser.NodeSUPPRESSES:
-				suppresses, err = makeStringList(child)
-				break
-
-			case parser.NodeSTATEMENTS:
-				statements = child
-				break
+				return err
 			}
 			}
 
 
-			if err != nil {
-				break
+			if err = rt.erp.Processor.AddRule(rule); err != nil {
+				err = rt.erp.NewRuntimeError(util.ErrInvalidState, err.Error(), rt.node)
 			}
 			}
 		}
 		}
+	}
 
 
-		if err == nil && statements != nil {
-			var desc string
-
-			sinkName := fmt.Sprint(name)
+	return nil, err
+}
 
 
-			if len(rt.node.Meta) > 0 &&
-				(rt.node.Meta[0].Type() == parser.MetaDataPreComment ||
-					rt.node.Meta[0].Type() == parser.MetaDataPostComment) {
-				desc = strings.TrimSpace(rt.node.Meta[0].Value())
-			}
+/*
+createRule creates a rule for the ECA engine.
+*/
+func (rt *sinkRuntime) createRule(vs parser.Scope, is map[string]interface{},
+	tid uint64) (*engine.Rule, *parser.ASTNode, error) {
 
 
-			rule := &engine.Rule{
-				Name:            sinkName,   // Name
-				Desc:            desc,       // Description
-				KindMatch:       kindMatch,  // Kind match
-				ScopeMatch:      scopeMatch, // Match on event cascade scope
-				StateMatch:      stateMatch, // No state match
-				Priority:        priority,   // Priority of the rule
-				SuppressionList: suppresses, // List of suppressed rules by this rule
-				Action: func(p engine.Processor, m engine.Monitor, e *engine.Event, tid uint64) error { // Action of the rule
+	var kindMatch, scopeMatch, suppresses []string
+	var stateMatch map[string]interface{}
+	var priority int
+	var statements *parser.ASTNode
+	var err error
 
 
-					// Create a new root variable scope
+	// Create default scope
 
 
-					sinkVS := scope.NewScope(fmt.Sprintf("sink: %v", sinkName))
+	scopeMatch = []string{}
 
 
-					// Create a new instance state with the monitor - everything called
-					// by the rule will have access to the current monitor.
+	// Get sink name
 
 
-					sinkIs := map[string]interface{}{
-						"monitor": m,
-					}
+	sinkName := fmt.Sprint(rt.node.Children[0].Token.Val)
 
 
-					err = sinkVS.SetValue("event", map[interface{}]interface{}{
-						"name":  e.Name(),
-						"kind":  strings.Join(e.Kind(), engine.RuleKindSeparator),
-						"state": e.State(),
-					})
+	// Collect values from children
 
 
-					if err == nil {
-						scope.SetParentOfScope(sinkVS, vs)
+	for _, child := range rt.node.Children[1:] {
 
 
-						if _, err = statements.Runtime.Eval(sinkVS, sinkIs, tid); err != nil {
+		switch child.Name {
 
 
-							if sre, ok := err.(*util.RuntimeErrorWithDetail); ok {
-								sre.Environment = sinkVS
+		case parser.NodeKINDMATCH:
+			kindMatch, err = rt.makeStringList(child, vs, is, tid)
+			break
 
 
-							} else {
-								var data interface{}
-								rerr := rt.erp.NewRuntimeError(util.ErrSink, err.Error(), rt.node).(*util.RuntimeError)
+		case parser.NodeSCOPEMATCH:
+			scopeMatch, err = rt.makeStringList(child, vs, is, tid)
+			break
 
 
-								if e, ok := err.(*util.RuntimeError); ok {
-									rerr = e
-								} else if r, ok := err.(*returnValue); ok {
-									rerr = r.RuntimeError
-									data = r.returnValue
-								}
+		case parser.NodeSTATEMATCH:
+			var val interface{}
+			stateMatch = make(map[string]interface{})
 
 
-								// Provide additional information for unexpected errors
+			if val, err = child.Runtime.Eval(vs, is, tid); err == nil {
+				for k, v := range val.(map[interface{}]interface{}) {
+					stateMatch[fmt.Sprint(k)] = v
+				}
+			}
+			break
 
 
-								err = &util.RuntimeErrorWithDetail{
-									RuntimeError: rerr,
-									Environment:  sinkVS,
-									Data:         data,
-								}
-							}
-						}
-					}
+		case parser.NodePRIORITY:
+			var val interface{}
 
 
-					return err
-				},
+			if val, err = child.Runtime.Eval(vs, is, tid); err == nil {
+				priority = int(math.Floor(val.(float64)))
 			}
 			}
+			break
 
 
-			if err = rt.erp.Processor.AddRule(rule); err != nil {
-				err = rt.erp.NewRuntimeError(util.ErrInvalidState, err.Error(), rt.node)
-			}
+		case parser.NodeSUPPRESSES:
+			suppresses, err = rt.makeStringList(child, vs, is, tid)
+			break
+
+		case parser.NodeSTATEMENTS:
+			statements = child
+			break
+		}
+
+		if err != nil {
+			break
 		}
 		}
 	}
 	}
 
 
-	return nil, err
+	return &engine.Rule{
+		Name:            sinkName,   // Name
+		KindMatch:       kindMatch,  // Kind match
+		ScopeMatch:      scopeMatch, // Match on event cascade scope
+		StateMatch:      stateMatch, // No state match
+		Priority:        priority,   // Priority of the rule
+		SuppressionList: suppresses, // List of suppressed rules by this rule
+	}, statements, err
+}
+
+/*
+makeStringList evaluates a given child node into a list of strings.
+*/
+func (rt *sinkRuntime) makeStringList(child *parser.ASTNode, vs parser.Scope,
+	is map[string]interface{}, tid uint64) ([]string, error) {
+
+	var ret []string
+
+	val, err := child.Runtime.Eval(vs, is, tid)
+
+	if err == nil {
+		for _, v := range val.([]interface{}) {
+			ret = append(ret, fmt.Sprint(v))
+		}
+	}
+
+	return ret, err
 }
 }
 
 
 // Sink child nodes
 // Sink child nodes

+ 184 - 156
interpreter/rt_statements.go

@@ -253,153 +253,180 @@ func (rt *loopRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint
 			}
 			}
 
 
 		} else if rt.node.Children[0].Name == parser.NodeIN {
 		} else if rt.node.Children[0].Name == parser.NodeIN {
-			var iterator func() (interface{}, error)
-			var val interface{}
 
 
-			it := rt.node.Children[0].Children[1]
+			err = rt.handleIterator(vs, is, tid)
+		}
+	}
 
 
-			val, err = it.Runtime.Eval(vs, is, tid)
+	return nil, err
+}
 
 
-			// Create an iterator object
+/*
+handleIterator handles iterator functions for loops.
+*/
+func (rt *loopRuntime) handleIterator(vs parser.Scope, is map[string]interface{}, tid uint64) error {
+	var res interface{}
 
 
-			if rterr, ok := err.(*util.RuntimeError); ok && rterr.Type == util.ErrIsIterator {
+	iterator, err := rt.getIterator(vs, is, tid)
 
 
-				// We got an iterator - all subsequent calls will return values
+	vars := rt.leftInVarName
 
 
-				iterator = func() (interface{}, error) {
-					return it.Runtime.Eval(vs, is, tid)
-				}
-				err = nil
+	for err == nil {
 
 
-			} else {
+		if res, err = rt.getIteratorValue(iterator); err == nil {
 
 
-				// We got a value over which we need to iterate
+			if len(vars) == 1 {
+				err = vs.SetValue(vars[0], res)
 
 
-				if valList, isList := val.([]interface{}); isList {
+			} else if resList, ok := res.([]interface{}); ok {
 
 
-					index := -1
-					end := len(valList)
+				if len(vars) != len(resList) {
+					err = fmt.Errorf("Assigned number of variables is different to "+
+						"number of values (%v variables vs %v values)",
+						len(vars), len(resList))
+				}
 
 
-					iterator = func() (interface{}, error) {
-						index++
-						if index >= end {
-							return nil, rt.erp.NewRuntimeError(util.ErrEndOfIteration, "", rt.node)
+				if err == nil {
+					for i, v := range vars {
+						if err == nil {
+							err = vs.SetValue(v, resList[i])
 						}
 						}
-						return valList[index], nil
 					}
 					}
+				}
 
 
-				} else if valMap, isMap := val.(map[interface{}]interface{}); isMap {
-					var keys []interface{}
+			} else {
 
 
-					index := -1
+				err = fmt.Errorf("Result for loop variable is not a list (value is %v)", res)
+			}
 
 
-					for k := range valMap {
-						keys = append(keys, k)
-					}
-					end := len(keys)
+			if err != nil {
+				return rt.erp.NewRuntimeError(util.ErrRuntimeError,
+					err.Error(), rt.node)
+			}
 
 
-					// Try to sort according to string value
+			// Execute block
 
 
-					sortutil.InterfaceStrings(keys)
+			_, err = rt.node.Children[1].Runtime.Eval(vs, is, tid)
+		}
 
 
-					iterator = func() (interface{}, error) {
-						index++
-						if index >= end {
-							return nil, rt.erp.NewRuntimeError(util.ErrEndOfIteration, "", rt.node)
-						}
-						key := keys[index]
-						return []interface{}{key, valMap[key]}, nil
-					}
+		// Check for continue
 
 
-				} else {
+		if err != nil {
+			if eoi, ok := err.(*util.RuntimeError); ok {
+				if eoi.Type == util.ErrContinueIteration {
+					err = nil
+				}
+			}
+		}
+	}
 
 
-					// A single value will do exactly one iteration
+	// Check for end of iteration error
 
 
-					index := -1
+	if eoi, ok := err.(*util.RuntimeError); ok {
+		if eoi.Type == util.ErrEndOfIteration {
+			err = nil
+		}
+	}
 
 
-					iterator = func() (interface{}, error) {
-						index++
-						if index > 0 {
-							return nil, rt.erp.NewRuntimeError(util.ErrEndOfIteration, "", rt.node)
-						}
-						return val, nil
-					}
-				}
-			}
+	return err
+}
 
 
-			vars := rt.leftInVarName
+/*
+getIteratorValue gets the next iterator value.
+*/
+func (rt *loopRuntime) getIteratorValue(iterator func() (interface{}, error)) (interface{}, error) {
+	var err error
+	var res interface{}
 
 
-			for err == nil {
-				var res interface{}
+	if res, err = iterator(); err != nil {
+		if eoi, ok := err.(*util.RuntimeError); ok {
+			if eoi.Type == util.ErrIsIterator {
+				err = nil
+			}
+		}
+	}
 
 
-				res, err = iterator()
+	return res, err
+}
 
 
-				if err != nil {
-					if eoi, ok := err.(*util.RuntimeError); ok {
-						if eoi.Type == util.ErrIsIterator {
-							err = nil
-						}
-					}
-				}
+/*
+getIterator create an iterator object.
+*/
+func (rt *loopRuntime) getIterator(vs parser.Scope, is map[string]interface{}, tid uint64) (func() (interface{}, error), error) {
+	var iterator func() (interface{}, error)
 
 
-				if err == nil {
+	it := rt.node.Children[0].Children[1]
 
 
-					if len(vars) == 1 {
-						err = vs.SetValue(vars[0], res)
+	val, err := it.Runtime.Eval(vs, is, tid)
 
 
-					} else if resList, ok := res.([]interface{}); ok {
+	// Create an iterator object
 
 
-						if len(vars) != len(resList) {
-							err = fmt.Errorf("Assigned number of variables is different to "+
-								"number of values (%v variables vs %v values)",
-								len(vars), len(resList))
-						}
+	if rterr, ok := err.(*util.RuntimeError); ok && rterr.Type == util.ErrIsIterator {
 
 
-						if err == nil {
-							for i, v := range vars {
-								if err == nil {
-									err = vs.SetValue(v, resList[i])
-								}
-							}
-						}
+		// We got an iterator - all subsequent calls will return values
 
 
-					} else {
+		iterator = func() (interface{}, error) {
+			return it.Runtime.Eval(vs, is, tid)
+		}
+		err = nil
 
 
-						err = fmt.Errorf("Result for loop variable is not a list (value is %v)", res)
-					}
+	} else {
 
 
-					if err != nil {
-						return nil, rt.erp.NewRuntimeError(util.ErrRuntimeError,
-							err.Error(), rt.node)
-					}
+		// We got a value over which we need to iterate
+
+		if valList, isList := val.([]interface{}); isList {
 
 
-					// Execute block
+			index := -1
+			end := len(valList)
 
 
-					_, err = rt.node.Children[1].Runtime.Eval(vs, is, tid)
+			iterator = func() (interface{}, error) {
+				index++
+				if index >= end {
+					return nil, rt.erp.NewRuntimeError(util.ErrEndOfIteration, "", rt.node)
 				}
 				}
+				return valList[index], nil
+			}
 
 
-				// Check for continue
+		} else if valMap, isMap := val.(map[interface{}]interface{}); isMap {
+			var keys []interface{}
 
 
-				if err != nil {
-					if eoi, ok := err.(*util.RuntimeError); ok {
-						if eoi.Type == util.ErrContinueIteration {
-							err = nil
-						}
-					}
+			index := -1
+
+			for k := range valMap {
+				keys = append(keys, k)
+			}
+			end := len(keys)
+
+			// Try to sort according to string value
+
+			sortutil.InterfaceStrings(keys)
+
+			iterator = func() (interface{}, error) {
+				index++
+				if index >= end {
+					return nil, rt.erp.NewRuntimeError(util.ErrEndOfIteration, "", rt.node)
 				}
 				}
+				key := keys[index]
+				return []interface{}{key, valMap[key]}, nil
 			}
 			}
 
 
-			// Check for end of iteration error
+		} else {
 
 
-			if eoi, ok := err.(*util.RuntimeError); ok {
-				if eoi.Type == util.ErrEndOfIteration {
-					err = nil
+			// A single value will do exactly one iteration
+
+			index := -1
+
+			iterator = func() (interface{}, error) {
+				index++
+				if index > 0 {
+					return nil, rt.erp.NewRuntimeError(util.ErrEndOfIteration, "", rt.node)
 				}
 				}
+				return val, nil
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	return nil, err
+	return iterator, err
 }
 }
 
 
 // Break statement
 // Break statement
@@ -485,63 +512,6 @@ Eval evaluate this runtime component.
 func (rt *tryRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint64) (interface{}, error) {
 func (rt *tryRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint64) (interface{}, error) {
 	var res interface{}
 	var res interface{}
 
 
-	evalExcept := func(errObj map[interface{}]interface{}, except *parser.ASTNode) bool {
-		ret := false
-
-		if len(except.Children) == 1 {
-
-			// We only have statements - any exception is handled here
-
-			evs := vs.NewChild(scope.NameFromASTNode(except))
-
-			except.Children[0].Runtime.Eval(evs, is, tid)
-
-			ret = true
-
-		} else if len(except.Children) == 2 {
-
-			// We have statements and the error object is available - any exception is handled here
-
-			evs := vs.NewChild(scope.NameFromASTNode(except))
-			evs.SetValue(except.Children[0].Token.Val, errObj)
-
-			except.Children[1].Runtime.Eval(evs, is, tid)
-
-			ret = true
-
-		} else {
-			errorVar := ""
-
-			for i := 0; i < len(except.Children); i++ {
-				child := except.Children[i]
-
-				if !ret && child.Name == parser.NodeSTRING {
-					exceptError, evalErr := child.Runtime.Eval(vs, is, tid)
-
-					// If we fail evaluating the string we panic as otherwise
-					// we would need to generate a new error while trying to handle another error
-					errorutil.AssertOk(evalErr)
-
-					ret = exceptError == fmt.Sprint(errObj["type"])
-
-				} else if ret && child.Name == parser.NodeAS {
-					errorVar = child.Children[0].Token.Val
-
-				} else if ret && child.Name == parser.NodeSTATEMENTS {
-					evs := vs.NewChild(scope.NameFromASTNode(except))
-
-					if errorVar != "" {
-						evs.SetValue(errorVar, errObj)
-					}
-
-					child.Runtime.Eval(evs, is, tid)
-				}
-			}
-		}
-
-		return ret
-	}
-
 	// Make sure the finally block is executed in any case
 	// Make sure the finally block is executed in any case
 
 
 	if finally := rt.node.Children[len(rt.node.Children)-1]; finally.Name == parser.NodeFINALLY {
 	if finally := rt.node.Children[len(rt.node.Children)-1]; finally.Name == parser.NodeFINALLY {
@@ -591,7 +561,7 @@ func (rt *tryRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint6
 
 
 			for i := 1; i < len(rt.node.Children); i++ {
 			for i := 1; i < len(rt.node.Children); i++ {
 				if child := rt.node.Children[i]; child.Name == parser.NodeEXCEPT {
 				if child := rt.node.Children[i]; child.Name == parser.NodeEXCEPT {
-					if evalExcept(errObj, child) {
+					if rt.evalExcept(vs, is, tid, errObj, child) {
 						err = nil
 						err = nil
 						break
 						break
 					}
 					}
@@ -603,6 +573,64 @@ func (rt *tryRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint6
 	return res, err
 	return res, err
 }
 }
 
 
+func (rt *tryRuntime) evalExcept(vs parser.Scope, is map[string]interface{},
+	tid uint64, errObj map[interface{}]interface{}, except *parser.ASTNode) bool {
+	ret := false
+
+	if len(except.Children) == 1 {
+
+		// We only have statements - any exception is handled here
+
+		evs := vs.NewChild(scope.NameFromASTNode(except))
+
+		except.Children[0].Runtime.Eval(evs, is, tid)
+
+		ret = true
+
+	} else if len(except.Children) == 2 {
+
+		// We have statements and the error object is available - any exception is handled here
+
+		evs := vs.NewChild(scope.NameFromASTNode(except))
+		evs.SetValue(except.Children[0].Token.Val, errObj)
+
+		except.Children[1].Runtime.Eval(evs, is, tid)
+
+		ret = true
+
+	} else {
+		errorVar := ""
+
+		for i := 0; i < len(except.Children); i++ {
+			child := except.Children[i]
+
+			if !ret && child.Name == parser.NodeSTRING {
+				exceptError, evalErr := child.Runtime.Eval(vs, is, tid)
+
+				// If we fail evaluating the string we panic as otherwise
+				// we would need to generate a new error while trying to handle another error
+				errorutil.AssertOk(evalErr)
+
+				ret = exceptError == fmt.Sprint(errObj["type"])
+
+			} else if ret && child.Name == parser.NodeAS {
+				errorVar = child.Children[0].Token.Val
+
+			} else if ret && child.Name == parser.NodeSTATEMENTS {
+				evs := vs.NewChild(scope.NameFromASTNode(except))
+
+				if errorVar != "" {
+					evs.SetValue(errorVar, errObj)
+				}
+
+				child.Runtime.Eval(evs, is, tid)
+			}
+		}
+	}
+
+	return ret
+}
+
 // Mutex Runtime
 // Mutex Runtime
 // =============
 // =============
 
 

+ 18 - 7
interpreter/rt_statements_test.go

@@ -361,13 +361,16 @@ Info->4`[1:] {
 		t.Error("Unexpected result: ", res)
 		t.Error("Unexpected result: ", res)
 		return
 		return
 	}
 	}
+}
+
+func TestLoopStatements2(t *testing.T) {
 
 
 	// Test nested loops
 	// Test nested loops
 
 
-	vs = scope.NewScope(scope.GlobalScope)
-	buf = addLogFunction(vs)
+	vs := scope.NewScope(scope.GlobalScope)
+	buf := addLogFunction(vs)
 
 
-	_, err = UnitTestEvalAndAST(
+	_, err := UnitTestEvalAndAST(
 		`
 		`
 for a in range(10, 3, -3) {
 for a in range(10, 3, -3) {
   for b in range(1, 3, 1) {
   for b in range(1, 3, 1) {
@@ -571,12 +574,16 @@ Info->10`[1:] {
 		return
 		return
 	}
 	}
 
 
+}
+
+func TestLoopStatements3(t *testing.T) {
+
 	// Loop over lists
 	// Loop over lists
 
 
-	vs = scope.NewScope(scope.GlobalScope)
-	buf = addLogFunction(vs)
+	vs := scope.NewScope(scope.GlobalScope)
+	buf := addLogFunction(vs)
 
 
-	_, err = UnitTestEvalAndAST(
+	_, err := UnitTestEvalAndAST(
 		`
 		`
 for a in [1,2] {
 for a in [1,2] {
   for b in [1,2,3,"Hans", 4] {
   for b in [1,2,3,"Hans", 4] {
@@ -816,10 +823,14 @@ for [1, b] in x {
 		t.Error("Unexpected result:", err)
 		t.Error("Unexpected result:", err)
 		return
 		return
 	}
 	}
+}
+
+func TestLoopStatements4(t *testing.T) {
+	vs := scope.NewScope(scope.GlobalScope)
 
 
 	// Test continue
 	// Test continue
 
 
-	_, err = UnitTestEval(`
+	_, err := UnitTestEval(`
 for [a] in [1,2,3] {
 for [a] in [1,2,3] {
   continue
   continue
   [a, b] := "Hans"
   [a, b] := "Hans"

+ 14 - 36
parser/helper.go

@@ -287,7 +287,6 @@ The following nested map structure is expected:
 func ASTFromJSONObject(jsonAST map[string]interface{}) (*ASTNode, error) {
 func ASTFromJSONObject(jsonAST map[string]interface{}) (*ASTNode, error) {
 	var astMeta []MetaData
 	var astMeta []MetaData
 	var astChildren []*ASTNode
 	var astChildren []*ASTNode
-	var pos, line, linepos int
 
 
 	nodeID := TokenANY
 	nodeID := TokenANY
 
 
@@ -302,43 +301,22 @@ func ASTFromJSONObject(jsonAST map[string]interface{}) (*ASTNode, error) {
 		}
 		}
 	}
 	}
 
 
-	value, ok := jsonAST["value"]
-	if !ok {
-		value = ""
-	}
-
-	identifier, ok := jsonAST["identifier"]
-	if !ok {
-		identifier = false
-	}
-
-	allowescapes, ok := jsonAST["allowescapes"]
-	if !ok {
-		allowescapes = false
-	}
-
-	if posString, ok := jsonAST["pos"]; ok {
-		pos, _ = strconv.Atoi(fmt.Sprint(posString))
-	} else {
-		pos = 0
-	}
-
-	if lineString, ok := jsonAST["line"]; ok {
-		line, _ = strconv.Atoi(fmt.Sprint(lineString))
-	} else {
-		line = 0
-	}
-
-	if lineposString, ok := jsonAST["linepos"]; ok {
-		linepos, _ = strconv.Atoi(fmt.Sprint(lineposString))
-	} else {
-		linepos = 0
+	getVal := func(k string, d interface{}) (interface{}, int) {
+		value, ok := jsonAST[k]
+		if !ok {
+			value = d
+		}
+		numVal, _ := strconv.Atoi(fmt.Sprint(value))
+		return value, numVal
 	}
 	}
 
 
-	source, ok := jsonAST["source"]
-	if !ok {
-		source = ""
-	}
+	value, _ := getVal("value", "")
+	identifier, _ := getVal("identifier", false)
+	allowescapes, _ := getVal("allowescapes", false)
+	_, pos := getVal("pos", "")
+	_, line := getVal("line", "")
+	_, linepos := getVal("linepos", "")
+	source, _ := getVal("source", "")
 
 
 	// Create meta data
 	// Create meta data
 
 

+ 47 - 14
parser/helper_test.go

@@ -114,14 +114,17 @@ identifier: a
 		t.Error("Unexpected result: ", msg)
 		t.Error("Unexpected result: ", msg)
 		return
 		return
 	}
 	}
+}
+
+func TestASTNode2(t *testing.T) {
 
 
-	n, err = ParseWithRuntime("", "- 1", &DummyRuntimeProvider{})
+	n, err := ParseWithRuntime("", "- 1", &DummyRuntimeProvider{})
 	if err != nil {
 	if err != nil {
 		t.Error("Cannot parse test AST:", err)
 		t.Error("Cannot parse test AST:", err)
 		return
 		return
 	}
 	}
 
 
-	n2, err = ParseWithRuntime("", "a - b", &DummyRuntimeProvider{})
+	n2, err := ParseWithRuntime("", "a - b", &DummyRuntimeProvider{})
 	if err != nil {
 	if err != nil {
 		t.Error("Cannot parse test AST:", err)
 		t.Error("Cannot parse test AST:", err)
 		return
 		return
@@ -192,14 +195,17 @@ number: 1 # wurst
 		t.Error("Unexpected result: ", msg)
 		t.Error("Unexpected result: ", msg)
 		return
 		return
 	}
 	}
+}
+
+func TestASTNode3(t *testing.T) {
 
 
-	n, err = ParseWithRuntime("", "1 #test", &DummyRuntimeProvider{})
+	n, err := ParseWithRuntime("", "1 #test", &DummyRuntimeProvider{})
 	if err != nil {
 	if err != nil {
 		t.Error("Cannot parse test AST:", err)
 		t.Error("Cannot parse test AST:", err)
 		return
 		return
 	}
 	}
 
 
-	n2, err = ParseWithRuntime("", "/*test*/ 1", &DummyRuntimeProvider{})
+	n2, err := ParseWithRuntime("", "/*test*/ 1", &DummyRuntimeProvider{})
 	if err != nil {
 	if err != nil {
 		t.Error("Cannot parse test AST:", err)
 		t.Error("Cannot parse test AST:", err)
 		return
 		return
@@ -241,6 +247,9 @@ number: 1 # test
 		t.Error("Unexpected result: ", msg)
 		t.Error("Unexpected result: ", msg)
 		return
 		return
 	}
 	}
+}
+
+func TestASTNode4(t *testing.T) {
 
 
 	// Test building an AST from an invalid
 	// Test building an AST from an invalid
 
 
@@ -299,6 +308,11 @@ func TestLABuffer(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
+	testLABufferPeek(t, buf)
+}
+
+func testLABufferPeek(t *testing.T, buf *LABuffer) {
+
 	// Check Peek
 	// Check Peek
 
 
 	if token, ok := buf.Peek(0); token.Val != "3" || !ok {
 	if token, ok := buf.Peek(0); token.Val != "3" || !ok {
@@ -320,6 +334,10 @@ func TestLABuffer(t *testing.T) {
 		t.Error("Unexpected result: ", token, ok)
 		t.Error("Unexpected result: ", token, ok)
 		return
 		return
 	}
 	}
+	testLABufferContinue(t, buf)
+}
+
+func testLABufferContinue(t *testing.T, buf *LABuffer) {
 
 
 	// Continue
 	// Continue
 
 
@@ -352,6 +370,10 @@ func TestLABuffer(t *testing.T) {
 		t.Error("Unexpected result: ", token, ok)
 		t.Error("Unexpected result: ", token, ok)
 		return
 		return
 	}
 	}
+	testLABufferPeek2(t, buf)
+}
+
+func testLABufferPeek2(t *testing.T, buf *LABuffer) {
 
 
 	// Check Peek
 	// Check Peek
 
 
@@ -370,6 +392,11 @@ func TestLABuffer(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
+	testLABufferContinue2(t, buf)
+}
+
+func testLABufferContinue2(t *testing.T, buf *LABuffer) {
+
 	// Continue
 	// Continue
 
 
 	if token, ok := buf.Next(); token.Val != "9" || !ok {
 	if token, ok := buf.Next(); token.Val != "9" || !ok {
@@ -395,25 +422,28 @@ func TestLABuffer(t *testing.T) {
 		t.Error("Unexpected result: ", token, ok)
 		t.Error("Unexpected result: ", token, ok)
 		return
 		return
 	}
 	}
+}
+
+func TestLABuffer2(t *testing.T) {
 
 
 	// New Buffer
 	// New Buffer
 
 
-	buf = NewLABuffer(Lex("test", "1 2 3"), 3)
+	buf := NewLABuffer(Lex("test", "1 2 3"), 3)
 
 
-	if token, ok := buf.Next(); token.Val != "1" || !ok {
-		t.Error("Unexpected result: ", token, ok)
+	if token, _ := buf.Next(); token.Val != "1" {
+		t.Error("Unexpected result: ", token)
 		return
 		return
 	}
 	}
 
 
-	if token, ok := buf.Next(); token.Val != "2" || !ok {
-		t.Error("Unexpected result: ", token, ok)
+	if token, _ := buf.Next(); token.Val != "2" {
+		t.Error("Unexpected result: ", token)
 		return
 		return
 	}
 	}
 
 
 	// Check Peek
 	// Check Peek
 
 
-	if token, ok := buf.Peek(0); token.Val != "3" || !ok {
-		t.Error("Unexpected result: ", token, ok)
+	if token, _ := buf.Peek(0); token.Val != "3" {
+		t.Error("Unexpected result: ", token)
 		return
 		return
 	}
 	}
 
 
@@ -427,8 +457,8 @@ func TestLABuffer(t *testing.T) {
 		return
 		return
 	}
 	}
 
 
-	if token, ok := buf.Next(); token.Val != "3" || !ok {
-		t.Error("Unexpected result: ", token, ok)
+	if token, _ := buf.Next(); token.Val != "3" {
+		t.Error("Unexpected result: ", token)
 		return
 		return
 	}
 	}
 
 
@@ -436,10 +466,13 @@ func TestLABuffer(t *testing.T) {
 		t.Error("Unexpected result: ", token, ok)
 		t.Error("Unexpected result: ", token, ok)
 		return
 		return
 	}
 	}
+}
+
+func TestLABuffer3(t *testing.T) {
 
 
 	// New Buffer - test edge case
 	// New Buffer - test edge case
 
 
-	buf = NewLABuffer(Lex("test", ""), 0)
+	buf := NewLABuffer(Lex("test", ""), 0)
 
 
 	if token, ok := buf.Peek(0); token.ID != TokenEOF || !ok {
 	if token, ok := buf.Peek(0); token.ID != TokenEOF || !ok {
 		t.Error("Unexpected result: ", token, ok)
 		t.Error("Unexpected result: ", token, ok)

+ 9 - 2
parser/lexer.go

@@ -21,8 +21,15 @@ import (
 	"unicode/utf8"
 	"unicode/utf8"
 )
 )
 
 
+/*
+NamePattern is the pattern for valid names.
+*/
 var NamePattern = regexp.MustCompile("^[A-Za-z][A-Za-z0-9]*$")
 var NamePattern = regexp.MustCompile("^[A-Za-z][A-Za-z0-9]*$")
-var NumberPattern = regexp.MustCompile("^[0-9].*$")
+
+/*
+numberPattern is a hint pattern for numbers.
+*/
+var numberPattern = regexp.MustCompile("^[0-9].*$")
 
 
 /*
 /*
 LexToken represents a token which is returned by the lexer.
 LexToken represents a token which is returned by the lexer.
@@ -588,7 +595,7 @@ func lexToken(l *lexer) lexFunc {
 
 
 	// Check for number
 	// Check for number
 
 
-	if NumberPattern.MatchString(keywordCandidate) {
+	if numberPattern.MatchString(keywordCandidate) {
 		_, err := strconv.ParseFloat(keywordCandidate, 64)
 		_, err := strconv.ParseFloat(keywordCandidate, 64)
 
 
 		if err == nil {
 		if err == nil {

+ 46 - 56
parser/parser.go

@@ -427,7 +427,7 @@ func ndSkink(p *parser, self *ASTNode) (*ASTNode, error) {
 
 
 		// Parse the rest of the parameters as children until we reach the body
 		// Parse the rest of the parameters as children until we reach the body
 
 
-		for err == nil && IsNotEndAndNotToken(p, TokenLBRACE) {
+		for err == nil && IsNotEndAndNotTokens(p, []LexTokenID{TokenLBRACE}) {
 			if exp, err = p.run(150); err == nil {
 			if exp, err = p.run(150); err == nil {
 				self.Children = append(self.Children, exp)
 				self.Children = append(self.Children, exp)
 
 
@@ -471,7 +471,7 @@ func ndFunc(p *parser, self *ASTNode) (*ASTNode, error) {
 		params := astNodeMap[TokenPARAMS].instance(p, nil)
 		params := astNodeMap[TokenPARAMS].instance(p, nil)
 		self.Children = append(self.Children, params)
 		self.Children = append(self.Children, params)
 
 
-		for err == nil && IsNotEndAndNotToken(p, TokenRPAREN) {
+		for err == nil && IsNotEndAndNotTokens(p, []LexTokenID{TokenRPAREN}) {
 
 
 			// Parse all the expressions inside
 			// Parse all the expressions inside
 
 
@@ -535,6 +535,8 @@ func ndIdentifier(p *parser, self *ASTNode) (*ASTNode, error) {
 			err = parseFuncCall(current)
 			err = parseFuncCall(current)
 		} else if p.node.Token.ID == TokenLBRACK && p.node.Token.Lline == self.Token.Lline {
 		} else if p.node.Token.ID == TokenLBRACK && p.node.Token.Lline == self.Token.Lline {
 
 
+			skipToken(p, TokenLBRACK)
+
 			// Composition access needs to be on the same line as the identifier
 			// Composition access needs to be on the same line as the identifier
 			// as we might otherwise have a list
 			// as we might otherwise have a list
 
 
@@ -568,7 +570,7 @@ func ndIdentifier(p *parser, self *ASTNode) (*ASTNode, error) {
 
 
 		// Read in parameters
 		// Read in parameters
 
 
-		for err == nil && IsNotEndAndNotToken(p, TokenRPAREN) {
+		for err == nil && IsNotEndAndNotTokens(p, []LexTokenID{TokenRPAREN}) {
 
 
 			// Parse all the expressions inside the directives
 			// Parse all the expressions inside the directives
 
 
@@ -582,8 +584,7 @@ func ndIdentifier(p *parser, self *ASTNode) (*ASTNode, error) {
 		}
 		}
 
 
 		if err == nil {
 		if err == nil {
-			err = skipToken(p, TokenRPAREN)
-			if err == nil {
+			if err = skipToken(p, TokenRPAREN); err == nil {
 				err = parseMore(current)
 				err = parseMore(current)
 			}
 			}
 		}
 		}
@@ -593,22 +594,18 @@ func ndIdentifier(p *parser, self *ASTNode) (*ASTNode, error) {
 
 
 	parseCompositionAccess = func(current *ASTNode) error {
 	parseCompositionAccess = func(current *ASTNode) error {
 		var exp *ASTNode
 		var exp *ASTNode
+		var err error
 
 
-		err := skipToken(p, TokenLBRACK)
-
-		if err == nil {
-
-			ca := astNodeMap[TokenCOMPACCESS].instance(p, nil)
-			current.Children = append(current.Children, ca)
+		ca := astNodeMap[TokenCOMPACCESS].instance(p, nil)
+		current.Children = append(current.Children, ca)
 
 
-			// Parse all the expressions inside the directives
+		// Parse all the expressions inside the directives
 
 
-			if exp, err = p.run(0); err == nil {
-				ca.Children = append(ca.Children, exp)
+		if exp, err = p.run(0); err == nil {
+			ca.Children = append(ca.Children, exp)
 
 
-				if err = skipToken(p, TokenRBRACK); err == nil {
-					err = parseMore(current)
-				}
+			if err = skipToken(p, TokenRBRACK); err == nil {
+				err = parseMore(current)
 			}
 			}
 		}
 		}
 
 
@@ -631,7 +628,7 @@ func ndList(p *parser, self *ASTNode) (*ASTNode, error) {
 
 
 	// Get the inner expression
 	// Get the inner expression
 
 
-	for err == nil && IsNotEndAndNotToken(p, TokenRBRACK) {
+	for err == nil && IsNotEndAndNotTokens(p, []LexTokenID{TokenRBRACK}) {
 
 
 		// Parse all the expressions inside
 		// Parse all the expressions inside
 
 
@@ -666,7 +663,7 @@ func ndMap(p *parser, self *ASTNode) (*ASTNode, error) {
 
 
 	// Get the inner expression
 	// Get the inner expression
 
 
-	for err == nil && IsNotEndAndNotToken(p, TokenRBRACE) {
+	for err == nil && IsNotEndAndNotTokens(p, []LexTokenID{TokenRBRACE}) {
 
 
 		// Parse all the expressions inside
 		// Parse all the expressions inside
 
 
@@ -778,55 +775,44 @@ func ndLoop(p *parser, self *ASTNode) (*ASTNode, error) {
 ndTry is used to parse a try block.
 ndTry is used to parse a try block.
 */
 */
 func ndTry(p *parser, self *ASTNode) (*ASTNode, error) {
 func ndTry(p *parser, self *ASTNode) (*ASTNode, error) {
-
 	try, err := parseInnerStatements(p, self)
 	try, err := parseInnerStatements(p, self)
 
 
-	if p.node.Token.ID != TokenFINALLY {
+	for err == nil && IsNotEndAndToken(p, TokenEXCEPT) {
+		except := p.node
 
 
-		for err == nil && IsNotEndAndToken(p, TokenEXCEPT) {
+		err = acceptChild(p, try, TokenEXCEPT)
 
 
-			except := p.node
+		for err == nil &&
+			IsNotEndAndNotTokens(p, []LexTokenID{TokenAS, TokenIDENTIFIER, TokenLBRACE}) {
 
 
-			err = acceptChild(p, try, TokenEXCEPT)
+			if err = acceptChild(p, except, TokenSTRING); err == nil {
 
 
-			if err == nil {
-				for err == nil &&
-					IsNotEndAndNotToken(p, TokenAS) &&
-					IsNotEndAndNotToken(p, TokenIDENTIFIER) &&
-					IsNotEndAndNotToken(p, TokenLBRACE) {
-
-					if err = acceptChild(p, except, TokenSTRING); err == nil {
-
-						// Skip commas
+				// Skip commas
 
 
-						if p.node.Token.ID == TokenCOMMA {
-							err = skipToken(p, TokenCOMMA)
-						}
-					}
+				if p.node.Token.ID == TokenCOMMA {
+					err = skipToken(p, TokenCOMMA)
 				}
 				}
+			}
+		}
 
 
-				if err == nil {
-
-					if p.node.Token.ID == TokenAS {
-						as := p.node
-
-						err = acceptChild(p, except, TokenAS)
-
-						if err == nil {
-							err = acceptChild(p, as, TokenIDENTIFIER)
-						}
+		if err == nil {
 
 
-					} else if p.node.Token.ID == TokenIDENTIFIER {
+			if p.node.Token.ID == TokenAS {
+				as := p.node
 
 
-						err = acceptChild(p, except, TokenIDENTIFIER)
-					}
+				if err = acceptChild(p, except, TokenAS); err == nil {
+					err = acceptChild(p, as, TokenIDENTIFIER)
 				}
 				}
 
 
-				if err == nil {
-					_, err = parseInnerStatements(p, except)
-				}
+			} else if p.node.Token.ID == TokenIDENTIFIER {
+
+				err = acceptChild(p, except, TokenIDENTIFIER)
 			}
 			}
 		}
 		}
+
+		if err == nil {
+			_, err = parseInnerStatements(p, except)
+		}
 	}
 	}
 
 
 	if err == nil && p.node.Token.ID == TokenFINALLY {
 	if err == nil && p.node.Token.ID == TokenFINALLY {
@@ -885,10 +871,14 @@ func IsNotEndAndToken(p *parser, i LexTokenID) bool {
 }
 }
 
 
 /*
 /*
-IsNotEndAndNotToken checks if the next token is not of a specific type or the end has been reached.
+IsNotEndAndNotTokens checks if the next token is not of a specific type or the end has been reached.
 */
 */
-func IsNotEndAndNotToken(p *parser, i LexTokenID) bool {
-	return p.node != nil && p.node.Name != NodeEOF && p.node.Token.ID != i
+func IsNotEndAndNotTokens(p *parser, tokens []LexTokenID) bool {
+	ret := p.node != nil && p.node.Name != NodeEOF
+	for _, t := range tokens {
+		ret = ret && p.node.Token.ID != t
+	}
+	return ret
 }
 }
 
 
 /*
 /*

+ 184 - 148
parser/prettyprinter.go

@@ -155,33 +155,14 @@ PrettyPrint produces pretty printed code from a given AST.
 func PrettyPrint(ast *ASTNode) (string, error) {
 func PrettyPrint(ast *ASTNode) (string, error) {
 	var visit func(ast *ASTNode, level int) (string, error)
 	var visit func(ast *ASTNode, level int) (string, error)
 
 
-	ppMetaData := func(ast *ASTNode, ppString string) string {
-		ret := ppString
-
-		// Add meta data
-
-		if len(ast.Meta) > 0 {
-			for _, meta := range ast.Meta {
-				if meta.Type() == MetaDataPreComment {
-					ret = fmt.Sprintf("/*%v*/ %v", meta.Value(), ret)
-				} else if meta.Type() == MetaDataPostComment {
-					ret = fmt.Sprintf("%v #%v", ret, meta.Value())
-				}
-			}
-		}
-
-		return ret
-	}
-
 	visit = func(ast *ASTNode, level int) (string, error) {
 	visit = func(ast *ASTNode, level int) (string, error) {
 		var buf bytes.Buffer
 		var buf bytes.Buffer
-		var numChildren int
 
 
 		if ast == nil {
 		if ast == nil {
 			return "", fmt.Errorf("Nil pointer in AST at level: %v", level)
 			return "", fmt.Errorf("Nil pointer in AST at level: %v", level)
 		}
 		}
 
 
-		numChildren = len(ast.Children)
+		numChildren := len(ast.Children)
 
 
 		tempKey := ast.Name
 		tempKey := ast.Name
 		tempParam := make(map[string]string)
 		tempParam := make(map[string]string)
@@ -209,194 +190,249 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			tempKey += fmt.Sprint("_", len(tempParam))
 			tempKey += fmt.Sprint("_", len(tempParam))
 		}
 		}
 
 
-		// Handle special cases - children in tempParam have been resolved
+		if res, ok := ppSpecialDefs(ast, level, tempParam, &buf); ok {
+			return res, nil
+		} else if res, ok := ppSpecialBlocks(ast, level, tempParam, &buf); ok {
+			return res, nil
+		} else if res, ok := ppSpecialStatements(ast, level, tempParam, &buf); ok {
+			return res, nil
+		}
 
 
-		if ast.Name == NodeSTATEMENTS {
+		if ast.Token != nil {
 
 
-			// For statements just concat all children
+			// Adding node value to template parameters
 
 
-			for i := 0; i < numChildren; i++ {
-				buf.WriteString(stringutil.GenerateRollingString(" ", level*4))
-				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
-				buf.WriteString("\n")
-			}
+			tempParam["val"] = ast.Token.Val
+			tempParam["qval"] = strconv.Quote(ast.Token.Val)
+		}
 
 
-			return ppMetaData(ast, buf.String()), nil
+		// Retrieve the template
 
 
-		} else if ast.Name == NodeSINK {
+		temp, ok := prettyPrinterMap[tempKey]
+		errorutil.AssertTrue(ok,
+			fmt.Sprintf("Could not find template for %v (tempkey: %v)",
+				ast.Name, tempKey))
 
 
-			buf.WriteString("sink ")
-			buf.WriteString(tempParam["c1"])
-			buf.WriteString("\n")
+		// Use the children as parameters for template
 
 
-			for i := 1; i < len(ast.Children)-1; i++ {
-				buf.WriteString("  ")
-				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
-				buf.WriteString("\n")
-			}
+		errorutil.AssertOk(temp.Execute(&buf, tempParam))
 
 
-			buf.WriteString("{\n")
-			buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
-			buf.WriteString("}\n")
+		return ppMetaData(ast, buf.String()), nil
+	}
 
 
-			return ppMetaData(ast, buf.String()), nil
+	return visit(ast, 0)
+}
 
 
-		} else if ast.Name == NodeFUNCCALL {
+/*
+ppMetaData pretty prints meta data.
+*/
+func ppMetaData(ast *ASTNode, ppString string) string {
+	ret := ppString
 
 
-			// For statements just concat all children
+	// Add meta data
 
 
-			for i := 0; i < numChildren; i++ {
-				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
-				if i < numChildren-1 {
-					buf.WriteString(", ")
-				}
+	if len(ast.Meta) > 0 {
+		for _, meta := range ast.Meta {
+			if meta.Type() == MetaDataPreComment {
+				ret = fmt.Sprintf("/*%v*/ %v", meta.Value(), ret)
+			} else if meta.Type() == MetaDataPostComment {
+				ret = fmt.Sprintf("%v #%v", ret, meta.Value())
 			}
 			}
+		}
+	}
 
 
-			return ppMetaData(ast, buf.String()), nil
+	return ret
+}
 
 
-		} else if ast.Name == NodeIDENTIFIER {
+/*
+ppSpecialDefs pretty prints special cases.
+*/
+func ppSpecialDefs(ast *ASTNode, level int, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+	numChildren := len(ast.Children)
 
 
-			buf.WriteString(ast.Token.Val)
+	if ast.Name == NodeFUNCCALL {
 
 
-			for i := 0; i < numChildren; i++ {
-				if ast.Children[i].Name == NodeIDENTIFIER {
-					buf.WriteString(".")
-					buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
-				} else if ast.Children[i].Name == NodeFUNCCALL {
-					buf.WriteString("(")
-					buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
-					buf.WriteString(")")
-				} else if ast.Children[i].Name == NodeCOMPACCESS {
-					buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
-				}
+		for i := 0; i < numChildren; i++ {
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+			if i < numChildren-1 {
+				buf.WriteString(", ")
 			}
 			}
+		}
 
 
-			return ppMetaData(ast, buf.String()), nil
+		return ppMetaData(ast, buf.String()), true
 
 
-		} else if ast.Name == NodeLIST {
+	} else if ast.Name == NodeSINK {
 
 
-			buf.WriteString("[")
-			i := 1
-			for ; i < numChildren; i++ {
-				buf.WriteString(tempParam[fmt.Sprint("c", i)])
-				buf.WriteString(", ")
-			}
-			buf.WriteString(tempParam[fmt.Sprint("c", i)])
-			buf.WriteString("]")
+		buf.WriteString("sink ")
+		buf.WriteString(tempParam["c1"])
+		buf.WriteString("\n")
 
 
-			return ppMetaData(ast, buf.String()), nil
+		for i := 1; i < len(ast.Children)-1; i++ {
+			buf.WriteString("  ")
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+			buf.WriteString("\n")
+		}
 
 
-		} else if ast.Name == NodeMAP {
+		buf.WriteString("{\n")
+		buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
+		buf.WriteString("}\n")
 
 
-			buf.WriteString("{")
-			i := 1
-			for ; i < numChildren; i++ {
-				buf.WriteString(tempParam[fmt.Sprint("c", i)])
-				buf.WriteString(", ")
-			}
-			buf.WriteString(tempParam[fmt.Sprint("c", i)])
-			buf.WriteString("}")
+		return ppMetaData(ast, buf.String()), true
+	}
 
 
-			return ppMetaData(ast, buf.String()), nil
+	return "", false
+}
 
 
-		} else if ast.Name == NodePARAMS {
+/*
+ppSpecialBlocks pretty prints special cases.
+*/
+func ppSpecialBlocks(ast *ASTNode, level int, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+	numChildren := len(ast.Children)
 
 
-			buf.WriteString("(")
-			i := 1
-			for ; i < numChildren; i++ {
-				buf.WriteString(tempParam[fmt.Sprint("c", i)])
-				buf.WriteString(", ")
-			}
+	// Handle special cases - children in tempParam have been resolved
+
+	if stringutil.IndexOf(ast.Name, []string{NodeSTATEMENTS}) != -1 {
+
+		// For statements just concat all children
+
+		for i := 0; i < numChildren; i++ {
+			buf.WriteString(stringutil.GenerateRollingString(" ", level*4))
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+			buf.WriteString("\n")
+		}
+
+		return ppMetaData(ast, buf.String()), true
+
+	} else if ast.Name == NodeLIST {
+
+		buf.WriteString("[")
+		i := 1
+		for ; i < numChildren; i++ {
 			buf.WriteString(tempParam[fmt.Sprint("c", i)])
 			buf.WriteString(tempParam[fmt.Sprint("c", i)])
-			buf.WriteString(")")
+			buf.WriteString(", ")
+		}
+		buf.WriteString(tempParam[fmt.Sprint("c", i)])
+		buf.WriteString("]")
 
 
-			return ppMetaData(ast, buf.String()), nil
+		return ppMetaData(ast, buf.String()), true
 
 
-		} else if ast.Name == NodeIF {
+	} else if ast.Name == NodeMAP {
 
 
-			writeGUARD := func(child int) {
-				buf.WriteString(tempParam[fmt.Sprint("c", child)])
-				buf.WriteString(" {\n")
-				buf.WriteString(tempParam[fmt.Sprint("c", child+1)])
-				buf.WriteString("}")
-			}
+		buf.WriteString("{")
+		i := 1
+		for ; i < numChildren; i++ {
+			buf.WriteString(tempParam[fmt.Sprint("c", i)])
+			buf.WriteString(", ")
+		}
+		buf.WriteString(tempParam[fmt.Sprint("c", i)])
+		buf.WriteString("}")
 
 
-			buf.WriteString("if ")
+		return ppMetaData(ast, buf.String()), true
 
 
-			writeGUARD(1)
+	} else if ast.Name == NodeTRY {
 
 
-			for i := 0; i < len(ast.Children); i += 2 {
-				if i+2 == len(ast.Children) && ast.Children[i].Children[0].Name == NodeTRUE {
-					buf.WriteString(" else {\n")
-					buf.WriteString(tempParam[fmt.Sprint("c", i+2)])
-					buf.WriteString("}")
-				} else if i > 0 {
-					buf.WriteString(" elif ")
-					writeGUARD(i + 1)
-				}
-			}
+		buf.WriteString("try {\n")
+		buf.WriteString(tempParam[fmt.Sprint("c1")])
 
 
-			buf.WriteString("\n")
+		buf.WriteString("}")
 
 
-			return ppMetaData(ast, buf.String()), nil
+		for i := 1; i < len(ast.Children); i++ {
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+		}
 
 
-		} else if ast.Name == NodeTRY {
+		buf.WriteString("\n")
 
 
-			buf.WriteString("try {\n")
-			buf.WriteString(tempParam[fmt.Sprint("c1")])
+		return ppMetaData(ast, buf.String()), true
 
 
-			buf.WriteString("}")
+	} else if ast.Name == NodeEXCEPT {
 
 
-			for i := 1; i < len(ast.Children); i++ {
-				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+		buf.WriteString(" except ")
+
+		for i := 0; i < len(ast.Children)-1; i++ {
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+
+			if ast.Children[i+1].Name != NodeAS && i < len(ast.Children)-2 {
+				buf.WriteString(",")
 			}
 			}
+			buf.WriteString(" ")
+		}
 
 
-			buf.WriteString("\n")
+		buf.WriteString("{\n")
+		buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
+		buf.WriteString("}")
 
 
-			return ppMetaData(ast, buf.String()), nil
+		return ppMetaData(ast, buf.String()), true
+	}
 
 
-		} else if ast.Name == NodeEXCEPT {
-			buf.WriteString(" except ")
+	return "", false
+}
 
 
-			for i := 0; i < len(ast.Children)-1; i++ {
-				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+/*
+ppSpecialStatements pretty prints special cases.
+*/
+func ppSpecialStatements(ast *ASTNode, level int, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+	numChildren := len(ast.Children)
 
 
-				if ast.Children[i+1].Name != NodeAS && i < len(ast.Children)-2 {
-					buf.WriteString(",")
-				}
-				buf.WriteString(" ")
-			}
+	if ast.Name == NodeIDENTIFIER {
+
+		buf.WriteString(ast.Token.Val)
 
 
-			buf.WriteString("{\n")
+		for i := 0; i < numChildren; i++ {
+			if ast.Children[i].Name == NodeIDENTIFIER {
+				buf.WriteString(".")
+				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+			} else if ast.Children[i].Name == NodeFUNCCALL {
+				buf.WriteString("(")
+				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+				buf.WriteString(")")
+			} else if ast.Children[i].Name == NodeCOMPACCESS {
+				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+			}
+		}
 
 
-			buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
+		return ppMetaData(ast, buf.String()), true
 
 
-			buf.WriteString("}")
+	} else if ast.Name == NodePARAMS {
 
 
-			return ppMetaData(ast, buf.String()), nil
+		buf.WriteString("(")
+		i := 1
+		for ; i < numChildren; i++ {
+			buf.WriteString(tempParam[fmt.Sprint("c", i)])
+			buf.WriteString(", ")
 		}
 		}
+		buf.WriteString(tempParam[fmt.Sprint("c", i)])
+		buf.WriteString(")")
 
 
-		if ast.Token != nil {
+		return ppMetaData(ast, buf.String()), true
 
 
-			// Adding node value to template parameters
+	} else if ast.Name == NodeIF {
 
 
-			tempParam["val"] = ast.Token.Val
-			tempParam["qval"] = strconv.Quote(ast.Token.Val)
+		writeGUARD := func(child int) {
+			buf.WriteString(tempParam[fmt.Sprint("c", child)])
+			buf.WriteString(" {\n")
+			buf.WriteString(tempParam[fmt.Sprint("c", child+1)])
+			buf.WriteString("}")
 		}
 		}
 
 
-		// Retrieve the template
+		buf.WriteString("if ")
 
 
-		temp, ok := prettyPrinterMap[tempKey]
-		errorutil.AssertTrue(ok,
-			fmt.Sprintf("Could not find template for %v (tempkey: %v)",
-				ast.Name, tempKey))
+		writeGUARD(1)
 
 
-		// Use the children as parameters for template
+		for i := 0; i < len(ast.Children); i += 2 {
+			if i+2 == len(ast.Children) && ast.Children[i].Children[0].Name == NodeTRUE {
+				buf.WriteString(" else {\n")
+				buf.WriteString(tempParam[fmt.Sprint("c", i+2)])
+				buf.WriteString("}")
+			} else if i > 0 {
+				buf.WriteString(" elif ")
+				writeGUARD(i + 1)
+			}
+		}
 
 
-		errorutil.AssertOk(temp.Execute(&buf, tempParam))
+		buf.WriteString("\n")
 
 
-		return ppMetaData(ast, buf.String()), nil
+		return ppMetaData(ast, buf.String()), true
 	}
 	}
 
 
-	return visit(ast, 0)
+	return "", false
 }
 }

+ 54 - 51
scope/varsscope.go

@@ -156,57 +156,7 @@ func (s *varsScope) setValue(varName string, varValue interface{}) error {
 		if container, ok, _ := s.getValue(cFields[0]); ok {
 		if container, ok, _ := s.getValue(cFields[0]); ok {
 
 
 			if len(cFields) > 2 {
 			if len(cFields) > 2 {
-
-				var containerAccess func(fields []string)
-
-				containerAccess = func(fields []string) {
-
-					// Get inner container
-
-					if mapContainer, ok := container.(map[interface{}]interface{}); ok {
-
-						if container, ok = mapContainer[fields[0]]; !ok {
-							err = fmt.Errorf("Container field %v does not exist",
-								strings.Join(cFields[:len(cFields)-len(fields)+1], "."))
-						}
-
-					} else if listContainer, ok := container.([]interface{}); ok {
-						var index int
-
-						if index, err = strconv.Atoi(fmt.Sprint(fields[0])); err == nil {
-
-							if index < 0 {
-
-								// Handle negative numbers
-
-								index = len(listContainer) + index
-							}
-
-							if index < len(listContainer) {
-								container = listContainer[index]
-							} else {
-								err = fmt.Errorf("Out of bounds access to list %v with index: %v",
-									strings.Join(cFields[:len(cFields)-len(fields)], "."), index)
-							}
-
-						} else {
-							container = nil
-							err = fmt.Errorf("List %v needs a number index not: %v",
-								strings.Join(cFields[:len(cFields)-len(fields)], "."), fields[0])
-						}
-
-					} else {
-						container = nil
-						err = fmt.Errorf("Variable %v is not a container",
-							strings.Join(cFields[:len(cFields)-len(fields)], "."))
-					}
-
-					if err == nil && len(fields) > 2 {
-						containerAccess(fields[1:])
-					}
-				}
-
-				containerAccess(cFields[1:])
+				container, err = s.containerAccess(container, cFields, cFields[1:])
 			}
 			}
 
 
 			if err == nil && container != nil {
 			if err == nil && container != nil {
@@ -265,6 +215,59 @@ func (s *varsScope) setValue(varName string, varValue interface{}) error {
 	return err
 	return err
 }
 }
 
 
+/*
+containerAccess recursively accesses a field in a container structure.
+*/
+func (s *varsScope) containerAccess(container interface{}, cFields []string, fields []string) (interface{}, error) {
+	var err error
+
+	// Get inner container
+
+	if mapContainer, ok := container.(map[interface{}]interface{}); ok {
+
+		if container, ok = mapContainer[fields[0]]; !ok {
+			err = fmt.Errorf("Container field %v does not exist",
+				strings.Join(cFields[:len(cFields)-len(fields)+1], "."))
+		}
+
+	} else if listContainer, ok := container.([]interface{}); ok {
+		var index int
+
+		if index, err = strconv.Atoi(fmt.Sprint(fields[0])); err == nil {
+
+			if index < 0 {
+
+				// Handle negative numbers
+
+				index = len(listContainer) + index
+			}
+
+			if index < len(listContainer) {
+				container = listContainer[index]
+			} else {
+				err = fmt.Errorf("Out of bounds access to list %v with index: %v",
+					strings.Join(cFields[:len(cFields)-len(fields)], "."), index)
+			}
+
+		} else {
+			container = nil
+			err = fmt.Errorf("List %v needs a number index not: %v",
+				strings.Join(cFields[:len(cFields)-len(fields)], "."), fields[0])
+		}
+
+	} else {
+		container = nil
+		err = fmt.Errorf("Variable %v is not a container",
+			strings.Join(cFields[:len(cFields)-len(fields)], "."))
+	}
+
+	if err == nil && len(fields) > 2 {
+		container, err = s.containerAccess(container, cFields, fields[1:])
+	}
+
+	return container, err
+}
+
 /*
 /*
 getScopeForVariable returns the scope (this or a parent scope) which holds a
 getScopeForVariable returns the scope (this or a parent scope) which holds a
 given variable.
 given variable.

+ 17 - 10
scope/varsscope_test.go

@@ -14,6 +14,8 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"testing"
 	"testing"
+
+	"devt.de/krotik/ecal/parser"
 )
 )
 
 
 func TestVarScopeSetMap(t *testing.T) {
 func TestVarScopeSetMap(t *testing.T) {
@@ -115,65 +117,70 @@ global {
 		return
 		return
 	}
 	}
 
 
-	err = parentVS.SetValue("xx.a", []interface{}{3, 4, 5})
+	testVarScopeSetMapErrors(t, parentVS)
+}
+
+func testVarScopeSetMapErrors(t *testing.T, parentVS parser.Scope) {
+
+	err := parentVS.SetValue("xx.a", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "List xx needs a number index not: a" {
+	if err.Error() != "List xx needs a number index not: a" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.2.b", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.2.b", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "List xx.2 needs a number index not: b" {
+	if err.Error() != "List xx.2 needs a number index not: b" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.2.b.1", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.2.b.1", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "List xx.2 needs a number index not: b" {
+	if err.Error() != "List xx.2 needs a number index not: b" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.2.1.b.1", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.2.1.b.1", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "Variable xx.2.1 is not a container" {
+	if err.Error() != "Variable xx.2.1 is not a container" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.2.1.2", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.2.1.2", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "Variable xx.2.1 is not a container" {
+	if err.Error() != "Variable xx.2.1 is not a container" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.5", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.5", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "Out of bounds access to list xx with index: 5" {
+	if err.Error() != "Out of bounds access to list xx with index: 5" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.5.1", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.5.1", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "Out of bounds access to list xx with index: 5" {
+	if err.Error() != "Out of bounds access to list xx with index: 5" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("xx.2.5.1", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("xx.2.5.1", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "Out of bounds access to list xx.2 with index: 5" {
+	if err.Error() != "Out of bounds access to list xx.2 with index: 5" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}
 
 
 	err = parentVS.SetValue("yy.2.5.1", []interface{}{3, 4, 5})
 	err = parentVS.SetValue("yy.2.5.1", []interface{}{3, 4, 5})
 
 
-	if err == nil || err.Error() != "Variable yy is not a container" {
+	if err.Error() != "Variable yy is not a container" {
 		t.Error("Unexpected result:", parentVS.String(), err)
 		t.Error("Unexpected result:", parentVS.String(), err)
 		return
 		return
 	}
 	}

+ 52 - 36
stdlib/adapter.go

@@ -61,32 +61,7 @@ func (ea *ECALFunctionAdapter) Run(instanceID string, vs parser.Scope,
 		// Try to convert into correct number types
 		// Try to convert into correct number types
 
 
 		if float64Arg, ok := arg.(float64); ok {
 		if float64Arg, ok := arg.(float64); ok {
-			switch expectedType.Kind() {
-			case reflect.Int:
-				arg = int(float64Arg)
-			case reflect.Int8:
-				arg = int8(float64Arg)
-			case reflect.Int16:
-				arg = int16(float64Arg)
-			case reflect.Int32:
-				arg = int32(float64Arg)
-			case reflect.Int64:
-				arg = int64(float64Arg)
-			case reflect.Uint:
-				arg = uint(float64Arg)
-			case reflect.Uint8:
-				arg = uint8(float64Arg)
-			case reflect.Uint16:
-				arg = uint16(float64Arg)
-			case reflect.Uint32:
-				arg = uint32(float64Arg)
-			case reflect.Uint64:
-				arg = uint64(float64Arg)
-			case reflect.Uintptr:
-				arg = uintptr(float64Arg)
-			case reflect.Float32:
-				arg = float32(float64Arg)
-			}
+			arg = ea.convertNumber(arg, float64Arg, expectedType)
 		}
 		}
 
 
 		givenType := reflect.TypeOf(arg)
 		givenType := reflect.TypeOf(arg)
@@ -134,16 +109,7 @@ func (ea *ECALFunctionAdapter) Run(instanceID string, vs parser.Scope,
 
 
 		// Convert result if it is a primitive type
 		// Convert result if it is a primitive type
 
 
-		switch v.Kind() {
-		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-			res = float64(v.Int())
-		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-			res = float64(v.Uint())
-		case reflect.Float32, reflect.Float64:
-			res = v.Float()
-		}
-
-		results = append(results, res)
+		results = append(results, ea.convertResultNumber(res, v))
 	}
 	}
 
 
 	ret = results
 	ret = results
@@ -157,6 +123,56 @@ func (ea *ECALFunctionAdapter) Run(instanceID string, vs parser.Scope,
 	return ret, err
 	return ret, err
 }
 }
 
 
+/*
+convertNumber converts number arguments into the right type.
+*/
+func (ea *ECALFunctionAdapter) convertNumber(arg interface{}, float64Arg float64, expectedType reflect.Type) interface{} {
+	switch expectedType.Kind() {
+	case reflect.Int:
+		arg = int(float64Arg)
+	case reflect.Int8:
+		arg = int8(float64Arg)
+	case reflect.Int16:
+		arg = int16(float64Arg)
+	case reflect.Int32:
+		arg = int32(float64Arg)
+	case reflect.Int64:
+		arg = int64(float64Arg)
+	case reflect.Uint:
+		arg = uint(float64Arg)
+	case reflect.Uint8:
+		arg = uint8(float64Arg)
+	case reflect.Uint16:
+		arg = uint16(float64Arg)
+	case reflect.Uint32:
+		arg = uint32(float64Arg)
+	case reflect.Uint64:
+		arg = uint64(float64Arg)
+	case reflect.Uintptr:
+		arg = uintptr(float64Arg)
+	case reflect.Float32:
+		arg = float32(float64Arg)
+	}
+
+	return arg
+}
+
+/*
+convertResultNumber converts result numbers into the right type.
+*/
+func (ea *ECALFunctionAdapter) convertResultNumber(res interface{}, v reflect.Value) interface{} {
+	switch v.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		res = float64(v.Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		res = float64(v.Uint())
+	case reflect.Float32, reflect.Float64:
+		res = v.Float()
+	}
+
+	return res
+}
+
 /*
 /*
 DocString returns the docstring of the wrapped function.
 DocString returns the docstring of the wrapped function.
 */
 */

+ 31 - 20
stdlib/adapter_test.go

@@ -21,14 +21,14 @@ import (
 	"devt.de/krotik/ecal/scope"
 	"devt.de/krotik/ecal/scope"
 )
 )
 
 
-func TestECALFunctionAdapter(t *testing.T) {
+func TestECALFunctionAdapterSimple(t *testing.T) {
 
 
 	res, err := runAdapterTest(
 	res, err := runAdapterTest(
 		reflect.ValueOf(strconv.Atoi),
 		reflect.ValueOf(strconv.Atoi),
 		[]interface{}{"1"},
 		[]interface{}{"1"},
 	)
 	)
 
 
-	if err != nil || res != float64(1) {
+	if errorutil.AssertOk(err); res != float64(1) {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -38,7 +38,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{"123", float64(0), float64(0)},
 		[]interface{}{"123", float64(0), float64(0)},
 	)
 	)
 
 
-	if err != nil || res != float64(123) {
+	if errorutil.AssertOk(err); res != float64(123) {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -48,7 +48,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{"123.123", float64(0)},
 		[]interface{}{"123.123", float64(0)},
 	)
 	)
 
 
-	if err != nil || res != float64(123.123) {
+	if errorutil.AssertOk(err); res != float64(123.123) {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -58,7 +58,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{"foo %v", "bar"},
 		[]interface{}{"foo %v", "bar"},
 	)
 	)
 
 
-	if err != nil || res != "foo bar" {
+	if errorutil.AssertOk(err); res != "foo bar" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -67,8 +67,9 @@ func TestECALFunctionAdapter(t *testing.T) {
 		reflect.ValueOf(math.Float32bits),
 		reflect.ValueOf(math.Float32bits),
 		[]interface{}{float64(11)},
 		[]interface{}{float64(11)},
 	)
 	)
+	errorutil.AssertOk(err)
 
 
-	if r := fmt.Sprintf("%X", uint32(res.(float64))); err != nil || r != fmt.Sprintf("%X", math.Float32bits(11)) {
+	if r := fmt.Sprintf("%X", uint32(res.(float64))); r != fmt.Sprintf("%X", math.Float32bits(11)) {
 		t.Error("Unexpected result: ", r, err)
 		t.Error("Unexpected result: ", r, err)
 		return
 		return
 	}
 	}
@@ -77,8 +78,9 @@ func TestECALFunctionAdapter(t *testing.T) {
 		reflect.ValueOf(math.Float32frombits),
 		reflect.ValueOf(math.Float32frombits),
 		[]interface{}{float64(math.Float32bits(11))},
 		[]interface{}{float64(math.Float32bits(11))},
 	)
 	)
+	errorutil.AssertOk(err)
 
 
-	if r := fmt.Sprintf("%v", res.(float64)); err != nil || r != "11" {
+	if r := fmt.Sprintf("%v", res.(float64)); r != "11" {
 		t.Error("Unexpected result: ", r, err)
 		t.Error("Unexpected result: ", r, err)
 		return
 		return
 	}
 	}
@@ -87,18 +89,23 @@ func TestECALFunctionAdapter(t *testing.T) {
 		reflect.ValueOf(math.Float32frombits),
 		reflect.ValueOf(math.Float32frombits),
 		[]interface{}{math.Float32bits(11)}, // Giving the correct type also works
 		[]interface{}{math.Float32bits(11)}, // Giving the correct type also works
 	)
 	)
+	errorutil.AssertOk(err)
 
 
-	if r := fmt.Sprintf("%v", res.(float64)); err != nil || r != "11" {
+	if r := fmt.Sprintf("%v", res.(float64)); r != "11" {
 		t.Error("Unexpected result: ", r, err)
 		t.Error("Unexpected result: ", r, err)
 		return
 		return
 	}
 	}
+}
 
 
-	res, err = runAdapterTest(
+func TestECALFunctionAdapterSimple2(t *testing.T) {
+
+	res, err := runAdapterTest(
 		reflect.ValueOf(math.Float64bits),
 		reflect.ValueOf(math.Float64bits),
 		[]interface{}{float64(11)},
 		[]interface{}{float64(11)},
 	)
 	)
+	errorutil.AssertOk(err)
 
 
-	if r := fmt.Sprintf("%X", uint64(res.(float64))); err != nil || r != fmt.Sprintf("%X", math.Float64bits(11)) {
+	if r := fmt.Sprintf("%X", uint64(res.(float64))); r != fmt.Sprintf("%X", math.Float64bits(11)) {
 		t.Error("Unexpected result: ", r, err)
 		t.Error("Unexpected result: ", r, err)
 		return
 		return
 	}
 	}
@@ -107,8 +114,9 @@ func TestECALFunctionAdapter(t *testing.T) {
 		reflect.ValueOf(math.Float64frombits),
 		reflect.ValueOf(math.Float64frombits),
 		[]interface{}{float64(math.Float64bits(11))},
 		[]interface{}{float64(math.Float64bits(11))},
 	)
 	)
+	errorutil.AssertOk(err)
 
 
-	if r := fmt.Sprintf("%v", res.(float64)); err != nil || r != "11" {
+	if r := fmt.Sprintf("%v", res.(float64)); r != "11" {
 		t.Error("Unexpected result: ", r, err)
 		t.Error("Unexpected result: ", r, err)
 		return
 		return
 	}
 	}
@@ -118,7 +126,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -128,7 +136,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -138,7 +146,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -148,7 +156,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -158,7 +166,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -168,7 +176,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -178,7 +186,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
@@ -188,14 +196,17 @@ func TestECALFunctionAdapter(t *testing.T) {
 		[]interface{}{float64(1)},
 		[]interface{}{float64(1)},
 	)
 	)
 
 
-	if err != nil || res != "1" {
+	if errorutil.AssertOk(err); res != "1" {
 		t.Error("Unexpected result: ", res, err)
 		t.Error("Unexpected result: ", res, err)
 		return
 		return
 	}
 	}
+}
+
+func TestECALFunctionAdapterErrors(t *testing.T) {
 
 
 	// Test Error cases
 	// Test Error cases
 
 
-	res, err = runAdapterTest(
+	res, err := runAdapterTest(
 		reflect.ValueOf(strconv.ParseFloat),
 		reflect.ValueOf(strconv.ParseFloat),
 		[]interface{}{"123.123", 0, 0},
 		[]interface{}{"123.123", 0, 0},
 	)
 	)

+ 117 - 93
stdlib/generate/generate.go

@@ -152,144 +152,168 @@ genStdlib contains all generated stdlib constructs.
 
 
 				// Write constants
 				// Write constants
 
 
-				outbuf.WriteString(fmt.Sprintf(`/*
+				writeConstants(&outbuf, pkgName, pkgSymbols, scope)
+
+				// Write function documentation
+
+				writeFuncDoc(&outbuf, pkgName, pkgDocs, pkgSymbols, scope)
+
+				// Write functions
+
+				writeFuncs(&outbuf, pkgName, pkgSymbols, scope)
+			}
+		}
+	}
+
+	// Write dummy statement
+	outbuf.WriteString("// Dummy statement to prevent declared and not used errors\n")
+	outbuf.WriteString("var Dummy = fmt.Sprint(reflect.ValueOf(fmt.Sprint))\n\n")
+
+	if err == nil {
+		err = ioutil.WriteFile(filename, outbuf.Bytes(), 0644)
+	}
+
+	if err != nil {
+		stderrPrint("Error:", err)
+	}
+}
+
+var (
+	fset = token.NewFileSet()
+	ctx  = &build.Default
+)
+
+/*
+writeConstants writes out all stdlib constant definitions.
+*/
+func writeConstants(outbuf *bytes.Buffer, pkgName string, pkgSymbols []string, scope *types.Scope) {
+
+	outbuf.WriteString(fmt.Sprintf(`/*
 %vConstMap contains the mapping of stdlib %v constants.
 %vConstMap contains the mapping of stdlib %v constants.
 */
 */
 var %vConstMap = map[interface{}]interface{}{
 var %vConstMap = map[interface{}]interface{}{
 `, pkgName, pkgName, pkgName))
 `, pkgName, pkgName, pkgName))
 
 
-				for _, name := range scope.Names() {
+	for _, name := range scope.Names() {
 
 
-					if !containsSymbol(pkgSymbols, name) {
-						continue
-					}
+		if !containsSymbol(pkgSymbols, name) {
+			continue
+		}
 
 
-					switch obj := scope.Lookup(name).(type) {
-					case *types.Const:
+		switch obj := scope.Lookup(name).(type) {
+		case *types.Const:
 
 
-						if unicode.IsUpper([]rune(name)[0]) {
+			if unicode.IsUpper([]rune(name)[0]) {
 
 
-							line := fmt.Sprintf(`	"%v": %v.%v,
+				line := fmt.Sprintf(`	"%v": %v.%v,
 `, name, pkgName, obj.Name())
 `, name, pkgName, obj.Name())
 
 
-							if basicType, ok := obj.Type().(*types.Basic); ok {
-
-								// Convert number constants so they can be used in calculations
-
-								switch basicType.Kind() {
-								case types.Int,
-									types.Int8,
-									types.Int16,
-									types.Int32,
-									types.Int64,
-									types.Uint,
-									types.Uint8,
-									types.Uint16,
-									types.Uint32,
-									types.Uint64,
-									types.Uintptr,
-									types.Float32,
-									types.UntypedInt,
-									types.UntypedFloat:
-
-									line = fmt.Sprintf(`	"%v": float64(%v.%v),
+				if basicType, ok := obj.Type().(*types.Basic); ok {
+
+					// Convert number constants so they can be used in calculations
+
+					switch basicType.Kind() {
+					case types.Int,
+						types.Int8,
+						types.Int16,
+						types.Int32,
+						types.Int64,
+						types.Uint,
+						types.Uint8,
+						types.Uint16,
+						types.Uint32,
+						types.Uint64,
+						types.Uintptr,
+						types.Float32,
+						types.UntypedInt,
+						types.UntypedFloat:
+
+						line = fmt.Sprintf(`	"%v": float64(%v.%v),
 `, name, pkgName, obj.Name())
 `, name, pkgName, obj.Name())
-								}
-							}
-
-							outbuf.WriteString(line)
-						}
 					}
 					}
 				}
 				}
 
 
-				outbuf.WriteString("}\n\n")
+				outbuf.WriteString(line)
+			}
+		}
+	}
 
 
-				// Write function documentation
+	outbuf.WriteString("}\n\n")
+}
 
 
-				outbuf.WriteString(fmt.Sprintf(`/*
+/*
+writeFuncDoc writes out all stdlib function documentation.
+*/
+func writeFuncDoc(outbuf *bytes.Buffer, pkgName string, pkgDocs map[string]*doc.Package,
+	pkgSymbols []string, scope *types.Scope) {
+
+	outbuf.WriteString(fmt.Sprintf(`/*
 %vFuncDocMap contains the documentation of stdlib %v functions.
 %vFuncDocMap contains the documentation of stdlib %v functions.
 */
 */
 var %vFuncDocMap = map[interface{}]interface{}{
 var %vFuncDocMap = map[interface{}]interface{}{
 `, pkgName, pkgName, pkgName))
 `, pkgName, pkgName, pkgName))
 
 
-				if pkgDoc, ok := pkgDocs[pkgName]; ok {
+	if pkgDoc, ok := pkgDocs[pkgName]; ok {
 
 
-					for _, name := range scope.Names() {
+		for _, name := range scope.Names() {
 
 
-						if !containsSymbol(pkgSymbols, name) {
-							continue
-						}
+			if !containsSymbol(pkgSymbols, name) {
+				continue
+			}
 
 
-						for _, f := range pkgDoc.Funcs {
-							if f.Name == name {
-								outbuf.WriteString(
-									fmt.Sprintf(`	"%v": %#v,
+			for _, f := range pkgDoc.Funcs {
+				if f.Name == name {
+					outbuf.WriteString(
+						fmt.Sprintf(`	"%v": %#v,
 `, name, f.Doc))
 `, name, f.Doc))
-							}
-						}
-					}
+				}
+			}
+		}
 
 
-				} else {
+	} else {
 
 
-					for _, name := range pkgSymbols {
-						switch scope.Lookup(name).(type) {
-						case *types.Func:
-							outbuf.WriteString(
-								fmt.Sprintf(`	"%v": "Function: %v",
+		for _, name := range pkgSymbols {
+			switch scope.Lookup(name).(type) {
+			case *types.Func:
+				outbuf.WriteString(
+					fmt.Sprintf(`	"%v": "Function: %v",
 `, name, name))
 `, name, name))
-						}
-					}
-				}
-
-				outbuf.WriteString("}\n\n")
+			}
+		}
+	}
 
 
-				// Write functions
+	outbuf.WriteString("}\n\n")
+}
 
 
-				outbuf.WriteString(fmt.Sprintf(`/*
+/*
+writeFuncs writes out all stdlib function definitions.
+*/
+func writeFuncs(outbuf *bytes.Buffer, pkgName string, pkgSymbols []string, scope *types.Scope) {
+	outbuf.WriteString(fmt.Sprintf(`/*
 %vFuncMap contains the mapping of stdlib %v functions.
 %vFuncMap contains the mapping of stdlib %v functions.
 */
 */
 var %vFuncMap = map[interface{}]interface{}{
 var %vFuncMap = map[interface{}]interface{}{
 `, pkgName, pkgName, pkgName))
 `, pkgName, pkgName, pkgName))
 
 
-				for _, name := range scope.Names() {
+	for _, name := range scope.Names() {
 
 
-					if !containsSymbol(pkgSymbols, name) {
-						continue
-					}
+		if !containsSymbol(pkgSymbols, name) {
+			continue
+		}
 
 
-					switch obj := scope.Lookup(name).(type) {
-					case *types.Func:
-						if unicode.IsUpper([]rune(name)[0]) {
-							outbuf.WriteString(
-								fmt.Sprintf(`	%#v: &ECALFunctionAdapter{reflect.ValueOf(%v), fmt.Sprint(%vFuncDocMap[%#v])},
+		switch obj := scope.Lookup(name).(type) {
+		case *types.Func:
+			if unicode.IsUpper([]rune(name)[0]) {
+				outbuf.WriteString(
+					fmt.Sprintf(`	%#v: &ECALFunctionAdapter{reflect.ValueOf(%v), fmt.Sprint(%vFuncDocMap[%#v])},
 `, name, obj.FullName(), pkgName, name))
 `, name, obj.FullName(), pkgName, name))
-						}
-					}
-				}
-
-				outbuf.WriteString("}\n\n")
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	// Write dummy statement
-	outbuf.WriteString("// Dummy statement to prevent declared and not used errors\n")
-	outbuf.WriteString("var Dummy = fmt.Sprint(reflect.ValueOf(fmt.Sprint))\n\n")
-
-	if err == nil {
-		err = ioutil.WriteFile(filename, outbuf.Bytes(), 0644)
-	}
-
-	if err != nil {
-		stderrPrint("Error:", err)
-	}
+	outbuf.WriteString("}\n\n")
 }
 }
 
 
-var (
-	fset = token.NewFileSet()
-	ctx  = &build.Default
-)
-
 /*
 /*
 getPackageDocs returns the source code documentation of as given Go package.
 getPackageDocs returns the source code documentation of as given Go package.
 Returns a short synopsis and a documentation object.
 Returns a short synopsis and a documentation object.

+ 1 - 1
stdlib/stdlib.go

@@ -145,7 +145,7 @@ func GetStdlibFunc(name string) (util.ECALFunction, bool) {
 GetPkgDocString returns the docstring of a stdlib package.
 GetPkgDocString returns the docstring of a stdlib package.
 */
 */
 func GetPkgDocString(name string) (string, bool) {
 func GetPkgDocString(name string) (string, bool) {
-	var res = ""
+	var res string
 	s, ok := genStdlib[fmt.Sprintf("%v-synopsis", name)]
 	s, ok := genStdlib[fmt.Sprintf("%v-synopsis", name)]
 	if ok {
 	if ok {
 		res = fmt.Sprint(s)
 		res = fmt.Sprint(s)

+ 1 - 1
util/error.go

@@ -122,7 +122,7 @@ func (re *RuntimeError) GetTrace() []*parser.ASTNode {
 }
 }
 
 
 /*
 /*
-GetTrace returns the current stacktrace as a string.
+GetTraceString returns the current stacktrace as a string.
 */
 */
 func (re *RuntimeError) GetTraceString() []string {
 func (re *RuntimeError) GetTraceString() []string {
 	res := []string{}
 	res := []string{}

+ 12 - 8
util/import_test.go

@@ -18,6 +18,7 @@ import (
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
+	"devt.de/krotik/common/errorutil"
 	"devt.de/krotik/common/fileutil"
 	"devt.de/krotik/common/fileutil"
 )
 )
 
 
@@ -62,28 +63,29 @@ func TestImportLocater(t *testing.T) {
 	expectedError := fmt.Sprintf("Import path is outside of code root: ..%vt",
 	expectedError := fmt.Sprintf("Import path is outside of code root: ..%vt",
 		string(os.PathSeparator))
 		string(os.PathSeparator))
 
 
-	if res != "" || err == nil || err.Error() != expectedError {
+	if res != "" || err.Error() != expectedError {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}
 
 
 	res, err = fil.Resolve(filepath.Join("..", importTestDir, "x"))
 	res, err = fil.Resolve(filepath.Join("..", importTestDir, "x"))
 
 
-	if res != "" || err == nil || !strings.HasPrefix(err.Error(), "Could not import path") {
+	if res != "" || !strings.HasPrefix(err.Error(), "Could not import path") {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}
 
 
 	res, err = fil.Resolve(filepath.Join("..", importTestDir, "x"))
 	res, err = fil.Resolve(filepath.Join("..", importTestDir, "x"))
 
 
-	if res != "" || err == nil || !strings.HasPrefix(err.Error(), "Could not import path") {
+	if res != "" || !strings.HasPrefix(err.Error(), "Could not import path") {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}
 
 
 	res, err = fil.Resolve(filepath.Join("test1", "myfile.ecal"))
 	res, err = fil.Resolve(filepath.Join("test1", "myfile.ecal"))
+	errorutil.AssertOk(err)
 
 
-	if res != codecontent || err != nil {
+	if res != codecontent {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}
@@ -93,23 +95,25 @@ func TestImportLocater(t *testing.T) {
 	mil.Files["foo"] = "bar"
 	mil.Files["foo"] = "bar"
 	mil.Files["test"] = "test1"
 	mil.Files["test"] = "test1"
 
 
-	res, err = mil.Resolve("xxx")
+	_, err = mil.Resolve("xxx")
 
 
-	if res != "" || err == nil || err.Error() != "Could not find import path: xxx" {
+	if err.Error() != "Could not find import path: xxx" {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}
 
 
 	res, err = mil.Resolve("foo")
 	res, err = mil.Resolve("foo")
+	errorutil.AssertOk(err)
 
 
-	if res != "bar" || err != nil {
+	if res != "bar" {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}
 
 
 	res, err = mil.Resolve("test")
 	res, err = mil.Resolve("test")
+	errorutil.AssertOk(err)
 
 
-	if res != "test1" || err != nil {
+	if res != "test1" {
 		t.Error("Unexpected result:", res, err)
 		t.Error("Unexpected result:", res, err)
 		return
 		return
 	}
 	}

+ 4 - 1
util/logging.go

@@ -44,6 +44,9 @@ type LogLevelLogger struct {
 	level  LogLevel
 	level  LogLevel
 }
 }
 
 
+/*
+NewLogLevelLogger wraps a given logger and adds level based filtering functionality.
+*/
 func NewLogLevelLogger(logger Logger, level string) (*LogLevelLogger, error) {
 func NewLogLevelLogger(logger Logger, level string) (*LogLevelLogger, error) {
 	llevel := LogLevel(strings.ToLower(level))
 	llevel := LogLevel(strings.ToLower(level))
 
 
@@ -234,7 +237,7 @@ type BufferLogger struct {
 }
 }
 
 
 /*
 /*
-NewNullLogger returns a buffer logger instance.
+NewBufferLogger returns a buffer logger instance.
 */
 */
 func NewBufferLogger(buf io.Writer) *BufferLogger {
 func NewBufferLogger(buf io.Writer) *BufferLogger {
 	return &BufferLogger{buf}
 	return &BufferLogger{buf}

+ 6 - 6
util/types.go

@@ -104,7 +104,7 @@ type ECALDebugger interface {
 	/*
 	/*
 	   StopThreads will continue all suspended threads and set them to be killed.
 	   StopThreads will continue all suspended threads and set them to be killed.
 	   Returns true if a waiting thread was resumed. Can wait for threads to end
 	   Returns true if a waiting thread was resumed. Can wait for threads to end
-	   by ensuring that for at least d time no state change occured.
+	   by ensuring that for at least d time no state change occurred.
 	*/
 	*/
 	StopThreads(d time.Duration) bool
 	StopThreads(d time.Duration) bool
 
 
@@ -157,18 +157,18 @@ type ECALDebugger interface {
 		ExtractValue copies a value from a suspended thread into the
 		ExtractValue copies a value from a suspended thread into the
 		global variable scope.
 		global variable scope.
 	*/
 	*/
-	ExtractValue(threadId uint64, varName string, destVarName string) error
+	ExtractValue(threadID uint64, varName string, destVarName string) error
 
 
 	/*
 	/*
 		InjectValue copies a value from an expression (using the global
 		InjectValue copies a value from an expression (using the global
 		variable scope) into a suspended thread.
 		variable scope) into a suspended thread.
 	*/
 	*/
-	InjectValue(threadId uint64, varName string, expression string) error
+	InjectValue(threadID uint64, varName string, expression string) error
 
 
 	/*
 	/*
 	   Continue will continue a suspended thread.
 	   Continue will continue a suspended thread.
 	*/
 	*/
-	Continue(threadId uint64, contType ContType)
+	Continue(threadID uint64, contType ContType)
 
 
 	/*
 	/*
 		Status returns the current status of the debugger.
 		Status returns the current status of the debugger.
@@ -176,9 +176,9 @@ type ECALDebugger interface {
 	Status() interface{}
 	Status() interface{}
 
 
 	/*
 	/*
-	   Describe decribes a thread currently observed by the debugger.
+	   Describe describes a thread currently observed by the debugger.
 	*/
 	*/
-	Describe(threadId uint64) interface{}
+	Describe(threadID uint64) interface{}
 }
 }
 
 
 /*
 /*