diff --git a/README-DE.md b/README-DE.md index c4c1cb8..9a81c4c 100644 --- a/README-DE.md +++ b/README-DE.md @@ -1,10 +1,11 @@ + ![](https://i.imgur.com/YedWe7W.png) # Minecraft Script Dokumentation -> Update 0.1.5: [Alle Änderungen](https://github.com/Stevertus/mcscript/releases) +> Update 0.2: [Alle Änderungen](https://github.com/Stevertus/mcscript/releases) Minecraft Script ist eine Programmiersprache für Entwickler der mcfunctions, sowie für die Minecraft Map und Package Erschaffer. Die .mcscript Dateien werden dabei zu mcfunction compiled und generiert. Dies bietet dem Entwickler erweiterte Möglichkeiten, wie zum Beispiel Modals, Loops, Variablen, Konstanten und Command-Wrapping. @@ -25,6 +26,7 @@ English documentation [here](https://github.com/Stevertus/mcscript/blob/master/R - [Globale Dateien](#global) 4) [Syntax](#syntax) - [Command Grouping](#groups) + - [Funktionen](#functions) - [Variablen](#vars) - [Boolean Variablen](#boolean) - [Konstanten](#consts) @@ -38,6 +40,7 @@ English documentation [here](https://github.com/Stevertus/mcscript/blob/master/R - [forEach-Loops](#foreach) - [Modals](#modals) - [System Modals](#systemModals) + - [Fehler und Debugging](#debugging) 5) [IDEs und Syntax Highlighting](#ide) ## 1) Installation @@ -78,12 +81,13 @@ Dieser Command wandelt alle .mcscript Dateien in .mcfunction Format um. Was in d In der Konsole werden alle generierten Dateien angezeigt oder ein Fehler ausgeworfen, falls etwas nicht korrekt war. Alternativ kannst mit `mcscript compile *filepath*` einen speziellen Pfad oder spezielle Datei angeben. +Mit der zusätzlichen `-fullErr` flag können ganze Fehler mit code Referenzen angezeigt werden. ### 2.3 mcscript watch Hiermit wird dein Code automatisch compiled, wenn du irgendwelche Änderungen machst (speicherst). So musst du nicht bei jeder Änderung den obigen Command eingeben. -Auch hier kann ein Pfad angegeben werden. +Auch hier kann ein Pfad angegeben und -fullErr verwendet werden. ### 2.4 mcscript add [url or package] Dieser Conmmand kann ein datapack zu deiner Welt hinzufügen. @@ -167,7 +171,7 @@ Zwei Leerzeilen können mit "##" erreicht werden. "as, at, positioned,align,dimension,rotated,anchored" können zusammengefasst werden: - as('@a'){ + as(@a){ /commands => /execute positioned ~ ~ ~ run command } @@ -182,18 +186,44 @@ asat(@s){ "Gruppen können auch aufgelistet werden: - as('@p'), at('@s'), positioned('~ ~1 ~'){ + as(@p), at(@s), positioned('~ ~1 ~'){ /say command } ==> /execute as @p at @s positioned ~ ~-1 ~ run say command // also with if - as('@p'), at('@s'), positioned('~ ~1 ~'), if(entity @s[tag=mytag]){ + as(@p), at(@s), positioned('~ ~1 ~'), if(@s[tag=mytag]){ /say command } ==> /execute as @p at @s positioned ~ ~-1 ~ if entity @s[tag=mytag] run say command + +### 4.2 Funktionen + ``` +[run] function "name|path" { + /commands +} +``` +> run optional +> ein Pfad sollte als String angegeben werden +> ein nur aus Buchstaben bestehender name auch ohne "" + +Eine Funktion generiert eine weitere mcfunction mit den angegebenen Namen oder Pfad. Mit dem run keyword kann eine Funktion auch direkt ausgeführt werden. +Dies ist eine alternative zu einer wesentlich komplizierteren Variante mit `#file:`. +Bsp: +``` +run function test { + /say function +} +/say not function += +/function prj:test +/say not function + +#file: ./test +/say function +``` -### 4.2 Variablen +### 4.3 Variablen Wie jede Programmiersprache hat auch Minecraft Script Variablen. Sie müssen wiefolgt initialisiert werden: `var test` Der Variablen kann ein Wert hinzugewiesen werden: @@ -253,7 +283,7 @@ Beispiel mit /data get: `var varResult = run: data get entity @s Pos[0]` -### 4.3 Boolean Variablen (Tags) +### 4.4 Boolean Variablen (Tags) > `bool [name] [selector](optional) = true|false` So können Wahrheitswerte deklariert werden. @@ -268,7 +298,7 @@ if(isCool){ } ``` -### 4.4 Konstanten +### 4.5 Konstanten Eine andere Art Variable ist die Konstante, so deklariert: > `const [name] = [value]` @@ -293,7 +323,7 @@ Auch kann hier ein [RegEx](https://developer.mozilla.org/de/docs/Web/JavaScript/ `$(const).repl([/regex/],["$&"])` -### 4.5 If/Else Statements +### 4.6 If/Else Statements If funktioniert ähnlich wie das Command Wrapping: @@ -324,7 +354,7 @@ Mit einigen extra Features: Hier darauf achten das Argument nicht zu verändern! ``` - if('entity @s[tag=test]'){ + if(@s[tag=test]){ /tag @s remove test } else { /tag @s remove test @@ -332,10 +362,10 @@ Hier darauf achten das Argument nicht zu verändern! ``` Hier werden beide ausgeführt!! Verbessert: ``` - if('entity @s[tag=test]'){ + if(@s[tag=test]){ /tag @s add testIf } - if('entity @s[tag=testIf]'){ + if(@s[tag=testIf]){ /tag @s remove test } else { /tag @s remove test @@ -352,7 +382,7 @@ Hier werden beide ausgeführt!! Verbessert: ``` -### 4.6 Logische Operatoren +### 4.7 Logische Operatoren In Kombination mit Command Gruppen und If-Else-Statements können zusätzlich logische Operatoren benutzt werden: @@ -410,7 +440,7 @@ if(test @s > test2 @a){ } ``` -### 4.7 Switch-Cases +### 4.8 Switch-Cases ``` switch([var_name]){ case <=|<|==|>|>= [other_var]|[number] { @@ -448,7 +478,7 @@ switch(test){ } ``` -### 4.8 For-Loops +### 4.9 For-Loops ``` for([from],[to],[var_name](optional)){ [actions] @@ -491,7 +521,7 @@ Das ist bei 2 dimensionalen Loops sinnvoll: # es wird 10x say mit 1.1 - 5.2 ausgegeben } -### 4.9 Raycasting +### 4.10 Raycasting ``` raycast([distance](optional), [block to travel through](optional),entity | block [target](optional) ){ [actions on hitted block or entity] @@ -550,7 +580,7 @@ raycast(10,"air",entity @e[type=armor_stand]) { Mcscript weiß nun, dass das Ziel eine Entity ist und führt den Command als diese aus, wenn sie getroffen wurde. Also würde der Armor Stand test sagen. -### 4.10 while-Loops +### 4.11 while-Loops Der while-Loop ist so zu definieren: ``` while([cond]){ @@ -586,7 +616,7 @@ while(test < 10){ } ``` -### 4.11 do-while-Loops +### 4.12 do-while-Loops ``` do { /commands @@ -595,7 +625,7 @@ do { Der do-while-Loop funktioniert ähnlich, wie der while-Loop mit dem kleinen Unterschied, dass der Codeblock ausgeführt wird und danach erst die Bedingung geprüft wird. Der Loop wird also mindestens einmal durchlaufen. -### 4.12 forEach-Loop +### 4.13 forEach-Loop ``` forEach(var [var_name] = [startwert]; [var_name] ==|>|<|<=|>=|!= [other_var]|[number]; [varname]++){ /commands @@ -620,7 +650,7 @@ forEach(var i = 2; i <= 10; i++){ ==> result = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 ``` -### 4.13 Modals +### 4.14 Modals > ``` > modal [name]([arguments]){ > [actions] @@ -696,11 +726,29 @@ Auch kann hier ein [RegEx](https://developer.mozilla.org/de/docs/Web/JavaScript/ `$(argument).repl([/regex/],["$&"])` -### 4.14 System Modals +### 4.15 System Modals Es gibt schon einige vordefinierte Modals, die hilfreich sein könnten. Bitte schaue dir dafür die spezifischen Dokumentationen [hier](https://github.com/Stevertus/mcscript/blob/master/Core%20Modals.md) an. Du hast Ideen, welche Modals unbedingt als Standart-Modal aufgegriffen werden müssen? Sende mir einfach die [Konfigurationsdatei](#24_Dev_mcscript_modals_54) zur Überprüfung. + +### 4.16 Error handling und Debugging +Minecraft Script zeigt mit der Version 0.2 nur noch begrenzt Fehler an mit der Zeilen- und Dateiangabe. +Benutze beim generieren bitte die Flag `-fullErr`, um vollständige alte Fehler wiederzuerlangen, falls du sie wünscht. + +Falls du Fehlerangaben findest, die im Kontext keinen Sinn machen, wende dich bitte an das Team. + +**Debug keyword** +Mit "Debug" kannst du deinen Code debuggen und auch mögliche Fehler in Minecraft Script viel leichter entdecken. Sie können irgendwo im Code platziert werden und beeinflussen den generierten Code nicht.* +* `debug message: [message]` +Sendet eine einfache Nachricht in die Konsole mit Zeilen- und Dateiangaben. +* `debug success: [message]` +Sendet eine erfolgreiche Nachricht mit grüner Kennzeichnung in die Konsole mit Zeilen- und Dateiangaben. +* `debug break: [message]` +Dein Programm bricht an dieser Stelle ab und sendet erneut eine Nachricht. +* `debug error: [message]` +Dein Programm bricht an dieser Stelle ab und gibt einen kritischen Fehler mit Systeminformationen und relevanten Codestellen aus. + ## IDEs und Syntax Highlighting @@ -712,3 +760,4 @@ Jetzt bleibt nichts mehr übrig als: **Happy Developing** -------------------------------------------------------- Vielen Dank an alle die Minecraft Script benutzen und diese Dokumentation gelesen haben. Bei Vorschlägen, Problemen oder Fehlern bitte mich kontaktieren. +Ich freue mich euch ebenfalls euch auf meinem Discord begrüßen zu dürfen und Vorschläge und Kritik zu hören. diff --git a/README.md b/README.md index 2681446..21e72f8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ + ![](https://i.imgur.com/YedWe7W.png) Minecraft Script Documentation ============================== -> Update 0.1.5: [All Changes](https://github.com/Stevertus/mcscript/releases) +> Update 0.2: [All Changes](https://github.com/Stevertus/mcscript/releases) Minecraft Script is a programming language for developers of mcfunctions, Minecraft maps and packages. The .mcscript files are therefore compiled and generated to the function format. This enables the developer extended possibilities, such as Modals, Loops, Varibles, Constants and Command-Wrapping. @@ -26,6 +27,7 @@ German documentation [here](https://github.com/Stevertus/mcscript/blob/master/RE - [Globale Dateien](#global) 4) [Syntax](#syntax) - [Command Grouping](#groups) + - [Functions](#functions) - [Variables](#vars) - [Boolean Variablen](#boolean) - [Constants](#consts) @@ -39,6 +41,7 @@ German documentation [here](https://github.com/Stevertus/mcscript/blob/master/RE - [forEach loops](#foreach) - [Modals](#modals) - [System Modals](#systemModals) + - [Error handling and debugging](#debugging) 5) [IDEs and Syntax Highlighting](#ide) ## Installation @@ -80,12 +83,14 @@ This command converts all .mcscript files into .mcfunction format. You can read The console displays all generated files or throws an error if something was not correct. Alternatively you can use `mcscript compile *filepath*` to set an custom directory or file. +With an additional `-fullErr` flag you can view full errors and code positions. + ### 2.3 mcscript watch This will automatically compile your code if you make any changes (save). So you do not have to enter the above command with every change. -Again, a path can be specified. +Again, a path and `-fullErr` can be specified. ### 2.4 mcscript add [url or package] This command adds a custom datapack to your directory. @@ -156,7 +161,7 @@ Blank lines and skipping lines are ignored. If a blank line is desired in the mcfunction, express this with a '#' without a comment. Two blank lines are reached with "##". -### 3.3 Command Grouping / Wrapping +### 4.1 Command Grouping / Wrapping > ```[subcommand]([argument]){ [wrapped actions] }``` "as, at, positioned,align,dimension,rotated,anchored" can be grouped together: @@ -183,12 +188,39 @@ asat(@s){ ==> /execute as @p at @s positioned ~ ~-1 ~ run say command // also with if - as('@p'), at('@s'), positioned('~ ~1 ~'), if(entity @s[tag=mytag]){ + as(@p), at(@s), positioned('~ ~1 ~'), if(entity @s[tag=mytag]){ /say command } ==> /execute as @p at @s positioned ~ ~-1 ~ if entity @s[tag=mytag] run say command + +### 4.2 Functions + ``` +[run] function "name|path" { + /commands +} +``` +> run optional +> a path should be given as string +> a name consisting of only characters, can be given without "" + +A function generates a new mcfunction with the given name or path. You can also execute the function directly with the `run` keyword. +This is an alternative to a more complicated varient with `#file:`. + +e.g: +``` +run function test { + /say function +} +/say not function += +/function prj:test +/say not function + +#file: ./test +/say function +``` -### 3.4 Variablen +### 4.3 Variables Like every other programming language there are variables. They are initialized as follows: `var test` The variable can take in a value: @@ -247,7 +279,7 @@ The result of the command is written to the variable res. Example with `/data get`: ' var varResult = run: data get entity @s Pos[0] ' -### 3.5 Boolean Variables (Tags) +### 4.4 Boolean Variables (Tags) > `bool [name] [selector](optional) = true|false` Boolean values can be declared like this. @@ -262,7 +294,7 @@ if(isCool){ } ``` -### 3.6 Constants +### 4.5 Constants Another type of variable is the constant, declared as following: `const test = [value]` @@ -287,7 +319,7 @@ In our example, we want to replace `a`: Also a [RegEx](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/RegExp) can be inserted here and can be also accessed with '$&' in the replacement: `$(aString).repl([/regex/],["$&"])` -### 3.6 If/Else Statements +### 4.6 If/Else Statements If functions are similar to grouping: @@ -346,7 +378,7 @@ With some additional features: } ``` -### 3.8 Logical operators +### 4.7 Logical operators In combination with grouping and if-else statements logical operators can be used: @@ -403,7 +435,7 @@ if(test @s > test2 @a){ } ``` -### 3.9 Switch-Cases +### 4.8 Switch-Cases ``` switch([var_name]){ case <=|<|==|>|>= [other_var]|[number] { @@ -441,7 +473,7 @@ switch(test){ } ``` -### 3.10 For-Loops +### 4.9 For-Loops One of the most helpful features is the for loop. It takes in neutral numbers. @@ -478,7 +510,7 @@ That makes especially with two-dimensional loops sence: # say with 1.1 - 5,2 is outputed 10 times } -### 3.11 Raycasting +### 4.10 Raycasting ``` raycast([distance](optional), [block to travel through](optional),entity | block [target](optional) ){ [actions on hitted block or entity] @@ -535,7 +567,7 @@ raycast(10,"air",entity @e[type=armor_stand]) { Now Mcscript knows that the target is an entity and executes as the entity if it´s hitted. So the armor stand would say test. -### 3.12 while loops +### 4.11 while loops The while loop is defined like so: ``` while([cond]){ @@ -571,7 +603,7 @@ while(test < 10){ } ``` -### 3.13 do-while-Loops +### 4.12 do-while-Loops ``` do { /commands @@ -580,7 +612,7 @@ do { The do-while loop works in a similar way to the while loop, with the small difference that the code block is executed and then the condition is checked. So the loop is executed at least one time. -### 3.14 forEach-Loop +### 4.13 forEach-Loop ``` forEach(var [var_name] = [start value]; [var_name] ==|>|<|<=|>=|!= [other_var]|[number]; [varname]++){ /commands @@ -606,7 +638,7 @@ forEach(var i = 2; i <= 10; i++){ ==> result = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 ``` -### 3.15 Modals +### 4.14 Modals Modals are like functions or methods. That means you can define them: @@ -673,11 +705,30 @@ In our example, we want to replace an entered test: Also a [RegEx](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/RegExp) can be inserted here and can be also accessed with '$&' in the replacement: `$(argument).repl([/regex/],["$&"])` -### 3.11 System Modals +### 4.15 System Modals There are already some helpful predefined modals. Please read the specific documentation [here](https://github.com/Stevertus/mcscript/blob/master/Core%20Modals.md). You have ideas which modals should be a standart? Send me your [configuration file](#ownmodal) to check. + +### 4.16 Error handling and Debugging +Minecraft Script shows since the version 0.2 only limeted errors with line and file displayed. +Please use the flag `-fullErr` at generation to get the old full errors back, if you want so. + +If you find errors that make no sense in the context, please notify the team. + +**Debug keyword** +You can debug your code with the keyword "Debug" and find some errors in Minecraft Script much easier. You can place these anywhere in your code and they have no affect on the compiled output. + +* `debug message: [message]` +Sends a simple message with line and file references. +* `debug success: [message]` +* Sends a successful message in green with line and file references. +* `debug break: [message]` +Your program breakes at this point and sends the message obove . +* `debug error: [message]` +Your program breakes at this point and sends a critical error with system information and relevant code positions. + ## IDEs and Syntax Highlighting diff --git a/_changelog.md b/_changelog.md index 7a91fee..5dd13d8 100644 --- a/_changelog.md +++ b/_changelog.md @@ -2,6 +2,18 @@ Minecraft Script Changes ============================== > Update your Version by running: `npm install -g mcscript@` + +### v0.2 +This is the first full release version of Minecraft Script! The language has all the necessary features now. I am open for new suggestions nevertheless. +* added: a brand new error handling system. If you find some nonsense errors please report an issue and I will fix it. +* added: the function and run function feature. Read more about its capabilities here. +* added: a few more debugging options with the debug keyword. Read more here. +* added: a -fullErr flag for compile and watch to see full Errors and files references +* changed: if an execute command is generated after an execute command they will be combined now. +* changed: if an error accures in a file you will be notified and the other files generate nevertheless. +* fixed: an load.mcfunction issue that it generates random stuff sometimes +* fixed: the watch mode crashes no longer if an error accures +* fixed: if a #file tag was added after commands the compiler used to create a file with the name of the first command ### v0.1.5 * added: multiline comments * added: global variables and `.gl.mcscript` files | Take a look at the documentation. diff --git a/bin/test-module.js b/bin/test-module.js index 16bf52b..a22ebdd 100644 --- a/bin/test-module.js +++ b/bin/test-module.js @@ -5,12 +5,18 @@ const addPack = require('./add.js'); const gen_new = require('../lib/gen_new.js'); const consoletheme = require('../lib/consoletheme.js'); +var fullError = false; +if(process.argv.indexOf("-fullErr") != -1){ //does our flag exist? + fullError = true + process.argv.splice(process.argv.indexOf("-fullErr"),1) +} + switch(process.argv[2]){ case 'compile': - lib.compile(process.argv[3] || './'); + lib.compile(process.argv[3] || './',fullError); break; case 'watch': - lib.watch(process.argv[3] || './'); + lib.watch(process.argv[3] || './',fullError); break; case 'modal': lib.genModals(process.argv[3] || './'); diff --git a/lib/forWeb.js b/lib/forWeb.js index 19af18c..0efed41 100644 --- a/lib/forWeb.js +++ b/lib/forWeb.js @@ -1,6 +1,7 @@ -InputStream = function(input,file = '') { +function InputStream(input, file = '') { if(file) file = ' in file ' + file var pos = 0, line = 1, col = 0; + return { next : next, peek : peek, @@ -8,99 +9,124 @@ InputStream = function(input,file = '') { getCol : getCol, eof : eof, croak : croak, + debugError : debugError }; + function next() { + var ch = input.charAt(pos++); + if (ch == "\n"){ + if(",;(){[".indexOf(input.charAt(pos--)) == -1){ + input[pos] = ";" + } + pos++ line++ col = 0 + } + else col++; return ch; + } + function peek() { + return input.charAt(pos); + } + function peekNext(num) { + return input.charAt(pos + num); + } + function getCol() { + let num = col let pos2 = pos - 1 + while(" \r\n\t".indexOf(input.charAt(pos2)) >= 0 && num >= 1){ + pos2-- num-- + } + return num + } + function eof() { + return peek() == ""; + } + function croak(msg) { - throw new Error(msg + " (" + line + ":" + col + ")" + file); + throw new Error(msg + " (" + (line - 1) + ":" + col + ")" + file); + } + function debugError(msg,type) { + let filer = file.split("/") + console.log(filer,file) + filer = filer[filer.length - 1] + if(type == "err") throw new Error("[Debug](" + (line - 1) + ":" + col + "|" + filer + ") " + msg); + else if(type == "break") throw new Error("[Brake](brake at " + (line - 1) + ":" + col + "|" + filer + ") " + msg) } } -var TokenStream = function(input) { +function TokenStream(input) { var current = null; - // Keywords var keywords = " if then else true false for as at asat positioned modal align dimension rotated" + " anchored while do forEach raycast stop continue switch case default var bool" - + " boolean tag score const override "; + + " boolean tag score const override function run "; var operators = "+-*/%=&|<>!"; - var puncs = ",;(){}[]"; + var puncs = ",;:(){}[]"; var whitespaces = " \r\n\t"; - - // Export Functions return { next : next, peek : peek, eof : eof, - croak : input.croak + croak : input.croak, + debugError : input.debugError }; - // is_keyword method: test if character is keyword function is_keyword(x) { return keywords.indexOf(" " + x + " ") >= 0; } - // is_digit method: test if character is a digit function is_digit(ch) { return /[0-9]/i.test(ch); } - // is_id_start method: test if character is id start function is_id_start(ch) { return /[a-zλ_.]/i.test(ch); } - // is_id method: test if character is id function is_id(ch) { return is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0; } - // is_op_char method: test if character is operator function is_op_char(ch) { return operators.indexOf(ch) >= 0; } - // is_punc method: test if character is punc function is_punc(ch) { return puncs.indexOf(ch) >= 0; } - // is_whitespace method: test if caracter is a witespace function is_whitespace(ch) { return whitespaces.indexOf(ch) >= 0; } - // read_while method: read while given function returns true to peek function read_while(predicate) { var str = ""; @@ -113,7 +139,6 @@ var TokenStream = function(input) { } - //read_number method: read to end of number function read_number() { var has_dot = false; @@ -128,15 +153,14 @@ var TokenStream = function(input) { } - return is_digit(ch); // check next char is digit, if not cancel + return is_digit(ch); }); - return { type: "num", value: parseFloat(number) }; // Return number object + return { type: "num", value: parseFloat(number) }; } - //read_ident method: read full intendent function read_ident() { var id = read_while(is_id); @@ -148,7 +172,6 @@ var TokenStream = function(input) { } - // read_escaped method: read escaped content function read_escaped(end) { var escaped = false, str = ""; @@ -176,18 +199,14 @@ var TokenStream = function(input) { } - // read_string method: Read Strings starting with " function read_string() { return { type: "str", value: read_escaped('"') }; } - - // read_string2 method: Read Strings starting with ' function read_string2() { return { type: "str", value: read_escaped("'") }; } - // skip_comment method: skip a comment function skip_comment() { read_while(function(ch){ return ch != "\n" }); input.next(); @@ -200,7 +219,6 @@ var TokenStream = function(input) { input.next() } - // readSelector method: read a selector function readSelector(){ let value = input.next() + input.next() @@ -211,53 +229,49 @@ var TokenStream = function(input) { return { type: "selector", value: value } } - - // read_comment method: Transfer comments starting with # function read_comment() { return { type: "comment", value: read_while(function(ch){ return ch != "\n" && ch != ";" }) } } + function read_debug() { + console.log("he") + return { type: "debugger", value: read_while(function(ch){ return ch != "\n" && ch != ";" }) } + } - // read_command method: Transfer commands starting with / function read_command() { let command = read_while(function(ch){ return ch != "\n" && ch != ";" }) if(command[0] == "/") command = command.substr(1) return { type: "command", value: command.split('run: ').join("")} } - // read_next method: read next token function read_next() { - // skip whitespaces read_while(is_whitespace); if (input.eof()) return null; - // read next token var ch = input.peek(); - // skip comment if(ch == "/" && input.peekNext(1) == "/"){ skip_comment() return read_next(); } - // skip line comment if(ch == "/" && input.peekNext(1) == "*"){ skip_line_comment() read_next() return read_next() } - // Transfer command if(ch == "r" && input.peekNext(1) == "u" && input.peekNext(2) == "n" && input.peekNext(3) == ":") { return read_command(); } + if(ch == "d" && input.peekNext(1) == "e" && input.peekNext(2) == "b" && input.peekNext(3) == "u" && input.peekNext(4) == "g") { + return read_debug(); + } - // Transfer command if(ch == "/" && input.getCol() === 0){ return read_command(); } - // Replace variable if(ch == "$"){ if(input.peekNext(1) == "("){ let res = {type: "num", value: read_while(function(ch){ return ch != ")" }) + ")"} @@ -266,48 +280,37 @@ var TokenStream = function(input) { } } - // Transfer mcfunction comment if (ch == "#") { return read_comment() } - //read selector if (ch == "@") return readSelector(); - // read string if (ch == '"') return read_string(); - // read string2 if (ch == "'") return read_string2(); - // read digit if (is_digit(ch)) return read_number(); - // read id if (is_id_start(ch)) return read_ident(); - // read punc if (is_punc(ch)) return { type : "punc", value : input.next() }; - // read operator if (is_op_char(ch)) return { type : "op", value : read_while(is_op_char) }; - // error, if can't handle input.croak("Can't handle character: " + JSON.stringify(ch)); } - // peek method: get next character function peek() { return current || (current = read_next()); } - // next method: go to next character function next() { var tok = current; current = null; @@ -318,6 +321,7 @@ var TokenStream = function(input) { } } parse = function(input) { + var PRECEDENCE = { "=": 1, "||": 2, @@ -326,161 +330,210 @@ parse = function(input) { "+": 10, "-": 10, "+=": 10, "-=": 10, "*": 20, "/": 20, "%": 20, "*=": 20, "/=": 20, "%=": 20, }; + return parse_toplevel(); + function is_punc(ch) { var tok = input.peek(); return tok && tok.type == "punc" && (!ch || tok.value == ch) && tok; } + function is_kw(kw) { var tok = input.peek(); return tok && tok.type == "kw" && (!kw || tok.value == kw) && tok; } + function is_op(op) { var tok = input.peek(); return tok && tok.type == "op" && (!op || tok.value == op) && tok; } + function skip_punc(ch) { if (is_punc(ch)) input.next(); else input.croak("Expecting punctuation: \"" + ch + "\""); } + function skip_kw(kw) { if (is_kw(kw)) input.next(); else input.croak("Expecting keyword: \"" + kw + "\""); } + function skip_op(op) { if (is_op(op)) input.next(); else input.croak("Expecting operator: \"" + op + "\""); } + function unexpected() { input.croak("Unexpected token: " + JSON.stringify(input.peek())); } + function maybe_binary(left, my_prec) { + var tok = is_op(); + if(left && left.type == "var" && input.peek().value == "++"){ input.next(); return {type: "binary", operator: "+=", left: left, right: {type:'num', value: 1}}; } + if(left && left.type == "var" && input.peek().value == "--"){ input.next(); return {type: "binary", operator: "-=", left: left, right: {type:'num', value: 1}}; } + if(left && left.type == "var" && left.value.slice(-2) === "--"){ left.value = left.value.substr(0, left.value.length - 2); return {type: "binary", operator: "-=", left: left, right: {type:'num', value: 1}}; } + if (tok) { + var his_prec = PRECEDENCE[tok.value]; if (his_prec > my_prec) { + input.next(); + return maybe_binary({ type : tok.value == "=" ? "assign" : "binary", operator : tok.value, left : left, right : maybe_binary(parse_atom(), his_prec) }, my_prec); + } } return left; } + function delimited(start, stop, separator, parser) { + var a = [], first = true; + skip_punc(start); + while (!input.eof()) { + if (is_punc(stop)) break; if (first) first = false; else skip_punc(separator); if (is_punc(stop)) break; a.push(parser()); + } + skip_punc(stop); return a; + } + function parse_call(func) { + ret = { type: "call", func: func, args: delimited("(", ")", ",", parse_expression), }; + if (is_punc("{")) ret.after = parse_expression(); + return ret; } + function parse_varname() { var name = input.next(); if (name.type != "var") input.croak("Expecting variable name"); return name.value; } + function parse_if() { + skip_kw("if"); - input.next(); var cond = parse_key_args(); input.next(); - if (!is_punc("{")) skip_kw("then"); + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") + var then = parse_expression(); var ret = { type: "if", cond: cond, then: then, }; + if (is_kw("else")) { input.next(); ret.else = parse_expression(); } return ret; } + function parse_while() { skip_kw("while"); - input.next(); var cond = parse_key_args(); input.next(); let custom; + if(input.peek().type == "str"){ custom = input.next(); } - if (!is_punc("{")) skip_kw("then"); + + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); + var ret = { type: "while", cond: cond, then: then, }; + if(custom) ret.customName = custom return ret; } + function parse_dowhile() { skip_kw("do"); let custom; + if(input.peek().type == "str"){ custom = input.next(); } - if (!is_punc("{")) skip_kw("then"); + + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); skip_kw("while"); - input.next(); var cond = parse_key_args(); input.next(); + var ret = { type: "dowhile", cond: cond, then: then, }; + if(custom) ret.customName = custom return ret; } + function parse_forEach() { + skip_kw("forEach"); input.next(); + let variable = parse_var(input.peek().value); + if(!variable.assign) variable.assign = { type: 'assign',operator: '=', left: { type: 'var', value: variable.name }, right: { type: 'num', value: 0 } }; - input.next(); - let cond = parse_key_args(); + + let cond = parse_key_args(true); input.next(); let binary = maybe_binary(input.next(),0); input.next(); let custom; + if(input.peek().type == "str"){ custom = input.next(); } - if (!is_punc("{")) skip_kw("then"); + + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); + var ret = { type: "forEach", cond: cond, @@ -488,93 +541,125 @@ parse = function(input) { binary: binary, then: then, }; + if(custom) ret.customName = custom; return ret; } + function parse_raycast(){ + skip_kw("raycast"); + let res = {type: "raycast", duration: 100, block: "air"}; + if(input.peek().value == "("){ + input.next(); + if(input.peek().type == "num"){ res.duration = input.next().value; input.next(); } + if(input.peek().value == "!"){ res.notblock = true; input.next(); } + if(input.peek().type == "str"){ res.block = input.next().value; input.next(); } + if(input.peek().value == "block"){ input.next(); + if(input.peek().type == "str"){ res.toBlock = input.next().value; } } + if(input.peek().value == "entity"){ input.next(); + if(input.peek().type == "selector" || input.peek().type == "var"){ res.entity = input.next().value; } } if(input.peek().value == ")") input.next(); } + res.end = parse_prog(); if(input.peek().value == ","){ + input.next(); res.while = parse_prog(); + } return res; } - function parse_key_args(){ + + function parse_key_args(punc_not_needed){ let cond = []; let before = {type: 'op', value: "&&"}; + if(!punc_not_needed){ + if(input.peek().value != "(" && input.peek().value != ",") input.croak("Please add () to specify arguments") + else input.next(); + } while(input.peek().value != ")" && input.peek().value != ";"){ let next = input.next(); + if(next.value == "!"){ before = next; continue; } + if(next.type == "selector"){ if(before.type == "binary") before.right.selector = next; else if(before.type == "var") before.selector = next; else cond.push(next); continue; } + if("> < >= <= == != ".indexOf(next.value + " ") > -1){ cond.splice(cond.indexOf(before),1); next = {type: 'binary', left: before, operator: next.value, right: input.next()}; if(before.op){ - next.op = before.op; - delete next.left.op; + next.op = before.op; + delete next.left.op; } cond.push(next); } + if(before.value == "&&"){ cond.push(next); } + if(before.value == "||" || before.value == ","){ next.op = "or"; cond.push(next); } + if(before.value == "!"){ next.not = true; cond.push(next); } + before = next; } - return cond; + if(cond.length > 0) return cond; + input.croak("Please specify at least one argument here") } + function parse_keyword(kw) { + skip_kw(kw); - input.next(); var cond = parse_key_args(); input.next(); + let next = input.peek() if(next.value == "{"){} + else if(next.value == ",") { input.next(); let nest = parse_keyword(input.peek().value); @@ -588,77 +673,110 @@ parse = function(input) { if(nest.nest) res.nest = res.nest.concat(nest.nest); return res; } - else skip_kw("then"); + else input.croak("Please finish your arguments or add a {.") + var then = parse_expression(); + var ret = { type: kw, cond: cond, then: then, }; + return ret; } + function parse_for() { + skip_kw("for"); + let args = delimited("(", ")", ",", parse_expression); let then = parse_expression(); + var ret = { type: "for", args: args, after: then }; + return ret; } + function parse_var(kw){ + skip_kw(kw); let name = input.next().value let res = {type:"initvar",name:name} + if(input.peek().type == "selector") res.selector = input.next(); if(input.peek().type == "var") res.selector = {type: "selector", value: input.next().value}; + let assign = maybe_binary({type:"var",value:name, selector: res.selector},0); + if(assign.type == "assign") res.assign = assign; return res; } + function parse_bol(kw){ skip_kw(kw); + let name = input.next().value; let res = {type:"initbool",name:name}; + if(input.peek().type == "selector") res.selector = input.next(); if(input.peek().type == "var") res.selector = {type: "selector", value: input.next().value}; + let assign = maybe_binary({type:"var",value:name, selector: res.selector},0); + if(assign.type == "assign" && assign.right.type == "bool") res.assign = assign; else input.croak("Your assignment is not right! Please assign a boolean value."); + return res; } + function parse_const(){ skip_kw('const'); + let name = input.next().value; + input.next(); + let assign = input.next(); + if(assign.type != "num" && assign.type != "str") input.croak("You have to assign a string or number to a constant!"); + return { type: "const", value: name, assign: assign.value }; } + function parse_modal(override = false) { skip_kw("modal"); let res = {type: "modal", call: parse_call(input.next().value)}; if(override) res.override = true return res } + function parse_override() { skip_kw("override"); if(is_kw('modal')) return parse_modal(true) } + function parse_switch(){ + skip_kw("switch"); + let res = {type: "switch", var: {}, cases:[]}; let variable = delimited("(",")",",",x => input.next()); + res.var = variable[0]; if(variable[1] && variable[1].type == "selector") res.var.selector = variable[1]; + res.cases = delimited("{","}",",",() => { let def = false; + if(input.peek().value == "default") { def = true; skip_kw("default"); @@ -666,38 +784,84 @@ parse = function(input) { skip_kw("case"); } let cond = []; + if(!def && input.peek().type == "op") cond.push(maybe_binary(res.var,0)); else if(!def && input.peek().type == "num") cond.push({type: "binary", left: res.var, right: input.next(),operator: "=="}); + let then; + if(input.peek().value == "{") then = parse_prog(); else then = parse_expression(); + if(input.peek().value == ";") skip_punc(";"); + return {type: def ? "default" : "case", cond: cond, then: then}; }) return res; } - function parse_lambda() { - return { - type: "lambda", - vars: delimited("(", ")", ",", parse_varname), - body: parse_expression() - }; + + function parse_function(from_run = false){ + skip_kw("function") + if(input.peek().type != "str" && input.peek().type != "var") input.croak("Please specify the name and path of the function using a string or one-word name") + let res = {type: "func"} + res.name = input.next().value + if(input.peek().value == "{" || !from_run){ + res.prog = parse_prog() + } + return res + } + function parse_run_function(){ + skip_kw("run") + if(input.peek().value == "function") return {type:"run_func",func: parse_function(true)} + else input.croak("Please specify a function") + } + + function parse_debugger(tok) { + tok.value = tok.value.substr(6).split(":") + if(tok.value.length < 2) input.croak("You need to end your debug mode with a : !") + let mode = tok.value[0] + tok.value = tok.value.slice(1).join(":").trim() + switch(mode) { + case "message": { + console.log(consoletheme.FgYellow,"[Debugger]" + consoletheme.FgCyan + " (message)",consoletheme.FgWhite,tok.value,consoletheme.Reset); + break + } + case "break": { + input.debugError(tok.value,"break") + break + } + case "error": { + input.debugError(tok.value,"err") + break + } + case "success": { + console.log(consoletheme.FgGreen,"[Debugger]" + consoletheme.FgCyan + " (success)",consoletheme.FgWhite,tok.value,consoletheme.Reset); + break + } + } + if(input.peek().value == ";") input.next() + return {type:"command",value:""} } + function parse_bool() { return { type : "bool", value : input.next().value == "true" }; } + function returnKey(keyword){ skip_kw(keyword); return {type: keyword}; } + function maybe_call(expr) { expr = expr(); return is_punc("(") ? parse_call(expr) : expr; } + function parse_atom() { + return maybe_call(function(){ if (is_punc("(")) { input.next(); @@ -705,6 +869,7 @@ parse = function(input) { skip_punc(")"); return exp; } + if (is_punc("{")) return parse_prog(); if (is_kw("if")) return parse_if(); if(is_kw("const")) return parse_const(); @@ -717,28 +882,42 @@ parse = function(input) { if (is_kw("stop")) return returnKey('stop'); if (is_kw("continue")) return returnKey('continue'); if (is_kw("switch")) return parse_switch(); + if (is_kw("while")) return parse_while(); if (is_kw("do")) return parse_dowhile(); if (is_kw("modal")) return parse_modal(); if (is_kw("override")) return parse_override(); + if (is_kw("function")) return parse_function(); + if (is_kw("run")) return parse_run_function(); if (is_kw("true") || is_kw("false")) return parse_bool(); + var tok = input.next(); + if(tok.type == "comment" || tok.type == "command"){ return tok; } + if(tok.type == "debugger"){ + return parse_debugger(tok) + } if(tok.type == "var"){ + if(input.peek().type == "selector"){ tok.selector = input.next(); } + if(input.peek().type == "var"){ tok.selector = {type: "selector", value: input.next().value}; } + return tok; + } if (tok.type == "num" || tok.type == "str") return tok; + unexpected(); }); } + function parse_toplevel() { var prog = []; while (!input.eof()) { @@ -747,12 +926,14 @@ parse = function(input) { } return { type: "prog", prog: prog }; } + function parse_prog() { var prog = delimited("{", "}", ";", parse_expression); if (prog.length == 0) return FALSE; if (prog.length == 1) return prog[0]; return { type: "prog", prog: prog }; } + function parse_expression() { return maybe_call(function(){ return maybe_binary(parse_atom(), 0); @@ -770,18 +951,19 @@ generate = function(exp,oldFile) { let prjPath = "" let prjName = "prjname" let res = js(exp); + res = res.split("run execute ").join("") for(let pro in processed){ res += processed[pro]; } for(let cnst of consts){ let con = res.split('$(' + cnst.name + ')') con.forEach(x => { - // if repl method is called + if(x.substr(0, 5) == '.repl'){ let strArr = [] if(x.substr(6,1) == "'") strArr = x.split("'") else strArr = x.split('"') - // regExp or not? + if(strArr[1][0] == '/'){ strArr[1] = strArr[1].substr(1,strArr[1].length - 2) let regs = new RegExp(strArr[1]) @@ -820,6 +1002,8 @@ generate = function(exp,oldFile) { case "rotated" : return make_indent(exp,"rotated"); case "anchored" : return make_indent(exp,"anchored"); case "positioned" : return make_indent(exp,"positioned"); + case "func" : return gen_func (exp); + case "run_func" : return gen_run_func (exp); case "for" : return gen_for (exp); case "forEach" : return gen_forEach(exp); case "raycast" : return gen_raycast(exp); @@ -828,7 +1012,7 @@ generate = function(exp,oldFile) { case "prog" : return js_prog (exp); case "call" : return js_call (exp); default: - throw new Error("Dunno how to generate mcfunction for " +exp.type+" in " + oldFile); + throw new Error("There is no way " +exp.type+" could generate in " + oldFile); } } function comment(exp){ @@ -841,7 +1025,7 @@ generate = function(exp,oldFile) { return exp.value; } function js_atom(exp) { - return JSON.stringify(exp.value); // cheating ;-) + return JSON.stringify(exp.value); } function make_var(name) { return name; @@ -855,10 +1039,11 @@ generate = function(exp,oldFile) { if(exp.operator == "=") return js_assign(exp); else if(exp.operator == "+=" && vars.indexOf(exp.left.value) >= 0 && exp.right.type == "num") return 'scoreboard players add ' + selector.value + ' ' + exp.left.value + ' ' + exp.right.value; else if(exp.operator == "-=" && vars.indexOf(exp.left.value) >= 0 && exp.right.type == "num") return 'scoreboard players remove ' + selector.value + ' ' + exp.left.value + ' ' + exp.right.value; + else if(exp.operator && vars.indexOf(exp.left.value) >= 0 && exp.right.type == "var" && vars.indexOf(exp.right.value) >= 0) return 'scoreboard players operation ' + selector.value + ' ' + exp.left.value + ' '+exp.operator+' ' + selector2.value + ' ' + exp.right.value else throw exp.left.value + " is not declared or you messed something up!\n in: " + oldFile } - // assign nodes are compiled the same as binary + function js_assign(exp) { if(tags.indexOf(exp.name) < 0 && exp.right.type == "bool") return js_assignBool(exp); let selector = exp.left.selector ? exp.left.selector : exp.left; @@ -954,6 +1139,22 @@ generate = function(exp,oldFile) { } return res + "\n" + _else } + function gen_func(exp){ + let name = exp.name.replace("./","") + if(exp.prog){ + processed[name] = "#file: ./" + name + "\n# this file is generated based on a function specified in" + oldFile + "\n" + processed[name] += js(exp.prog) + } + return "" + } + function gen_run_func(exp){ + if(exp.func.prog){ + gen_func(exp.func) + return "function " + prjName + ":" + exp.func.name.replace("./","") + } + if(exp.func.name) return "function " + prjName + ":" + exp.func.name.replace("./","") + throw "don´t know how to generate run function in " + filename + } function testforBinary(cond){ let selector = cond.left.selector ? cond.left.selector : cond.left let selector2 = cond.right.selector ? cond.right.selector : cond.right @@ -1078,38 +1279,40 @@ generate = function(exp,oldFile) { } function gen_raycast(exp){ let id = genID + 1 - let init = { type: 'initvar', - name: 'mcscript_raycast', - assign: - { type: 'assign', - operator: '=', - left: { type: 'var', value:'mcscript_raycast', selector: 'raycast' + id }, - right: { type: 'num', value: 0 } + let init = + { + type: 'initvar', + name: 'mcscript_raycast', + assign: + { type: 'assign', + operator: '=', + left: { type: 'var', value:'mcscript_raycast', selector: 'raycast' + id }, + right: { type: 'num', value: 0 } + } } -} -let res = js_init(init) -let whileObj = {cond: {}, then: {type: "prog", prog: []}, after: {type: "prog", prog: []}} -if(exp.while){ - if(exp.while.type == "prog") whileObj.then = exp.while - else whileObj.then.prog.push(exp.while) -} -whileObj.then.prog.push({type: "command", value: "scoreboard players add raycast"+ id + " mcscript_raycast 1"}) -let end = {"type":"if","cond":[{"type":"str","value":"score raycast"+ id + " mcscript_raycast matches .." + exp.duration + " if entity @s[tag=mcscriptStop] positioned ^ ^ ^1"}],"then":exp.end} -whileObj.after.prog.push(end) -let operator = "unless" -if(exp.notblock) operator = "if" -if(exp.toBlock) operator = "if block ~ ~ ~ " + exp.toBlock + " " + operator -if(exp.entity) { - let entity = exp.entity - if(entity.slice(-1) == "]") entity = entity.substr(0,entity.length - 1) + ",distance=..0.7,sort=nearest]" - else entity += "[distance=..0.7,sort=nearest]" - whileObj.after.prog[0].cond[0].value += " as " + entity.replace("0.7","2") + " at @s" - whileObj.then.prog.push({type: "command", value: "execute positioned ^ ^ ^1 if entity "+entity + " run tag @s add mcscriptStop"}) - whileObj.then.prog.push({type: "command", value: "execute positioned ^ ^ ^1 positioned ~ ~-1 ~ if entity "+entity + " run tag @s add mcscriptStop"}) -} else whileObj.then.prog.push({type: "command", value: "execute positioned ^ ^ ^1 "+operator+" block ~ ~ ~ "+exp.block+" run tag @s add mcscriptStop"}) -whileObj.cond = [{"type":"str","value":"block ~ ~ ~ " + exp.block,not: exp.notblock}, {"type":"str","value":"score raycast"+ id + " mcscript_raycast matches .." + exp.duration}] -gen_while(whileObj,true, "raycast$(id)", " positioned ^ ^ ^1") -return 'scoreboard players set raycast'+id+' mcscript_raycast 0\nexecute positioned ~ ~1 ~ run function '+prjName+':mcscript/raycast' + id + '\n' + let res = js_init(init) + let whileObj = {cond: {}, then: {type: "prog", prog: []}, after: {type: "prog", prog: []}} + if(exp.while){ + if(exp.while.type == "prog") whileObj.then = exp.while + else whileObj.then.prog.push(exp.while) + } + whileObj.then.prog.push({type: "command", value: "scoreboard players add raycast"+ id + " mcscript_raycast 1"}) + let end = {"type":"if","cond":[{"type":"str","value":"score raycast"+ id + " mcscript_raycast matches .." + exp.duration + " if entity @s[tag=mcscriptStop] positioned ^ ^ ^1"}],"then":exp.end} + whileObj.after.prog.push(end) + let operator = "unless" + if(exp.notblock) operator = "if" + if(exp.toBlock) operator = "if block ~ ~ ~ " + exp.toBlock + " " + operator + if(exp.entity) { + let entity = exp.entity + if(entity.slice(-1) == "]") entity = entity.substr(0,entity.length - 1) + ",distance=..0.7,sort=nearest]" + else entity += "[distance=..0.7,sort=nearest]" + whileObj.after.prog[0].cond[0].value += " as " + entity.replace("0.7","2") + " at @s" + whileObj.then.prog.push({type: "command", value: "execute positioned ^ ^ ^1 if entity "+entity + " run tag @s add mcscriptStop"}) + whileObj.then.prog.push({type: "command", value: "execute positioned ^ ^ ^1 positioned ~ ~-1 ~ if entity "+entity + " run tag @s add mcscriptStop"}) + } else whileObj.then.prog.push({type: "command", value: "execute positioned ^ ^ ^1 "+operator+" block ~ ~ ~ "+exp.block+" run tag @s add mcscriptStop"}) + whileObj.cond = [{"type":"str","value":"block ~ ~ ~ " + exp.block,not: exp.notblock}, {"type":"str","value":"score raycast"+ id + " mcscript_raycast matches .." + exp.duration}] + gen_while(whileObj,true, "raycast$(id)", " positioned ^ ^ ^1") + return 'scoreboard players set raycast'+id+' mcscript_raycast 0\nexecute positioned ~ ~1 ~ run function '+prjName+':mcscript/raycast' + id + '\n' } function gen_forEach(exp){ let res = js_init(exp.var) @@ -1140,6 +1343,7 @@ function gen_while(exp,checkBefore = true, customName = "while$(id)", prefix = " if(cond.type == "binary"){ middle += ' ' + nameOp + testforBinary(cond) } + if(cond.op && cond.op == "or"){ retString += "execute"+prefix+" if entity @s[tag=!mcscriptStop]" + " " + nameOp +" " + cond.value + " run function " + prjName + ':mcscript/' + customName + '\n' } else { @@ -1178,9 +1382,10 @@ function gen_modal(modal,exp){ newVal = exp.args[num].value oldVal = arg.value } - //after = after.split('$(' + oldVal + ')').join(newVal) + let con = after.split('$(' + oldVal + ')') con.forEach(x => { + let tempVal = newVal if(x.substr(0, 5) == '.repl'){ let strArr = [] if(x.substr(6,1) == "'") strArr = x.split("'") @@ -1188,12 +1393,14 @@ function gen_modal(modal,exp){ if(strArr[1][0] == '/'){ strArr[1] = strArr[1].substr(1,strArr[1].length - 2) let regs = new RegExp(strArr[1]) - newVal = newVal.replace(regs, strArr[3]) - } else newVal = newVal.split(strArr[1]).join(strArr[3]) - con[con.indexOf(x)] = strArr[4].substr(1) + tempVal = newVal.replace(regs, strArr[3]) + } else tempVal = tempVal.split(strArr[1]).join(strArr[3]) + con[con.indexOf(x)] = tempVal + strArr[4].substr(1) + } else { + if(con.indexOf(x) != 0) con[con.indexOf(x)] = tempVal + con[con.indexOf(x)] } }) - after = con.join(newVal); + after = con.join(''); } else throw "At modal call \"" + modal.func + "\" no " + num+ ". argument was found!" }) @@ -1201,8 +1408,13 @@ function gen_modal(modal,exp){ } } function checkFilename(data,oldFile,then,ignoreSingle = false){ + if(data[0] != ""){ + let file = oldFile.replace(".mcscript","") + then(file,data[0].split("\n")) + } if(data.length > 1){ - data.forEach(dat => { + for(let i = 1; i < data.length; i++){ + let dat = data[i] if(dat != ""){ dat = dat.split("\n") let file = dat[0].trim() @@ -1217,7 +1429,7 @@ function checkFilename(data,oldFile,then,ignoreSingle = false){ dat.shift(0) then(file,dat) } - }) + } } else { let file = oldFile.replace(".mcscript","") if(!ignoreSingle) then(file,data) @@ -1226,7 +1438,7 @@ function checkFilename(data,oldFile,then,ignoreSingle = false){ handleFiles = function(data,oldFile){ let files = [] if(oldFile == 'load.mcscript'){ - data = '#file: ./mcscript/load\n' + data + data = '#file: ./mcscript/load\n#file: ./load\nfunction '+prjName+':mcscript/load' + "\n" + data.replace("#file: ./load","") } let savedData = data let extendArr = [] @@ -1245,10 +1457,8 @@ handleFiles = function(data,oldFile){ checkFilename(extendArr,oldFile,function(file,dat){ dat = "\n# Extended from "+oldFile+" to "+ file + ".mcfunction\n" + dat.join("\n") let editFiles = files.find(function (obj) { return obj.name === file + '.mcfunction' }); - console.log(editFiles) if(editFiles) files[files.indexOf(editFiles)].data += "\n" + dat else { - console.log(dat); files.push({name: file + '.mcfunction', data: dat}) } },true) @@ -1270,7 +1480,12 @@ var myExtObject = (function() { compile: function(files){ let output = [] for(let file of files){ - output = output.concat(compile(file.content, file.name)) + try{ + output = output.concat(compile(file.content, file.name)) + } + catch(err){ + if(err) console.log(err.message + file.name) + } } return output } diff --git a/lib/generator.js b/lib/generator.js index 44f44ed..c6d6ece 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -17,6 +17,7 @@ generate = function(exp,oldFile, prjPath = oldFile.split("/").indexOf("functions let processed = {}; let res = js(exp); + res = res.split("run execute ").join("") for(let pro in processed){ res += processed[pro]; } @@ -71,6 +72,8 @@ generate = function(exp,oldFile, prjPath = oldFile.split("/").indexOf("functions case "rotated" : return make_indent(exp,"rotated"); case "anchored" : return make_indent(exp,"anchored"); case "positioned" : return make_indent(exp,"positioned"); + case "func" : return gen_func (exp); + case "run_func" : return gen_run_func (exp); case "for" : return gen_for (exp); case "forEach" : return gen_forEach(exp); case "raycast" : return gen_raycast(exp); @@ -79,7 +82,7 @@ generate = function(exp,oldFile, prjPath = oldFile.split("/").indexOf("functions case "prog" : return js_prog (exp); case "call" : return js_call (exp); default: - throw new Error("Dunno how to generate mcfunction for " +exp.type+" in " + oldFile); + throw new Error("There is no way " +exp.type+" could generate in " + oldFile); } } function comment(exp){ @@ -206,6 +209,22 @@ generate = function(exp,oldFile, prjPath = oldFile.split("/").indexOf("functions } return res + "\n" + _else } + function gen_func(exp){ + let name = exp.name.replace("./","") + if(exp.prog){ + processed[name] = "#file: ./" + name + "\n# this file is generated based on a function specified in" + oldFile + "\n" + processed[name] += js(exp.prog) + } + return "" + } + function gen_run_func(exp){ + if(exp.func.prog){ + gen_func(exp.func) + return "function " + prjName + ":" + exp.func.name.replace("./","") + } + if(exp.func.name) return "function " + prjName + ":" + exp.func.name.replace("./","") + throw "don´t know how to generate run function in " + filename + } function testforBinary(cond){ let selector = cond.left.selector ? cond.left.selector : cond.left let selector2 = cond.right.selector ? cond.right.selector : cond.right @@ -530,7 +549,7 @@ const parseCode = function(ast,oldFile, preOptions = null){ } let data = generate(ast,oldFile, prjPath, prjName) if(oldFile.split("/").splice(-2).join('/') == 'functions/load.mcscript' || oldFile.split("/").splice(-2).join('/') == 'scripts/load.mcscript'){ - data = '#file: ./mcscript/load\n#file: ./load\nfunction '+prjName+':mcscript/load' + data + data = '#file: ./mcscript/load\n#file: ./load\nfunction '+prjName+':mcscript/load' + "\n" + data.replace("#file: ./load","") if(tags.length) data += '\nexecute unless entity @e[tag=mcscriptTags] at @p run summon armor_stand ~ ~ ~ {Tags:[mcscriptTags],Invisible:1,Invulnerable:1,NoGravity:1}' } let savedData = data @@ -578,8 +597,13 @@ exports.getVars = function(ast, oldFile, preOptions = null){ } function checkFilename(data,oldFile,then,ignoreSingle = false){ + if(data[0] != ""){ + let file = oldFile.replace(".mcscript","") + then(file,data[0].split("\n")) + } if(data.length > 1){ - data.forEach(dat => { + for(let i = 1; i < data.length; i++){ + let dat = data[i] if(dat != ""){ dat = dat.split("\n") let file = dat[0].trim() @@ -594,7 +618,7 @@ function checkFilename(data,oldFile,then,ignoreSingle = false){ dat.shift(0) then(file,dat) } - }) + } } else { let file = oldFile.replace(".mcscript","") if(!ignoreSingle) then(file,data) diff --git a/lib/index.js b/lib/index.js index f6b736e..3bbd036 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,7 +6,7 @@ const gen = require('./generator.js') const consoletheme = require("./consoletheme.js") var preOptions -const compile = function(path){ +const compile = function(path,fullErr){ let num = 1; if(fs.lstatSync(path).isFile()){ @@ -35,11 +35,11 @@ const compile = function(path){ return false }) for(let file of globals){ - readFile(file,{noParse: 'vars'}); + readFile(file,{noParse: 'vars',fullErr: fullErr}); } for(let file of files){ num++; - readFile(file); + readFile(file,{fullErr: fullErr}); } console.log(consoletheme.FgGreen,"Read " + num + " Files and compiled successfully",consoletheme.Reset); @@ -80,7 +80,7 @@ const genAst = function(path){ } } -const watch = function(path){ +const watch = function(path,fullErr){ console.log(consoletheme.FgGreen,"Now I watch your files on "+path+" to change! *-*",consoletheme.Reset) @@ -93,11 +93,7 @@ const watch = function(path){ if (filename && filename.split('.').pop() == 'mcscript' && counter) { - try { - compile(path) - } catch(err){ - console.log(consoletheme.FgCyan,err,consoletheme.Reset); - } + compile(path,{fullErr: fullErr}) } counter = !counter; @@ -124,7 +120,19 @@ function readFile(file, options = {}){ if(options.noParse == "modal") gen.getModals(parser.parse(lexer.lexer(data)),file); else if(options.noParse == "json") gen.getAst(parser.parse(lexer.lexer(data)),file) else if(options.noParse == "vars") changeOptions(gen.getVars(parser.parse(lexer.lexer(data)),file, preOptions)) - else gen.parseCode(parser.parse(lexer.lexer(data, file)),file, preOptions) + else { + try { + gen.parseCode(parser.parse(lexer.lexer(data, file)),file, preOptions) + } catch(err){ + if(err.message && err.message.substr(0,7) == "[Debug]"){ + console.log(err.message) + let oldMess = err.message.split(" ")[0].substr(7) + err.message = err.message.split(" ").slice(1).join(" ") + console.log(consoletheme.FgRed,"[Debugger]",consoletheme.FgCyan,oldMess,consoletheme.FgWhite,err,consoletheme.Reset,); + } else if(!options.fullErr) console.log(consoletheme.FgRed,"[Error]",err.message,"\n\n "+file+" was not compiled!",consoletheme.Reset); + else console.log(err); + } + } }); } exports.compile = compile; diff --git a/lib/lexer.js b/lib/lexer.js index 1fc923d..e5cb7d4 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -13,6 +13,7 @@ function InputStream(input, file = '') { getCol : getCol, eof : eof, croak : croak, + debugError : debugError }; // next Method returns next token @@ -81,9 +82,14 @@ function InputStream(input, file = '') { // Throws an error with actual position in File function croak(msg) { - - throw new Error(msg + " (" + line + ":" + col + ")" + file); - + throw new Error(msg + " (" + (line - 1) + ":" + col + ")" + file); + } + function debugError(msg,type) { + let filer = file.split("/") + console.log(filer,file) + filer = filer[filer.length - 1] + if(type == "err") throw new Error("[Debug](" + (line - 1) + ":" + col + "|" + filer + ") " + msg); + else if(type == "break") throw new Error("[Brake](brake at " + (line - 1) + ":" + col + "|" + filer + ") " + msg) } } @@ -95,9 +101,9 @@ function TokenStream(input) { // Keywords var keywords = " if then else true false for as at asat positioned modal align dimension rotated" + " anchored while do forEach raycast stop continue switch case default var bool" - + " boolean tag score const override "; + + " boolean tag score const override function run "; var operators = "+-*/%=&|<>!"; - var puncs = ",;(){}[]"; + var puncs = ",;:(){}[]"; var whitespaces = " \r\n\t"; @@ -106,7 +112,8 @@ function TokenStream(input) { next : next, peek : peek, eof : eof, - croak : input.croak + croak : input.croak, + debugError : input.debugError }; // is_keyword method: test if character is keyword @@ -260,6 +267,10 @@ function TokenStream(input) { function read_comment() { return { type: "comment", value: read_while(function(ch){ return ch != "\n" && ch != ";" }) } } + function read_debug() { + console.log("he") + return { type: "debugger", value: read_while(function(ch){ return ch != "\n" && ch != ";" }) } + } // read_command method: Transfer commands starting with / function read_command() { @@ -295,6 +306,9 @@ function TokenStream(input) { if(ch == "r" && input.peekNext(1) == "u" && input.peekNext(2) == "n" && input.peekNext(3) == ":") { return read_command(); } + if(ch == "d" && input.peekNext(1) == "e" && input.peekNext(2) == "b" && input.peekNext(3) == "u" && input.peekNext(4) == "g") { + return read_debug(); + } // Transfer command if(ch == "/" && input.getCol() === 0){ diff --git a/lib/parser.js b/lib/parser.js index 9bcf8ca..e0810f8 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,4 +1,6 @@ const fs = require('fs'); +const consoletheme = require("./consoletheme.js") + var FALSE = { type: "str", value: '' }; // return parse function @@ -141,10 +143,9 @@ exports.parse = function(input) { function parse_if() { skip_kw("if"); - input.next(); var cond = parse_key_args(); input.next(); - if (!is_punc("{")) skip_kw("then"); + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); var ret = { @@ -163,7 +164,6 @@ exports.parse = function(input) { // parse_while method: parses while loop function parse_while() { skip_kw("while"); - input.next(); var cond = parse_key_args(); input.next(); let custom; @@ -172,7 +172,7 @@ exports.parse = function(input) { custom = input.next(); } - if (!is_punc("{")) skip_kw("then"); + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); var ret = { @@ -194,10 +194,9 @@ exports.parse = function(input) { custom = input.next(); } - if (!is_punc("{")) skip_kw("then"); + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); skip_kw("while"); - input.next(); var cond = parse_key_args(); input.next(); @@ -221,8 +220,7 @@ exports.parse = function(input) { if(!variable.assign) variable.assign = { type: 'assign',operator: '=', left: { type: 'var', value: variable.name }, right: { type: 'num', value: 0 } }; - input.next(); - let cond = parse_key_args(); + let cond = parse_key_args(true); input.next(); let binary = maybe_binary(input.next(),0); input.next(); @@ -232,7 +230,7 @@ exports.parse = function(input) { custom = input.next(); } - if (!is_punc("{")) skip_kw("then"); + if (!is_punc("{")) input.croak("The statements must be in a {} block. Please add a {.") var then = parse_expression(); var ret = { @@ -302,10 +300,13 @@ exports.parse = function(input) { } // parse_key_args method: parses keyword arguments - function parse_key_args(){ + function parse_key_args(punc_not_needed){ let cond = []; let before = {type: 'op', value: "&&"}; - + if(!punc_not_needed){ + if(input.peek().value != "(" && input.peek().value != ",") input.croak("Please add () to specify arguments") + else input.next(); + } while(input.peek().value != ")" && input.peek().value != ";"){ let next = input.next(); @@ -347,14 +348,14 @@ exports.parse = function(input) { before = next; } - return cond; + if(cond.length > 0) return cond; + input.croak("Please specify at least one argument here") } // parse_keyword method: parses a keyword function parse_keyword(kw) { skip_kw(kw); - input.next(); var cond = parse_key_args(); input.next(); @@ -374,7 +375,8 @@ exports.parse = function(input) { if(nest.nest) res.nest = res.nest.concat(nest.nest); return res; } - else skip_kw("then"); + else input.croak("Please finish your arguments or add a {.") + var then = parse_expression(); @@ -506,16 +508,49 @@ exports.parse = function(input) { }) return res; } - - // parse_lambda method: parse a lambda - function parse_lambda() { - return { - type: "lambda", - vars: delimited("(", ")", ",", parse_varname), - body: parse_expression() - }; + // parses the function keyword + function parse_function(from_run = false){ + skip_kw("function") + if(input.peek().type != "str" && input.peek().type != "var") input.croak("Please specify the name and path of the function using a string or one-word name") + let res = {type: "func"} + res.name = input.next().value + if(input.peek().value == "{" || !from_run){ // Do I need to pass a program? + res.prog = parse_prog() + } + return res + } + function parse_run_function(){ + skip_kw("run") + if(input.peek().value == "function") return {type:"run_func",func: parse_function(true)} + else input.croak("Please specify a function") + } + // parse_debugger method: read a debug message + function parse_debugger(tok) { + tok.value = tok.value.substr(6).split(":") + if(tok.value.length < 2) input.croak("You need to end your debug mode with a : !") + let mode = tok.value[0] + tok.value = tok.value.slice(1).join(":").trim() + switch(mode) { + case "message": { + console.log(consoletheme.FgYellow,"[Debugger]" + consoletheme.FgCyan + " (message)",consoletheme.FgWhite,tok.value,consoletheme.Reset); + break + } + case "break": { + input.debugError(tok.value,"break") + break + } + case "error": { + input.debugError(tok.value,"err") + break + } + case "success": { + console.log(consoletheme.FgGreen,"[Debugger]" + consoletheme.FgCyan + " (success)",consoletheme.FgWhite,tok.value,consoletheme.Reset); + break + } + } + if(input.peek().value == ";") input.next() + return {type:"command",value:""} } - // parse_bool method: parse a bool function parse_bool() { return { @@ -564,6 +599,8 @@ exports.parse = function(input) { if (is_kw("do")) return parse_dowhile(); if (is_kw("modal")) return parse_modal(); if (is_kw("override")) return parse_override(); + if (is_kw("function")) return parse_function(); + if (is_kw("run")) return parse_run_function(); if (is_kw("true") || is_kw("false")) return parse_bool(); var tok = input.next(); @@ -571,7 +608,9 @@ exports.parse = function(input) { if(tok.type == "comment" || tok.type == "command"){ return tok; } - + if(tok.type == "debugger"){ + return parse_debugger(tok) + } if(tok.type == "var"){ if(input.peek().type == "selector"){ diff --git a/package.json b/package.json index e1f0969..4ef65e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcscript", - "version": "0.1.4", + "version": "0.1.5", "description": "Minecraft Script to mcfunction compiler cli", "homepage":"http://stevertus.ga/mcscript", "scripts": {