This commit is contained in:
Marvin Blum
2015-10-25 13:04:30 +01:00
parent 276dd2c1e9
commit 2e7e198047
6 changed files with 317 additions and 317 deletions

View File

@@ -36,7 +36,7 @@ func parseBlock() {
} else if accept("return") {
parseReturn()
} else if accept("case") || accept("default") {
return
return
} else {
parseStatement()
}
@@ -54,34 +54,34 @@ func parseVar() {
if accept("=") {
next()
appendOut(" = ", false)
if accept("[") {
parseArray()
parseArray()
} else {
parseExpression(true)
parseExpression(true)
}
}
expect(";")
expect(";")
appendOut(";", true)
}
func parseArray() {
expect("[")
appendOut("[", false)
if !accept("]") {
parseExpression(true)
for accept(",") {
next()
appendOut(",", false)
parseExpression(true)
}
}
expect("]")
appendOut("]", false)
expect("[")
appendOut("[", false)
if !accept("]") {
parseExpression(true)
for accept(",") {
next()
appendOut(",", false)
parseExpression(true)
}
}
expect("]")
appendOut("]", false)
}
func parseIf() {
@@ -188,12 +188,12 @@ func parseForeach() {
appendOut("{", true)
parseBlock()
expect("}")
appendOut("} forEach (" + expr + ");", true)
appendOut("} forEach ("+expr+");", true)
}
func parseFunction() {
expect("func")
appendOut(get().token + " = {", true)
appendOut(get().token+" = {", true)
next()
expect("(")
parseFunctionParameter()
@@ -215,7 +215,7 @@ func parseFunctionParameter() {
for !accept(")") {
name := get().token
next()
appendOut(name + " = _this select " + strconv.FormatInt(i, 10) + ";", true)
appendOut(name+" = _this select "+strconv.FormatInt(i, 10)+";", true)
i++
if !accept(")") {
@@ -266,46 +266,46 @@ func parseAssignment() {
}
func parseFunctionCall(out bool, name string) string {
output := ""
output := ""
expect("(")
leftParams, leftParamCount := parseParameter(false)
expect(")")
if accept("(") {
// buildin function
next()
rightParams, rightParamCount := parseParameter(false)
expect(")")
if leftParamCount > 1 {
leftParams = "["+leftParams+"]"
}
if rightParamCount > 1 {
rightParams = "["+rightParams+"]"
}
if leftParamCount > 0 {
output = leftParams+" "+name+" "+rightParams
} else {
output = name+" "+rightParams
}
// buildin function
next()
rightParams, rightParamCount := parseParameter(false)
expect(")")
if leftParamCount > 1 {
leftParams = "[" + leftParams + "]"
}
if rightParamCount > 1 {
rightParams = "[" + rightParams + "]"
}
if leftParamCount > 0 {
output = leftParams + " " + name + " " + rightParams
} else {
output = name + " " + rightParams
}
} else {
output = "["+leftParams+"] call "+name
output = "[" + leftParams + "] call " + name
}
if out {
appendOut(output, false)
appendOut(output, false)
}
return output
}
func parseParameter(out bool) (string, int) {
output := ""
count := 0
output := ""
count := 0
for !accept(")") {
output += parseExpression(out)
count++
@@ -315,119 +315,119 @@ func parseParameter(out bool) (string, int) {
output += ", "
}
}
if out {
appendOut(output, false)
appendOut(output, false)
}
return output, count
}
func parseExpression(out bool) string {
output := parseArith()
for accept("<") || accept(">") || accept("&") || accept("|") || accept("=") {
if accept("<") {
output += "<"
next()
} else if accept(">") {
output += ">"
next()
} else if accept("&") {
next()
expect("&")
output += "&&"
} else if accept("|") {
next()
expect("|")
output += "||"
} else {
output += "="
next()
}
if accept("=") {
output += "="
next()
}
output += parseExpression(false)
if accept("<") {
output += "<"
next()
} else if accept(">") {
output += ">"
next()
} else if accept("&") {
next()
expect("&")
output += "&&"
} else if accept("|") {
next()
expect("|")
output += "||"
} else {
output += "="
next()
}
if accept("=") {
output += "="
next()
}
output += parseExpression(false)
}
if out {
appendOut(output, false)
appendOut(output, false)
}
return output
}
func parseIdentifier() string {
output := ""
if seek("(") && !accept("!") && !accept("-") {
name := get().token
next()
output = "("+parseFunctionCall(false, name)+")"
} else if accept("!") || accept("-") {
output = get().token
next()
if !accept("(") {
output += get().token
next()
} else {
output += parseTerm()
}
} else {
output = get().token
next()
}
return output
output := ""
if seek("(") && !accept("!") && !accept("-") {
name := get().token
next()
output = "(" + parseFunctionCall(false, name) + ")"
} else if accept("!") || accept("-") {
output = get().token
next()
if !accept("(") {
output += get().token
next()
} else {
output += parseTerm()
}
} else {
output = get().token
next()
}
return output
}
func parseTerm() string {
if accept("(") {
expect("(")
output := "("+parseExpression(false)+")"
expect(")")
return output
}
return parseIdentifier()
if accept("(") {
expect("(")
output := "(" + parseExpression(false) + ")"
expect(")")
return output
}
return parseIdentifier()
}
func parseFactor() string {
output := parseTerm()
for accept("*") || accept("/") { // TODO: modulo?
if accept("*") {
output += "*"
} else {
output += "/"
}
next()
output += parseExpression(false)
}
return output
output := parseTerm()
for accept("*") || accept("/") { // TODO: modulo?
if accept("*") {
output += "*"
} else {
output += "/"
}
next()
output += parseExpression(false)
}
return output
}
func parseArith() string {
output := parseFactor()
for accept("+") || accept("-") {
if accept("+") {
output += "+"
} else {
output += "-"
}
next()
output += parseExpression(false)
}
output := parseFactor()
for accept("+") || accept("-") {
if accept("+") {
output += "+"
} else {
output += "-"
}
next()
output += parseExpression(false)
}
return output
}

View File

@@ -38,11 +38,11 @@ func expect(token string) {
// Returns true, if the next token matches expected one.
// Does not throw parse errors and checks if token is available.
func seek(token string) bool {
if tokenIndex+1 >= len(tokens) {
return false
}
return tokenEqual(token, tokens[tokenIndex+1])
if tokenIndex+1 >= len(tokens) {
return false
}
return tokenEqual(token, tokens[tokenIndex+1])
}
// Increases token counter, so that the next token is compared.
@@ -72,8 +72,8 @@ func tokenEqual(a string, b Token) bool {
// Appends the output string to current SQF code output.
func appendOut(str string, newLine bool) {
out += str
if newLine && pretty {
out += "\n"
out += "\n"
}
}

View File

@@ -1,118 +1,118 @@
package asl_test
import (
"testing"
"io/ioutil"
"asl"
"asl"
"io/ioutil"
"testing"
)
func TestParserDeclaration(t *testing.T) {
got := getCompiled(t, "test/tokenizer_var.asl")
want := "x = 1;\narray = [1,2,3];\n"
equal(t, got, want)
got := getCompiled(t, "test/tokenizer_var.asl")
want := "x = 1;\narray = [1,2,3];\n"
equal(t, got, want)
}
func TestParserAssignment(t *testing.T) {
got := getCompiled(t, "test/parser_assignment.asl")
want := "x = 1;\n"
equal(t, got, want)
got := getCompiled(t, "test/parser_assignment.asl")
want := "x = 1;\n"
equal(t, got, want)
}
func TestParserIf(t *testing.T) {
got := getCompiled(t, "test/tokenizer_if.asl")
want := "if (a<b) then {\n};\n"
equal(t, got, want)
got := getCompiled(t, "test/tokenizer_if.asl")
want := "if (a<b) then {\n};\n"
equal(t, got, want)
}
func TestParserWhile(t *testing.T) {
got := getCompiled(t, "test/tokenizer_while.asl")
want := "while {true} do {\n};"
equal(t, got, want)
got := getCompiled(t, "test/tokenizer_while.asl")
want := "while {true} do {\n};"
equal(t, got, want)
}
func TestParserFor(t *testing.T) {
got := getCompiled(t, "test/tokenizer_for.asl")
want := "for [{i=0}, {i<100}, {i=i+1}] do {\n};\n"
equal(t, got, want)
got := getCompiled(t, "test/tokenizer_for.asl")
want := "for [{i=0}, {i<100}, {i=i+1}] do {\n};\n"
equal(t, got, want)
}
func TestParserForeach(t *testing.T) {
got := getCompiled(t, "test/tokenizer_foreach.asl")
want := "{\n} forEach (allUnits);\n"
equal(t, got, want)
got := getCompiled(t, "test/tokenizer_foreach.asl")
want := "{\n} forEach (allUnits);\n"
equal(t, got, want)
}
func TestParserSwitch(t *testing.T) {
}
func TestParserFunction(t *testing.T) {
got := getCompiled(t, "test/tokenizer_func.asl")
want := "TestFunction = {\nparam0 = _this select 0;\nparam1 = _this select 1;\nreturn true;\n};\n"
equal(t, got, want)
got := getCompiled(t, "test/tokenizer_func.asl")
want := "TestFunction = {\nparam0 = _this select 0;\nparam1 = _this select 1;\nreturn true;\n};\n"
equal(t, got, want)
}
func TestParserAssignResult(t *testing.T) {
got := getCompiled(t, "test/parser_assign_result.asl")
want := "x = ([1, 2, 3] call foo);\ny = ([1, 2, 3] call bar);\n"
equal(t, got, want)
got := getCompiled(t, "test/parser_assign_result.asl")
want := "x = ([1, 2, 3] call foo);\ny = ([1, 2, 3] call bar);\n"
equal(t, got, want)
}
func TestParserExpression(t *testing.T) {
got := getCompiled(t, "test/parser_expression.asl")
want := "x = -(1+(2+3))/(6*(someVariable+99-100))-(20)+!anotherVariable+([] call foo);\n"
equal(t, got, want)
got := getCompiled(t, "test/parser_expression.asl")
want := "x = -(1+(2+3))/(6*(someVariable+99-100))-(20)+!anotherVariable+([] call foo);\n"
equal(t, got, want)
}
func TestParserExpression2(t *testing.T) {
got := getCompiled(t, "test/parser_expression2.asl")
want := "x = true||(3>=4&&5<8);\n"
equal(t, got, want)
got := getCompiled(t, "test/parser_expression2.asl")
want := "x = true||(3>=4&&5<8);\n"
equal(t, got, want)
}
func TestParserFunctionCall(t *testing.T) {
got := getCompiled(t, "test/parser_func_call.asl")
want := "myFunc = {\na = _this select 0;\nb = _this select 1;\nreturn a>b;\n};\n[1+3/4, 2-(66*22)/3-((123))] call myFunc;\n"
equal(t, got, want)
got := getCompiled(t, "test/parser_func_call.asl")
want := "myFunc = {\na = _this select 0;\nb = _this select 1;\nreturn a>b;\n};\n[1+3/4, 2-(66*22)/3-((123))] call myFunc;\n"
equal(t, got, want)
}
func TestParserBuildinFunctionCall(t *testing.T) {
got := getCompiled(t, "test/parser_buildin_func.asl")
want := "_x = (([player, foo] getVar bar) setHit [\"head\", \"tail\"]);\n"
equal(t, got, want)
got := getCompiled(t, "test/parser_buildin_func.asl")
want := "_x = (([player, foo] getVar bar) setHit [\"head\", \"tail\"]);\n"
equal(t, got, want)
}
func getCompiled(t *testing.T, file string) string {
code, err := ioutil.ReadFile(file)
if err != nil {
t.Error("Could not read test file: "+file)
t.FailNow()
}
code, err := ioutil.ReadFile(file)
if err != nil {
t.Error("Could not read test file: " + file)
t.FailNow()
}
tokens := asl.Tokenize(code)
return asl.Parse(tokens, true)
}
func equal(t *testing.T, got, want string) {
if got != want {
t.Error("Results do not equal, got:")
t.Log(got)
t.Log("expected:")
t.Log(want)
t.FailNow()
}
if got != want {
t.Error("Results do not equal, got:")
t.Log(got)
t.Log("expected:")
t.Log(want)
t.FailNow()
}
}

View File

@@ -52,43 +52,43 @@ func Tokenize(code []byte) []Token {
code = removeComments(code)
tokens := make([]Token, 0)
token, mask, isstring := "", false, false
for i := range code {
c := code[i]
// string masks (backslash)
if c == '\\' && !mask {
token += "\\"
mask = true
continue
token += "\\"
mask = true
continue
}
// string
if c == '"' && !mask {
token += "\""
isstring = !isstring
continue
token += "\""
isstring = !isstring
continue
}
if isstring {
token += string(c)
token += string(c)
} else {
// delimeter, keyword or variable/expression
if byteArrayContains(delimiter, c) {
if token != "" {
tokens = append(tokens, Token{token})
}
tokens = append(tokens, Token{string(c)})
token = ""
} else if stringArrayContains(keywords, strings.ToLower(token)) && !isIdentifierCharacter(c) {
tokens = append(tokens, Token{token})
token = ""
} else if !byteArrayContains(whitespace, c) {
token += string(c)
}
// delimeter, keyword or variable/expression
if byteArrayContains(delimiter, c) {
if token != "" {
tokens = append(tokens, Token{token})
}
tokens = append(tokens, Token{string(c)})
token = ""
} else if stringArrayContains(keywords, strings.ToLower(token)) && !isIdentifierCharacter(c) {
tokens = append(tokens, Token{token})
token = ""
} else if !byteArrayContains(whitespace, c) {
token += string(c)
}
}
mask = false
}
@@ -104,26 +104,26 @@ func removeComments(code []byte) []byte {
for i := 0; i < len(code); i++ {
c := code[i]
// do not remove comments from strings
if c == '\\' && !mask {
mask = true
}
if c == '"' && !mask {
isstring = !isstring
mask = true
}
// single/multi line comment
if !isstring {
if c == '/' && nextChar(code, i) == '/' {
i = skipSingleLineComment(code, i+1)
continue
} else if c == '/' && nextChar(code, i) == '*' {
i = skipMultiLineComment(code, i+1)
continue
}
}
if c == '"' && !mask {
isstring = !isstring
}
// single/multi line comment
if !isstring {
if c == '/' && nextChar(code, i) == '/' {
i = skipSingleLineComment(code, i+1)
continue
} else if c == '/' && nextChar(code, i) == '*' {
i = skipMultiLineComment(code, i+1)
continue
}
}
newcode[j] = c
j++
@@ -187,11 +187,11 @@ func stringArrayContains(haystack []string, needle string) bool {
// Checks if a character is allowed for identifiers.
func isIdentifierCharacter(c byte) bool {
for i := range identifier {
if identifier[i] == c {
return true
}
}
return false
for i := range identifier {
if identifier[i] == c {
return true
}
}
return false
}

View File

@@ -1,52 +1,52 @@
package asl
import (
"testing"
"io/ioutil"
"io/ioutil"
"testing"
)
func TestTokenizerVar(t *testing.T) {
got := getTokens(t, "test/tokenizer_var.asl")
got := getTokens(t, "test/tokenizer_var.asl")
want := []string{"var", "x", "=", "1", ";", "var", "array", "=", "[", "1", ",", "2", ",", "3", "]", ";"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerIf(t *testing.T) {
got := getTokens(t, "test/tokenizer_if.asl")
got := getTokens(t, "test/tokenizer_if.asl")
want := []string{"if", "a", "<", "b", "{", "}"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerWhile(t *testing.T) {
got := getTokens(t, "test/tokenizer_while.asl")
got := getTokens(t, "test/tokenizer_while.asl")
want := []string{"while", "true", "{", "}"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerFor(t *testing.T) {
got := getTokens(t, "test/tokenizer_for.asl")
got := getTokens(t, "test/tokenizer_for.asl")
want := []string{"for", "var", "i", "=", "0", ";", "i", "<", "100", ";", "i", "=", "i", "+", "1", "{", "}"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerForach(t *testing.T) {
got := getTokens(t, "test/tokenizer_foreach.asl")
got := getTokens(t, "test/tokenizer_foreach.asl")
want := []string{"foreach", "allUnits", "{", "}"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerSwitch(t *testing.T) {
got := getTokens(t, "test/tokenizer_switch.asl")
got := getTokens(t, "test/tokenizer_switch.asl")
want := []string{"switch", "x", "{", "case", "1", ":", "x", "=", "1", ";", "break", ";", "case", "2", ":", "x", "=", "2", ";", "break", ";", "default", ":", "x", "=", "3", ";", "}"}
compareLength(t, &got, &want)
@@ -54,64 +54,64 @@ func TestTokenizerSwitch(t *testing.T) {
}
func TestTokenizerFunction(t *testing.T) {
got := getTokens(t, "test/tokenizer_func.asl")
got := getTokens(t, "test/tokenizer_func.asl")
want := []string{"func", "TestFunction", "(", "param0", ",", "param1", ")", "{", "return", "true", ";", "}"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerExpression(t *testing.T) {
got := getTokens(t, "test/tokenizer_expr.asl")
got := getTokens(t, "test/tokenizer_expr.asl")
want := []string{"x", "=", "(", "(", "1", "+", "2", "+", "3", ")", "*", "4", "/", "2", ")", "+", "foo", "(", "1", ",", "2", ",", "3", ")", ";"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func TestTokenizerIdentifier(t *testing.T) {
got := getTokens(t, "test/tokenizer_identifier.asl")
got := getTokens(t, "test/tokenizer_identifier.asl")
want := []string{"var", "format", "=", "\"should not be for mat!\"", ";"}
compareLength(t, &got, &want)
compareTokens(t, &got, &want)
}
func compareLength(t *testing.T, got *[]Token, want *[]string) {
if len(*got) != len(*want) {
t.Error("Length of tokens got and expected tokens not equal, was:")
gotlist, wantlist := "", ""
for i := range *got {
gotlist += (*got)[i].token+" "
}
for i := range *want {
wantlist += (*want)[i]+" "
}
t.Log(gotlist)
t.Log("expected:")
t.Log(wantlist)
t.FailNow()
if len(*got) != len(*want) {
t.Error("Length of tokens got and expected tokens not equal, was:")
gotlist, wantlist := "", ""
for i := range *got {
gotlist += (*got)[i].token + " "
}
for i := range *want {
wantlist += (*want)[i] + " "
}
t.Log(gotlist)
t.Log("expected:")
t.Log(wantlist)
t.FailNow()
}
}
func compareTokens(t *testing.T, got *[]Token, want *[]string) {
for i := range *got {
if (*got)[i].token != (*want)[i] {
t.Error("Tokens do not match: "+(*got)[i].token+" != "+(*want)[i])
}
for i := range *got {
if (*got)[i].token != (*want)[i] {
t.Error("Tokens do not match: " + (*got)[i].token + " != " + (*want)[i])
}
}
}
func getTokens(t *testing.T, file string) []Token {
code, err := ioutil.ReadFile(file)
if err != nil {
t.Error("Could not read test file: "+file)
t.FailNow()
}
code, err := ioutil.ReadFile(file)
if err != nil {
t.Error("Could not read test file: " + file)
t.FailNow()
}
return Tokenize(code)
}

View File

@@ -9,12 +9,12 @@ import (
const version = "0.1"
func usage() {
fmt.Println("Usage: asl [-v|-r|-pretty] <input file/folder> [<output file/folder>]\n")
fmt.Println("-v (optional) shows asl version")
fmt.Println("-r (optional) recursivly compile all asl files in folder")
fmt.Println("-pretty (optional) activates pretty printing\n")
fmt.Println("<input file/folder> file or directory to compile")
fmt.Println("<output file/folder> (optional) output file/folder, if not set, files will be created alongside their asl files")
fmt.Println("Usage: asl [-v|-r|-pretty] <input file/folder> [<output file/folder>]\n")
fmt.Println("-v (optional) shows asl version")
fmt.Println("-r (optional) recursivly compile all asl files in folder")
fmt.Println("-pretty (optional) activates pretty printing\n")
fmt.Println("<input file/folder> file or directory to compile")
fmt.Println("<output file/folder> (optional) output file/folder, if not set, files will be created alongside their asl files")
}
func main() {
@@ -23,5 +23,5 @@ func main() {
token := asl.Tokenize(code)
out := asl.Parse(token, true)
fmt.Print("OUTPUT:\n-------\n"+out)
fmt.Print("OUTPUT:\n-------\n" + out)
}