92 Commits

Author SHA1 Message Date
Marvin Blum
5fe69dc3d4 Grammar in readme and improved structure. 2018-09-03 16:46:32 +02:00
Marvin Blum
611307d68a Typos in readme. 2018-09-03 16:41:48 +02:00
Marvin Blum
98a4873427 Fixed deadlock. 2017-02-04 23:34:01 +01:00
Marvin Blum
9096adfeed Updated changelog. 2016-02-08 15:10:56 +01:00
Marvin Blum
944b91c16b Added test for bugfix, cleaned up repo (issue #35 and #36). 2016-02-08 15:09:48 +01:00
Marvin Blum
28fbbd7e10 Fixed unary function compile. 2016-02-07 17:13:33 +01:00
Ozan Eğitmen
3311fa79c7 Merge pull request #34 from 654wak654/master
Updated np++ Highlighting
2016-01-26 22:12:48 +02:00
Ozan Eğitmen
bb026d45c7 Merge remote-tracking branch 'refs/remotes/DeKugelschieber/master' 2016-01-26 22:07:21 +02:00
Ozan Eğitmen
80cbcd8f9a Updated np++ Highlighting
Changed comments to not so bright colors, added BIS function
highlighting.
2016-01-26 22:07:20 +02:00
Marvin Blum
346859761e Update README.md 2016-01-25 18:38:40 +01:00
Marvin Blum
b13dc22807 Update README.md 2016-01-25 18:14:57 +01:00
Marvin Blum
da3d097d5d Update README.md 2016-01-25 18:14:17 +01:00
Marvin Blum
6dd9323d78 Updated version number for next release (but not worth yet). 2016-01-24 16:45:25 +01:00
Marvin Blum
e598cae71f Updated changelog. 2016-01-20 18:42:59 +01:00
Marvin Blum
97d220eb47 Merge branch 'master' of https://github.com/DeKugelschieber/asl 2016-01-20 18:38:45 +01:00
Marvin Blum
9786c63d52 Issue #31, added new line after while for pretty printing. 2016-01-20 18:38:04 +01:00
Ozan Eğitmen
51cc0f5700 Update README.md Again 2016-01-20 18:34:38 +02:00
Ozan Eğitmen
3b40b3427d Update README.md 2016-01-20 18:31:01 +02:00
Ozan Eğitmen
3c1ccbba76 Merge pull request #32 from 654wak654/master
Tools Update
2016-01-20 18:29:27 +02:00
Ozan Eğitmen
3ee4000ef5 Tools Update
New ASL GUI, updated syntax highlighting.
2016-01-20 18:26:57 +02:00
Marvin Blum
005a3bf101 Added type info to readme. 2016-01-14 12:01:12 +01:00
Marvin Blum
a156f2b76d Updated changelog and readyme. 2016-01-14 11:54:36 +01:00
Marvin Blum
737b96d5ac Fixed binary buildin functions and tests. 2016-01-14 11:44:02 +01:00
Marvin Blum
e3a7c5d6ac Missunderstood supportInfo parameter. 2016-01-06 23:01:07 +01:00
Marvin Blum
e1bf92f4aa Unary buildin functions, need to fix test! 2016-01-06 22:37:59 +01:00
Marvin Blum
fbfbb8a55c Fixed NaN type, updated changelog for future release, added support for null and unary build in functions. Need to add binary build in functions. 2016-01-06 22:15:09 +01:00
Marvin Blum
9d909a0b8a Finished loading type file. 2016-01-06 21:43:17 +01:00
Marvin Blum
3f6b854a09 Now splits by windows and unix new lines, added parsing structure. 2015-12-17 23:23:49 +01:00
Marvin Blum
05e6937201 Started loading type information. 2015-12-17 22:49:56 +01:00
Marvin Blum
ed1d19851e Updated ToDo. 2015-12-15 23:56:20 +01:00
Marvin Blum
4dfa2ee3ef Issue #17 and concurrent compiling. 2015-12-15 23:53:47 +01:00
Marvin Blum
29804f0d20 Renamed parser helper, upcounted version number. 2015-12-15 23:23:24 +01:00
Marvin Blum
59831fed32 Merge branch 'master' of https://github.com/DeKugelschieber/asl 2015-12-15 23:19:56 +01:00
Marvin Blum
69e684a230 go fmt. 2015-12-15 23:19:41 +01:00
Ozan Eğitmen
90e8941a2c np++ highlighter update 2015-11-29 14:35:45 +02:00
Ozan Eğitmen
7ca3cfb1ad Update README.md 2015-11-26 18:46:04 +02:00
Ozan Eğitmen
65a7c4aae3 Update README.md 2015-11-26 18:45:23 +02:00
Ozan Eğitmen
acced0474b GUI Tools Refurnishing 2015-11-26 18:44:08 +02:00
Marvin Blum
d07611c3a5 Fixed "switch" in keyword list. 2015-11-26 15:26:53 +01:00
Marvin Blum
afbc290281 Merge pull request #29 from 654wak654/master
Recompiled for previous fix
2015-11-23 16:17:45 +01:00
Ozan Eğitmen
2e94cf8efb Recompiled for previous fix 2015-11-23 17:14:16 +02:00
Marvin Blum
41b8b74639 Merge pull request #28 from 654wak654/master
Release Version of ASL GUI
2015-11-23 16:11:57 +01:00
Ozan Eğitmen
df79809cdd Changed error keyword from error to panic 2015-11-23 17:09:08 +02:00
Ozan Eğitmen
1c67be7d2e Release Version 2015-11-23 16:19:11 +02:00
Ozan Eğitmen
8477b443b0 Update README.md 2015-11-22 00:17:13 +02:00
Marvin Blum
511ceb128f Merge pull request #26 from 654wak654/master
ASL GUI Beta 2
2015-11-15 22:03:32 +01:00
Ozan Eğitmen
07ce5e7a0b Updated ASL GUI.exe 2015-11-15 19:57:53 +02:00
Ozan Eğitmen
ca26fbf6d4 Update README.md 2015-11-15 19:56:59 +02:00
Ozan Eğitmen
0c63114c44 Update README.md 2015-11-15 19:55:00 +02:00
Ozan Eğitmen
1eb5ce8035 Updated readme 2015-11-15 19:53:57 +02:00
Ozan Eğitmen
68f05f09ea v0.2.1 2015-11-15 19:51:14 +02:00
Marvin Blum
aa83a9a093 Merge pull request #25 from 654wak654/master
ASL GUI Beta
2015-11-15 14:35:10 +01:00
Ozan Eğitmen
5bcfda10bd Update README.md 2015-11-15 15:24:31 +02:00
Ozan Eğitmen
664b435715 Beta Release Commit 2015-11-15 15:23:21 +02:00
Marvin Blum
f1f564d89b Updated version. 2015-11-14 16:48:24 +01:00
Marvin Blum
fa766cc78c Updated README. 2015-11-14 16:45:07 +01:00
Marvin Blum
59f02f7782 Issue #23. 2015-11-14 16:43:36 +01:00
Marvin Blum
1208bda68c Issue #21. 2015-11-14 16:22:39 +01:00
Ozan Eğitmen
11c9d19c25 Initial commit, has missing code 2015-11-07 22:01:19 +03:00
Marvin Blum
3990a20cb0 Added changelog. 2015-11-07 14:38:34 +01:00
Marvin Blum
07250ba2a7 Updated readme. 2015-11-05 19:02:51 +01:00
Marvin Blum
2bc11fb125 Removed "in" keyword from tokenizer. Updated readme. 2015-11-03 13:28:00 +01:00
Marvin Blum
0ca6566454 Merge pull request #20 from DeKugelschieber/revert-19-new-repo-example
Revert "New repo layout and unified test"
2015-11-02 18:13:37 +01:00
Marvin Blum
5c540ce481 Revert "New repo layout and unified test" 2015-11-02 18:13:18 +01:00
Marvin Blum
f6b5ed4dd6 Merge pull request #19 from nikolauska/new-repo-example
New repo layout and unified test
2015-11-02 11:28:20 +01:00
nikolauska
afad182f28 add .project file back that was accidentally removed 2015-11-02 12:12:04 +03:00
nikolauska
d2c32b3b85 added new repository layout and unified tests 2015-11-02 12:00:18 +03:00
Marvin Blum
67e950b41d Started better error output by adding line and column number. Need to
get rid of panic.
2015-10-31 16:49:33 +01:00
Marvin Blum
6c55846e9f Issue #18, but syntax is: foreach (variable) => (expression) {...}. 2015-10-31 16:40:58 +01:00
Marvin Blum
3ee9a310e8 Removed preprocessor from "whats missing". 2015-10-31 16:28:43 +01:00
Marvin Blum
8d5c0a6905 Added documentation, added new line before preprocessor line. 2015-10-31 16:27:10 +01:00
Marvin Blum
276ab86668 Added preprocessor line. 2015-10-31 16:20:04 +01:00
Marvin Blum
dc4eaf4b0f main package must remain, stupid me. 2015-10-30 19:38:17 +01:00
Marvin Blum
1d7f435530 Added folder to gitignore. 2015-10-30 19:11:05 +01:00
Marvin Blum
aedf8e715e Removed test 'in' folder. 2015-10-30 19:09:46 +01:00
Marvin Blum
4da540317f Version number. 2015-10-30 19:05:18 +01:00
Marvin Blum
6e4cef91bc Issue #15. 2015-10-30 19:03:50 +01:00
Marvin Blum
ae8fad1abc Updated readme. 2015-10-28 19:20:23 +01:00
Marvin Blum
755d5af263 Updated readme. 2015-10-28 19:09:39 +01:00
Marvin Blum
bf751e63c9 Merge branch 'master' of https://github.com/DeKugelschieber/asl.git 2015-10-28 19:00:42 +01:00
Marvin Blum
c3d4661b7f Issue #6. 2015-10-28 19:00:32 +01:00
Marvin Blum
d7013b01c3 Issue #6. 2015-10-28 18:58:40 +01:00
Marvin Blum
393ddd4326 Added compiler context. 2015-10-28 18:38:49 +01:00
Marvin Blum
53ea74a19b That's it basically, need own context to compile sub parts within other
compilation.
2015-10-28 18:26:29 +01:00
Marvin Blum
0a66f15704 Merge remote-tracking branch 'origin/master' into feature/code-keyword
Conflicts:
	in/simple.asl
2015-10-28 18:13:55 +01:00
Marvin Blum
352bfd6a9b Issue #9. 2015-10-28 18:12:20 +01:00
Marvin Blum
0b26c5bdf8 Issue #11. 2015-10-28 18:08:08 +01:00
Marvin Blum
1cbcda4261 Started to add special function to inline code (issue #6). 2015-10-28 17:59:38 +01:00
Marvin Blum
bba215cd8d Fixed switch in test and readme. 2015-10-28 17:50:46 +01:00
Marvin Blum
a64bef135d Issue #13. 2015-10-28 17:04:51 +01:00
Marvin Blum
7ed05f913b Fixed unit tests. 2015-10-27 22:06:49 +01:00
Marvin Blum
a703189f0c Edited readme. 2015-10-27 19:09:29 +01:00
38 changed files with 6001 additions and 965 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/bin/ /bin/
/pkg/ /pkg/
/out/ /out/
/in/

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>asl</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.googlecode.goclipse.goBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.googlecode.goclipse.core.goNature</nature>
</natures>
</projectDescription>

36
CHANGELOG.md Normal file
View File

@@ -0,0 +1,36 @@
# Changelog
**1.2.2**
* bugfix: deadlock on compiling multile files at once
**1.2.1**
* bugfix: new line after while for pretty printing
* bugfix: build in unary function with multiple arguments
**1.2.0**
* better error output
* concurrent compiling
* errors are handled per file and won't stop the whole compilation
* function name check for build in functions
* simpler syntax for "null" and unary buildin functions
**1.1.1**
* arrays can now be declared within expressions
* code keyword bug fix
**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

159
README.md
View File

@@ -11,30 +11,44 @@ Main reasons for ASL:
* easy to learn and understand * easy to learn and understand
* full replacement of SQF * full replacement of SQF
* compatible with Arma wiki and commands * compatible with Arma wiki and commands
* comfortable
## Usage ## Usage
ASL is a command line tool. After you have downloaded it, navigation to the binary and execute it: ASL is a command line tool. After you have downloaded it, navigate to the binary and execute it:
``` ```
asl [-v|-r|-pretty|--help] <input directory> <output directory> asl.exe [-v|-r|-pretty|--help] <input directory> <output directory>
``` ```
| Parameter | Optional/Required | Meaning | | Parameter | Optional/Required | Description |
| --------- | ----------------- | ------- | | --------- | ----------------- | ----------- |
| -v | optional | Shows ASL version. | | -v | optional | Show ASL version. |
| -r | optional | Read input directory recursively. | | -r | optional | Read input directory recursively. |
| -pretty | optional | Enable pretty printing to SQF. | | -pretty | optional | Enable pretty printing to SQF. |
| --help | optional | Show usage. | | --help | optional | Show usage. |
| input directory | required | Directory to read ASL files from (use ./ for relative paths). | | input directory | required | Input directory for ASL files (use ./ for relative paths). |
| output directory | required | Directory for SQF output. Can be the same as input directory (use ./ for relative paths). | | output directory | required | Output directory for SQF files. Can be the same as input directory (use ./ for relative paths). |
**Example:** **Example:**
``` ```
asl ./missions/myMission/myScripts ./missions/myMission/compiledScripts asl.exe ./missions/myMission/myScripts ./missions/myMission/compiledScripts
``` ```
Since 1.2.0 ASL requires a [supportInfo](https://community.bistudio.com/wiki/supportInfo) file, which must be generated, named "types" and placed right next to the binary. The content looks like:
```
...
t:DIARY_RECORD
t:LOCATION
b:ARRAY waypointattachobject SCALAR,OBJECT
b:OBJECT,GROUP enableattack BOOL
...
```
The types file will be delivered with the current release, but not updated when Arma is.
## Syntax ## Syntax
### Comments ### Comments
@@ -66,45 +80,52 @@ var array = [1, 2, 3];
var one = array[0]; var one = array[0];
// accessing using a statement: // accessing using a statement:
var zwo = array[33/3-2]; var two = array[33/3-2];
// it is possble to use arrays in expressions:
var emptyArray = one-[1];
``` ```
### Control structures ### Control structures
Controll structure syntax is C-like. Notice they are all using the same brackets and do not require to set a semicolon at the end, unlike in SQF. Controll structure syntax is C-like. Notice the same brackets for all structures and no semicolon at the end, unlike in SQF:
``` ```
if 1 < 2 { if 1 < 2 {
// ... // ...
} else { // no else if yet
// ...
} }
while 1 < 2 { while 1 < 2 {
// ... // ...
} }
for var _i = 0; _i < 100; _i = _i+1 { // var before identifier is optional for var _i = 0; _i < 100; _i = _i+1 {
// ... // ...
} }
each allUnits { // foreach, iterates over all units in this case for _i = 0; _i < 100; _i = _i+1 { // same as above, var is optional before identifier "_i"
// element is available as _x here // ...
} }
switch x { foreach _unit => allUnits { // iterates over all units in this case
// element is available as "_unit" AND "_x" here ("_x" is used by SQF's foreach)
}
switch x { // there is no "break" in SQF
case 1: case 1:
// ... // ...
break;
case 2: case 2:
// ... // ...
break;
default: default:
// ... // ...
} }
try { try { // handles errors in "catch" block
// ... // errors thrown here...
} catch { } catch {
// ... // ... will be handled here
} }
``` ```
@@ -118,14 +139,25 @@ func add(_a, _b) {
} }
// Call it: // Call it:
var _x = add(1, 2); // result in _x is 3
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
```
When trying to define a function with a name that exists in SQF's build in function set, you'll get an compile error. So declaring `func hint()...` won't compile.
### Call build in commands ### Call build in commands
To call SQF build in commands (like hint, getDir, addItem, ...) we have to use a different syntax. To call SQF build in commands (like hint, getDir, addItem, ...) use the same syntax when using functions. An exception are "binary" functions. These are functions which accept parameters on both sides of the function name. Here is an example for "addItem":
``` ```
addItem(someUnit)("NVGoogles"); addItem(someUnit)("NVGoogles");
@@ -134,7 +166,7 @@ addItem(someUnit)("NVGoogles");
someUnit addItem "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 the parameters behind the 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.
``` ```
foo(x, y, z)(1, 2, 3); foo(x, y, z)(1, 2, 3);
@@ -143,11 +175,18 @@ foo(x, y, z)(1, 2, 3);
[x, y, z] foo [1, 2, 3]; [x, y, z] foo [1, 2, 3];
``` ```
If the build in function accepts no parameters (null function) or on one side only (unary function), it can be called with a single pair of brackets:
```
hint("your text");
shownWatch();
```
### Special functions ### Special functions
There are some special functions in SQF, which also require special syntax in ASL. The examples presented here show how they are written in ASL and what the output will look like. Remember that ASL is case sensitive! There are some special functions in SQF, which also require special syntax in ASL. The examples presented here shows how they are written in ASL and what the output will look like. Remember that ASL is case sensitive!
**exitWith** **exitwith**
``` ```
exitwith { // NOT exitWith! exitwith { // NOT exitWith!
@@ -160,7 +199,7 @@ if (true) exitWith {
}; };
``` ```
**waitUntil** **waituntil**
``` ```
waituntil(condition); // NOT waitUntil! waituntil(condition); // NOT waitUntil!
@@ -173,16 +212,49 @@ waitUntil {condition};
waitUntil {expression;condition}; waitUntil {expression;condition};
``` ```
**code**
The code function is used to compile inline code. This does **not** replace SQF's compile build in function, but will return the contained ASL code as SQF:
```
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 in SQF. 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 like normal identifiers:
```
if __LINE__ == 22 {
// ...
}
if __FILE__ == "myScript.sqf" {
// ...
}
```
## List of all keywords ## 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. Keywords must not be used as identifiers. Here is a full list of all keywords in ASL. Remember that build in function names must not be used neither, else you'll get an compile error.
| Keyword | | Keyword |
| ------- | | ------- |
| var | | var |
| if | | if |
| while | | while |
| witch | | switch |
| for | | for |
| foreach | | foreach |
| func | | func |
@@ -195,10 +267,31 @@ Keywords should not be used as identifiers. Here is a full list of all keywords
| catch | | catch |
| exitwith | | exitwith |
| waituntil | | waituntil |
| code |
## What's missing?
The following features are not implemented yet, but might be in a future version:
**scopes**
Scopes won't be supported, since it's a stupid concept and can be replaced by functions.
**else if**
Planned.
**Selector in expression**
Selectors in expressions do not work yet, so this is not possible:
```
var x = ([1, 2, 3]-[1, 2])[0]; // should result in 3
```
## Contribute ## Contribute
To contribute please create pull requests or explain your ideas in the issue section on GitHub. Report any bugs or incompatible ASL <-> SQF syntax you can find. To contribute, please create pull requests or explain your ideas in the issue section on GitHub. Report any bugs or incompatible ASL <-> SQF syntax you can find with a short example.
## Further information ## Further information
@@ -206,6 +299,12 @@ For further information you can read the SQF tutorial and documentation of scrip
* [Arma Wiki](https://community.bistudio.com/wiki/Main_Page) * [Arma Wiki](https://community.bistudio.com/wiki/Main_Page)
* [Scripting commands](https://community.bistudio.com/wiki/Category:Scripting_Commands_Arma_3) * [Scripting commands](https://community.bistudio.com/wiki/Category:Scripting_Commands_Arma_3)
* [Scripting preprocessor](https://community.bistudio.com/wiki/PreProcessor_Commands)
Interesting pages to visit:
* [Bohemia forum topic](https://forums.bistudio.com/topic/185649-asl-arma-scripting-language-compiler/)
* [Armaholic page](http://www.armaholic.com/page.php?id=29720)
## License ## License

22
ToDo.md
View File

@@ -1,22 +0,0 @@
# ToDo
* ~~assign to returned values~~
* ~~sqf: ... sqf whitespace~~
* ~~solution for build in commands which do not require left values~~
* ~~pretty/minified printing~~
* ~~usage~~
* ~~recursive compiling~~
* concurrent compiling
* ~~inline buildin function call -> foo(a)(bar(x)(y));~~
* ~~negative values e.g. -1, operator !~~
* ~~tokenizer splits commands like "format" -> for, mat~~
* ~~try ... catch~~
* better error reporting
- recover panic
- line, column
## Special cases
* ~~exitWith...~~
* ~~waitUntil...~~
* ~~!buildInFunc()()...~~

6
build Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
export GOPATH=/home/marvin/Projekte/asl
go build -ldflags "-s -w" src/main/asl.go

View File

@@ -1,6 +0,0 @@
var array = [1, 2, 3];
var x = array[0];
if array[(1-1)+10/10] == 2 {
foo();
}

6
run Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
export GOPATH=/home/marvin/Projekte/asl
go run src/main/asl.go $1 $2

6
run_tests Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
export GOPATH=/home/marvin/Projekte/asl
go test parser tokenizer types

View File

@@ -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
}

View File

@@ -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"
}
}

View File

@@ -1,160 +0,0 @@
package asl_test
import (
"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)
}
func TestParserAssignment(t *testing.T) {
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)
}
func TestParserWhile(t *testing.T) {
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)
}
func TestParserForeach(t *testing.T) {
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)
}
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)
}
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)
}
func TestParserExpression2(t *testing.T) {
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)
}
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)
}
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"
equal(t, got, want)
}
func TestParserTryCatch(t *testing.T) {
got := getCompiled(t, "test/parser_try_catch.asl")
want := "try {\n} catch {\n};\n"
equal(t, got, want)
}
func TestParserNegationFunctionCall(t *testing.T) {
got := getCompiled(t, "test/parser_negation.asl")
want := "x = !([] call foo);\n"
equal(t, got, want)
}
func TestParserExitWith(t *testing.T) {
got := getCompiled(t, "test/parser_exitwith.asl")
want := "if (true) exitWith {\n};\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"
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"
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()
}
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()
}
}

View File

@@ -1,17 +1,23 @@
package main package main
import ( import (
"asl"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath"
"os" "os"
"parser"
"path/filepath"
"strings" "strings"
"tokenizer"
"types"
) )
const version = "1.0.0" const (
const extension = ".asl" version = "1.2.2"
const sqfextension = ".sqf" extension = ".asl"
sqfextension = ".sqf"
typeinfo = "types"
PathSeparator = string(os.PathSeparator)
)
type ASLFile struct { type ASLFile struct {
in string in string
@@ -19,11 +25,13 @@ type ASLFile struct {
newname string newname string
} }
var recursive bool = false var (
var pretty bool = false recursive bool = false
var exit bool = false pretty bool = false
var aslFiles []ASLFile exit bool = false
var inDir string aslFiles []ASLFile
inDir string
)
func usage() { func usage() {
fmt.Println("Usage: asl [-v|-r|-pretty|--help] <input directory> <output directory>\n") fmt.Println("Usage: asl [-v|-r|-pretty|--help] <input directory> <output directory>\n")
@@ -35,10 +43,14 @@ func usage() {
fmt.Println("<output directory> output directory, directory structure will be created corresponding to input directory") fmt.Println("<output directory> output directory, directory structure will be created corresponding to input directory")
} }
// Parses compiler flags.
func flags(flag string) bool { func flags(flag string) bool {
flag = strings.ToLower(flag) flag = strings.ToLower(flag)
if flag[0] == '-' { if flag[0] != '-' {
return false
}
if flag == "-v" { if flag == "-v" {
fmt.Println("asl version " + version) fmt.Println("asl version " + version)
exit = true exit = true
@@ -54,9 +66,16 @@ func flags(flag string) bool {
return true return true
} }
return false // Loads types from types file.
// If none is provided, an error will be printed.
func loadTypes() {
if err := types.LoadTypes(typeinfo); err != nil {
fmt.Println("No 'types' file provided. Please add type information to this file from 'supportInfo' script command output.")
exit = true
}
} }
// Creates a list of all ASL files to compile.
func readAslFiles(path string) { func readAslFiles(path string) {
dir, err := ioutil.ReadDir(path) dir, err := ioutil.ReadDir(path)
@@ -69,12 +88,12 @@ func readAslFiles(path string) {
name := dir[i].Name() name := dir[i].Name()
if dir[i].IsDir() && recursive { if dir[i].IsDir() && recursive {
readAslFiles(filepath.FromSlash(path+"/"+name)) readAslFiles(filepath.FromSlash(path + PathSeparator + name))
continue continue
} }
if !dir[i].IsDir() && strings.ToLower(filepath.Ext(name)) == extension { 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)]) out := filepath.FromSlash("./" + path[len(inDir):len(path)])
newname := name[:len(name)-len(filepath.Ext(name))] newname := name[:len(name)-len(filepath.Ext(name))]
@@ -84,28 +103,46 @@ func readAslFiles(path string) {
} }
} }
func compile(path string) { // Recovers and prints thrown error.
for i := 0; i < len(aslFiles); i++ { func recoverCompileError(file string) {
out := filepath.FromSlash(path+"/"+aslFiles[i].out+"/"+aslFiles[i].newname+sqfextension) if r := recover(); r != nil {
fmt.Println(aslFiles[i].in+" -> "+out) fmt.Println("Compile error in file "+file+":", r)
code, err := ioutil.ReadFile(aslFiles[i].in) }
if err != nil {
fmt.Println("Error reading file: "+aslFiles[i].in)
continue
} }
token := asl.Tokenize(code) // Compiles a single ASL file.
sqf := asl.Parse(token, pretty) func compileFile(path string, file ASLFile) {
defer recoverCompileError(file.in)
os.MkdirAll(filepath.FromSlash(path+"/"+aslFiles[i].out), 0777) // read file
out := filepath.FromSlash(path + PathSeparator + file.out + PathSeparator + file.newname + sqfextension)
fmt.Println(file.in + " -> " + out)
code, err := ioutil.ReadFile(file.in)
if err != nil {
fmt.Println("Error reading file: " + file.in)
return
}
// compile
token := tokenizer.Tokenize(code, false)
compiler := parser.Compiler{}
sqf := compiler.Parse(token, pretty)
os.MkdirAll(filepath.FromSlash(path+PathSeparator+file.out), 0777)
err = ioutil.WriteFile(out, []byte(sqf), 0666) err = ioutil.WriteFile(out, []byte(sqf), 0666)
if err != nil { if err != nil {
fmt.Println("Error writing file: "+aslFiles[i].out) fmt.Println("Error writing file: " + file.out)
fmt.Println(err) fmt.Println(err)
} }
} }
// Compiles ASL files.
func compile(path string) {
for i := 0; i < len(aslFiles); i++ {
compileFile(path, aslFiles[i])
}
} }
func main() { func main() {
@@ -118,7 +155,15 @@ func main() {
} }
var i int var i int
for i = 1; i < len(args) && flags(args[i]); i++ {} for i = 1; i < len(args) && flags(args[i]); i++ {
}
if exit {
return
}
// load type information
loadTypes()
if exit { if exit {
return return

568
src/parser/parser.go Normal file
View File

@@ -0,0 +1,568 @@
package parser
import (
"errors"
"tokenizer"
"types"
)
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)
c.parseExpression(true)
}
c.expect(";")
c.appendOut(";", true)
}
func (c *Compiler) parseArray(out bool) string {
output := ""
c.expect("[")
output += "["
if !c.accept("]") {
output += c.parseExpression(false)
for c.accept(",") {
c.next()
output += "," + c.parseExpression(false)
}
}
c.expect("]")
output += "]"
if out {
c.appendOut(output, false)
}
return output
}
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("};", true)
}
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")
// check for build in function
if buildin := types.GetFunction(c.get().Token); buildin != nil {
panic(errors.New(c.get().Token + " is a build in function, choose a different name"))
}
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]), true), 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("(")
paramsStr, paramCount := c.parseParameter(false)
c.expect(")")
// buildin function
buildin := types.GetFunction(name)
if buildin != nil {
if buildin.Type == types.NULL {
output = name
} else if buildin.Type == types.UNARY {
output = c.parseUnaryFunction(name, paramsStr, paramCount)
} else {
output = c.parseBinaryFunction(name, paramsStr, buildin, paramCount)
}
} else {
output = "[" + paramsStr + "] call " + name
}
if out {
c.appendOut(output, false)
}
return output
}
func (c *Compiler) parseUnaryFunction(name, paramsStr string, paramCount int) string {
output := ""
if paramCount == 1 {
output = name + " " + paramsStr
} else {
output = name + " [" + paramsStr + "]"
}
return output
}
func (c *Compiler) parseBinaryFunction(name string, leftParamsStr string, buildin *types.FunctionType, paramCount int) string {
output := ""
c.next()
rightParamsStr, rightParamCount := c.parseParameter(false)
c.expect(")")
if paramCount > 1 {
leftParamsStr = "[" + leftParamsStr + "]"
}
if rightParamCount > 1 {
rightParamsStr = "[" + rightParamsStr + "]"
}
if paramCount > 0 {
output = leftParamsStr + " " + name + " " + rightParamsStr
} else {
output = name + " " + rightParamsStr
}
return output
}
func (c *Compiler) parseParameter(out bool) (string, int) {
output := ""
count := 0
for !c.accept(")") {
expr := c.parseExpression(out)
output += expr
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.accept("[") {
output += c.parseArray(false)
} 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
}

View File

@@ -0,0 +1,89 @@
package parser
import (
"errors"
"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(errors.New("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(errors.New("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"
}
}

228
src/parser/parser_test.go Normal file
View File

@@ -0,0 +1,228 @@
package parser_test
import (
"io/ioutil"
"parser"
"testing"
"tokenizer"
"types"
)
const (
types_file = "../../test/types"
)
func TestParserDeclaration(t *testing.T) {
got := getCompiled(t, "../../test/tokenizer_var.asl")
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;\r\n"
equal(t, got, want)
}
func TestParserIf(t *testing.T) {
got := getCompiled(t, "../../test/tokenizer_if.asl")
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 {\r\n};\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 {\r\n};\r\n"
equal(t, got, want)
}
func TestParserForeach(t *testing.T) {
got := getCompiled(t, "../../test/tokenizer_foreach.asl")
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 = {\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);\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);\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);\r\n"
equal(t, got, want)
}
func TestParserFunctionCall(t *testing.T) {
got := getCompiled(t, "../../test/parser_func_call.asl")
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 TestParserNullBuildinFunctionCall(t *testing.T) {
types.LoadTypes(types_file)
got := getCompiled(t, "../../test/parser_null_buildin_func.asl")
want := "_volume = (radioVolume);\r\n"
equal(t, got, want)
}
func TestParserUnaryBuildinFunctionCall(t *testing.T) {
types.LoadTypes(types_file)
got := getCompiled(t, "../../test/parser_unary_buildin_func.asl")
want := "_isReady = (unitReady soldier);\r\n"
equal(t, got, want)
}
func TestParserBinaryBuildinFunctionCall(t *testing.T) {
types.LoadTypes(types_file)
got := getCompiled(t, "../../test/parser_binary_buildin_func.asl")
want := "someCar setHit [\"motor\", 1];\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 {\r\n};\r\n"
equal(t, got, want)
}
func TestParserTryCatch(t *testing.T) {
got := getCompiled(t, "../../test/parser_try_catch.asl")
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);\r\n"
equal(t, got, want)
}
func TestParserExitWith(t *testing.T) {
got := getCompiled(t, "../../test/parser_exitwith.asl")
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};\r\n"
equal(t, got, want)
}
func TestParserArray(t *testing.T) {
got := getCompiled(t, "../../test/parser_array.asl")
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) {
types.LoadTypes(types_file)
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)
}
func TestParserExpressionArray(t *testing.T) {
got := getCompiled(t, "../../test/parser_expression_array.asl")
want := "x = [1,2,3]-[2,3];\r\n"
equal(t, got, want)
}
// bugfix: unary function parsing (e.g. "format")
func TestBugfixParserUnaryFunction(t *testing.T) {
got := getCompiled(t, "../../test/bugfix_unary_func_format.asl")
want := "format [\"%1 %2\", \"value1\", \"value2\"];\r\n[\"a\", \"b\", \"c\"] call someFunc;\r\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()
}
tokens := tokenizer.Tokenize(code, false)
compiler := parser.Compiler{}
return compiler.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()
}
}

View File

@@ -1,14 +1,18 @@
package asl package tokenizer
import ( import (
"strings" "strings"
) )
type Token struct { type Token struct {
token string Token string
Preprocessor bool
Line int
Column int
} }
var delimiter = []byte{ var (
delimiter = []byte{
'=', '=',
';', ';',
'{', '{',
@@ -29,7 +33,7 @@ var delimiter = []byte{
'*', '*',
'/'} // TODO: modulo? '/'} // TODO: modulo?
var keywords = []string{ keywords = []string{
"var", "var",
"if", "if",
"while", "while",
@@ -45,20 +49,34 @@ var keywords = []string{
"try", "try",
"catch", "catch",
"exitwith", "exitwith",
"waituntil"} "waituntil",
"code"}
var whitespace = []byte{' ', '\n', '\t', '\r'} whitespace = []byte{' ', '\n', '\t', '\r'}
var identifier = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" identifier = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
preprocessor = byte('#')
new_line = []byte{'\r', '\n'}
)
// Tokenizes the given byte array into syntax tokens, // Tokenizes the given byte array into syntax tokens,
// which can be parsed later. // which can be parsed later.
func Tokenize(code []byte) []Token { func Tokenize(code []byte, doStripSlashes bool) []Token {
if doStripSlashes {
code = stripSlashes(code)
}
code = removeComments(code) code = removeComments(code)
tokens := make([]Token, 0) 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] c := code[i]
column++
if byteArrayContains(new_line, c) {
line++
column = 0
}
// string masks (backslash) // string masks (backslash)
if c == '\\' && !mask { if c == '\\' && !mask {
@@ -77,16 +95,19 @@ func Tokenize(code []byte) []Token {
if isstring { if isstring {
token += string(c) token += string(c)
} else { } else {
// delimeter, keyword or variable/expression // preprocessor, delimeter, keyword or variable/expression
if byteArrayContains(delimiter, c) { if c == preprocessor {
tokens = append(tokens, preprocessorLine(code, &i, line, column))
token = ""
} else if byteArrayContains(delimiter, c) {
if token != "" { 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 = "" token = ""
} else if stringArrayContains(strings.ToLower(token)) && !isIdentifierCharacter(c) { } else if stringArrayContains(strings.ToLower(token)) && !isIdentifierCharacter(c) {
tokens = append(tokens, Token{token}) tokens = append(tokens, Token{token, false, line, column})
token = "" token = ""
} else if !byteArrayContains(whitespace, c) { } else if !byteArrayContains(whitespace, c) {
token += string(c) token += string(c)
@@ -99,6 +120,28 @@ func Tokenize(code []byte) []Token {
return tokens return tokens
} }
// Removes slashes from input code.
// This is used for the "code" keyword for correct strings in resulting code.
func stripSlashes(code []byte) []byte {
newcode := make([]byte, len(code))
j, mask := 0, false
for i := 0; i < len(code); i++ {
c := code[i]
if c == '\\' && !mask {
mask = true
continue
}
newcode[j] = code[i]
mask = false
j++
}
return newcode
}
// Removes all comments from input byte array. // Removes all comments from input byte array.
// Comments are single line comments, starting with // (two slashes), // Comments are single line comments, starting with // (two slashes),
// multi line comments with /* ... */ (slash star, star slash). // multi line comments with /* ... */ (slash star, star slash).
@@ -137,6 +180,35 @@ func removeComments(code []byte) []byte {
return newcode[:j] 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. // Returns the next character in code starting at i.
// If no character is left, '0' will be returned. // If no character is left, '0' will be returned.
func nextChar(code []byte, i int) byte { func nextChar(code []byte, i int) byte {

View File

@@ -1,12 +1,13 @@
package asl package tokenizer_test
import ( import (
"io/ioutil" "io/ioutil"
"testing" "testing"
"tokenizer"
) )
func TestTokenizerVar(t *testing.T) { 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", "]", ";"} want := []string{"var", "x", "=", "1", ";", "var", "array", "=", "[", "1", ",", "2", ",", "3", "]", ";"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -14,7 +15,7 @@ func TestTokenizerVar(t *testing.T) {
} }
func TestTokenizerIf(t *testing.T) { func TestTokenizerIf(t *testing.T) {
got := getTokens(t, "test/tokenizer_if.asl") got := getTokens(t, "../../test/tokenizer_if.asl")
want := []string{"if", "a", "<", "b", "{", "}"} want := []string{"if", "a", "<", "b", "{", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -22,7 +23,7 @@ func TestTokenizerIf(t *testing.T) {
} }
func TestTokenizerWhile(t *testing.T) { func TestTokenizerWhile(t *testing.T) {
got := getTokens(t, "test/tokenizer_while.asl") got := getTokens(t, "../../test/tokenizer_while.asl")
want := []string{"while", "true", "{", "}"} want := []string{"while", "true", "{", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -30,7 +31,7 @@ func TestTokenizerWhile(t *testing.T) {
} }
func TestTokenizerFor(t *testing.T) { 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", "{", "}"} want := []string{"for", "var", "i", "=", "0", ";", "i", "<", "100", ";", "i", "=", "i", "+", "1", "{", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -38,23 +39,23 @@ func TestTokenizerFor(t *testing.T) {
} }
func TestTokenizerForach(t *testing.T) { func TestTokenizerForach(t *testing.T) {
got := getTokens(t, "test/tokenizer_foreach.asl") got := getTokens(t, "../../test/tokenizer_foreach.asl")
want := []string{"foreach", "allUnits", "{", "}"} want := []string{"foreach", "unit", "=", ">", "allUnits", "{", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
compareTokens(t, &got, &want) compareTokens(t, &got, &want)
} }
func TestTokenizerSwitch(t *testing.T) { 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", ";", "}"} want := []string{"switch", "x", "{", "case", "1", ":", "x", "=", "1", ";", "case", "2", ":", "x", "=", "2", ";", "default", ":", "x", "=", "3", ";", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
compareTokens(t, &got, &want) compareTokens(t, &got, &want)
} }
func TestTokenizerFunction(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", ";", "}"} want := []string{"func", "TestFunction", "(", "param0", ",", "param1", ")", "{", "return", "true", ";", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -62,7 +63,7 @@ func TestTokenizerFunction(t *testing.T) {
} }
func TestTokenizerExpression(t *testing.T) { 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", ")", ";"} want := []string{"x", "=", "(", "(", "1", "+", "2", "+", "3", ")", "*", "4", "/", "2", ")", "+", "foo", "(", "1", ",", "2", ",", "3", ")", ";"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -70,20 +71,45 @@ func TestTokenizerExpression(t *testing.T) {
} }
func TestTokenizerIdentifier(t *testing.T) { 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!\"", ";"} want := []string{"var", "format", "=", "\"should not be for mat!\"", ";"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
compareTokens(t, &got, &want) 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 TestTokenizerMask(t *testing.T) {
got := getTokens(t, "../../test/tokenizer_mask.asl")
want := []string{"var", "x", "=", "\"Hello \\\"World\\\"\"", ";",
"var", "y", "=", "code", "(", "\"var z = \\\"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) { if len(*got) != len(*want) {
t.Error("Length of tokens got and expected tokens not equal, was:") t.Error("Length of tokens got and expected tokens not equal, was:")
gotlist, wantlist := "", "" gotlist, wantlist := "", ""
for i := range *got { for i := range *got {
gotlist += (*got)[i].token + " " gotlist += (*got)[i].Token + " "
} }
for i := range *want { for i := range *want {
@@ -97,15 +123,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 { for i := range *got {
if (*got)[i].token != (*want)[i] { if (*got)[i].Token != (*want)[i] {
t.Error("Tokens do not match: " + (*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) code, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
@@ -113,5 +139,5 @@ func getTokens(t *testing.T, file string) []Token {
t.FailNow() t.FailNow()
} }
return Tokenize(code) return tokenizer.Tokenize(code, false)
} }

146
src/types/loader.go Normal file
View File

@@ -0,0 +1,146 @@
package types
import (
"io/ioutil"
"strings"
)
const (
// type for object types
TYPE = 1
NAN = "NaN"
ARRAY = "ARRAY"
// types for functions
NULL = 2
UNARY = 3
BINARY = 4
win_new_line = "\r\n"
unix_new_line = "\n"
)
type FunctionType struct {
Name string
Type int // one of the constants NULL, UNARY, BINARY
ArgsLeft int
ArgsRight int // number of args on left side for binary functions
}
var functions []FunctionType
// Returns function type information by name.
// If not found, the parameter will be nil.
func GetFunction(name string) *FunctionType {
name = strings.ToLower(name)
for _, function := range functions {
if function.Name == name {
return &function
}
}
return nil
}
// Loads type information from file.
// The format is specified by 'supportInfo' command: https://community.bistudio.com/wiki/supportInfo
func LoadTypes(path string) error {
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
data := strings.Replace(win_new_line, unix_new_line, string(content), -1) // make this work on windows and unix
functions = make([]FunctionType, 0)
parseTypes(data)
return nil
}
func parseTypes(content string) {
lines := strings.Split(content, unix_new_line)
for _, line := range lines {
if len(line) < 3 {
continue
}
if line[0] == 'n' {
parseNullFunction(line)
} else if line[0] == 'u' {
parseUnaryFunction(line)
} else if line[0] == 'b' {
parseBinaryFunction(line)
}
}
}
func parseNullFunction(line string) {
parts := getParts(line)
functions = append(functions, FunctionType{parts[0], NULL, 0, 0})
}
func parseUnaryFunction(line string) {
parts := getParts(line)
if len(parts) < 2 {
return
}
args := getArgs(parts[1])
var argsCount int
if args[0] != ARRAY {
argsCount = len(args) - getNaNArgs(args)
}
functions = append(functions, FunctionType{parts[0], UNARY, argsCount, 0})
}
func parseBinaryFunction(line string) {
parts := getParts(line)
if len(parts) < 3 {
return
}
argsLeft := getArgs(parts[0])
argsRight := getArgs(parts[2])
var argsLeftCount int
var argsRightCount int
if argsLeft[0] != ARRAY {
argsLeftCount = len(argsLeft) - getNaNArgs(argsLeft)
}
if argsRight[0] != ARRAY {
argsRightCount = len(argsRight) - getNaNArgs(argsRight)
}
functions = append(functions, FunctionType{parts[1], BINARY, argsLeftCount, argsRightCount})
}
func getParts(line string) []string {
line = line[2:]
return strings.Split(line, " ")
}
func getArgs(part string) []string {
return strings.Split(part, ",")
}
func getNaNArgs(args []string) int {
nan := 0
for _, arg := range args {
if arg == NAN {
nan++
}
}
return nan
}

18
src/types/loader_test.go Normal file
View File

@@ -0,0 +1,18 @@
package types_test
import (
"testing"
"types"
)
func TestTypesGetFunction(t *testing.T) {
if err := types.LoadTypes("../../test/types"); err != nil {
t.Error(err)
}
function := types.GetFunction("hint")
if function == nil {
t.Error("Function 'hint' not found in type list")
}
}

View File

@@ -0,0 +1,2 @@
format("%1 %2", "value1", "value2"); // must result in format [...];
someFunc("a", "b", "c"); // must result in ... call someFunc;

View File

@@ -0,0 +1 @@
setHit(someCar)("motor", 1);

View File

@@ -1 +0,0 @@
var _x = setHit(getVar(player, foo)(bar))("head", "tail");

1
test/parser_code.asl Normal file
View File

@@ -0,0 +1 @@
var inline_code = code("var a = 1;var b = 2;if a < b {foo();}");

View File

@@ -0,0 +1 @@
var x = [1, 2, 3]-[2, 3];

View File

@@ -0,0 +1,3 @@
func myFunc(a = 1, b = 2) {
return a+b;
}

View File

@@ -0,0 +1 @@
var _volume = radioVolume();

View File

@@ -0,0 +1 @@
var _isReady = unitReady(soldier);

1
test/tokenizer_code.asl Normal file
View File

@@ -0,0 +1 @@
var x = code("var x = 5;");

View File

@@ -1,3 +1,3 @@
foreach allUnits { foreach unit => allUnits {
// ... // ...
} }

2
test/tokenizer_mask.asl Normal file
View File

@@ -0,0 +1,2 @@
var x = "Hello \"World\"";
var y = code("var z = \"Hello \\"World\\"\";");

View File

@@ -0,0 +1,2 @@
#define HELLO_WORLD "Hello World!"
hint(HELLO_WORLD);

View File

@@ -1,10 +1,8 @@
switch x { switch x {
case 1: case 1:
x = 1; x = 1;
break;
case 2: case 2:
x = 2; x = 2;
break;
default: default:
x = 3; x = 3;
} }

2198
test/types Normal file

File diff suppressed because it is too large Load Diff

BIN
tools/ASL GUI.exe Normal file

Binary file not shown.

20
tools/README.md Normal file
View File

@@ -0,0 +1,20 @@
#ASL Tools
A visual tool set to ease the work of asl developers.
Maintained by yours truly: [654wak654](https://github.com/654wak654/)
**Notepad++ Syntax Higligthing**: https://github.com/DeKugelschieber/asl/blob/master/tools/asl.xml
Feel free to contribute with another text editor's syntax higligthing plugin.
##ASL GUI <img src="https://img.shields.io/badge/version-1.1.0.0-orange.svg" alt="version">
An optional Java interface to make the compile procces of ASL faster and more user-friendly. It's released under the MIT licence just like the core project. It also helps with error reporting of asl.
**Version 1.1.0.0**
- New Arma 3-themed look, and error reporting straight from asl.
- Program now depends on asl.exe, they need to be in the same directory.
**Version 1.0.0.0**
- More style changes and bug fixes, marked ready for release.
**Version 0.3.0.0:**
- Fixed some possible bugs, did some style fixes and other code adjustments. It's now is readable without getting cataracts. Mostly anyway...

33
tools/asl.xml Normal file

File diff suppressed because one or more lines are too long

2198
types Normal file

File diff suppressed because it is too large Load Diff