30 Commits
1.1.1 ... 1.2.0

Author SHA1 Message Date
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
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
Ozan Eğitmen
11c9d19c25 Initial commit, has missing code 2015-11-07 22:01:19 +03:00
22 changed files with 5487 additions and 335 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # Changelog
**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** **1.1.1**
* arrays can now be declared within expressions * arrays can now be declared within expressions

View File

@@ -141,6 +141,8 @@ var _x = add();
// result in _x is 0 // result in _x is 0
``` ```
When trying to define a function with a name that exists in the build in function set, you'll get an compile error.
### 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, ...) we have to use a different syntax.
@@ -161,6 +163,13 @@ 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 does not accept parameters or only on one side (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 show how they are written in ASL and what the output will look like. Remember that ASL is case sensitive!
@@ -234,7 +243,7 @@ Keywords should not be used as identifiers. Here is a full list of all keywords
| var | | var |
| if | | if |
| while | | while |
| witch | | switch |
| for | | for |
| foreach | | foreach |
| func | | func |
@@ -251,7 +260,7 @@ Keywords should not be used as identifiers. Here is a full list of all keywords
## What's missing? ## What's missing?
The following features are not implemented yet, but will be in 1.1.0 or a future version: The following features are not implemented yet, but will be in 1.3.0 or a future version:
* scopes * scopes
* else if * else if
@@ -262,7 +271,7 @@ scopes won't be supported, since they are a stupid concept and can be replaced b
Selectors in expressions do not work (yet): Selectors in expressions do not work (yet):
``` ```
var x = ([1, 2, 3]-[1, 2])[0]; // should result in 3 var x = ([1, 2, 3]-[1, 2])[0]; // should result in 3, but does not work
``` ```
## Contribute ## Contribute
@@ -277,6 +286,11 @@ For further information you can read the SQF tutorial and documentation of scrip
* [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) * [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
MIT MIT

View File

@@ -6,14 +6,15 @@
* ~~pretty/minified printing~~ * ~~pretty/minified printing~~
* ~~usage~~ * ~~usage~~
* ~~recursive compiling~~ * ~~recursive compiling~~
* concurrent compiling * ~~concurrent compiling~~
* ~~inline buildin function call -> foo(a)(bar(x)(y));~~ * ~~inline buildin function call -> foo(a)(bar(x)(y));~~
* ~~negative values e.g. -1, operator !~~ * ~~negative values e.g. -1, operator !~~
* ~~tokenizer splits commands like "format" -> for, mat~~ * ~~tokenizer splits commands like "format" -> for, mat~~
* ~~try ... catch~~ * ~~try ... catch~~
* better error reporting * ~~better error reporting~~
- recover panic - ~~recover panic~~
- line, column - ~~line, column~~
* type check for functions
## Special cases ## Special cases

BIN
gui/ASL GUI.exe Normal file

Binary file not shown.

14
gui/README.md Normal file
View File

@@ -0,0 +1,14 @@
ASL GUI <img src="https://img.shields.io/badge/version-1.0.0.0-lightgrey.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.
Maintained by yours truly: [654wak654](https://github.com/654wak654/)
**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...

View File

@@ -0,0 +1,67 @@
/*
* The MIT License
*
* Copyright 2015 Ozan Egitmen.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package asl.gui;
public class DlgError extends javax.swing.JDialog {
boolean isAbort = true;
public DlgError(java.awt.Frame parent, boolean modal, String errorMessage) {
super(parent, modal);
initComponents();
lblError.setText(errorMessage);
}
private void initComponents() {
lblError = new javax.swing.JLabel();
lblTitle = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setTitle("ERROR");
setIconImage(null);
setMinimumSize(new java.awt.Dimension(380, 150));
setResizable(false);
setType(java.awt.Window.Type.POPUP);
lblError.setFont(new java.awt.Font("Segoe UI Light", 0, 16));
lblError.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
lblError.setText("Some error");
lblTitle.setFont(new java.awt.Font("Segoe UI Light", 0, 16));
lblTitle.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
lblTitle.setText("asl.exe has encountered an error:");
lblTitle.setToolTipText("");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(lblTitle, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE).addComponent(lblError, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE));
layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addContainerGap().addComponent(lblTitle).addGap(18, 18, 18).addComponent(lblError).addGap(27, 27, 27)));
pack();
}
private javax.swing.JLabel lblError;
private javax.swing.JLabel lblTitle;
}

370
gui/src/asl/gui/Main.java Normal file
View File

@@ -0,0 +1,370 @@
/*
* The MIT License
*
* Copyright 2015 Ozan Egitmen.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package asl.gui;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.BorderFactory;
import javax.swing.JFileChooser;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.plaf.ColorUIResource;
public class Main extends javax.swing.JFrame {
Preferences prefs = Preferences.userRoot().node(this.getClass().getName());
boolean inputError = false, outputError = false, aslError = false;
public Main() {
initComponents();
getContentPane().setBackground(Color.WHITE);
lblASLError.setText(" ");
lblInputError.setText(" ");
lblOutputError.setText(" ");
txtASLDir.setText(prefs.get("aslDir", ""));
txtInputDir.setText(prefs.get("inputDir", ""));
txtOutputDir.setText(prefs.get("outputDir", ""));
cbCompileAll.setSelected(prefs.getBoolean("compileAll", false));
cbPrettyPrinting.setSelected(prefs.getBoolean("prettyPrinting", false));
}
private String fileChooser(String title, int fileType) {
JFileChooser chooser = new JFileChooser();
if (fileType == 0) {
chooser.setFileFilter(new FileNameExtensionFilter("Executable", "exe"));
chooser.setAcceptAllFileFilterUsed(false);
}
chooser.setFileSelectionMode(fileType);
chooser.setDialogTitle(title);
String selectedPath = "";
if (chooser.showOpenDialog(null) == 0)
selectedPath = chooser.getSelectedFile().toString();
else
chooser.cancelSelection();
return selectedPath;
}
private void setErrorCondition(int i, boolean j) {
if (i == 0) {
lblASLError.setText(j ? "asl.exe isn't in this location! You can click this message to download it." : " ");
aslError = j;
} else if (i == 1) {
lblInputError.setText(j ? "This folder doesn't exist!" : " ");
inputError = j;
} else {
lblOutputError.setText(j ? "Output folder doesn't exsist! Click this message to create it." : " ");
outputError = j;
}
}
private void initComponents() {
lblInput = new javax.swing.JLabel();
txtInputDir = new javax.swing.JTextField();
lblOutput = new javax.swing.JLabel();
txtOutputDir = new javax.swing.JTextField();
btnInput = new javax.swing.JButton();
btnOutput = new javax.swing.JButton();
lblASL = new javax.swing.JLabel();
txtASLDir = new javax.swing.JTextField();
btnASL = new javax.swing.JButton();
jSeparator = new javax.swing.JSeparator();
lblASLSmall = new javax.swing.JLabel();
lblInputSmall = new javax.swing.JLabel();
lblOutputSmall = new javax.swing.JLabel();
cbCompileAll = new javax.swing.JCheckBox();
cbPrettyPrinting = new javax.swing.JCheckBox();
btnCompile = new javax.swing.JButton();
lblASLError = new javax.swing.JLabel();
lblInputError = new javax.swing.JLabel();
lblOutputError = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("ASL GUI");
setResizable(false);
lblInput.setFont(new java.awt.Font("Microsoft JhengHei UI Light", 0, 16));
lblInput.setText("Input Directory:");
lblInput.setOpaque(true);
txtInputDir.setFont(new java.awt.Font("Segoe UI Light", 0, 16));
lblOutput.setFont(new java.awt.Font("Microsoft JhengHei UI Light", 0, 16));
lblOutput.setText("Output Directory:");
lblOutput.setOpaque(true);
txtOutputDir.setFont(new java.awt.Font("Segoe UI Light", 0, 16));
btnInput.setText("...");
btnInput.setToolTipText("Opens a dialog to select input file");
btnInput.setFocusable(false);
btnInput.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
btnInputMouseClicked(evt);
}
});
btnOutput.setText("...");
btnOutput.setToolTipText("Opens a dialog to select output directory");
btnOutput.setFocusable(false);
btnOutput.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
btnOutputMouseClicked(evt);
}
});
lblASL.setFont(new java.awt.Font("Microsoft JhengHei UI Light", 0, 16));
lblASL.setText("ASL Compiler Directory");
lblASL.setOpaque(true);
txtASLDir.setFont(new java.awt.Font("Segoe UI Light", 0, 16));
btnASL.setText("...");
btnASL.setToolTipText("Opens a dialog to select the compiler location");
btnASL.setFocusable(false);
btnASL.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
btnASLMouseClicked(evt);
}
});
jSeparator.setToolTipText("");
lblASLSmall.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 10));
lblASLSmall.setText("Location of the asl.exe file.");
lblASLSmall.setOpaque(true);
lblInputSmall.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 10));
lblInputSmall.setText("Directory of scripts that will be compiled in to the output directory.");
lblInputSmall.setOpaque(true);
lblOutputSmall.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 10));
lblOutputSmall.setText("Directory that the compiled .sqf script(s) will be saved in.");
lblOutputSmall.setOpaque(true);
cbCompileAll.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 11));
cbCompileAll.setText("Compile all scripts in subfolders too.");
cbCompileAll.setFocusable(false);
cbCompileAll.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
cbCompileAllStateChanged(evt);
}
});
cbPrettyPrinting.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 11));
cbPrettyPrinting.setText("Activate pretty printing.");
cbPrettyPrinting.setFocusable(false);
cbPrettyPrinting.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
cbPrettyPrintingStateChanged(evt);
}
});
btnCompile.setFont(new java.awt.Font("Microsoft JhengHei UI Light", 0, 16));
btnCompile.setText("Compile");
btnCompile.setToolTipText("Opens a dialog to select output directory");
btnCompile.setFocusable(false);
btnCompile.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
btnCompileMouseClicked(evt);
}
});
lblASLError.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 10));
lblASLError.setForeground(java.awt.Color.red);
lblASLError.setText("Some error");
lblASLError.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
lblASLErrorMouseClicked(evt);
}
});
lblInputError.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 10));
lblInputError.setForeground(java.awt.Color.red);
lblInputError.setText("Some error");
lblOutputError.setFont(new java.awt.Font("Microsoft YaHei UI", 0, 10));
lblOutputError.setForeground(java.awt.Color.red);
lblOutputError.setText("Some error");
lblOutputError.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
lblOutputErrorMouseClicked(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(jSeparator).addGroup(layout.createSequentialGroup().addGap(15, 15, 15).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(lblOutputError, javax.swing.GroupLayout.PREFERRED_SIZE, 371, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(lblInputError, javax.swing.GroupLayout.PREFERRED_SIZE, 371, javax.swing.GroupLayout.PREFERRED_SIZE).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false).addComponent(lblASLError, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 371, javax.swing.GroupLayout.PREFERRED_SIZE).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(lblASL).addComponent(lblASLSmall).addGroup(layout.createSequentialGroup().addComponent(txtASLDir, javax.swing.GroupLayout.PREFERRED_SIZE, 320, javax.swing.GroupLayout.PREFERRED_SIZE).addGap(6, 6, 6).addComponent(btnASL)).addGroup(layout.createSequentialGroup().addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING).addComponent(txtOutputDir, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 320, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(lblOutput, javax.swing.GroupLayout.Alignment.LEADING)).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(btnOutput)).addComponent(lblOutputSmall).addComponent(lblInput).addGroup(layout.createSequentialGroup().addComponent(txtInputDir, javax.swing.GroupLayout.PREFERRED_SIZE, 320, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(btnInput)).addComponent(lblInputSmall).addComponent(cbCompileAll).addComponent(cbPrettyPrinting))).addComponent(btnCompile, javax.swing.GroupLayout.PREFERRED_SIZE, 373, javax.swing.GroupLayout.PREFERRED_SIZE)).addGap(20, 20, 20)));
layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addGap(6, 6, 6).addComponent(lblASL).addGap(3, 3, 3).addComponent(lblASLSmall).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(txtASLDir, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(btnASL, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE)).addGap(4, 4, 4).addComponent(lblASLError).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(jSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE).addGap(3, 3, 3).addComponent(lblInput).addGap(3, 3, 3).addComponent(lblInputSmall, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(txtInputDir, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(btnInput, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE)).addGap(4, 4, 4).addComponent(lblInputError).addGap(6, 6, 6).addComponent(lblOutput).addGap(3, 3, 3).addComponent(lblOutputSmall, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE).addGap(6, 6, 6).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(txtOutputDir, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(btnOutput, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE)).addGap(4, 4, 4).addComponent(lblOutputError).addGap(6, 6, 6).addComponent(cbCompileAll).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED).addComponent(cbPrettyPrinting).addGap(11, 11, 11).addComponent(btnCompile, javax.swing.GroupLayout.PREFERRED_SIZE, 41, javax.swing.GroupLayout.PREFERRED_SIZE).addGap(11, 11, 11)));
pack();
setLocationRelativeTo(null);
}
private void btnInputMouseClicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
String path = fileChooser("Select input directory", 1);
File inputDir = new File(path);
if (inputDir.exists()) {
prefs.put("inputDir", path);
txtInputDir.setText(path);
if (inputError)
setErrorCondition(1, false);
}
}
}
private void btnOutputMouseClicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
String path = fileChooser("Select output directory", 1);
File outputDir = new File(path);
if (outputDir.exists()) {
prefs.put("outputDir", path);
txtOutputDir.setText(path);
if (outputError)
setErrorCondition(2, false);
}
}
}
private void btnASLMouseClicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
String path = fileChooser("Select 'asl.exe' location", 0);
File asl = new File(path);
if (asl.exists()) {
prefs.put("aslDir", path);
txtASLDir.setText(path);
if (aslError)
setErrorCondition(0, false);
}
}
}
private void btnCompileMouseClicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
JTextField[] dirFields = {txtASLDir, txtInputDir, txtOutputDir};
for (byte i = 0; i < 3; i++) {
File bleh = new File(dirFields[i].getText());
setErrorCondition(i, !bleh.exists());
}
if (aslError || inputError || outputError)
return;
String prettyPrinting = cbPrettyPrinting.isSelected() ? "-pretty" : "", compileAll = cbCompileAll.isSelected() ? "-r" : "", asl = txtASLDir.getText(), input = txtInputDir.getText(), output = txtOutputDir.getText(), error = " ";
try {
Process aslProcess = new ProcessBuilder(asl, compileAll, prettyPrinting, input, output).start();
BufferedReader br = new BufferedReader(new InputStreamReader(aslProcess.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
if (line.toLowerCase().contains("panic")) {
error = line;
}
}
aslProcess.waitFor();
} catch (IOException | InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
if (!error.equals(" ")) {
DlgError showError = new DlgError(this, true, error);
showError.setLocationRelativeTo(this);
showError.setVisible(true);
}
}
}
private void cbCompileAllStateChanged(javax.swing.event.ChangeEvent evt) {
prefs.putBoolean("compileAll", cbCompileAll.isSelected());
}
private void cbPrettyPrintingStateChanged(javax.swing.event.ChangeEvent evt) {
prefs.putBoolean("prettyPrinting", cbPrettyPrinting.isSelected());
}
private void lblOutputErrorMouseClicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt) && outputError) {
new File(txtOutputDir.getText()).mkdirs();
setErrorCondition(2, false);
}
}
private void lblASLErrorMouseClicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt) && aslError) {
try {
URI github = new URI("https://github.com/DeKugelschieber/asl/releases");
java.awt.Desktop.getDesktop().browse(github);
} catch (URISyntaxException | IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
setErrorCondition(0, false);
}
}
public static void main(String args[]) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
UIManager.put("ToolTip.background", new ColorUIResource(255, 255, 255));
UIManager.put("ToolTip.foreground", new ColorUIResource(87, 87, 87));
Border lineBorder = BorderFactory.createLineBorder(new Color(118, 118, 118));
UIManager.put("ToolTip.border", lineBorder);
Border compoundBorder = BorderFactory.createCompoundBorder(UIManager.getBorder("ToolTip.border"), BorderFactory.createEmptyBorder(0, 2, 2, 3));
UIManager.put("ToolTip.border", compoundBorder);
java.awt.EventQueue.invokeLater(() -> {
new Main().setVisible(true);
});
}
private javax.swing.JButton btnASL;
private javax.swing.JButton btnCompile;
private javax.swing.JButton btnInput;
private javax.swing.JButton btnOutput;
private javax.swing.JCheckBox cbCompileAll;
private javax.swing.JCheckBox cbPrettyPrinting;
private javax.swing.JSeparator jSeparator;
private javax.swing.JLabel lblASL;
private javax.swing.JLabel lblASLError;
private javax.swing.JLabel lblASLSmall;
private javax.swing.JLabel lblInput;
private javax.swing.JLabel lblInputError;
private javax.swing.JLabel lblInputSmall;
private javax.swing.JLabel lblOutput;
private javax.swing.JLabel lblOutputError;
private javax.swing.JLabel lblOutputSmall;
private javax.swing.JTextField txtASLDir;
private javax.swing.JTextField txtInputDir;
private javax.swing.JTextField txtOutputDir;
}

View File

@@ -1,34 +1,36 @@
package main package main
import ( import (
"parser"
"tokenizer"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath"
"os" "os"
"parser"
"path/filepath"
"strings" "strings"
"tokenizer"
"types"
) )
const ( const (
version = "1.1.1" version = "1.2.0"
extension = ".asl" extension = ".asl"
sqfextension = ".sqf" sqfextension = ".sqf"
PathSeparator = string(os.PathSeparator) typeinfo = "types"
PathSeparator = string(os.PathSeparator)
) )
type ASLFile struct { type ASLFile struct {
in string in string
out string out string
newname string newname string
} }
var ( var (
recursive bool = false recursive bool = false
pretty bool = false pretty bool = false
exit bool = false exit bool = false
aslFiles []ASLFile aslFiles []ASLFile
inDir string inDir string
) )
func usage() { func usage() {
@@ -41,78 +43,118 @@ 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] != '-' {
if flag == "-v" { return false
fmt.Println("asl version "+version) }
exit = true
} else if flag == "-r" {
recursive = true
} else if flag == "-pretty" {
pretty = true
} else if flag == "--help" {
usage()
exit = true
}
return true if flag == "-v" {
} fmt.Println("asl version " + version)
exit = true
} else if flag == "-r" {
recursive = true
} else if flag == "-pretty" {
pretty = true
} else if flag == "--help" {
usage()
exit = true
}
return false return true
} }
// 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)
if err != nil { if err != nil {
fmt.Println("Error reading in directory!") fmt.Println("Error reading in directory!")
return return
} }
for i := 0; i < len(dir); i++ { for i := 0; i < len(dir); i++ {
name := dir[i].Name() name := dir[i].Name()
if dir[i].IsDir() && recursive { if dir[i].IsDir() && recursive {
readAslFiles(filepath.FromSlash(path+PathSeparator+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+PathSeparator+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))]
file := ASLFile{in, out, newname} file := ASLFile{in, out, newname}
aslFiles = append(aslFiles, file) aslFiles = append(aslFiles, file)
} }
} }
} }
// Recovers and prints thrown error.
func recoverCompileError(file string, waiter chan bool) {
if r := recover(); r != nil {
fmt.Println("Compile error in file "+file+":", r)
}
waiter <- true // the show must go on
}
// Compiles a single ASL file.
func compileFile(path string, file ASLFile, waiter chan bool) {
defer recoverCompileError(file.in, waiter)
// 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)
if err != nil {
fmt.Println("Error writing file: " + file.out)
fmt.Println(err)
}
waiter <- true // done
}
// Compiles ASL files concurrently.
func compile(path string) { func compile(path string) {
for i := 0; i < len(aslFiles); i++ { waiter := make(chan bool, len(aslFiles))
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)
if err != nil { // fire compile
fmt.Println("Error reading file: "+aslFiles[i].in) for i := 0; i < len(aslFiles); i++ {
continue go compileFile(path, aslFiles[i], waiter)
} }
token := tokenizer.Tokenize(code, false) // wait until all files are compiled
compiler := parser.Compiler{} for i := 0; i < len(aslFiles); i++ {
sqf := compiler.Parse(token, pretty) <-waiter
}
os.MkdirAll(filepath.FromSlash(path+PathSeparator+aslFiles[i].out), 0777)
err = ioutil.WriteFile(out, []byte(sqf), 0666)
if err != nil {
fmt.Println("Error writing file: "+aslFiles[i].out)
fmt.Println(err)
}
}
} }
func main() { func main() {
@@ -120,29 +162,37 @@ func main() {
// flags // flags
if len(args) < 2 { if len(args) < 2 {
usage() usage()
return return
} }
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 { if exit {
return return
}
// load type information
loadTypes()
if exit {
return
} }
// in/out parameter // in/out parameter
out := "" out := ""
if i < len(args) { if i < len(args) {
inDir = args[i] inDir = args[i]
i++ i++
} else { } else {
return return
} }
if i < len(args) { if i < len(args) {
out = args[i] out = args[i]
} }
readAslFiles(inDir) readAslFiles(inDir)

View File

@@ -1,7 +1,9 @@
package parser package parser
import ( import (
"errors"
"tokenizer" "tokenizer"
"types"
) )
const new_line = "\r\n" const new_line = "\r\n"
@@ -10,7 +12,7 @@ const new_line = "\r\n"
// and writes SQF code into desired location. // and writes SQF code into desired location.
func (c *Compiler) Parse(token []tokenizer.Token, prettyPrinting bool) string { func (c *Compiler) Parse(token []tokenizer.Token, prettyPrinting bool) string {
if !c.initParser(token, prettyPrinting) { if !c.initParser(token, prettyPrinting) {
return "" return ""
} }
for c.tokenIndex < len(token) { for c.tokenIndex < len(token) {
@@ -21,9 +23,9 @@ func (c *Compiler) Parse(token []tokenizer.Token, prettyPrinting bool) string {
} }
func (c *Compiler) parseBlock() { func (c *Compiler) parseBlock() {
if c.get().Preprocessor { if c.get().Preprocessor {
c.parsePreprocessor() c.parsePreprocessor()
} else if c.accept("var") { } else if c.accept("var") {
c.parseVar() c.parseVar()
} else if c.accept("if") { } else if c.accept("if") {
c.parseIf() c.parseIf()
@@ -42,9 +44,9 @@ func (c *Compiler) parseBlock() {
} else if c.accept("try") { } else if c.accept("try") {
c.parseTryCatch() c.parseTryCatch()
} else if c.accept("exitwith") { } else if c.accept("exitwith") {
c.parseExitWith() c.parseExitWith()
} else if c.accept("waituntil") { } else if c.accept("waituntil") {
c.parseWaitUntil() c.parseWaitUntil()
} else if c.accept("case") || c.accept("default") { } else if c.accept("case") || c.accept("default") {
return return
} else { } else {
@@ -57,9 +59,9 @@ func (c *Compiler) parseBlock() {
} }
func (c *Compiler) parsePreprocessor() { func (c *Compiler) parsePreprocessor() {
// we definitely want a new line before and after // we definitely want a new line before and after
c.appendOut(new_line+c.get().Token+new_line, false) c.appendOut(new_line+c.get().Token+new_line, false)
c.next() c.next()
} }
func (c *Compiler) parseVar() { func (c *Compiler) parseVar() {
@@ -78,7 +80,7 @@ func (c *Compiler) parseVar() {
} }
func (c *Compiler) parseArray(out bool) string { func (c *Compiler) parseArray(out bool) string {
output := "" output := ""
c.expect("[") c.expect("[")
output += "[" output += "["
@@ -87,7 +89,7 @@ func (c *Compiler) parseArray(out bool) string {
for c.accept(",") { for c.accept(",") {
c.next() c.next()
output += ","+c.parseExpression(false) output += "," + c.parseExpression(false)
} }
} }
@@ -95,7 +97,7 @@ func (c *Compiler) parseArray(out bool) string {
output += "]" output += "]"
if out { if out {
c.appendOut(output, false) c.appendOut(output, false)
} }
return output return output
@@ -215,6 +217,12 @@ func (c *Compiler) parseForeach() {
func (c *Compiler) parseFunction() { func (c *Compiler) parseFunction() {
c.expect("func") 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.appendOut(c.get().Token+" = {", true)
c.next() c.next()
c.expect("(") c.expect("(")
@@ -239,12 +247,12 @@ func (c *Compiler) parseFunctionParameter() {
c.next() c.next()
if c.accept("=") { if c.accept("=") {
c.next() c.next()
value := c.get().Token value := c.get().Token
c.next() c.next()
c.appendOut("[\""+name+"\","+value+"]", false) c.appendOut("[\""+name+"\","+value+"]", false)
} else { } else {
c.appendOut("\""+name+"\"", false) c.appendOut("\""+name+"\"", false)
} }
if !c.accept(")") { if !c.accept(")") {
@@ -279,47 +287,47 @@ func (c *Compiler) parseTryCatch() {
} }
func (c *Compiler) parseExitWith() { func (c *Compiler) parseExitWith() {
c.expect("exitwith") c.expect("exitwith")
c.expect("{") c.expect("{")
c.appendOut("if (true) exitWith {", true) c.appendOut("if (true) exitWith {", true)
c.parseBlock() c.parseBlock()
c.expect("}") c.expect("}")
c.appendOut("};", true) c.appendOut("};", true)
} }
func (c *Compiler) parseWaitUntil() { func (c *Compiler) parseWaitUntil() {
c.expect("waituntil") c.expect("waituntil")
c.expect("(") c.expect("(")
c.appendOut("waitUntil {", false) c.appendOut("waitUntil {", false)
c.parseExpression(true) c.parseExpression(true)
if c.accept(";") { if c.accept(";") {
c.next() c.next()
c.appendOut(";", false) c.appendOut(";", false)
c.parseExpression(true) c.parseExpression(true)
} }
c.expect(")") c.expect(")")
c.expect(";") c.expect(";")
c.appendOut("};", true) c.appendOut("};", true)
} }
func (c *Compiler) parseInlineCode() string { func (c *Compiler) parseInlineCode() string {
c.expect("code") c.expect("code")
c.expect("(") c.expect("(")
code := c.get().Token code := c.get().Token
c.next() c.next()
output := "{}" output := "{}"
if len(code) > 2 { if len(code) > 2 {
compiler := Compiler{} compiler := Compiler{}
output = "{"+compiler.Parse(tokenizer.Tokenize([]byte(code[1:len(code)-1]), true), false)+"}" output = "{" + compiler.Parse(tokenizer.Tokenize([]byte(code[1:len(code)-1]), true), false) + "}"
} }
c.expect(")") c.expect(")")
return output return output
} }
// Everything that does not start with a keyword. // Everything that does not start with a keyword.
@@ -359,30 +367,22 @@ func (c *Compiler) parseFunctionCall(out bool, name string) string {
output := "" output := ""
c.expect("(") c.expect("(")
leftParams, leftParamCount := c.parseParameter(false) paramsStr, paramCount := c.parseParameter(false)
c.expect(")") c.expect(")")
if c.accept("(") { // buildin function
// buildin function buildin := types.GetFunction(name)
c.next()
rightParams, rightParamCount := c.parseParameter(false)
c.expect(")")
if leftParamCount > 1 { if buildin != nil {
leftParams = "[" + leftParams + "]" if buildin.Type == types.NULL {
} output = name
} else if buildin.Type == types.UNARY {
if rightParamCount > 1 { output = c.parseUnaryFunction(name, paramsStr, paramCount)
rightParams = "[" + rightParams + "]"
}
if leftParamCount > 0 {
output = leftParams + " " + name + " " + rightParams
} else { } else {
output = name + " " + rightParams output = c.parseBinaryFunction(name, paramsStr, buildin, paramCount)
} }
} else { } else {
output = "[" + leftParams + "] call " + name output = "[" + paramsStr + "] call " + name
} }
if out { if out {
@@ -392,12 +392,49 @@ func (c *Compiler) parseFunctionCall(out bool, name string) string {
return output return output
} }
func (c *Compiler) parseUnaryFunction(name, paramsStr string, paramCount int) string {
output := ""
if paramCount == 1 {
output = name + " " + paramsStr
} else {
output = "[" + paramsStr + "] call " + name
}
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) { func (c *Compiler) parseParameter(out bool) (string, int) {
output := "" output := ""
count := 0 count := 0
for !c.accept(")") { for !c.accept(")") {
output += c.parseExpression(out) expr := c.parseExpression(out)
output += expr
count++ count++
if !c.accept(")") { if !c.accept(")") {
@@ -459,19 +496,19 @@ func (c *Compiler) parseIdentifier() string {
output := "" output := ""
if c.accept("code") { if c.accept("code") {
output += c.parseInlineCode() output += c.parseInlineCode()
} else if c.seek("(") && !c.accept("!") && !c.accept("-") { } else if c.seek("(") && !c.accept("!") && !c.accept("-") {
name := c.get().Token name := c.get().Token
c.next() c.next()
output = "(" + c.parseFunctionCall(false, name) + ")" output = "(" + c.parseFunctionCall(false, name) + ")"
} else if c.accept("[") { } else if c.accept("[") {
output += c.parseArray(false) output += c.parseArray(false)
} else if c.seek("[") { } else if c.seek("[") {
output += "("+c.get().Token output += "(" + c.get().Token
c.next() c.next()
c.expect("[") c.expect("[")
output += " select ("+c.parseExpression(false)+"))" output += " select (" + c.parseExpression(false) + "))"
c.expect("]") c.expect("]")
} else if c.accept("!") || c.accept("-") { } else if c.accept("!") || c.accept("-") {
output = c.get().Token output = c.get().Token
c.next() c.next()

View File

@@ -1,16 +1,17 @@
package parser package parser
import ( import (
"strconv" "errors"
"tokenizer" "strconv"
"tokenizer"
) )
type Compiler struct { type Compiler struct {
tokens []tokenizer.Token tokens []tokenizer.Token
tokenIndex int tokenIndex int
out string out string
offset int offset int
pretty bool pretty bool
} }
// Initilizes the parser. // Initilizes the parser.
@@ -38,7 +39,7 @@ func (c *Compiler) accept(token string) bool {
// Throws if current token does not match expected one. // Throws if current token does not match expected one.
func (c *Compiler) expect(token string) { func (c *Compiler) expect(token string) {
if !c.tokenEqual(token, c.get()) { 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)) 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() c.next()
@@ -62,7 +63,7 @@ func (c *Compiler) next() {
// Returns current token or throws, if no more tokens are available. // Returns current token or throws, if no more tokens are available.
func (c *Compiler) get() tokenizer.Token { func (c *Compiler) get() tokenizer.Token {
if c.tokenIndex >= len(c.tokens) { if c.tokenIndex >= len(c.tokens) {
panic("No more tokens") panic(errors.New("No more tokens"))
} }
return c.tokens[c.tokenIndex] return c.tokens[c.tokenIndex]

View File

@@ -1,168 +1,195 @@
package parser_test package parser_test
import ( import (
"tokenizer"
"parser"
"io/ioutil" "io/ioutil"
"parser"
"testing" "testing"
"tokenizer"
"types"
)
const (
types_file = "../../test/types"
) )
func TestParserDeclaration(t *testing.T) { func TestParserDeclaration(t *testing.T) {
got := getCompiled(t, "test/tokenizer_var.asl") got := getCompiled(t, "../../test/tokenizer_var.asl")
want := "x = 1;\r\narray = [1,2,3];\r\n" want := "x = 1;\r\narray = [1,2,3];\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserAssignment(t *testing.T) { func TestParserAssignment(t *testing.T) {
got := getCompiled(t, "test/parser_assignment.asl") got := getCompiled(t, "../../test/parser_assignment.asl")
want := "x = 1;\r\n" want := "x = 1;\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserIf(t *testing.T) { func TestParserIf(t *testing.T) {
got := getCompiled(t, "test/tokenizer_if.asl") got := getCompiled(t, "../../test/tokenizer_if.asl")
want := "if (a<b) then {\r\n};\r\n" want := "if (a<b) then {\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserWhile(t *testing.T) { func TestParserWhile(t *testing.T) {
got := getCompiled(t, "test/tokenizer_while.asl") got := getCompiled(t, "../../test/tokenizer_while.asl")
want := "while {true} do {\r\n};" want := "while {true} do {\r\n};"
equal(t, got, want) equal(t, got, want)
} }
func TestParserFor(t *testing.T) { func TestParserFor(t *testing.T) {
got := getCompiled(t, "test/tokenizer_for.asl") got := getCompiled(t, "../../test/tokenizer_for.asl")
want := "for [{i=0}, {i<100}, {i=i+1}] do {\r\n};\r\n" want := "for [{i=0}, {i<100}, {i=i+1}] do {\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserForeach(t *testing.T) { func TestParserForeach(t *testing.T) {
got := getCompiled(t, "test/tokenizer_foreach.asl") got := getCompiled(t, "../../test/tokenizer_foreach.asl")
want := "{\r\nunit = _x;\r\n} forEach (allUnits);\r\n" want := "{\r\nunit = _x;\r\n} forEach (allUnits);\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserSwitch(t *testing.T) { func TestParserSwitch(t *testing.T) {
got := getCompiled(t, "test/tokenizer_switch.asl") 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" 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) equal(t, got, want)
} }
func TestParserFunction(t *testing.T) { func TestParserFunction(t *testing.T) {
got := getCompiled(t, "test/tokenizer_func.asl") got := getCompiled(t, "../../test/tokenizer_func.asl")
want := "TestFunction = {\r\nparams [\"param0\",\"param1\"];\r\nreturn true;\r\n};\r\n" want := "TestFunction = {\r\nparams [\"param0\",\"param1\"];\r\nreturn true;\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserAssignResult(t *testing.T) { func TestParserAssignResult(t *testing.T) {
got := getCompiled(t, "test/parser_assign_result.asl") got := getCompiled(t, "../../test/parser_assign_result.asl")
want := "x = ([1, 2, 3] call foo);\r\ny = ([1, 2, 3] call bar);\r\n" want := "x = ([1, 2, 3] call foo);\r\ny = ([1, 2, 3] call bar);\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserExpression(t *testing.T) { func TestParserExpression(t *testing.T) {
got := getCompiled(t, "test/parser_expression.asl") got := getCompiled(t, "../../test/parser_expression.asl")
want := "x = -(1+(2+3))/(6*(someVariable+99-100))-(20)+!anotherVariable+([] call foo);\r\n" want := "x = -(1+(2+3))/(6*(someVariable+99-100))-(20)+!anotherVariable+([] call foo);\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserExpression2(t *testing.T) { func TestParserExpression2(t *testing.T) {
got := getCompiled(t, "test/parser_expression2.asl") got := getCompiled(t, "../../test/parser_expression2.asl")
want := "x = true||(3>=4&&5<8);\r\n" want := "x = true||(3>=4&&5<8);\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserFunctionCall(t *testing.T) { func TestParserFunctionCall(t *testing.T) {
got := getCompiled(t, "test/parser_func_call.asl") 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" 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) equal(t, got, want)
} }
func TestParserBuildinFunctionCall(t *testing.T) { func TestParserNullBuildinFunctionCall(t *testing.T) {
got := getCompiled(t, "test/parser_buildin_func.asl") types.LoadTypes(types_file)
want := "_x = (([player, foo] getVar bar) setHit [\"head\", \"tail\"]);\r\n"
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) equal(t, got, want)
} }
func TestParserOperator(t *testing.T) { func TestParserOperator(t *testing.T) {
got := getCompiled(t, "test/parser_operator.asl") 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" want := "if (x==y&&x!=y&&x<=y&&x>=y&&x<y&&x>y) then {\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserTryCatch(t *testing.T) { func TestParserTryCatch(t *testing.T) {
got := getCompiled(t, "test/parser_try_catch.asl") got := getCompiled(t, "../../test/parser_try_catch.asl")
want := "try {\r\n} catch {\r\n};\r\n" want := "try {\r\n} catch {\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserNegationFunctionCall(t *testing.T) { func TestParserNegationFunctionCall(t *testing.T) {
got := getCompiled(t, "test/parser_negation.asl") got := getCompiled(t, "../../test/parser_negation.asl")
want := "x = !([] call foo);\r\n" want := "x = !([] call foo);\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserExitWith(t *testing.T) { func TestParserExitWith(t *testing.T) {
got := getCompiled(t, "test/parser_exitwith.asl") got := getCompiled(t, "../../test/parser_exitwith.asl")
want := "if (true) exitWith {\r\n};\r\n" want := "if (true) exitWith {\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserWaitUntil(t *testing.T) { func TestParserWaitUntil(t *testing.T) {
got := getCompiled(t, "test/parser_waituntil.asl") got := getCompiled(t, "../../test/parser_waituntil.asl")
want := "waitUntil {x=x+1;x<100};\r\n" want := "waitUntil {x=x+1;x<100};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserArray(t *testing.T) { func TestParserArray(t *testing.T) {
got := getCompiled(t, "test/parser_array.asl") got := getCompiled(t, "../../test/parser_array.asl")
want := "x = [1,2,3];\r\ny = (x select (1));\r\n" want := "x = [1,2,3];\r\ny = (x select (1));\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserFunctionParams(t *testing.T) { func TestParserFunctionParams(t *testing.T) {
got := getCompiled(t, "test/parser_func_params.asl") got := getCompiled(t, "../../test/parser_func_params.asl")
want := "myFunc = {\r\nparams [[\"a\",1],[\"b\",2]];\r\nreturn a+b;\r\n};\r\n" want := "myFunc = {\r\nparams [[\"a\",1],[\"b\",2]];\r\nreturn a+b;\r\n};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserInlineCode(t *testing.T) { func TestParserInlineCode(t *testing.T) {
got := getCompiled(t, "test/parser_code.asl") got := getCompiled(t, "../../test/parser_code.asl")
want := "inline_code = {a = 1;b = 2;if (a<b) then {[] call foo;};};\r\n" want := "inline_code = {a = 1;b = 2;if (a<b) then {[] call foo;};};\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserPreprocessor(t *testing.T) { func TestParserPreprocessor(t *testing.T) {
got := getCompiled(t, "test/tokenizer_preprocessor.asl") 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" want := "\r\n#define HELLO_WORLD \"Hello World!\"\r\nhint HELLO_WORLD;\r\n"
equal(t, got, want) equal(t, got, want)
} }
func TestParserExpressionArray(t *testing.T) { func TestParserExpressionArray(t *testing.T) {
got := getCompiled(t, "test/parser_expression_array.asl") got := getCompiled(t, "../../test/parser_expression_array.asl")
want := "x = [1,2,3]-[2,3];\r\n" want := "x = [1,2,3]-[2,3];\r\n"
equal(t, got, want) equal(t, got, want)
@@ -176,7 +203,7 @@ func getCompiled(t *testing.T, file string) string {
t.FailNow() t.FailNow()
} }
tokens := tokenizer.Tokenize(code) tokens := tokenizer.Tokenize(code, false)
compiler := parser.Compiler{} compiler := parser.Compiler{}
return compiler.Parse(tokens, true) return compiler.Parse(tokens, true)

View File

@@ -5,63 +5,65 @@ import (
) )
type Token struct { type Token struct {
Token string Token string
Preprocessor bool Preprocessor bool
Line int Line int
Column int Column int
} }
var delimiter = []byte{ var (
'=', delimiter = []byte{
';', '=',
'{', ';',
'}', '{',
'(', '}',
')', '(',
'[', ')',
']', '[',
'<', ']',
'>', '<',
'!', '>',
',', '!',
':', ',',
'&', ':',
'|', '&',
'+', '|',
'-', '+',
'*', '-',
'/'} // TODO: modulo? '*',
'/'} // TODO: modulo?
var keywords = []string{ keywords = []string{
"var", "var",
"if", "if",
"while", "while",
"switch", "switch",
"for", "for",
"foreach", "foreach",
"func", "func",
"true", "true",
"false", "false",
"case", "case",
"default", "default",
"return", "return",
"try", "try",
"catch", "catch",
"exitwith", "exitwith",
"waituntil", "waituntil",
"code"} "code"}
var whitespace = []byte{' ', '\n', '\t', '\r'} whitespace = []byte{' ', '\n', '\t', '\r'}
var identifier = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" identifier = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
var preprocessor = byte('#') preprocessor = byte('#')
var new_line = []byte{'\r', '\n'} 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, doStripSlashes bool) []Token { func Tokenize(code []byte, doStripSlashes bool) []Token {
if doStripSlashes { if doStripSlashes {
code = stripSlashes(code); code = stripSlashes(code)
} }
code = removeComments(code) code = removeComments(code)
tokens := make([]Token, 0) tokens := make([]Token, 0)
@@ -72,8 +74,8 @@ func Tokenize(code []byte, doStripSlashes bool) []Token {
column++ column++
if byteArrayContains(new_line, c) { if byteArrayContains(new_line, c) {
line++ line++
column = 0 column = 0
} }
// string masks (backslash) // string masks (backslash)
@@ -95,8 +97,8 @@ func Tokenize(code []byte, doStripSlashes bool) []Token {
} else { } else {
// preprocessor, delimeter, keyword or variable/expression // preprocessor, delimeter, keyword or variable/expression
if c == preprocessor { if c == preprocessor {
tokens = append(tokens, preprocessorLine(code, &i, line, column)) tokens = append(tokens, preprocessorLine(code, &i, line, column))
token = "" token = ""
} else if byteArrayContains(delimiter, c) { } else if byteArrayContains(delimiter, c) {
if token != "" { if token != "" {
tokens = append(tokens, Token{token, false, line, column}) tokens = append(tokens, Token{token, false, line, column})
@@ -121,23 +123,23 @@ func Tokenize(code []byte, doStripSlashes bool) []Token {
// Removes slashes from input code. // Removes slashes from input code.
// This is used for the "code" keyword for correct strings in resulting code. // This is used for the "code" keyword for correct strings in resulting code.
func stripSlashes(code []byte) []byte { func stripSlashes(code []byte) []byte {
newcode := make([]byte, len(code)) newcode := make([]byte, len(code))
j, mask := 0, false j, mask := 0, false
for i := 0; i < len(code); i++ { for i := 0; i < len(code); i++ {
c := code[i] c := code[i]
if c == '\\' && !mask { if c == '\\' && !mask {
mask = true mask = true
continue continue
} }
newcode[j] = code[i] newcode[j] = code[i]
mask = false mask = false
j++ j++
} }
return newcode return newcode
} }
// Removes all comments from input byte array. // Removes all comments from input byte array.
@@ -180,31 +182,31 @@ func removeComments(code []byte) []byte {
// Reads preprocessor command until end of line // Reads preprocessor command until end of line
func preprocessorLine(code []byte, i *int, lineNr, column int) Token { func preprocessorLine(code []byte, i *int, lineNr, column int) Token {
c := byte('0') c := byte('0')
var line string var line string
for *i < len(code) { for *i < len(code) {
c = code[*i] c = code[*i]
if byteArrayContains(new_line, c) { if byteArrayContains(new_line, c) {
break break
} }
line += string(c) line += string(c)
(*i)++ (*i)++
} }
// read all new line characters (\r and \n) // read all new line characters (\r and \n)
c = code[*i] c = code[*i]
for byteArrayContains(new_line, c) { for byteArrayContains(new_line, c) {
(*i)++ (*i)++
c = code[*i] c = code[*i]
} }
(*i)-- // for will count up 1, so subtract it here (*i)-- // for will count up 1, so subtract it here
return Token{line, true, lineNr, column} return Token{line, true, lineNr, column}
} }
// Returns the next character in code starting at i. // Returns the next character in code starting at i.

View File

@@ -1,13 +1,13 @@
package tokenizer_test package tokenizer_test
import ( import (
"tokenizer"
"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)
@@ -15,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)
@@ -23,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)
@@ -31,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)
@@ -39,7 +39,7 @@ 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", "unit", "=", ">", "allUnits", "{", "}"} want := []string{"foreach", "unit", "=", ">", "allUnits", "{", "}"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -47,7 +47,7 @@ func TestTokenizerForach(t *testing.T) {
} }
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", ";", "case", "2", ":", "x", "=", "2", ";", "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)
@@ -55,7 +55,7 @@ func TestTokenizerSwitch(t *testing.T) {
} }
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)
@@ -63,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)
@@ -71,7 +71,7 @@ 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)
@@ -79,7 +79,7 @@ func TestTokenizerIdentifier(t *testing.T) {
} }
func TestTokenizerInlineCode(t *testing.T) { func TestTokenizerInlineCode(t *testing.T) {
got := getTokens(t, "test/tokenizer_code.asl") got := getTokens(t, "../../test/tokenizer_code.asl")
want := []string{"var", "x", "=", "code", "(", "\"var x = 5;\"", ")", ";"} want := []string{"var", "x", "=", "code", "(", "\"var x = 5;\"", ")", ";"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
@@ -87,18 +87,17 @@ func TestTokenizerInlineCode(t *testing.T) {
} }
func TestTokenizerPreprocessor(t *testing.T) { func TestTokenizerPreprocessor(t *testing.T) {
got := getTokens(t, "test/tokenizer_preprocessor.asl") got := getTokens(t, "../../test/tokenizer_preprocessor.asl")
want := []string{"#define HELLO_WORLD \"Hello World!\"", "hint", "(", ")", "(", "HELLO_WORLD", ")", ";"} want := []string{"#define HELLO_WORLD \"Hello World!\"", "hint", "(", "HELLO_WORLD", ")", ";"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
compareTokens(t, &got, &want) compareTokens(t, &got, &want)
} }
func TestTokenizerMask(t *testing.T) { func TestTokenizerMask(t *testing.T) {
got := getTokens(t, "test/tokenizer_mask.asl") got := getTokens(t, "../../test/tokenizer_mask.asl")
//var y = code("var z = \"Hello \\"World\\"\";");
want := []string{"var", "x", "=", "\"Hello \\\"World\\\"\"", ";", want := []string{"var", "x", "=", "\"Hello \\\"World\\\"\"", ";",
"var", "y", "=", "code", "(", "\"var z = \\\"Hello \\\\\"World\\\\\"\\\";\"", ")", ";"} "var", "y", "=", "code", "(", "\"var z = \\\"Hello \\\\\"World\\\\\"\\\";\"", ")", ";"}
compareLength(t, &got, &want) compareLength(t, &got, &want)
compareTokens(t, &got, &want) compareTokens(t, &got, &want)

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 @@
setHit(someCar)("motor", 1);

View File

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

View File

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

View File

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

View File

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

2198
test/types Normal file

File diff suppressed because it is too large Load Diff

2198
types Normal file

File diff suppressed because it is too large Load Diff