mirror of
https://github.com/Kugelschieber/asl.git
synced 2026-01-18 12:00:25 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3990a20cb0 | ||
|
|
07250ba2a7 | ||
|
|
2bc11fb125 | ||
|
|
0ca6566454 | ||
|
|
5c540ce481 | ||
|
|
f6b5ed4dd6 | ||
|
|
afad182f28 | ||
|
|
d2c32b3b85 | ||
|
|
67e950b41d | ||
|
|
6c55846e9f | ||
|
|
3ee9a310e8 | ||
|
|
8d5c0a6905 | ||
|
|
276ab86668 | ||
|
|
dc4eaf4b0f | ||
|
|
1d7f435530 | ||
|
|
aedf8e715e | ||
|
|
4da540317f | ||
|
|
6e4cef91bc | ||
|
|
ae8fad1abc | ||
|
|
755d5af263 | ||
|
|
bf751e63c9 | ||
|
|
c3d4661b7f | ||
|
|
d7013b01c3 | ||
|
|
393ddd4326 | ||
|
|
53ea74a19b | ||
|
|
0a66f15704 | ||
|
|
352bfd6a9b | ||
|
|
0b26c5bdf8 | ||
|
|
1cbcda4261 | ||
|
|
bba215cd8d | ||
|
|
a64bef135d | ||
|
|
7ed05f913b | ||
|
|
a703189f0c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/bin/
|
||||
/pkg/
|
||||
/out/
|
||||
/in/
|
||||
|
||||
14
CHANGELOG.md
Normal file
14
CHANGELOG.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
**1.1.0**
|
||||
|
||||
* changed syntax of foreach
|
||||
* private function variables
|
||||
* default values for function parameters
|
||||
* added preprocessor
|
||||
* code inlining using new keyword "code"
|
||||
* some code and repo cleanup
|
||||
|
||||
**1.0.0**
|
||||
|
||||
* first release
|
||||
83
README.md
83
README.md
@@ -11,6 +11,7 @@ Main reasons for ASL:
|
||||
* easy to learn and understand
|
||||
* full replacement of SQF
|
||||
* compatible with Arma wiki and commands
|
||||
* comfortable
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -76,6 +77,8 @@ Controll structure syntax is C-like. Notice they are all using the same brackets
|
||||
```
|
||||
if 1 < 2 {
|
||||
// ...
|
||||
} else { // no else if yet
|
||||
// ...
|
||||
}
|
||||
|
||||
while 1 < 2 {
|
||||
@@ -86,17 +89,16 @@ for var _i = 0; _i < 100; _i = _i+1 { // var before identifier is optional
|
||||
// ...
|
||||
}
|
||||
|
||||
each allUnits { // foreach, iterates over all units in this case
|
||||
// element is available as _x here
|
||||
foreach unit => allUnits { // foreach, iterates over all units in this case
|
||||
// element is available as "unit" here
|
||||
// _x is still available due to how SQF works!
|
||||
}
|
||||
|
||||
switch x {
|
||||
switch x { // there is no "break" in SQF
|
||||
case 1:
|
||||
// ...
|
||||
break;
|
||||
case 2:
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
// ...
|
||||
}
|
||||
@@ -123,6 +125,19 @@ var _x = add(1, 2);
|
||||
// result in _x is 3
|
||||
```
|
||||
|
||||
Functions support predefined parameters:
|
||||
|
||||
```
|
||||
func add(_a = 0, _b = 0) {
|
||||
return _a+_b;
|
||||
}
|
||||
|
||||
// Call it:
|
||||
|
||||
var _x = add();
|
||||
// result in _x is 0
|
||||
```
|
||||
|
||||
### Call build in commands
|
||||
|
||||
To call SQF build in commands (like hint, getDir, addItem, ...) we have to use a different syntax.
|
||||
@@ -134,7 +149,7 @@ addItem(someUnit)("NVGoogles");
|
||||
someUnit addItem "NVGoogles";
|
||||
```
|
||||
|
||||
Where the first brackets contain the parameters used in front of SQF command and the second ones behind SQF command. If more than one parameter is passed, it will be converted to an array.
|
||||
Where the first brackets contain the parameters used in front of SQF command and the second ones behind SQF command. If more than one parameter is passed, it will be converted to an array. This syntax can be used for **all** build in functions (also spawn and so on).
|
||||
|
||||
```
|
||||
foo(x, y, z)(1, 2, 3);
|
||||
@@ -173,6 +188,40 @@ waitUntil {condition};
|
||||
waitUntil {expression;condition};
|
||||
```
|
||||
|
||||
**code**
|
||||
|
||||
The code function is used to compile inline code. This does **not** replace SQF compile buildin function, but will return the contained ASL code as SQF.
|
||||
|
||||
```
|
||||
// input:
|
||||
var x = code("var y = 5;"); // pass as string
|
||||
|
||||
// output:
|
||||
x = {y = 5;};
|
||||
```
|
||||
|
||||
## Preprocessor
|
||||
|
||||
The preprocessor works like the original one, with some limitations.
|
||||
Please visit the link at the bottom, to read about the preprocessor and how to use it. Generally, preprocessor lines must start with the hash key (#) and must stay in their own line. They are always printed as seperate lines. These features are not supported:
|
||||
|
||||
* replacing parts of words
|
||||
* multi line preprocessor commands
|
||||
* __EXEC (not used in SQF anyway)
|
||||
|
||||
If you use *__EXEC*, it will be replaced by a function call to it ([] call __EXEC).
|
||||
*__LINE__* and *__FILE__* can be used, since they are identifiers:
|
||||
|
||||
```
|
||||
if __LINE__ == 22 {
|
||||
// ...
|
||||
}
|
||||
|
||||
if __FILE__ == "myScript.sqf" {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## List of all keywords
|
||||
|
||||
Keywords should not be used as identifiers. Here is a full list of all keywords in ASL. Remember that build in function names should not be used neither.
|
||||
@@ -195,6 +244,27 @@ Keywords should not be used as identifiers. Here is a full list of all keywords
|
||||
| catch |
|
||||
| exitwith |
|
||||
| waituntil |
|
||||
| code |
|
||||
|
||||
## What's missing?
|
||||
|
||||
The following features are not implemented yet, but will be in 1.1.0 or a future version:
|
||||
|
||||
* scopes
|
||||
* else if
|
||||
* arrays within expressions (like someArray-[someEntity])
|
||||
|
||||
scopes won't be supported, since they are a stupid concept and can be replaced by functions.
|
||||
There is a simple workaround for arrays within expressions:
|
||||
|
||||
```
|
||||
// want: ... forEach allCurators-[myCurator];
|
||||
|
||||
var myCuratorArray = [myCurator]; // or a more complex expression within array
|
||||
foreach allCurators-myCuratorArray {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
@@ -206,6 +276,7 @@ For further information you can read the SQF tutorial and documentation of scrip
|
||||
|
||||
* [Arma Wiki](https://community.bistudio.com/wiki/Main_Page)
|
||||
* [Scripting commands](https://community.bistudio.com/wiki/Category:Scripting_Commands_Arma_3)
|
||||
* [Scripting preprocessor](https://community.bistudio.com/wiki/PreProcessor_Commands)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
var array = [1, 2, 3];
|
||||
var x = array[0];
|
||||
|
||||
if array[(1-1)+10/10] == 2 {
|
||||
foo();
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
package asl
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const TAB = " "
|
||||
|
||||
// Parses tokens, validates code to a specific degree
|
||||
// and writes SQF code into desired location.
|
||||
func Parse(token []Token, prettyPrinting bool) string {
|
||||
if !initParser(token, prettyPrinting) {
|
||||
return ""
|
||||
}
|
||||
|
||||
for tokenIndex < len(token) {
|
||||
parseBlock()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func parseBlock() {
|
||||
if accept("var") {
|
||||
parseVar()
|
||||
} else if accept("if") {
|
||||
parseIf()
|
||||
} else if accept("while") {
|
||||
parseWhile()
|
||||
} else if accept("switch") {
|
||||
parseSwitch()
|
||||
} else if accept("for") {
|
||||
parseFor()
|
||||
} else if accept("foreach") {
|
||||
parseForeach()
|
||||
} else if accept("func") {
|
||||
parseFunction()
|
||||
} else if accept("return") {
|
||||
parseReturn()
|
||||
} else if accept("try") {
|
||||
parseTryCatch()
|
||||
} else if accept("exitwith") {
|
||||
parseExitWith()
|
||||
} else if accept("waituntil") {
|
||||
parseWaitUntil()
|
||||
} else if accept("case") || accept("default") {
|
||||
return
|
||||
} else {
|
||||
parseStatement()
|
||||
}
|
||||
|
||||
if !end() && !accept("}") {
|
||||
parseBlock()
|
||||
}
|
||||
}
|
||||
|
||||
func parseVar() {
|
||||
expect("var")
|
||||
appendOut(get().token, false)
|
||||
next()
|
||||
|
||||
if accept("=") {
|
||||
next()
|
||||
appendOut(" = ", false)
|
||||
|
||||
if accept("[") {
|
||||
parseArray()
|
||||
} else {
|
||||
parseExpression(true)
|
||||
}
|
||||
}
|
||||
|
||||
expect(";")
|
||||
appendOut(";", true)
|
||||
}
|
||||
|
||||
func parseArray() {
|
||||
expect("[")
|
||||
appendOut("[", false)
|
||||
|
||||
if !accept("]") {
|
||||
parseExpression(true)
|
||||
|
||||
for accept(",") {
|
||||
next()
|
||||
appendOut(",", false)
|
||||
parseExpression(true)
|
||||
}
|
||||
}
|
||||
|
||||
expect("]")
|
||||
appendOut("]", false)
|
||||
}
|
||||
|
||||
func parseIf() {
|
||||
expect("if")
|
||||
appendOut("if (", false)
|
||||
parseExpression(true)
|
||||
appendOut(") then {", true)
|
||||
expect("{")
|
||||
parseBlock()
|
||||
expect("}")
|
||||
|
||||
if accept("else") {
|
||||
next()
|
||||
expect("{")
|
||||
appendOut("} else {", true)
|
||||
parseBlock()
|
||||
expect("}")
|
||||
}
|
||||
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
func parseWhile() {
|
||||
expect("while")
|
||||
appendOut("while {", false)
|
||||
parseExpression(true)
|
||||
appendOut("} do {", true)
|
||||
expect("{")
|
||||
parseBlock()
|
||||
expect("}")
|
||||
appendOut("};", false)
|
||||
}
|
||||
|
||||
func parseSwitch() {
|
||||
expect("switch")
|
||||
appendOut("switch (", false)
|
||||
parseExpression(true)
|
||||
appendOut(") do {", true)
|
||||
expect("{")
|
||||
parseSwitchBlock()
|
||||
expect("}")
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
func parseSwitchBlock() {
|
||||
if accept("}") {
|
||||
return
|
||||
}
|
||||
|
||||
if accept("case") {
|
||||
next()
|
||||
appendOut("case ", false)
|
||||
parseExpression(true)
|
||||
expect(":")
|
||||
appendOut(":", true)
|
||||
|
||||
if !accept("case") && !accept("}") && !accept("default") {
|
||||
appendOut("{", true)
|
||||
parseBlock()
|
||||
appendOut("};", true)
|
||||
}
|
||||
} else if accept("default") {
|
||||
next()
|
||||
expect(":")
|
||||
appendOut("default:", true)
|
||||
|
||||
if !accept("}") {
|
||||
appendOut("{", true)
|
||||
parseBlock()
|
||||
appendOut("};", true)
|
||||
}
|
||||
}
|
||||
|
||||
parseSwitchBlock()
|
||||
}
|
||||
|
||||
func parseFor() {
|
||||
expect("for")
|
||||
appendOut("for [{", false)
|
||||
|
||||
// var in first assignment is optional
|
||||
if accept("var") {
|
||||
next()
|
||||
}
|
||||
|
||||
parseExpression(true)
|
||||
expect(";")
|
||||
appendOut("}, {", false)
|
||||
parseExpression(true)
|
||||
expect(";")
|
||||
appendOut("}, {", false)
|
||||
parseExpression(true)
|
||||
appendOut("}] do {", true)
|
||||
expect("{")
|
||||
parseBlock()
|
||||
expect("}")
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
func parseForeach() {
|
||||
expect("foreach")
|
||||
expr := parseExpression(false)
|
||||
expect("{")
|
||||
appendOut("{", true)
|
||||
parseBlock()
|
||||
expect("}")
|
||||
appendOut("} forEach ("+expr+");", true)
|
||||
}
|
||||
|
||||
func parseFunction() {
|
||||
expect("func")
|
||||
appendOut(get().token+" = {", true)
|
||||
next()
|
||||
expect("(")
|
||||
parseFunctionParameter()
|
||||
expect(")")
|
||||
expect("{")
|
||||
parseBlock()
|
||||
expect("}")
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
func parseFunctionParameter() {
|
||||
// empty parameter list
|
||||
if accept("{") {
|
||||
return
|
||||
}
|
||||
|
||||
i := int64(0)
|
||||
|
||||
for !accept(")") {
|
||||
name := get().token
|
||||
next()
|
||||
appendOut(name+" = _this select "+strconv.FormatInt(i, 10)+";", true)
|
||||
i++
|
||||
|
||||
if !accept(")") {
|
||||
expect(",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseReturn() {
|
||||
expect("return")
|
||||
appendOut("return ", false)
|
||||
parseExpression(true)
|
||||
expect(";")
|
||||
appendOut(";", true)
|
||||
}
|
||||
|
||||
func parseTryCatch() {
|
||||
expect("try")
|
||||
expect("{")
|
||||
appendOut("try {", true)
|
||||
parseBlock()
|
||||
expect("}")
|
||||
expect("catch")
|
||||
expect("{")
|
||||
appendOut("} catch {", true)
|
||||
parseBlock()
|
||||
expect("}")
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
func parseExitWith() {
|
||||
expect("exitwith")
|
||||
expect("{")
|
||||
appendOut("if (true) exitWith {", true)
|
||||
parseBlock()
|
||||
expect("}")
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
func parseWaitUntil() {
|
||||
expect("waituntil")
|
||||
expect("(")
|
||||
appendOut("waitUntil {", false)
|
||||
parseExpression(true)
|
||||
|
||||
if accept(";") {
|
||||
next()
|
||||
appendOut(";", false)
|
||||
parseExpression(true)
|
||||
}
|
||||
|
||||
expect(")")
|
||||
expect(";")
|
||||
appendOut("};", true)
|
||||
}
|
||||
|
||||
// Everything that does not start with a keyword.
|
||||
func parseStatement() {
|
||||
// empty block
|
||||
if accept("}") || accept("case") || accept("default") {
|
||||
return
|
||||
}
|
||||
|
||||
// variable or function name
|
||||
name := get().token
|
||||
next()
|
||||
|
||||
if accept("=") {
|
||||
appendOut(name, false)
|
||||
parseAssignment()
|
||||
} else {
|
||||
parseFunctionCall(true, name)
|
||||
expect(";")
|
||||
appendOut(";", true)
|
||||
}
|
||||
|
||||
if !end() {
|
||||
parseBlock()
|
||||
}
|
||||
}
|
||||
|
||||
func parseAssignment() {
|
||||
expect("=")
|
||||
appendOut(" = ", false)
|
||||
parseExpression(true)
|
||||
expect(";")
|
||||
appendOut(";", true)
|
||||
}
|
||||
|
||||
func parseFunctionCall(out bool, name string) string {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
output = "[" + leftParams + "] call " + name
|
||||
}
|
||||
|
||||
if out {
|
||||
appendOut(output, false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func parseParameter(out bool) (string, int) {
|
||||
output := ""
|
||||
count := 0
|
||||
|
||||
for !accept(")") {
|
||||
output += parseExpression(out)
|
||||
count++
|
||||
|
||||
if !accept(")") {
|
||||
expect(",")
|
||||
output += ", "
|
||||
}
|
||||
}
|
||||
|
||||
if out {
|
||||
appendOut(output, false)
|
||||
}
|
||||
|
||||
return output, count
|
||||
}
|
||||
|
||||
func parseExpression(out bool) string {
|
||||
output := parseArith()
|
||||
|
||||
for accept("<") || 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 if accept("=") {
|
||||
output += "="
|
||||
next()
|
||||
} else {
|
||||
next()
|
||||
expect("=")
|
||||
output += "!="
|
||||
}
|
||||
|
||||
if accept("=") {
|
||||
output += "="
|
||||
next()
|
||||
}
|
||||
|
||||
output += parseExpression(false)
|
||||
}
|
||||
|
||||
if out {
|
||||
appendOut(output, false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func parseIdentifier() string {
|
||||
output := ""
|
||||
|
||||
if seek("(") && !accept("!") && !accept("-") {
|
||||
name := get().token
|
||||
next()
|
||||
output = "(" + parseFunctionCall(false, name) + ")"
|
||||
} else if seek("[") {
|
||||
output += "("+get().token
|
||||
next()
|
||||
expect("[")
|
||||
output += " select ("+parseExpression(false)+"))"
|
||||
expect("]")
|
||||
} else if accept("!") || accept("-") {
|
||||
output = get().token
|
||||
next()
|
||||
output += parseTerm()
|
||||
} else {
|
||||
output = get().token
|
||||
next()
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func parseTerm() string {
|
||||
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
|
||||
}
|
||||
|
||||
func parseArith() string {
|
||||
output := parseFactor()
|
||||
|
||||
for accept("+") || accept("-") {
|
||||
if accept("+") {
|
||||
output += "+"
|
||||
} else {
|
||||
output += "-"
|
||||
}
|
||||
|
||||
next()
|
||||
output += parseExpression(false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package asl
|
||||
|
||||
var tokens []Token
|
||||
var tokenIndex int
|
||||
var out string
|
||||
var offset int
|
||||
var pretty bool
|
||||
|
||||
// Initilizes the parser.
|
||||
func initParser(token []Token, prettyPrinting bool) bool {
|
||||
if len(token) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
tokens = token
|
||||
tokenIndex = 0
|
||||
out = ""
|
||||
offset = 0
|
||||
pretty = prettyPrinting
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true, if current token matches expected one.
|
||||
// Does not throw parse errors and checks if token is available.
|
||||
func accept(token string) bool {
|
||||
return tokenIndex < len(tokens) && tokenEqual(token, get())
|
||||
}
|
||||
|
||||
// Hard version of "accept".
|
||||
// Throws if current token does not match expected one.
|
||||
func expect(token string) {
|
||||
if !tokenEqual(token, get()) {
|
||||
panic("Parse error, expected '" + token + "' but was '" + get().token + "'")
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// 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])
|
||||
}
|
||||
|
||||
// Increases token counter, so that the next token is compared.
|
||||
func next() {
|
||||
tokenIndex++
|
||||
}
|
||||
|
||||
// Returns current token or throws, if no more tokens are available.
|
||||
func get() Token {
|
||||
if tokenIndex >= len(tokens) {
|
||||
panic("No more tokens")
|
||||
}
|
||||
|
||||
return tokens[tokenIndex]
|
||||
}
|
||||
|
||||
// Returns true if the end of input code was reached.
|
||||
func end() bool {
|
||||
return tokenIndex == len(tokens)
|
||||
}
|
||||
|
||||
// Checks if two strings match.
|
||||
func tokenEqual(a string, b Token) bool {
|
||||
return a == b.token
|
||||
}
|
||||
|
||||
// Appends the output string to current SQF code output.
|
||||
func appendOut(str string, newLine bool) {
|
||||
out += str
|
||||
|
||||
if newLine && pretty {
|
||||
out += "\r\n"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"asl"
|
||||
"parser"
|
||||
"tokenizer"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@@ -9,9 +10,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const version = "1.0.0"
|
||||
const extension = ".asl"
|
||||
const sqfextension = ".sqf"
|
||||
const (
|
||||
version = "1.1.0"
|
||||
extension = ".asl"
|
||||
sqfextension = ".sqf"
|
||||
PathSeparator = string(os.PathSeparator)
|
||||
)
|
||||
|
||||
type ASLFile struct {
|
||||
in string
|
||||
@@ -19,11 +23,13 @@ type ASLFile struct {
|
||||
newname string
|
||||
}
|
||||
|
||||
var recursive bool = false
|
||||
var pretty bool = false
|
||||
var exit bool = false
|
||||
var aslFiles []ASLFile
|
||||
var inDir string
|
||||
var (
|
||||
recursive bool = false
|
||||
pretty bool = false
|
||||
exit bool = false
|
||||
aslFiles []ASLFile
|
||||
inDir string
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Println("Usage: asl [-v|-r|-pretty|--help] <input directory> <output directory>\n")
|
||||
@@ -69,12 +75,12 @@ func readAslFiles(path string) {
|
||||
name := dir[i].Name()
|
||||
|
||||
if dir[i].IsDir() && recursive {
|
||||
readAslFiles(filepath.FromSlash(path+"/"+name))
|
||||
readAslFiles(filepath.FromSlash(path+PathSeparator+name))
|
||||
continue
|
||||
}
|
||||
|
||||
if !dir[i].IsDir() && strings.ToLower(filepath.Ext(name)) == extension {
|
||||
in := filepath.FromSlash(path+"/"+dir[i].Name())
|
||||
in := filepath.FromSlash(path+PathSeparator+dir[i].Name())
|
||||
out := filepath.FromSlash("./"+path[len(inDir):len(path)])
|
||||
newname := name[:len(name)-len(filepath.Ext(name))]
|
||||
|
||||
@@ -86,7 +92,7 @@ func readAslFiles(path string) {
|
||||
|
||||
func compile(path string) {
|
||||
for i := 0; i < len(aslFiles); i++ {
|
||||
out := filepath.FromSlash(path+"/"+aslFiles[i].out+"/"+aslFiles[i].newname+sqfextension)
|
||||
out := filepath.FromSlash(path+PathSeparator+aslFiles[i].out+PathSeparator+aslFiles[i].newname+sqfextension)
|
||||
fmt.Println(aslFiles[i].in+" -> "+out)
|
||||
code, err := ioutil.ReadFile(aslFiles[i].in)
|
||||
|
||||
@@ -95,10 +101,11 @@ func compile(path string) {
|
||||
continue
|
||||
}
|
||||
|
||||
token := asl.Tokenize(code)
|
||||
sqf := asl.Parse(token, pretty)
|
||||
token := tokenizer.Tokenize(code)
|
||||
compiler := parser.Compiler{}
|
||||
sqf := compiler.Parse(token, pretty)
|
||||
|
||||
os.MkdirAll(filepath.FromSlash(path+"/"+aslFiles[i].out), 0777)
|
||||
os.MkdirAll(filepath.FromSlash(path+PathSeparator+aslFiles[i].out), 0777)
|
||||
err = ioutil.WriteFile(out, []byte(sqf), 0666)
|
||||
|
||||
if err != nil {
|
||||
|
||||
528
src/parser/parser.go
Normal file
528
src/parser/parser.go
Normal file
@@ -0,0 +1,528 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"tokenizer"
|
||||
)
|
||||
|
||||
const new_line = "\r\n"
|
||||
|
||||
// Parses tokens, validates code to a specific degree
|
||||
// and writes SQF code into desired location.
|
||||
func (c *Compiler) Parse(token []tokenizer.Token, prettyPrinting bool) string {
|
||||
if !c.initParser(token, prettyPrinting) {
|
||||
return ""
|
||||
}
|
||||
|
||||
for c.tokenIndex < len(token) {
|
||||
c.parseBlock()
|
||||
}
|
||||
|
||||
return c.out
|
||||
}
|
||||
|
||||
func (c *Compiler) parseBlock() {
|
||||
if c.get().Preprocessor {
|
||||
c.parsePreprocessor()
|
||||
} else if c.accept("var") {
|
||||
c.parseVar()
|
||||
} else if c.accept("if") {
|
||||
c.parseIf()
|
||||
} else if c.accept("while") {
|
||||
c.parseWhile()
|
||||
} else if c.accept("switch") {
|
||||
c.parseSwitch()
|
||||
} else if c.accept("for") {
|
||||
c.parseFor()
|
||||
} else if c.accept("foreach") {
|
||||
c.parseForeach()
|
||||
} else if c.accept("func") {
|
||||
c.parseFunction()
|
||||
} else if c.accept("return") {
|
||||
c.parseReturn()
|
||||
} else if c.accept("try") {
|
||||
c.parseTryCatch()
|
||||
} else if c.accept("exitwith") {
|
||||
c.parseExitWith()
|
||||
} else if c.accept("waituntil") {
|
||||
c.parseWaitUntil()
|
||||
} else if c.accept("case") || c.accept("default") {
|
||||
return
|
||||
} else {
|
||||
c.parseStatement()
|
||||
}
|
||||
|
||||
if !c.end() && !c.accept("}") {
|
||||
c.parseBlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) parsePreprocessor() {
|
||||
// we definitely want a new line before and after
|
||||
c.appendOut(new_line+c.get().Token+new_line, false)
|
||||
c.next()
|
||||
}
|
||||
|
||||
func (c *Compiler) parseVar() {
|
||||
c.expect("var")
|
||||
c.appendOut(c.get().Token, false)
|
||||
c.next()
|
||||
|
||||
if c.accept("=") {
|
||||
c.next()
|
||||
c.appendOut(" = ", false)
|
||||
|
||||
if c.accept("[") {
|
||||
c.parseArray()
|
||||
} else {
|
||||
c.parseExpression(true)
|
||||
}
|
||||
}
|
||||
|
||||
c.expect(";")
|
||||
c.appendOut(";", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseArray() {
|
||||
c.expect("[")
|
||||
c.appendOut("[", false)
|
||||
|
||||
if !c.accept("]") {
|
||||
c.parseExpression(true)
|
||||
|
||||
for c.accept(",") {
|
||||
c.next()
|
||||
c.appendOut(",", false)
|
||||
c.parseExpression(true)
|
||||
}
|
||||
}
|
||||
|
||||
c.expect("]")
|
||||
c.appendOut("]", false)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseIf() {
|
||||
c.expect("if")
|
||||
c.appendOut("if (", false)
|
||||
c.parseExpression(true)
|
||||
c.appendOut(") then {", true)
|
||||
c.expect("{")
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
|
||||
if c.accept("else") {
|
||||
c.next()
|
||||
c.expect("{")
|
||||
c.appendOut("} else {", true)
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
}
|
||||
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseWhile() {
|
||||
c.expect("while")
|
||||
c.appendOut("while {", false)
|
||||
c.parseExpression(true)
|
||||
c.appendOut("} do {", true)
|
||||
c.expect("{")
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("};", false)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseSwitch() {
|
||||
c.expect("switch")
|
||||
c.appendOut("switch (", false)
|
||||
c.parseExpression(true)
|
||||
c.appendOut(") do {", true)
|
||||
c.expect("{")
|
||||
c.parseSwitchBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseSwitchBlock() {
|
||||
if c.accept("}") {
|
||||
return
|
||||
}
|
||||
|
||||
if c.accept("case") {
|
||||
c.next()
|
||||
c.appendOut("case ", false)
|
||||
c.parseExpression(true)
|
||||
c.expect(":")
|
||||
c.appendOut(":", true)
|
||||
|
||||
if !c.accept("case") && !c.accept("}") && !c.accept("default") {
|
||||
c.appendOut("{", true)
|
||||
c.parseBlock()
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
} else if c.accept("default") {
|
||||
c.next()
|
||||
c.expect(":")
|
||||
c.appendOut("default:", true)
|
||||
|
||||
if !c.accept("}") {
|
||||
c.appendOut("{", true)
|
||||
c.parseBlock()
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
}
|
||||
|
||||
c.parseSwitchBlock()
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFor() {
|
||||
c.expect("for")
|
||||
c.appendOut("for [{", false)
|
||||
|
||||
// var in first assignment is optional
|
||||
if c.accept("var") {
|
||||
c.next()
|
||||
}
|
||||
|
||||
c.parseExpression(true)
|
||||
c.expect(";")
|
||||
c.appendOut("}, {", false)
|
||||
c.parseExpression(true)
|
||||
c.expect(";")
|
||||
c.appendOut("}, {", false)
|
||||
c.parseExpression(true)
|
||||
c.appendOut("}] do {", true)
|
||||
c.expect("{")
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseForeach() {
|
||||
c.expect("foreach")
|
||||
element := c.get().Token
|
||||
c.next()
|
||||
c.expect("=")
|
||||
c.expect(">")
|
||||
expr := c.parseExpression(false)
|
||||
c.expect("{")
|
||||
c.appendOut("{", true)
|
||||
c.appendOut(element+" = _x;", true)
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("} forEach ("+expr+");", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFunction() {
|
||||
c.expect("func")
|
||||
c.appendOut(c.get().Token+" = {", true)
|
||||
c.next()
|
||||
c.expect("(")
|
||||
c.parseFunctionParameter()
|
||||
c.expect(")")
|
||||
c.expect("{")
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFunctionParameter() {
|
||||
// empty parameter list
|
||||
if c.accept("{") {
|
||||
return
|
||||
}
|
||||
|
||||
c.appendOut("params [", false)
|
||||
|
||||
for !c.accept(")") {
|
||||
name := c.get().Token
|
||||
c.next()
|
||||
|
||||
if c.accept("=") {
|
||||
c.next()
|
||||
value := c.get().Token
|
||||
c.next()
|
||||
c.appendOut("[\""+name+"\","+value+"]", false)
|
||||
} else {
|
||||
c.appendOut("\""+name+"\"", false)
|
||||
}
|
||||
|
||||
if !c.accept(")") {
|
||||
c.expect(",")
|
||||
c.appendOut(",", false)
|
||||
}
|
||||
}
|
||||
|
||||
c.appendOut("];", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseReturn() {
|
||||
c.expect("return")
|
||||
c.appendOut("return ", false)
|
||||
c.parseExpression(true)
|
||||
c.expect(";")
|
||||
c.appendOut(";", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseTryCatch() {
|
||||
c.expect("try")
|
||||
c.expect("{")
|
||||
c.appendOut("try {", true)
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.expect("catch")
|
||||
c.expect("{")
|
||||
c.appendOut("} catch {", true)
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseExitWith() {
|
||||
c.expect("exitwith")
|
||||
c.expect("{")
|
||||
c.appendOut("if (true) exitWith {", true)
|
||||
c.parseBlock()
|
||||
c.expect("}")
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseWaitUntil() {
|
||||
c.expect("waituntil")
|
||||
c.expect("(")
|
||||
c.appendOut("waitUntil {", false)
|
||||
c.parseExpression(true)
|
||||
|
||||
if c.accept(";") {
|
||||
c.next()
|
||||
c.appendOut(";", false)
|
||||
c.parseExpression(true)
|
||||
}
|
||||
|
||||
c.expect(")")
|
||||
c.expect(";")
|
||||
c.appendOut("};", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseInlineCode() string {
|
||||
c.expect("code")
|
||||
c.expect("(")
|
||||
|
||||
code := c.get().Token
|
||||
c.next()
|
||||
output := "{}"
|
||||
|
||||
if len(code) > 2 {
|
||||
compiler := Compiler{}
|
||||
output = "{"+compiler.Parse(tokenizer.Tokenize([]byte(code[1:len(code)-1])), false)+"}"
|
||||
}
|
||||
|
||||
c.expect(")")
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Everything that does not start with a keyword.
|
||||
func (c *Compiler) parseStatement() {
|
||||
// empty block
|
||||
if c.accept("}") || c.accept("case") || c.accept("default") {
|
||||
return
|
||||
}
|
||||
|
||||
// variable or function name
|
||||
name := c.get().Token
|
||||
c.next()
|
||||
|
||||
if c.accept("=") {
|
||||
c.appendOut(name, false)
|
||||
c.parseAssignment()
|
||||
} else {
|
||||
c.parseFunctionCall(true, name)
|
||||
c.expect(";")
|
||||
c.appendOut(";", true)
|
||||
}
|
||||
|
||||
if !c.end() {
|
||||
c.parseBlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) parseAssignment() {
|
||||
c.expect("=")
|
||||
c.appendOut(" = ", false)
|
||||
c.parseExpression(true)
|
||||
c.expect(";")
|
||||
c.appendOut(";", true)
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFunctionCall(out bool, name string) string {
|
||||
output := ""
|
||||
|
||||
c.expect("(")
|
||||
leftParams, leftParamCount := c.parseParameter(false)
|
||||
c.expect(")")
|
||||
|
||||
if c.accept("(") {
|
||||
// buildin function
|
||||
c.next()
|
||||
rightParams, rightParamCount := c.parseParameter(false)
|
||||
c.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
|
||||
}
|
||||
|
||||
if out {
|
||||
c.appendOut(output, false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (c *Compiler) parseParameter(out bool) (string, int) {
|
||||
output := ""
|
||||
count := 0
|
||||
|
||||
for !c.accept(")") {
|
||||
output += c.parseExpression(out)
|
||||
count++
|
||||
|
||||
if !c.accept(")") {
|
||||
c.expect(",")
|
||||
output += ", "
|
||||
}
|
||||
}
|
||||
|
||||
if out {
|
||||
c.appendOut(output, false)
|
||||
}
|
||||
|
||||
return output, count
|
||||
}
|
||||
|
||||
func (c *Compiler) parseExpression(out bool) string {
|
||||
output := c.parseArith()
|
||||
|
||||
for c.accept("<") || c.accept(">") || c.accept("&") || c.accept("|") || c.accept("=") || c.accept("!") {
|
||||
if c.accept("<") {
|
||||
output += "<"
|
||||
c.next()
|
||||
} else if c.accept(">") {
|
||||
output += ">"
|
||||
c.next()
|
||||
} else if c.accept("&") {
|
||||
c.next()
|
||||
c.expect("&")
|
||||
output += "&&"
|
||||
} else if c.accept("|") {
|
||||
c.next()
|
||||
c.expect("|")
|
||||
output += "||"
|
||||
} else if c.accept("=") {
|
||||
output += "="
|
||||
c.next()
|
||||
} else {
|
||||
c.next()
|
||||
c.expect("=")
|
||||
output += "!="
|
||||
}
|
||||
|
||||
if c.accept("=") {
|
||||
output += "="
|
||||
c.next()
|
||||
}
|
||||
|
||||
output += c.parseExpression(false)
|
||||
}
|
||||
|
||||
if out {
|
||||
c.appendOut(output, false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (c *Compiler) parseIdentifier() string {
|
||||
output := ""
|
||||
|
||||
if c.accept("code") {
|
||||
output += c.parseInlineCode()
|
||||
} else if c.seek("(") && !c.accept("!") && !c.accept("-") {
|
||||
name := c.get().Token
|
||||
c.next()
|
||||
output = "(" + c.parseFunctionCall(false, name) + ")"
|
||||
} else if c.seek("[") {
|
||||
output += "("+c.get().Token
|
||||
c.next()
|
||||
c.expect("[")
|
||||
output += " select ("+c.parseExpression(false)+"))"
|
||||
c.expect("]")
|
||||
} else if c.accept("!") || c.accept("-") {
|
||||
output = c.get().Token
|
||||
c.next()
|
||||
output += c.parseTerm()
|
||||
} else {
|
||||
output = c.get().Token
|
||||
c.next()
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (c *Compiler) parseTerm() string {
|
||||
if c.accept("(") {
|
||||
c.expect("(")
|
||||
output := "(" + c.parseExpression(false) + ")"
|
||||
c.expect(")")
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
return c.parseIdentifier()
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFactor() string {
|
||||
output := c.parseTerm()
|
||||
|
||||
for c.accept("*") || c.accept("/") { // TODO: modulo?
|
||||
if c.accept("*") {
|
||||
output += "*"
|
||||
} else {
|
||||
output += "/"
|
||||
}
|
||||
|
||||
c.next()
|
||||
output += c.parseExpression(false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (c *Compiler) parseArith() string {
|
||||
output := c.parseFactor()
|
||||
|
||||
for c.accept("+") || c.accept("-") {
|
||||
if c.accept("+") {
|
||||
output += "+"
|
||||
} else {
|
||||
output += "-"
|
||||
}
|
||||
|
||||
c.next()
|
||||
output += c.parseExpression(false)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
88
src/parser/parserHelper.go
Normal file
88
src/parser/parserHelper.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"tokenizer"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
tokens []tokenizer.Token
|
||||
tokenIndex int
|
||||
out string
|
||||
offset int
|
||||
pretty bool
|
||||
}
|
||||
|
||||
// Initilizes the parser.
|
||||
func (c *Compiler) initParser(token []tokenizer.Token, prettyPrinting bool) bool {
|
||||
if len(token) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
c.tokens = token
|
||||
c.tokenIndex = 0
|
||||
c.out = ""
|
||||
c.offset = 0
|
||||
c.pretty = prettyPrinting
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true, if current token matches expected one.
|
||||
// Does not throw parse errors and checks if token is available.
|
||||
func (c *Compiler) accept(token string) bool {
|
||||
return c.tokenIndex < len(c.tokens) && c.tokenEqual(token, c.get())
|
||||
}
|
||||
|
||||
// Hard version of "accept".
|
||||
// Throws if current token does not match expected one.
|
||||
func (c *Compiler) expect(token string) {
|
||||
if !c.tokenEqual(token, c.get()) {
|
||||
panic("Parse error, expected '" + token + "' but was '" + c.get().Token + "' in line "+strconv.Itoa(c.get().Line)+" at "+strconv.Itoa(c.get().Column))
|
||||
}
|
||||
|
||||
c.next()
|
||||
}
|
||||
|
||||
// Returns true, if the next token matches expected one.
|
||||
// Does not throw parse errors and checks if token is available.
|
||||
func (c *Compiler) seek(token string) bool {
|
||||
if c.tokenIndex+1 >= len(c.tokens) {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.tokenEqual(token, c.tokens[c.tokenIndex+1])
|
||||
}
|
||||
|
||||
// Increases token counter, so that the next token is compared.
|
||||
func (c *Compiler) next() {
|
||||
c.tokenIndex++
|
||||
}
|
||||
|
||||
// Returns current token or throws, if no more tokens are available.
|
||||
func (c *Compiler) get() tokenizer.Token {
|
||||
if c.tokenIndex >= len(c.tokens) {
|
||||
panic("No more tokens")
|
||||
}
|
||||
|
||||
return c.tokens[c.tokenIndex]
|
||||
}
|
||||
|
||||
// Returns true if the end of input code was reached.
|
||||
func (c *Compiler) end() bool {
|
||||
return c.tokenIndex == len(c.tokens)
|
||||
}
|
||||
|
||||
// Checks if two strings match.
|
||||
func (c *Compiler) tokenEqual(a string, b tokenizer.Token) bool {
|
||||
return a == b.Token
|
||||
}
|
||||
|
||||
// Appends the output string to current SQF code output.
|
||||
func (c *Compiler) appendOut(str string, newLine bool) {
|
||||
c.out += str
|
||||
|
||||
if newLine && c.pretty {
|
||||
c.out += "\r\n"
|
||||
}
|
||||
}
|
||||
@@ -1,137 +1,162 @@
|
||||
package asl_test
|
||||
package parser_test
|
||||
|
||||
import (
|
||||
"asl"
|
||||
"tokenizer"
|
||||
"parser"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParserDeclaration(t *testing.T) {
|
||||
got := getCompiled(t, "test/tokenizer_var.asl")
|
||||
want := "x = 1;\narray = [1,2,3];\n"
|
||||
want := "x = 1;\r\narray = [1,2,3];\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserAssignment(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_assignment.asl")
|
||||
want := "x = 1;\n"
|
||||
want := "x = 1;\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserIf(t *testing.T) {
|
||||
got := getCompiled(t, "test/tokenizer_if.asl")
|
||||
want := "if (a<b) then {\n};\n"
|
||||
want := "if (a<b) then {\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserWhile(t *testing.T) {
|
||||
got := getCompiled(t, "test/tokenizer_while.asl")
|
||||
want := "while {true} do {\n};"
|
||||
want := "while {true} do {\r\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"
|
||||
want := "for [{i=0}, {i<100}, {i=i+1}] do {\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserForeach(t *testing.T) {
|
||||
got := getCompiled(t, "test/tokenizer_foreach.asl")
|
||||
want := "{\n} forEach (allUnits);\n"
|
||||
want := "{\r\nunit = _x;\r\n} forEach (allUnits);\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserSwitch(t *testing.T) {
|
||||
|
||||
got := getCompiled(t, "test/tokenizer_switch.asl")
|
||||
want := "switch (x) do {\r\ncase 1:\r\n{\r\nx = 1;\r\n};\r\ncase 2:\r\n{\r\nx = 2;\r\n};\r\ndefault:\r\n{\r\nx = 3;\r\n};\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
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"
|
||||
want := "TestFunction = {\r\nparams [\"param0\",\"param1\"];\r\nreturn true;\r\n};\r\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"
|
||||
want := "x = ([1, 2, 3] call foo);\r\ny = ([1, 2, 3] call bar);\r\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"
|
||||
want := "x = -(1+(2+3))/(6*(someVariable+99-100))-(20)+!anotherVariable+([] call foo);\r\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"
|
||||
want := "x = true||(3>=4&&5<8);\r\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"
|
||||
want := "myFunc = {\r\nparams [\"a\",\"b\"];\r\nreturn a>b;\r\n};\r\n[1+3/4, 2-(66*22)/3-((123))] call myFunc;\r\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"
|
||||
want := "_x = (([player, foo] getVar bar) setHit [\"head\", \"tail\"]);\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserOperator(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_operator.asl")
|
||||
want := "if (x==y&&x!=y&&x<=y&&x>=y&&x<y&&x>y) then {\n};\n"
|
||||
want := "if (x==y&&x!=y&&x<=y&&x>=y&&x<y&&x>y) then {\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserTryCatch(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_try_catch.asl")
|
||||
want := "try {\n} catch {\n};\n"
|
||||
want := "try {\r\n} catch {\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserNegationFunctionCall(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_negation.asl")
|
||||
want := "x = !([] call foo);\n"
|
||||
want := "x = !([] call foo);\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserExitWith(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_exitwith.asl")
|
||||
want := "if (true) exitWith {\n};\n"
|
||||
want := "if (true) exitWith {\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserWaitUntil(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_waituntil.asl")
|
||||
want := "waitUntil {x=x+1;x<100};\n"
|
||||
want := "waitUntil {x=x+1;x<100};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserArray(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_array.asl")
|
||||
want := "x = [1,2,3];\ny = (x select (1));\n"
|
||||
want := "x = [1,2,3];\r\ny = (x select (1));\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserFunctionParams(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_func_params.asl")
|
||||
want := "myFunc = {\r\nparams [[\"a\",1],[\"b\",2]];\r\nreturn a+b;\r\n};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserInlineCode(t *testing.T) {
|
||||
got := getCompiled(t, "test/parser_code.asl")
|
||||
want := "inline_code = {a = 1;b = 2;if (a<b) then {[] call foo;};};\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
|
||||
func TestParserPreprocessor(t *testing.T) {
|
||||
got := getCompiled(t, "test/tokenizer_preprocessor.asl")
|
||||
want := "\r\n#define HELLO_WORLD \"Hello World!\"\r\nhint HELLO_WORLD;\r\n"
|
||||
|
||||
equal(t, got, want)
|
||||
}
|
||||
@@ -144,9 +169,10 @@ func getCompiled(t *testing.T, file string) string {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
tokens := asl.Tokenize(code)
|
||||
tokens := tokenizer.Tokenize(code)
|
||||
compiler := parser.Compiler{}
|
||||
|
||||
return asl.Parse(tokens, true)
|
||||
return compiler.Parse(tokens, true)
|
||||
}
|
||||
|
||||
func equal(t *testing.T, got, want string) {
|
||||
@@ -1,11 +1,14 @@
|
||||
package asl
|
||||
package tokenizer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
token string
|
||||
Token string
|
||||
Preprocessor bool
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
var delimiter = []byte{
|
||||
@@ -45,20 +48,29 @@ var keywords = []string{
|
||||
"try",
|
||||
"catch",
|
||||
"exitwith",
|
||||
"waituntil"}
|
||||
"waituntil",
|
||||
"code"}
|
||||
|
||||
var whitespace = []byte{' ', '\n', '\t', '\r'}
|
||||
var identifier = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
||||
var preprocessor = byte('#')
|
||||
var new_line = []byte{'\r', '\n'}
|
||||
|
||||
// Tokenizes the given byte array into syntax tokens,
|
||||
// which can be parsed later.
|
||||
func Tokenize(code []byte) []Token {
|
||||
code = removeComments(code)
|
||||
tokens := make([]Token, 0)
|
||||
token, mask, isstring := "", false, false
|
||||
token, mask, isstring, line, column := "", false, false, 0, 0
|
||||
|
||||
for i := range code {
|
||||
for i := 0; i < len(code); i++ {
|
||||
c := code[i]
|
||||
column++
|
||||
|
||||
if byteArrayContains(new_line, c) {
|
||||
line++
|
||||
column = 0
|
||||
}
|
||||
|
||||
// string masks (backslash)
|
||||
if c == '\\' && !mask {
|
||||
@@ -77,16 +89,19 @@ func Tokenize(code []byte) []Token {
|
||||
if isstring {
|
||||
token += string(c)
|
||||
} else {
|
||||
// delimeter, keyword or variable/expression
|
||||
if byteArrayContains(delimiter, c) {
|
||||
// preprocessor, delimeter, keyword or variable/expression
|
||||
if c == preprocessor {
|
||||
tokens = append(tokens, preprocessorLine(code, &i, line, column))
|
||||
token = ""
|
||||
} else if byteArrayContains(delimiter, c) {
|
||||
if token != "" {
|
||||
tokens = append(tokens, Token{token})
|
||||
tokens = append(tokens, Token{token, false, line, column})
|
||||
}
|
||||
|
||||
tokens = append(tokens, Token{string(c)})
|
||||
tokens = append(tokens, Token{string(c), false, line, column})
|
||||
token = ""
|
||||
} else if stringArrayContains(strings.ToLower(token)) && !isIdentifierCharacter(c) {
|
||||
tokens = append(tokens, Token{token})
|
||||
tokens = append(tokens, Token{token, false, line, column})
|
||||
token = ""
|
||||
} else if !byteArrayContains(whitespace, c) {
|
||||
token += string(c)
|
||||
@@ -137,6 +152,35 @@ func removeComments(code []byte) []byte {
|
||||
return newcode[:j]
|
||||
}
|
||||
|
||||
// Reads preprocessor command until end of line
|
||||
func preprocessorLine(code []byte, i *int, lineNr, column int) Token {
|
||||
c := byte('0')
|
||||
var line string
|
||||
|
||||
for *i < len(code) {
|
||||
c = code[*i]
|
||||
|
||||
if byteArrayContains(new_line, c) {
|
||||
break
|
||||
}
|
||||
|
||||
line += string(c)
|
||||
(*i)++
|
||||
}
|
||||
|
||||
// read all new line characters (\r and \n)
|
||||
c = code[*i]
|
||||
|
||||
for byteArrayContains(new_line, c) {
|
||||
(*i)++
|
||||
c = code[*i]
|
||||
}
|
||||
|
||||
(*i)-- // for will count up 1, so subtract it here
|
||||
|
||||
return Token{line, true, lineNr, column}
|
||||
}
|
||||
|
||||
// Returns the next character in code starting at i.
|
||||
// If no character is left, '0' will be returned.
|
||||
func nextChar(code []byte, i int) byte {
|
||||
@@ -1,6 +1,7 @@
|
||||
package asl
|
||||
package tokenizer_test
|
||||
|
||||
import (
|
||||
"tokenizer"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
@@ -39,7 +40,7 @@ func TestTokenizerFor(t *testing.T) {
|
||||
|
||||
func TestTokenizerForach(t *testing.T) {
|
||||
got := getTokens(t, "test/tokenizer_foreach.asl")
|
||||
want := []string{"foreach", "allUnits", "{", "}"}
|
||||
want := []string{"foreach", "unit", "=", ">", "allUnits", "{", "}"}
|
||||
|
||||
compareLength(t, &got, &want)
|
||||
compareTokens(t, &got, &want)
|
||||
@@ -47,7 +48,7 @@ func TestTokenizerForach(t *testing.T) {
|
||||
|
||||
func TestTokenizerSwitch(t *testing.T) {
|
||||
got := getTokens(t, "test/tokenizer_switch.asl")
|
||||
want := []string{"switch", "x", "{", "case", "1", ":", "x", "=", "1", ";", "break", ";", "case", "2", ":", "x", "=", "2", ";", "break", ";", "default", ":", "x", "=", "3", ";", "}"}
|
||||
want := []string{"switch", "x", "{", "case", "1", ":", "x", "=", "1", ";", "case", "2", ":", "x", "=", "2", ";", "default", ":", "x", "=", "3", ";", "}"}
|
||||
|
||||
compareLength(t, &got, &want)
|
||||
compareTokens(t, &got, &want)
|
||||
@@ -77,13 +78,29 @@ func TestTokenizerIdentifier(t *testing.T) {
|
||||
compareTokens(t, &got, &want)
|
||||
}
|
||||
|
||||
func compareLength(t *testing.T, got *[]Token, want *[]string) {
|
||||
func TestTokenizerInlineCode(t *testing.T) {
|
||||
got := getTokens(t, "test/tokenizer_code.asl")
|
||||
want := []string{"var", "x", "=", "code", "(", "\"var x = 5;\"", ")", ";"}
|
||||
|
||||
compareLength(t, &got, &want)
|
||||
compareTokens(t, &got, &want)
|
||||
}
|
||||
|
||||
func TestTokenizerPreprocessor(t *testing.T) {
|
||||
got := getTokens(t, "test/tokenizer_preprocessor.asl")
|
||||
want := []string{"#define HELLO_WORLD \"Hello World!\"", "hint", "(", ")", "(", "HELLO_WORLD", ")", ";"}
|
||||
|
||||
compareLength(t, &got, &want)
|
||||
compareTokens(t, &got, &want)
|
||||
}
|
||||
|
||||
func compareLength(t *testing.T, got *[]tokenizer.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 + " "
|
||||
gotlist += (*got)[i].Token + " "
|
||||
}
|
||||
|
||||
for i := range *want {
|
||||
@@ -97,15 +114,15 @@ func compareLength(t *testing.T, got *[]Token, want *[]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func compareTokens(t *testing.T, got *[]Token, want *[]string) {
|
||||
func compareTokens(t *testing.T, got *[]tokenizer.Token, want *[]string) {
|
||||
for i := range *got {
|
||||
if (*got)[i].token != (*want)[i] {
|
||||
t.Error("Tokens do not match: " + (*got)[i].token + " != " + (*want)[i])
|
||||
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 {
|
||||
func getTokens(t *testing.T, file string) []tokenizer.Token {
|
||||
code, err := ioutil.ReadFile(file)
|
||||
|
||||
if err != nil {
|
||||
@@ -113,5 +130,5 @@ func getTokens(t *testing.T, file string) []Token {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return Tokenize(code)
|
||||
return tokenizer.Tokenize(code)
|
||||
}
|
||||
1
test/parser_code.asl
Normal file
1
test/parser_code.asl
Normal file
@@ -0,0 +1 @@
|
||||
var inline_code = code("var a = 1;var b = 2;if a < b {foo();}");
|
||||
3
test/parser_func_params.asl
Normal file
3
test/parser_func_params.asl
Normal file
@@ -0,0 +1,3 @@
|
||||
func myFunc(a = 1, b = 2) {
|
||||
return a+b;
|
||||
}
|
||||
1
test/tokenizer_code.asl
Normal file
1
test/tokenizer_code.asl
Normal file
@@ -0,0 +1 @@
|
||||
var x = code("var x = 5;");
|
||||
@@ -1,3 +1,3 @@
|
||||
foreach allUnits {
|
||||
foreach unit => allUnits {
|
||||
// ...
|
||||
}
|
||||
|
||||
2
test/tokenizer_preprocessor.asl
Normal file
2
test/tokenizer_preprocessor.asl
Normal file
@@ -0,0 +1,2 @@
|
||||
#define HELLO_WORLD "Hello World!"
|
||||
hint()(HELLO_WORLD);
|
||||
@@ -1,10 +1,8 @@
|
||||
switch x {
|
||||
case 1:
|
||||
x = 1;
|
||||
break;
|
||||
case 2:
|
||||
x = 2;
|
||||
break;
|
||||
default:
|
||||
x = 3;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user