Browse Source

feat: Better pretty printer

Matthias Ladkau 3 years ago
parent
commit
18e42f771d

+ 36 - 20
cli/tool/format.go

@@ -25,8 +25,6 @@ import (
 Format formats a given set of ECAL files.
 */
 func Format() error {
-	var err error
-
 	wd, _ := os.Getwd()
 
 	dir := flag.String("dir", wd, "Root directory for ECAL files")
@@ -54,31 +52,49 @@ func Format() error {
 
 	fmt.Fprintln(flag.CommandLine.Output(), fmt.Sprintf("Formatting all %v files in %v", *ext, *dir))
 
-	err = filepath.Walk(*dir,
-		func(path string, i os.FileInfo, err error) error {
-			if err == nil && !i.IsDir() {
-				var data []byte
-				var ast *parser.ASTNode
-				var srcFormatted string
+	return FormatFiles(*dir, *ext)
+}
+
+/*
+FormatFiles formats all ECAL files in a given directory with a given ending.
+*/
+func FormatFiles(dir string, ext string) error {
+	var err error
+
+	// Try to resolve symbolic links
+
+	scanDir, lerr := os.Readlink(dir)
+	if lerr != nil {
+		scanDir = dir
+	}
+
+	if err == nil {
+		err = filepath.Walk(scanDir,
+			func(path string, i os.FileInfo, err error) error {
+				if err == nil && !i.IsDir() {
+					var data []byte
+					var ast *parser.ASTNode
+					var srcFormatted string
 
-				if strings.HasSuffix(path, *ext) {
-					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(fmt.Sprintln(srcFormatted)), i.Mode())
+								}
 							}
-						}
 
-						if ferr != nil {
-							fmt.Fprintln(flag.CommandLine.Output(), 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))
+							}
 						}
 					}
 				}
-			}
-			return err
-		})
+				return err
+			})
+	}
 
 	return err
 }

+ 31 - 11
cli/tool/interpret.go

@@ -368,25 +368,18 @@ func (i *CLIInterpreter) HandleInput(ot OutputTerminal, line string, tid uint64)
 		ot.WriteString(fmt.Sprint("\n"))
 		ot.WriteString(fmt.Sprint("Console supports all normal ECAL statements and the following special commands:\n"))
 		ot.WriteString(fmt.Sprint("\n"))
+		ot.WriteString(fmt.Sprint("    @format - Format all .ecal files in the current root directory.\n"))
 		ot.WriteString(fmt.Sprint("    @reload - Clear the interpreter and reload the initial file if it was given.\n"))
-		ot.WriteString(fmt.Sprint("    @sym [glob] - List all available inbuild functions and available stdlib packages of ECAL.\n"))
 		ot.WriteString(fmt.Sprint("    @std <package> [glob] - List all available constants and functions of a stdlib package.\n"))
+		ot.WriteString(fmt.Sprint("    @sym [glob] - List all available inbuild functions and available stdlib packages of ECAL.\n"))
 		if i.CustomHelpString != "" {
 			ot.WriteString(i.CustomHelpString)
 		}
 		ot.WriteString(fmt.Sprint("\n"))
 		ot.WriteString(fmt.Sprint("Add an argument after a list command to do a full text search. The search string should be in glob format.\n"))
 
-	} else if strings.HasPrefix(line, "@reload") {
-
-		// Reload happens in a separate thread as it may be suspended on start
-
-		go func() {
-			err := i.LoadInitialFile(i.RuntimeProvider.NewThreadID())
-			ot.WriteString(fmt.Sprintln(fmt.Sprintln("Interpreter reloaded:", err)))
-		}()
-		ot.WriteString(fmt.Sprintln(fmt.Sprintln("Reloading interpreter state")))
-
+	} else if i.handleSpecialStatements(ot, line) {
+		return
 	} else if strings.HasPrefix(line, "@sym") {
 		i.displaySymbols(ot, strings.Split(line, " ")[1:])
 
@@ -424,6 +417,33 @@ func (i *CLIInterpreter) HandleInput(ot OutputTerminal, line string, tid uint64)
 	}
 }
 
+/*
+handleSpecialStatements handles inbuild special statements.
+*/
+func (i *CLIInterpreter) handleSpecialStatements(ot OutputTerminal, line string) bool {
+
+	if strings.HasPrefix(line, "@format") {
+		err := FormatFiles(*i.Dir, ".ecal")
+		ot.WriteString(fmt.Sprintln(fmt.Sprintln("Files formatted:", err)))
+
+		return true
+
+	} else if strings.HasPrefix(line, "@reload") {
+
+		// Reload happens in a separate thread as it may be suspended on start
+
+		go func() {
+			err := i.LoadInitialFile(i.RuntimeProvider.NewThreadID())
+			ot.WriteString(fmt.Sprintln(fmt.Sprintln("Interpreter reloaded:", err)))
+		}()
+		ot.WriteString(fmt.Sprintln(fmt.Sprintln("Reloading interpreter state")))
+
+		return true
+	}
+
+	return false
+}
+
 /*
 displaySymbols lists all available inbuild functions and available stdlib packages of ECAL.
 */

+ 1 - 1
cli/tool/interpret_test.go

@@ -323,7 +323,7 @@ func TestHandleInput(t *testing.T) {
 	l2 := ""
 	tin.LogLevel = &l2
 
-	testTerm.in = []string{"?", "@reload", "@sym", "@std", "@cus", "q"}
+	testTerm.in = []string{"?", "@format", "@reload", "@sym", "@std", "@cus", "q"}
 
 	if err := tin.Interpret(true); err != nil {
 		t.Error("Unexpected result:", err)

+ 2 - 2
parser/lexer_test.go

@@ -271,10 +271,10 @@ func TestCommentLexing(t *testing.T) {
 
 	input := `name /* foo
 		bar
-	x*/ 'b/* - */la' /*test*/`
+    x*/ 'b/* - */la' /*test*/`
 	if res := LexToList("mytest", input); fmt.Sprint(res) != `["name" /*  foo
 		bar
-	x */ v:"b/* - */la" /* test */ EOF]` {
+    x */ v:"b/* - */la" /* test */ EOF]` {
 		t.Error("Unexpected lexer result:", res)
 		return
 	}

+ 2 - 2
parser/main_test.go

@@ -147,7 +147,7 @@ func markASTNodesAsPrettyPrinted(n *ASTNode) {
 
 func UnitTestPrettyPrinting(input, astOutput, ppOutput string) error {
 	astres, err := ParseWithRuntime("mytest", input, &DummyRuntimeProvider{})
-	if err != nil || fmt.Sprint(astres) != astOutput {
+	if err != nil || (astOutput != "" && fmt.Sprint(astres) != astOutput) {
 		return fmt.Errorf("Unexpected parser output:\n%v expected was:\n%v Error: %v", astres, astOutput, err)
 	}
 
@@ -161,7 +161,7 @@ func UnitTestPrettyPrinting(input, astOutput, ppOutput string) error {
 	// Make sure the pretty printed result is valid and gets the same parse tree
 
 	astres2, err := ParseWithRuntime("mytest", ppres, &DummyRuntimeProvider{})
-	if err != nil || fmt.Sprint(astres2) != astOutput {
+	if err != nil || (astOutput != "" && fmt.Sprint(astres2) != astOutput) {
 		return fmt.Errorf("Unexpected parser output from pretty print string:\n%v expected was:\n%v Error: %v", astres2, astOutput, err)
 	}
 

+ 2 - 2
parser/parser_main_test.go

@@ -102,10 +102,10 @@ func TestCommentParsing(t *testing.T) {
 
 	// Comment parsing without statements
 
-	input := `/* This is  a comment*/ a := 1 + 1 # foo bar`
+	input := `/* This is  a comment */ a := 1 + 1 # foo bar`
 	expectedOutput := `
 :=
-  identifier: a #  This is  a comment
+  identifier: a #  This is  a comment 
   plus
     number: 1
     number: 1 #  foo bar

+ 164 - 54
parser/prettyprinter.go

@@ -11,15 +11,22 @@
 package parser
 
 import (
+	"bufio"
 	"bytes"
 	"fmt"
 	"strconv"
+	"strings"
 	"text/template"
 
 	"devt.de/krotik/common/errorutil"
 	"devt.de/krotik/common/stringutil"
 )
 
+/*
+IndentationLevel is the level of indentation which the pretty printer should use
+*/
+const IndentationLevel = 4
+
 /*
 Map of AST nodes corresponding to lexer tokens
 */
@@ -126,7 +133,7 @@ func init() {
 
 		// Loop statement
 
-		NodeLOOP + "_2": template.Must(template.New(NodeLOOP).Parse("for {{.c1}} {\n{{.c2}}}\n")),
+		NodeLOOP + "_2": template.Must(template.New(NodeLOOP).Parse("for {{.c1}} {\n{{.c2}}}")),
 		NodeBREAK:       template.Must(template.New(NodeBREAK).Parse("break")),
 		NodeCONTINUE:    template.Must(template.New(NodeCONTINUE).Parse("continue")),
 
@@ -153,13 +160,13 @@ func init() {
 PrettyPrint produces pretty printed code from a given AST.
 */
 func PrettyPrint(ast *ASTNode) (string, error) {
-	var visit func(ast *ASTNode, level int) (string, error)
+	var visit func(ast *ASTNode, path []*ASTNode) (string, error)
 
-	visit = func(ast *ASTNode, level int) (string, error) {
+	visit = func(ast *ASTNode, path []*ASTNode) (string, error) {
 		var buf bytes.Buffer
 
 		if ast == nil {
-			return "", fmt.Errorf("Nil pointer in AST at level: %v", level)
+			return "", fmt.Errorf("Nil pointer in AST")
 		}
 
 		numChildren := len(ast.Children)
@@ -171,7 +178,7 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 
 		if numChildren > 0 {
 			for i, child := range ast.Children {
-				res, err := visit(child, level+1)
+				res, err := visit(child, append(path, child))
 				if err != nil {
 					return "", err
 				}
@@ -190,11 +197,13 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			tempKey += fmt.Sprint("_", len(tempParam))
 		}
 
-		if res, ok := ppSpecialDefs(ast, level, tempParam, &buf); ok {
+		if res, ok := ppSpecialDefs(ast, path, tempParam, &buf); ok {
 			return res, nil
-		} else if res, ok := ppSpecialBlocks(ast, level, tempParam, &buf); ok {
+		} else if res, ok := ppSpecialBlocks(ast, path, tempParam, &buf); ok {
 			return res, nil
-		} else if res, ok := ppSpecialStatements(ast, level, tempParam, &buf); ok {
+		} else if res, ok := ppContainerBlocks(ast, path, tempParam, &buf); ok {
+			return res, nil
+		} else if res, ok := ppSpecialStatements(ast, path, tempParam, &buf); ok {
 			return res, nil
 		}
 
@@ -217,26 +226,92 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 
 		errorutil.AssertOk(temp.Execute(&buf, tempParam))
 
-		return ppMetaData(ast, buf.String()), nil
+		return ppPostProcessing(ast, path, buf.String()), nil
 	}
 
-	return visit(ast, 0)
+	res, err := visit(ast, []*ASTNode{ast})
+
+	return strings.TrimSpace(res), err
 }
 
 /*
-ppMetaData pretty prints meta data.
+ppPostProcessing applies post processing rules.
 */
-func ppMetaData(ast *ASTNode, ppString string) string {
+func ppPostProcessing(ast *ASTNode, path []*ASTNode, ppString string) string {
 	ret := ppString
 
 	// Add meta data
 
 	if len(ast.Meta) > 0 {
+
 		for _, meta := range ast.Meta {
+			metaValue := meta.Value()
 			if meta.Type() == MetaDataPreComment {
-				ret = fmt.Sprintf("/*%v*/ %v", meta.Value(), ret)
+				var buf bytes.Buffer
+
+				scanner := bufio.NewScanner(strings.NewReader(metaValue))
+				for scanner.Scan() {
+					buf.WriteString(fmt.Sprintf(" %v\n", strings.TrimSpace(scanner.Text())))
+				}
+				buf.Truncate(buf.Len() - 1) // Remove the last newline
+
+				if strings.Index(buf.String(), "\n") == -1 {
+					buf.WriteString(" ")
+				}
+
+				ret = fmt.Sprintf("/*%v*/\n%v", buf.String(), ret)
+
 			} else if meta.Type() == MetaDataPostComment {
-				ret = fmt.Sprintf("%v #%v", ret, meta.Value())
+				metaValue = strings.TrimSpace(strings.ReplaceAll(metaValue, "\n", ""))
+				ret = fmt.Sprintf("%v # %v", ret, metaValue)
+			}
+		}
+	}
+
+	// Apply indentation
+
+	if len(path) > 1 {
+		if stringutil.IndexOf(ast.Name, []string{
+			NodeSTATEMENTS,
+			NodeMAP,
+			NodeLIST,
+			NodeKINDMATCH,
+			NodeSTATEMATCH,
+			NodeSCOPEMATCH,
+			NodePRIORITY,
+			NodeSUPPRESSES,
+		}) != -1 {
+			parent := path[len(path)-2]
+
+			indentSpaces := stringutil.GenerateRollingString(" ", IndentationLevel)
+			ret = strings.ReplaceAll(ret, "\n", "\n"+indentSpaces)
+
+			// Add initial indent only if we are inside a block statement
+
+			if stringutil.IndexOf(parent.Name, []string{
+				NodeASSIGN,
+				NodePRESET,
+				NodeKVP,
+				NodeLIST,
+				NodeFUNCCALL,
+				NodeKINDMATCH,
+				NodeSTATEMATCH,
+				NodeSCOPEMATCH,
+				NodePRIORITY,
+				NodeSUPPRESSES,
+			}) == -1 {
+				ret = fmt.Sprintf("%v%v", indentSpaces, ret)
+			}
+
+			// Remove indentation from last line unless we have a special case
+
+			if stringutil.IndexOf(parent.Name, []string{
+				NodeSINK,
+			}) == -1 || ast.Name == NodeSTATEMENTS {
+
+				if idx := strings.LastIndex(ret, "\n"); idx != -1 {
+					ret = ret[:idx+1] + ret[idx+IndentationLevel+1:]
+				}
 			}
 		}
 	}
@@ -247,7 +322,7 @@ func ppMetaData(ast *ASTNode, ppString string) string {
 /*
 ppSpecialDefs pretty prints special cases.
 */
-func ppSpecialDefs(ast *ASTNode, level int, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+func ppSpecialDefs(ast *ASTNode, path []*ASTNode, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
 	numChildren := len(ast.Children)
 
 	if ast.Name == NodeFUNCCALL {
@@ -259,7 +334,7 @@ func ppSpecialDefs(ast *ASTNode, level int, tempParam map[string]string, buf *by
 			}
 		}
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 
 	} else if ast.Name == NodeSINK {
 
@@ -268,7 +343,6 @@ func ppSpecialDefs(ast *ASTNode, level int, tempParam map[string]string, buf *by
 		buf.WriteString("\n")
 
 		for i := 1; i < len(ast.Children)-1; i++ {
-			buf.WriteString("  ")
 			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
 			buf.WriteString("\n")
 		}
@@ -277,57 +351,97 @@ func ppSpecialDefs(ast *ASTNode, level int, tempParam map[string]string, buf *by
 		buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
 		buf.WriteString("}\n")
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 	}
 
 	return "", false
 }
 
 /*
-ppSpecialBlocks pretty prints special cases.
+ppContainerBlocks pretty prints container structures.
 */
-func ppSpecialBlocks(ast *ASTNode, level int, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+func ppContainerBlocks(ast *ASTNode, path []*ASTNode, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
 	numChildren := len(ast.Children)
 
-	// Handle special cases - children in tempParam have been resolved
-
-	if stringutil.IndexOf(ast.Name, []string{NodeSTATEMENTS}) != -1 {
-
-		// For statements just concat all children
+	if ast.Name == NodeLIST {
+		multilineThreshold := 4
+		buf.WriteString("[")
 
-		for i := 0; i < numChildren; i++ {
-			buf.WriteString(stringutil.GenerateRollingString(" ", level*4))
-			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+		if numChildren > multilineThreshold {
 			buf.WriteString("\n")
 		}
 
-		return ppMetaData(ast, buf.String()), true
+		for i := 0; i < numChildren; i++ {
 
-	} else if ast.Name == NodeLIST {
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
 
-		buf.WriteString("[")
-		i := 1
-		for ; i < numChildren; i++ {
-			buf.WriteString(tempParam[fmt.Sprint("c", i)])
-			buf.WriteString(", ")
+			if i < numChildren-1 {
+				if numChildren > multilineThreshold {
+					buf.WriteString(",")
+				} else {
+					buf.WriteString(", ")
+				}
+			}
+			if numChildren > multilineThreshold {
+				buf.WriteString("\n")
+			}
 		}
-		buf.WriteString(tempParam[fmt.Sprint("c", i)])
+
 		buf.WriteString("]")
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 
 	} else if ast.Name == NodeMAP {
-
+		multilineThreshold := 2
 		buf.WriteString("{")
-		i := 1
-		for ; i < numChildren; i++ {
-			buf.WriteString(tempParam[fmt.Sprint("c", i)])
-			buf.WriteString(", ")
+
+		if numChildren > multilineThreshold {
+			buf.WriteString("\n")
 		}
-		buf.WriteString(tempParam[fmt.Sprint("c", i)])
+
+		for i := 0; i < numChildren; i++ {
+
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+
+			if i < numChildren-1 {
+				if numChildren > multilineThreshold {
+					buf.WriteString(",")
+				} else {
+					buf.WriteString(", ")
+				}
+			}
+			if numChildren > multilineThreshold {
+				buf.WriteString("\n")
+			}
+		}
+
 		buf.WriteString("}")
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
+
+	}
+
+	return "", false
+}
+
+/*
+ppSpecialBlocks pretty prints special cases.
+*/
+func ppSpecialBlocks(ast *ASTNode, path []*ASTNode, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+	numChildren := len(ast.Children)
+
+	// Handle special cases - children in tempParam have been resolved
+
+	if ast.Name == NodeSTATEMENTS {
+
+		// For statements just concat all children
+
+		for i := 0; i < numChildren; i++ {
+			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+			buf.WriteString("\n")
+		}
+
+		return ppPostProcessing(ast, path, buf.String()), true
 
 	} else if ast.Name == NodeTRY {
 
@@ -340,9 +454,7 @@ func ppSpecialBlocks(ast *ASTNode, level int, tempParam map[string]string, buf *
 			buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
 		}
 
-		buf.WriteString("\n")
-
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 
 	} else if ast.Name == NodeEXCEPT {
 
@@ -361,7 +473,7 @@ func ppSpecialBlocks(ast *ASTNode, level int, tempParam map[string]string, buf *
 		buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
 		buf.WriteString("}")
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 	}
 
 	return "", false
@@ -370,7 +482,7 @@ func ppSpecialBlocks(ast *ASTNode, level int, tempParam map[string]string, buf *
 /*
 ppSpecialStatements pretty prints special cases.
 */
-func ppSpecialStatements(ast *ASTNode, level int, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
+func ppSpecialStatements(ast *ASTNode, path []*ASTNode, tempParam map[string]string, buf *bytes.Buffer) (string, bool) {
 	numChildren := len(ast.Children)
 
 	if ast.Name == NodeIDENTIFIER {
@@ -390,7 +502,7 @@ func ppSpecialStatements(ast *ASTNode, level int, tempParam map[string]string, b
 			}
 		}
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 
 	} else if ast.Name == NodePARAMS {
 
@@ -403,7 +515,7 @@ func ppSpecialStatements(ast *ASTNode, level int, tempParam map[string]string, b
 		buf.WriteString(tempParam[fmt.Sprint("c", i)])
 		buf.WriteString(")")
 
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 
 	} else if ast.Name == NodeIF {
 
@@ -429,9 +541,7 @@ func ppSpecialStatements(ast *ASTNode, level int, tempParam map[string]string, b
 			}
 		}
 
-		buf.WriteString("\n")
-
-		return ppMetaData(ast, buf.String()), true
+		return ppPostProcessing(ast, path, buf.String()), true
 	}
 
 	return "", false

+ 321 - 1
parser/prettyprinter_test.go

@@ -29,7 +29,7 @@ func TestErrorHandling(t *testing.T) {
 	astres.Children[1].Children[1] = nil
 
 	ppres, err := PrettyPrint(astres)
-	if err == nil || err.Error() != "Nil pointer in AST at level: 2" {
+	if err == nil || err.Error() != "Nil pointer in AST" {
 		t.Errorf("Unexpected result: %v error: %v", ppres, err)
 		return
 	}
@@ -207,3 +207,323 @@ or
 		return
 	}
 }
+
+func TestSpecialCasePrinting1(t *testing.T) {
+	input := `a := {"a":1,"b":1,"c":1,"d"  :  1,  "e":1,"f":1,"g":1,"h":1,}`
+
+	if err := UnitTestPrettyPrinting(input, "",
+		`a := {
+    "a" : 1,
+    "b" : 1,
+    "c" : 1,
+    "d" : 1,
+    "e" : 1,
+    "f" : 1,
+    "g" : 1,
+    "h" : 1
+}`); err != nil {
+		t.Error(err)
+		return
+	}
+
+	input = `a := {"a":1,"b":1,"c":1,"d"  :  {"a":1,"b":{"a":1,"b":1,"c":1,"d":1},"c":1,"d"  :  1,  "e":1,"f":{"a":1,"b":1},"g":1,"h":1,},  "e":1,"f":1,"g":1,"h":1,}`
+
+	if err := UnitTestPrettyPrinting(input, "",
+		`a := {
+    "a" : 1,
+    "b" : 1,
+    "c" : 1,
+    "d" : {
+        "a" : 1,
+        "b" : {
+            "a" : 1,
+            "b" : 1,
+            "c" : 1,
+            "d" : 1
+        },
+        "c" : 1,
+        "d" : 1,
+        "e" : 1,
+        "f" : {"a" : 1, "b" : 1},
+        "g" : 1,
+        "h" : 1
+    },
+    "e" : 1,
+    "f" : 1,
+    "g" : 1,
+    "h" : 1
+}`); err != nil {
+		t.Error(err)
+		return
+	}
+
+	input = `a := [1,2,3,[1,2,[1,2],3,[1,2,3,4],[1,2,3,4,5],4,5],4,5]`
+
+	if err := UnitTestPrettyPrinting(input, "",
+		`a := [
+    1,
+    2,
+    3,
+    [
+        1,
+        2,
+        [1, 2],
+        3,
+        [1, 2, 3, 4],
+        [
+            1,
+            2,
+            3,
+            4,
+            5
+        ],
+        4,
+        5
+    ],
+    4,
+    5
+]`); err != nil {
+		t.Error(err)
+		return
+	}
+
+	input = `a := [1,2,3,[1,2,{"a":1,"b":1,"c":1,"d":1},3,[1,2,3,4],4,5],4,5]`
+
+	if err := UnitTestPrettyPrinting(input, "",
+		`a := [
+    1,
+    2,
+    3,
+    [
+        1,
+        2,
+        {
+            "a" : 1,
+            "b" : 1,
+            "c" : 1,
+            "d" : 1
+        },
+        3,
+        [1, 2, 3, 4],
+        4,
+        5
+    ],
+    4,
+    5
+]`); err != nil {
+		t.Error(err)
+		return
+	}
+
+}
+
+func TestSpecialCasePrinting2(t *testing.T) {
+	input := `
+a := 1
+a := 2
+sink RegisterNewPlayer
+  kindmatch   ["foo",2]
+  statematch {"a":1,"b":1,"c":1,"d":1}
+scopematch []
+suppresses ["abs"]
+priority 0
+{
+log("1223")
+log("1223")
+func foo (z=[1,2,3,4,5]) {
+a := 1
+b := 2
+}
+log("1223")
+try {
+x := [1,2,3,4]
+    raise("test 12", null, [1,2,3])
+} except e {
+p := 1
+}
+}
+`
+
+	if err := UnitTestPrettyPrinting(input, "",
+		`a := 1
+a := 2
+sink RegisterNewPlayer
+    kindmatch ["foo", 2]
+    statematch {
+        "a" : 1,
+        "b" : 1,
+        "c" : 1,
+        "d" : 1
+    }
+    scopematch []
+    suppresses ["abs"]
+    priority 0
+{
+    log("1223")
+    log("1223")
+    func foo(z=[
+        1,
+        2,
+        3,
+        4,
+        5
+    ]) {
+        a := 1
+        b := 2
+    }
+    log("1223")
+    try {
+        x := [1, 2, 3, 4]
+        raise("test 12", null, [1, 2, 3])
+    } except e {
+        p := 1
+    }
+}`); err != nil {
+		t.Error(err)
+	}
+
+	input = `
+	/*
+
+	Some initial comment
+	
+	bla
+	*/
+a := 1
+func aaa() {
+mutex myresource {
+  globalResource := "new value"
+}
+func myfunc(a, b, c=1) {
+  a := 1 + 1 # Test
+}
+x := [ 1,2,3,4,5]
+a:=1;b:=1
+/*Foo*/
+Foo := {
+  "super" : [ Bar ]
+  
+  /* 
+   * Object IDs
+   */
+  "id" : 0 # aaaa
+  "idx" : 0
+
+/*Constructor*/
+  "init" : func(id) 
+{ 
+    super[0]()
+    this.id := id
+  }
+
+  /* 
+  Return the object ID
+  */
+  "getId" : func() {
+      return this.idx
+  }
+
+  /* 
+    Set the object ID
+  */
+  "setId" : func(id) {
+      this.idx := id
+  }
+}
+for a in range(2, 10, 2) {
+	a := 1
+}
+for a > 0 {
+  a := 1
+}
+if a == 1 {
+    a := a + 1
+} elif a == 2 {
+    a := a + 2
+} else {
+    a := 99
+}
+try {
+    raise("MyError", "My error message", [1,2,3])
+} except "MyError" as e {
+    log(e)
+}
+}
+b:=1
+`
+
+	if err := UnitTestPrettyPrinting(input, "",
+		`/* 
+ 
+ Some initial comment
+ 
+ bla
+ */
+a := 1
+func aaa() {
+    mutex myresource {
+        globalResource := "new value"
+    }
+    
+    func myfunc(a, b, c=1) {
+        a := 1 + 1 # Test
+    }
+    x := [
+        1,
+        2,
+        3,
+        4,
+        5
+    ]
+    a := 1
+    b := 1
+    /* Foo */
+    Foo := {
+        "super" : [Bar],
+        /* 
+         * Object IDs
+         */
+        "id" : 0 # aaaa,
+        "idx" : 0,
+        /* Constructor */
+        "init" : func (id) {
+            super[0]()
+            this.id := id
+        },
+        /* 
+         Return the object ID
+         */
+        "getId" : func () {
+            return this.idx
+        },
+        /* 
+         Set the object ID
+         */
+        "setId" : func (id) {
+            this.idx := id
+        }
+    }
+    for a in range(2, 10, 2) {
+        a := 1
+    }
+    for a > 0 {
+        a := 1
+    }
+    if a == 1 {
+        a := a + 1
+    } elif a == 2 {
+        a := a + 2
+    } else {
+        a := 99
+    }
+    try {
+        raise("MyError", "My error message", [1, 2, 3])
+    } except "MyError" as e {
+        log(e)
+    }
+}
+b := 1`); err != nil {
+		t.Error(err)
+		return
+	}
+}