├── .gitignore ├── Changelog.md ├── Features.md ├── LICENSE ├── README.md ├── Todo.md ├── dist ├── mcs.js └── mcs.min.js ├── gulpfile.js ├── index.js ├── package-lock.json ├── package.json ├── src ├── back.js ├── compiler.js ├── front.js ├── parser.js └── pre-defined-macros.js ├── test ├── new_syntax.mcs └── syntax_output │ ├── myNamespace │ ├── callAMacro.mcfunction │ ├── changes.mcfunction │ └── myFolder │ │ └── myFunction.mcfunction │ └── output.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,macos,linux,windows 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### Node ### 47 | # Logs 48 | logs 49 | *.log 50 | npm-debug.log* 51 | yarn-debug.log* 52 | yarn-error.log* 53 | 54 | # Runtime data 55 | pids 56 | *.pid 57 | *.seed 58 | *.pid.lock 59 | 60 | # Directory for instrumented libs generated by jscoverage/JSCover 61 | lib-cov 62 | 63 | # Coverage directory used by tools like istanbul 64 | coverage 65 | 66 | # nyc test coverage 67 | .nyc_output 68 | 69 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 70 | .grunt 71 | 72 | # Bower dependency directory (https://bower.io/) 73 | bower_components 74 | 75 | # node-waf configuration 76 | .lock-wscript 77 | 78 | # Compiled binary addons (http://nodejs.org/api/addons.html) 79 | build/Release 80 | 81 | # Dependency directories 82 | node_modules/ 83 | jspm_packages/ 84 | 85 | # Typescript v1 declaration files 86 | typings/ 87 | 88 | # Optional npm cache directory 89 | .npm 90 | 91 | # Optional eslint cache 92 | .eslintcache 93 | 94 | # Optional REPL history 95 | .node_repl_history 96 | 97 | # Output of 'npm pack' 98 | *.tgz 99 | 100 | # Yarn Integrity file 101 | .yarn-integrity 102 | 103 | # dotenv environment variables file 104 | .env 105 | 106 | 107 | ### Windows ### 108 | # Windows thumbnail cache files 109 | Thumbs.db 110 | ehthumbs.db 111 | ehthumbs_vista.db 112 | 113 | # Folder config file 114 | Desktop.ini 115 | 116 | # Recycle Bin used on file shares 117 | $RECYCLE.BIN/ 118 | 119 | # Windows Installer files 120 | *.cab 121 | *.msi 122 | *.msm 123 | *.msp 124 | 125 | # Windows shortcuts 126 | *.lnk 127 | 128 | # End of https://www.gitignore.io/api/node,macos,linux,windows 129 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## Version 2.2.0 3 | Added exponential operations 4 | - Used with ^ 5 | 6 | ## Version 2.1.0 7 | Fixed some issues 8 | - Execute can now be used as a regular command (Fixes #3) 9 | - Selectors and Relatives can now hold variables/logic to be evaluated (Fixes #2) 10 | 11 | ## Version 2.0.0 12 | Revamped the entire system, with a new parser, a compiler and a lot of new features! 13 | - No need for new lines! Use semicolons to end a statement; 14 | - Better variables (use $ every time) 15 | - Arrays 16 | - Selectors (`@a[score_hello=5]`) 17 | - Relative (`~`) 18 | - Call functions just like you would in normal commands (`function :[folder/]`) 19 | - Groups, create sub folders/groups of functions 20 | - Macros, call "methods" to write more efficiently. 21 | - If, Else if, Else 22 | - For loops (`for(var $i = 0; $i < 5; $i = $i + 1)`) 23 | - Foreach loops (`foreach(var $i in range(0,5))`) 24 | - Evaluation Blocks, use mcs inside a string (```"this is an eval block -> `return 1+2;` "```) 25 | - Settings, set your own namespace (`@!namespace: myNamespace`) 26 | 27 | You can check out the new syntax in use [here](https://github.com/PandawanFr/mcs/blob/master/test/new_syntax.mcs) and its [output](https://github.com/PandawanFr/mcs/tree/master/test/syntax_output). 28 | -------------------------------------------------------------------------------- /Features.md: -------------------------------------------------------------------------------- 1 | v settings (@!namespace) 2 | v groups 3 | v functions 4 | v Parsed comments 5 | v Basic commands 6 | v variables (str, num, bool) 7 | v arrays (declare + calling) 8 | v variable set 9 | v use variable in command (say $hello) 10 | v make sure string variables don't show their "" when using 11 | v Eval blocks (` `) 12 | v If, else if, else 13 | v For loops 14 | v Foreach loops 15 | v range macro for foreach 16 | v execute block 17 | v macro, with parameters 18 | v macro commands 19 | v macro return 20 | v macro calling 21 | v variable selectors 22 | v Multiple groups 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pandawan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mcs: Minecraft Script 2 | 3 | [![npm](https://img.shields.io/npm/v/mcs.svg?style=flat-square)](https://www.npmjs.com/package/mcs) 4 | [![license](https://img.shields.io/github/license/pandawanfr/mcs.svg?style=flat-square)](https://github.com/pandawanfr/mcs) 5 | 6 | #### A pre-processor to write Minecraft Functions more efficiently. 7 | 8 | Try it with the [Online Editor](https://pandawanfr.github.io/MCSEditor/)! 9 | 10 | Check out the [changelog](https://github.com/PandawanFr/mcs/blob/master/Changelog.md) for a list of new features! 11 | 12 | *Latest Compatible Minecraft Version: 1.12* 13 | 14 | Note: I called it Script because it has a syntax similar to JavaScript. 15 | 16 | # Documentation 17 | Learn how to write in Minecraft Script in the [Wiki](https://github.com/PandawanFr/mcs/wiki). 18 | 19 | # Installation 20 | `mcs` has been tested with node and as a standalone (web) script. Though it should also support CommonJS (node, browserify) and AMD (RequireJS). 21 | 22 | ## Node 23 | Installation via `npm`: 24 | 25 | ```shell 26 | $ npm install mcs 27 | 28 | > var mcs = require('mcs'); 29 | > mcs('function hello { say hello world; }'); 30 | ``` 31 | Alternatively you can install mcs globally so that it may be run from the command line. 32 | 33 | ```shell 34 | $ npm install mcs -g 35 | $ mcs ./input.mcs ./output/ 36 | ``` 37 | 38 | ## Standalone/Script 39 | 40 | Add to your html 41 | 42 | ```html 43 | 44 | ``` 45 | 46 | ### Or include it manually 47 | 48 | Download [mcs.min.js](https://github.com/PandawanFr/mcs/blob/master/dist/mcs.min.js) 49 | 50 | Add to your html 51 | 52 | ```html 53 | 54 | ``` 55 | 56 | # Usage 57 | 58 | ## JS 59 | ```javascript 60 | var input = 'function hello { say hello world; }' 61 | var result = mcs(input) 62 | // result = { "_namespace": { "_type": "namespace", "hello": { "_type": "function", "value": "say hello world\n" } } } 63 | ``` 64 | `mcs()` takes one required argument, the input (string to convert), and returns a JSON object with namespaces, groups, and functions (you can differentiate them using _type). 65 | 66 | ## CLI 67 | ```shell 68 | $ mcs [input] [output (optional)] 69 | ``` 70 | Using `mcs` in the CLI takes one require argument, the `input` file (.mcs file), and outputs to the (optional) `output` directory. If no output directory is given, `./` is used. 71 | 72 | # TODO 73 | Check out the current todo list [here](https://github.com/PandawanFr/mcs/blob/master/Todo.md). 74 | 75 | # Contributing 76 | 1. Create an issue and describe your idea 77 | 2. [Fork it](https://github.com/PandawanFr/mcs/fork) 78 | 3. Checkout this repository 79 | 4. Install the dependencies `npm install` or `yarn` 80 | 5. Edit the files in `/src` 81 | 6. Test your changes with 82 | ```shell 83 | # Use your own input/output 84 | $ npm run build 85 | $ node index.js [input] [output] 86 | 87 | 88 | # Alternatively: 89 | # Use test/new_syntax.mcs as input, and test/output/ as output 90 | $ npm run test 91 | 92 | # Build the source files AND use test/new_syntax.mcs as input, and test/output/ as output 93 | $ npm run bnt 94 | 95 | # Autocompile/build whenever you change the src files (instead of npm run build) 96 | $ gulp 97 | ``` 98 | 8. You can also test your changes on the web by copying the `dist/mcs.min.js` file after building 99 | 9. Once done, create a pull request 100 | 101 | # Authors 102 | Made by [Pandawan](http://twitter.com/PandawanYT). 103 | Thanks to [@coolsa](https://github.com/coolsa) for contributing. 104 | Thanks to [Andrew Mast](https://github.com/AndrewMast) and [Chris Smith](https://github.com/chris13524) for helping out and providing feedback! 105 | 106 | # License 107 | Please see the [LICENSE](https://github.com/PandawanFr/mcs/blob/master/LICENSE) file 108 | 109 | `mcs` is not affiliated with Minecraft or Mojang AB. 110 | -------------------------------------------------------------------------------- /Todo.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | # For Next version 3 | - Add variable parsing inside function and group names 4 | - Upgrade to MC 1.13 (once released) 5 | -------------------------------------------------------------------------------- /dist/mcs.js: -------------------------------------------------------------------------------- 1 | /* This will appear at the top/start of the final dist file */ 2 | (function() { 3 | 4 | var mcs = (function() { 5 | 6 | var mcs = function(input) { 7 | if (!input || typeof(input) !== 'string') { 8 | var err = 'Error: Object to parse is not a valid string or does not exist.'; 9 | console.error(err); 10 | return err; 11 | } 12 | 13 | /* Compile the AST into a final JSON object */ 14 | 15 | function Compiler(exp) { 16 | 17 | var debug = true, 18 | oldDebug = true, 19 | addTop = "", 20 | inFunc = false, 21 | current = [], 22 | currentFunc = '', 23 | namespace = '', 24 | prefix = []; 25 | 26 | // Environment is used to remember/manage the scope 27 | function Environment(parent) { 28 | this.vars = Object.create(parent ? parent.vars : null); 29 | this.parent = parent; 30 | } 31 | Environment.prototype = { 32 | extend: function() { 33 | return new Environment(this); 34 | }, 35 | lookup: function(name) { 36 | var scope = this; 37 | while (scope) { 38 | if (Object.prototype.hasOwnProperty.call(scope.vars, name)) 39 | return scope; 40 | scope = scope.parent; 41 | } 42 | }, 43 | get: function(name) { 44 | if (name in this.vars) 45 | return this.vars[name]; 46 | err("Undefined variable " + name); 47 | }, 48 | set: function(name, value) { 49 | var scope = this.lookup(name); 50 | // let's not allow defining globals from a nested environment 51 | if (!scope && this.parent) 52 | err("Undefined variable " + name); 53 | return (scope || this).vars[name] = value; 54 | }, 55 | def: function(name, value) { 56 | return this.vars[name] = value; 57 | } 58 | }; 59 | 60 | // Declare global/root scope 61 | var env = new Environment(); 62 | 63 | var output = {}; 64 | evaluate(exp, env); 65 | 66 | return output; 67 | 68 | 69 | // Actual logic 70 | 71 | function err(msg) { 72 | if (debug) console.error(msg); 73 | throw new Error(msg); 74 | } 75 | 76 | // Apply operator 77 | function apply_op(op, a, b) { 78 | function num(x) { 79 | if (typeof x != "number") 80 | err("Expected number but got " + x); 81 | return x; 82 | } 83 | 84 | function div(x) { 85 | if (num(x) == 0) 86 | err("Divide by zero"); 87 | return x; 88 | } 89 | if (op == "+" && (typeof a == "string" || typeof b == "string")) { 90 | return a + b; 91 | } 92 | switch (op) { 93 | case "+": 94 | return num(a) + num(b); 95 | case "-": 96 | return num(a) - num(b); 97 | case "*": 98 | return num(a) * num(b); 99 | case "/": 100 | return num(a) / div(b); 101 | case "%": 102 | return num(a) % div(b); 103 | case "^": 104 | return Math.pow(num(a), num(b)); 105 | case "&&": 106 | return a !== false && b; 107 | case "||": 108 | return a !== false ? a : b; 109 | case "<": 110 | return num(a) < num(b); 111 | case ">": 112 | return num(a) > num(b); 113 | case "<=": 114 | return num(a) <= num(b); 115 | case ">=": 116 | return num(a) >= num(b); 117 | case "==": 118 | return a === b; 119 | case "!=": 120 | return a !== b; 121 | } 122 | err("Can't apply operator " + op); 123 | } 124 | // Evaluates things quickly 125 | // Evaluates things normally except that it doesn't return anything except when the return keyword is used. 126 | // Also evaluates commands differently as they are added to the output 127 | function quickEvaler(exp, env) { 128 | function quickEval(element) { 129 | // Don't need to return commands, they get added automatically 130 | if (element.type == "command") make_command(env, element); 131 | else if (element.type == "return" || element.type == "if") { 132 | return evaluate(element, env); 133 | } else evaluate(element, env); 134 | } 135 | 136 | // If its more than one line 137 | if (exp.type == "prog") { 138 | // Loops through every exp 139 | for (var i = 0; i < exp.prog.length; i++) { 140 | // Get its value, return if needed 141 | var x = quickEval(exp.prog[i]); 142 | if (x) return x; 143 | } 144 | } 145 | // If its only one line 146 | else { 147 | // Get its value, return if needed 148 | var x = quickEval(exp); 149 | if (x) return x; 150 | } 151 | } 152 | 153 | // Create a JS macro to evaluate when called 154 | function make_macro(env, exp) { 155 | if (exp.name == "range") err("Range is a pre-defined macro, please use another name"); 156 | 157 | function macro() { 158 | var names = exp.vars; 159 | var scope = env.extend(); 160 | for (var i = 0; i < names.length; ++i) 161 | scope.def(names[i], i < arguments.length ? arguments[i] : false); 162 | 163 | var x = quickEvaler(exp.body, scope); 164 | return x; 165 | } 166 | return env.set(exp.name, macro); 167 | } 168 | 169 | // Macros have their name as a reg, so need to separate macros from actual regs 170 | function reg_or_macro(env, exp) { 171 | oldDebug = debug; 172 | try { 173 | debug = false; 174 | if (exp.value == "range") return range_macro; 175 | var possible = env.get(exp.value); 176 | debug = oldDebug; 177 | return possible; 178 | } catch (e) { 179 | debug = oldDebug; 180 | return exp.value; 181 | } 182 | } 183 | 184 | // If/else if/else statements 185 | function make_if(env, exp) { 186 | var cond = evaluate(exp.cond, env); 187 | if (cond !== false) { 188 | var x = quickEvaler(exp.then, env.extend()); 189 | if (x) return x; 190 | } else if (exp.else) { 191 | var y = quickEvaler(exp.else, env.extend()); 192 | if (y) return y; 193 | } 194 | return false; 195 | } 196 | // For loops 197 | function make_for(env, exp) { 198 | // Create a new env 199 | var newEnv = env.extend(); 200 | // Evaluate the first param (declaring variable) 201 | evaluate(exp.params[0], newEnv); 202 | // While the second param is valid 203 | while (evaluate(exp.params[1], newEnv)) { 204 | // Evaluate the for content 205 | evaluate(exp.then, newEnv); 206 | // Evaluate the last param (setting/modifying the variable) 207 | evaluate(exp.params[2], newEnv); 208 | } 209 | } 210 | // Foreach loops 211 | function make_foreach(env, exp) { 212 | // Create a new env 213 | var newEnv = env.extend(); 214 | // Get the array 215 | var arr = evaluate(exp.param, env); 216 | // Define the variable 217 | newEnv.def(exp.variable.value, 0); 218 | // Loop through all of them 219 | for (var i = 0; i < arr.length; i++) { 220 | // Set the variable to the correct value 221 | newEnv.set(exp.variable.value, arr[i]); 222 | // Evaluate 223 | evaluate(exp.then, newEnv); 224 | } 225 | } 226 | // Execute blocks 227 | function make_execute(env, exp) { 228 | // Evaluate all the values 229 | var selector = evaluate(exp.selector, env); 230 | var pos1 = evaluate(exp.pos[0], env); 231 | var pos2 = evaluate(exp.pos[1], env); 232 | var pos3 = evaluate(exp.pos[2], env); 233 | // Add prefix 234 | prefix.push("execute " + selector + " " + pos1 + " " + pos2 + " " + pos3 + " "); 235 | // Evaluate content 236 | evaluate(exp.prog, env.extend()); 237 | // pop 238 | prefix.pop(); 239 | } 240 | // Strings can also have evals inside them 241 | function make_string(env, exp) { 242 | // If it's an array, then it contains evals 243 | if (Array.isArray(exp.value)) { 244 | var final = ""; 245 | for (var i = 0; i < exp.value.length; i++) { 246 | if (exp.value[i].type == "prog") { 247 | var x = evaluate(exp.value[i], env.extend()); 248 | final += x; 249 | } else final += exp.value[i].value; 250 | } 251 | return final; 252 | } 253 | // If it's a basic string 254 | else { 255 | return exp.value; 256 | } 257 | } 258 | 259 | // Relatives can be evaluated 260 | function make_relative(env, exp) { 261 | if (exp.value && exp.value.length > 0) { 262 | var final = "~"; 263 | for (var i = 0; i < exp.value.length; i++) { 264 | final += evaluate(exp.value[i], env); 265 | } 266 | return final; 267 | } else { 268 | return "~"; 269 | } 270 | } 271 | 272 | // Selectors can be evaluated 273 | function make_selector(env, exp) { 274 | // If the selector's value is an array 275 | if (Array.isArray(exp.value)) { 276 | if (exp.value && exp.value.length > 0) { 277 | var final = exp.prefix + "["; 278 | for (var i = 0; i < exp.value.length; i++) { 279 | // Only compile ivars (already created variables/calls) 280 | if (exp.value[i].type == "ivar") { 281 | var x = evaluate(exp.value[i], env.extend()); 282 | final += x; 283 | } else final += exp.value[i].value; 284 | } 285 | return final + "]"; 286 | } else { 287 | return exp.prefix; 288 | } 289 | } else { 290 | return exp.value; 291 | } 292 | } 293 | 294 | // Create an array 295 | function make_array(env, exp) { 296 | var arr = {}; 297 | var index = 0; 298 | exp.value.forEach(function(element) { 299 | var obj = evaluate(element, env); 300 | arr[index] = obj; 301 | index++; 302 | }); 303 | return arr; 304 | } 305 | // Get the ivar's value 306 | function get_ivar(env, exp) { 307 | var ivar = env.get(exp.value); 308 | if (exp.index) { 309 | var index = evaluate(exp.index, env); 310 | if (typeof(index) != "number") err("Array index must be a number"); 311 | return ivar[index]; 312 | } else return ivar; 313 | } 314 | // Assign can either be a declaration or a modification 315 | function make_assign(env, exp) { 316 | // Modify a current variable, use set 317 | if (exp.left.type == "ivar") return env.set(exp.left.value, evaluate(exp.right, env)); 318 | // Declare a new variable, define (def) 319 | else if (exp.left.type == "var") { 320 | return env.def(exp.left.value, evaluate(exp.right, env)); 321 | } 322 | } 323 | // Need to compile JSON the way that MC would accept it 324 | function make_json(env, exp) { 325 | var json = "{"; 326 | for (var i = 0; i < exp.value.length; i++) { 327 | var jsonToAdd = ""; 328 | // if it's a string, add quotes around it 329 | if (exp.value[i].type == "str") { 330 | jsonToAdd = "\"" + evaluate(exp.value[i], env) + "\""; 331 | } 332 | // If it's an array, JSONinfy it 333 | else if (exp.value[i].type == "array") { 334 | var temp = evaluate(exp.value[i], env); 335 | jsonToAdd = JSON.stringify(Object.keys(temp).map(function(k) { 336 | return temp[k]; 337 | })); 338 | } 339 | // if it's something else, evaluate it, it might be something interesting 340 | else { 341 | jsonToAdd = evaluate(exp.value[i], env); 342 | } 343 | json += jsonToAdd; 344 | } 345 | json += "}"; 346 | return json; 347 | } 348 | // Create a comment and add it 349 | function make_comment(env, exp) { 350 | addToOutput(currentFunc, exp.value + "\n"); 351 | } 352 | // Create a command 353 | function make_command(env, exp) { 354 | var cmd = ""; 355 | var lastVal = ""; 356 | if (env.parent == null) err("Commands cannot be used in root"); 357 | for (var i = 0; i < exp.value.length; i++) { 358 | var valueToAdd = evaluate(exp.value[i], env); 359 | if (valueToAdd != ":" && lastVal != "" & lastVal != ":") cmd += " "; 360 | cmd += valueToAdd; 361 | 362 | lastVal = valueToAdd; 363 | } 364 | // Whenever a command is read, add it to the output 365 | var prefixToAdd = (prefix && prefix.length > 0) ? prefix.join('') : ''; 366 | addToOutput(currentFunc, prefixToAdd + cmd + "\n"); 367 | return cmd; 368 | } 369 | // Programs are anything inside a {} with more than one statement 370 | function make_prog(env, exp) { 371 | var final = ""; 372 | exp.prog.forEach(function(exp) { 373 | if (exp.type == "command") { 374 | var cmd = evaluate(exp, env); 375 | final += cmd + "\n"; 376 | } 377 | // Need to add returned items because of eval blocks 378 | else if (exp.type == "return") { 379 | var output = evaluate(exp, env); 380 | final += output; 381 | } else evaluate(exp, env); 382 | }); 383 | return final; 384 | } 385 | // Make a function, evaluate, get out of function 386 | function make_func(env, exp) { 387 | if (inFunc) err("Cannot declare a function inside another"); 388 | inFunc = true; 389 | currentFunc = exp.name; 390 | evaluate(exp.body, env.extend()); 391 | // No need to add anything to the output here, whenever a command is found, it adds it when read 392 | currentFunc = ''; 393 | inFunc = false; 394 | } 395 | // Make a group, evaluate inside, get out of group 396 | function make_group(env, exp) { 397 | if (inFunc) err("Groups cannot be inside functions"); 398 | current.push(exp.name); 399 | evaluate(exp.body, env.extend()); 400 | current.pop(); 401 | } 402 | 403 | function make_setting(env, exp) { 404 | if (env.parent != null) err("Settings must be declared in the root"); 405 | if (exp.name == "namespace") { 406 | if (namespace && namespace != "namespace") err("Cannot declare namespace more than once"); 407 | if (namespace && namespace == "_namespace") err("Please declare the namespace BEFORE writing any functions"); 408 | namespace = exp.value; 409 | } else { 410 | err("No setting found with the name " + exp.name); 411 | } 412 | } 413 | 414 | // Add the given key-value pair to the output 415 | function addToOutput(name, value) { 416 | 417 | // We need the namespace now, if it doesn't exist, set it! (Use _namespace so that there's less chance of conflict) 418 | if (!namespace) namespace = "_namespace"; 419 | if (!output[namespace]) output[namespace] = { 420 | _type: "namespace" 421 | }; 422 | // Get the current position to setup our group 423 | var curOutput = output[namespace]; 424 | 425 | // Check whether or not we are in a group 426 | if (current) { 427 | // Get the current group 428 | current.forEach(function(element) { 429 | if (!curOutput[element]) curOutput[element] = { 430 | _type: "group" 431 | }; 432 | curOutput = curOutput[element]; 433 | }); 434 | 435 | // If it doesn't exist yet, set instead of add (or else it says undefined at the start) 436 | if (curOutput.hasOwnProperty(name)) { 437 | curOutput[name].value += value; 438 | } else { 439 | curOutput[name] = { 440 | _type: "function", 441 | value: value 442 | }; 443 | } 444 | } 445 | // No groups 446 | else { 447 | // If it doesn't exist yet, set instead of add (or else it says undefined at the start) 448 | if (curOutput.hasOwnProperty(name)) { 449 | curOutput[name] += value; 450 | } else { 451 | curOutput[name] = { 452 | _type: "function", 453 | value: value 454 | }; 455 | } 456 | } 457 | } 458 | 459 | // Whether or not a JSON object is empty 460 | function isJSONEmpty(obj) { 461 | for (var prop in obj) { 462 | if (obj.hasOwnProperty(prop)) 463 | return false; 464 | } 465 | return JSON.stringify(obj) === JSON.stringify({}); 466 | } 467 | 468 | // Evaluates all the tokens and compiles commands 469 | function evaluate(exp, env) { 470 | switch (exp.type) { 471 | case "num": 472 | case "bool": 473 | case "kw": 474 | return exp.value; 475 | case "str": 476 | return make_string(env, exp); 477 | case "eval": 478 | return evaluate(exp.value, env.extend()); 479 | case "colon": 480 | return ":"; 481 | case "relative": 482 | return make_relative(env, exp); 483 | case "selector": 484 | return make_selector(env, exp); 485 | case "comma": 486 | return ","; 487 | case "json": 488 | return make_json(env, exp); 489 | case "reg": 490 | return reg_or_macro(env, exp); 491 | case "comment": 492 | return make_comment(env, exp); 493 | case "command": 494 | return make_command(env, exp); 495 | case "array": 496 | return make_array(env, exp); 497 | case "ivar": 498 | return get_ivar(env, exp); 499 | case "assign": 500 | return make_assign(env, exp); 501 | case "binary": 502 | return apply_op(exp.operator, evaluate(exp.left, env), evaluate(exp.right, env)); 503 | case "macro": 504 | return make_macro(env, exp); 505 | case "return": 506 | return evaluate(exp.value, env); 507 | case "if": 508 | return make_if(env, exp); 509 | case "for": 510 | return make_for(env, exp); 511 | case "foreach": 512 | return make_foreach(env, exp); 513 | case "execute": 514 | return make_execute(env, exp); 515 | case "function": 516 | return make_func(env, exp); 517 | case "group": 518 | return make_group(env, exp); 519 | case "setting": 520 | return make_setting(env, exp); 521 | case "prog": 522 | return make_prog(env, exp); 523 | case "call": 524 | var macro = evaluate(exp.func, env); 525 | return macro.apply(null, exp.args.map(function(arg) { 526 | return evaluate(arg, env); 527 | })); 528 | default: 529 | err("Unable to evaluate " + exp.type); 530 | } 531 | } 532 | } 533 | 534 | /* Advanced Parser 535 | InputStream reads characters 536 | TokenStream is the lexer (converts everything into tokens) 537 | Parser tries to create node structures (AST) out of the tokens 538 | 539 | Parser based on: http://lisperator.net/pltut/ 540 | */ 541 | 542 | // List of all commands that exist in mc 543 | var availableCommands = ["advancement", "ban", "blockdata", "clear", "clone", "debug", "defaultgamemode", "deop", "difficulty", "effect", "enchant", "entitydata", "execute", "fill", "function", "gamemode", "gamerule", "give", "help", "kick", "kill", "list", "locate", "me", "op", "pardon", "particle", "playsound", "publish", "recipe", "reload", "replaceitem", "save", "say", "scoreboard", "seed", "setblock", "setidletimeout", "setmaxplayers", "setworldspawn", "spawnpoint", "spreadplayers", "stats", "stop", "stopsound", "summon", "teleport", "tell", "tellraw", "testfor", "testforblock", "testforblocks", "time", "title", "toggledownfall", "tp", "transferserver", "trigger", "weather", "whitelist", "worldborder", "wsserver", "xp"]; 544 | 545 | // InputStream (Read input character by character) 546 | function InputStream(input) { 547 | var pos = 0, 548 | line = 1, 549 | col = 0, 550 | lastVal = null, 551 | lastWasNewLineVal = true; 552 | return { 553 | next: next, 554 | peek: peek, 555 | eof: eof, 556 | croak: croak, 557 | last: last, 558 | lastWasNewLine: lastWasNewLine 559 | }; 560 | 561 | function next() { 562 | // Knows whether or not we switched to a new line 563 | if (peek() == "\n") lastWasNewLineVal = true; 564 | else if ("\r\t ".indexOf(peek()) == -1) lastWasNewLineVal = false; 565 | // Get the last character 566 | lastVal = peek(); 567 | 568 | var ch = input.charAt(pos++); 569 | if (ch == "\n") line++, col = 0; 570 | else col++; 571 | return ch; 572 | } 573 | 574 | function last() { 575 | return lastVal; 576 | } 577 | 578 | function lastWasNewLine() { 579 | return lastWasNewLineVal; 580 | } 581 | 582 | function peek() { 583 | return input.charAt(pos); 584 | } 585 | 586 | function eof() { 587 | return peek() == ""; 588 | } 589 | 590 | function croak(msg) { 591 | var err = msg + ' at (' + line + ':' + col + ')'; 592 | console.error(err); 593 | throw new Error(err); 594 | } 595 | } 596 | 597 | // Lexer (converts everything into tokens) 598 | function TokenStream(input) { 599 | var current = null; 600 | // List of all keywords that are available 601 | var keywords = " function macro group if elseif else return execute true false var for foreach in "; 602 | var lastVal = null; 603 | return { 604 | next: next, 605 | peek: peek, 606 | eof: eof, 607 | croak: input.croak, 608 | last: last 609 | }; 610 | 611 | function is_keyword(x) { 612 | return keywords.indexOf(" " + x + " ") >= 0; 613 | } 614 | 615 | function is_digit(ch) { 616 | return /[0-9]/i.test(ch); 617 | } 618 | 619 | function is_id_start(ch) { 620 | return /[a-z0-9_\$]/i.test(ch); 621 | } 622 | 623 | function is_id(ch) { 624 | return is_id_start(ch); 625 | } 626 | 627 | function is_ivar(ch) { 628 | return /\$[a-z0-9-_]/i.test(ch); 629 | } 630 | 631 | function is_op_char(ch) { 632 | return "+-*/%^=&|<>!".indexOf(ch) >= 0; 633 | } 634 | 635 | function is_punc(ch) { 636 | return ",;(){}[]".indexOf(ch) >= 0; 637 | } 638 | 639 | function is_whitespace(ch) { 640 | return " \t\n\r".indexOf(ch) >= 0; 641 | } 642 | // Read until the given predicate returns false 643 | function read_while(predicate) { 644 | var str = ""; 645 | while (!input.eof() && predicate(input.peek())) 646 | str += input.next(); 647 | return str; 648 | } 649 | 650 | function try_number() { 651 | input.next(); 652 | if (is_digit(input.peek())) { 653 | var num = read_number(); 654 | num.value *= -1; 655 | return num; 656 | } 657 | input.croak("Can't handle character: " + input.peek()); 658 | } 659 | 660 | function read_number() { 661 | var has_dot = false; 662 | var number = read_while(function(ch) { 663 | if (ch == ".") { 664 | if (has_dot) return false; 665 | has_dot = true; 666 | return true; 667 | } 668 | return is_digit(ch); 669 | }); 670 | return { 671 | type: "num", 672 | value: parseFloat(number) 673 | }; 674 | } 675 | // Read identifiers, can return a keyword, an ivar ($variable), or reg (anything else) 676 | function read_ident() { 677 | var id = read_while(is_id); 678 | var type; 679 | if (is_keyword(id)) type = "kw"; 680 | else if (is_ivar(id)) type = "ivar" 681 | else type = "reg"; 682 | 683 | return { 684 | type: type, 685 | value: id 686 | }; 687 | } 688 | 689 | function read_escaped(end) { 690 | var escaped = false, 691 | str = ""; 692 | input.next(); 693 | while (!input.eof()) { 694 | var ch = input.next(); 695 | if (escaped) { 696 | str += ch; 697 | escaped = false; 698 | } else if (ch == "\\") { 699 | escaped = true; 700 | } else if (ch == end) { 701 | break; 702 | } else { 703 | str += ch; 704 | } 705 | } 706 | return str; 707 | } 708 | /* Evaluation blocks 709 | Inside a string, content inside `` will be parsed as if it was normal syntax. 710 | This allows for easier variable/macro integration: "math result: `math(1,2) + 2`" rather than "math result: " + (math(1,2) + 2) 711 | (Although the second option is still available if you need it). 712 | */ 713 | function read_evaled(val) { 714 | // Don't do it if it doesn't need evaluation 715 | if (val.indexOf("`") >= 0) { 716 | 717 | var evalBlock = false, 718 | final = [], 719 | str = ""; 720 | var arr = val.split(''); 721 | 722 | for (var i = 0; i < arr.length; i++) { 723 | var ch = arr[i]; 724 | 725 | // Currently in an eval block 726 | if (evalBlock) { 727 | if (ch == '`') { 728 | evalBlock = false; 729 | // Parse the whole thing as if it was a full code block 730 | var parsedEval = Parser(TokenStream(InputStream(str))); 731 | if (parsedEval.prog.length != 0) { 732 | for (var x = 0; x < parsedEval.prog.length; x++) { 733 | if (parsedEval.prog[x].type == "comment") { 734 | input.croak("Comments are not allowed in evaluation blocks"); 735 | } else if (parsedEval.prog[x].type == "function") { 736 | input.croak("Functions are not allowed in evaluation blocks"); 737 | } else if (parsedEval.prog[x].type == "macro") { 738 | input.croak("Creating macros is not allowed in evaluation blocks"); 739 | } 740 | } 741 | final.push(parsedEval); 742 | } 743 | str = ""; 744 | } else { 745 | str += ch; 746 | 747 | if (i == arr.length - 1) { 748 | if (str) final.push({ 749 | type: "str", 750 | value: str 751 | }); 752 | } 753 | } 754 | } 755 | // Don't evalBlock 756 | else { 757 | if (ch == '`') { 758 | evalBlock = true; 759 | if (str) final.push({ 760 | type: "str", 761 | value: str 762 | }); 763 | str = ""; 764 | } else { 765 | str += ch; 766 | if (i == arr.length - 1) { 767 | if (str) final.push({ 768 | type: "str", 769 | value: str 770 | }); 771 | } 772 | } 773 | } 774 | } 775 | return final; 776 | } else { 777 | return val; 778 | } 779 | } 780 | 781 | function read_string() { 782 | return { 783 | type: "str", 784 | value: read_evaled(read_escaped('"')) 785 | }; 786 | } 787 | 788 | function selector_or_setting() { 789 | var lastNewLine = input.lastWasNewLine(); 790 | input.next(); 791 | if (input.peek() == '!') { 792 | if (!lastNewLine) input.croak('Settings with "@!" need to start at the begining of a line'); 793 | return read_settings(); 794 | } else { 795 | return read_selector(); 796 | } 797 | } 798 | 799 | function read_selector() { 800 | var output = read_while(function(ch) { 801 | return (!is_whitespace(ch) && ch != ';') 802 | }); 803 | 804 | return { 805 | type: 'selector', 806 | value: '@' + output 807 | }; 808 | } 809 | 810 | function read_settings() { 811 | var output = read_while(function(ch) { 812 | return ch != "\n" 813 | }); 814 | return { 815 | type: "setting", 816 | value: output.replace('\r', '') 817 | }; 818 | } 819 | 820 | function read_relative() { 821 | var val = read_while(function(ch) { 822 | return (!is_whitespace(ch) && ch != ";"); 823 | }); 824 | return { 825 | type: 'relative', 826 | value: val 827 | }; 828 | } 829 | 830 | function read_colon() { 831 | input.next(); 832 | return { 833 | type: 'colon' 834 | }; 835 | } 836 | // Read comments that need to be added (#) 837 | function read_comment() { 838 | if (!input.lastWasNewLine()) input.croak('Comments with "#" need to start at the begining of a line'); 839 | var output = read_while(function(ch) { 840 | return ch != "\n" 841 | }); 842 | return { 843 | type: "comment", 844 | value: output.replace('\r', '') 845 | }; 846 | } 847 | 848 | function skip_comment() { 849 | read_while(function(ch) { 850 | return ch != "\n" 851 | }); 852 | input.next(); 853 | } 854 | // Check whether or not the line is a // comment, skip it if so 855 | function check_comment() { 856 | var output = read_while(function(ch) { 857 | return ch == "/" 858 | }); 859 | if (output == '//') { 860 | skip_comment(); 861 | } else { 862 | input.next(); 863 | } 864 | } 865 | // Read the next character, assign tokens 866 | function read_next() { 867 | read_while(is_whitespace); 868 | if (input.eof()) return null; 869 | var ch = input.peek(); 870 | if (ch == "#") { 871 | return read_comment(); 872 | } 873 | if (ch == "/") { 874 | check_comment(); 875 | return read_next(); 876 | } 877 | if (ch == "@") return selector_or_setting(); 878 | if (ch == '"') return read_string(); 879 | if (ch == "~") return read_relative(); 880 | if (ch == ":") return read_colon(); 881 | if (is_digit(ch)) return read_number(); 882 | if (is_id_start(ch)) return read_ident(); 883 | if (is_punc(ch)) return { 884 | type: "punc", 885 | value: input.next() 886 | }; 887 | if (is_op_char(ch)) return { 888 | type: "op", 889 | value: read_while(is_op_char) 890 | }; 891 | 892 | input.croak("Can't handle character: " + ch); 893 | } 894 | 895 | function peek() { 896 | return current || (current = read_next()); 897 | } 898 | 899 | function last() { 900 | return lastVal; 901 | } 902 | 903 | function next() { 904 | lastVal = peek(); 905 | var tok = current; 906 | current = null; 907 | return tok || read_next(); 908 | } 909 | 910 | function eof() { 911 | return peek() == null; 912 | } 913 | } 914 | 915 | // Parser (actually parses data) 916 | var FALSE = { 917 | type: "bool", 918 | value: false 919 | }; 920 | 921 | function Parser(input) { 922 | // Order of math operations, greater means included first 923 | var PRECEDENCE = { 924 | "=": 1, 925 | "||": 2, 926 | "&&": 3, 927 | "<": 7, 928 | ">": 7, 929 | "<=": 7, 930 | ">=": 7, 931 | "==": 7, 932 | "!=": 7, 933 | "+": 10, 934 | "-": 10, 935 | "*": 20, 936 | "/": 20, 937 | "%": 20, 938 | "^": 30, 939 | }; 940 | return parse_toplevel(); 941 | 942 | function is_punc(ch) { 943 | var tok = input.peek(); 944 | return tok && tok.type == "punc" && (!ch || tok.value == ch) && tok; 945 | } 946 | 947 | function is_kw(kw) { 948 | var tok = input.peek(); 949 | return tok && tok.type == "kw" && (!kw || tok.value == kw) && tok; 950 | } 951 | 952 | function is_op(op) { 953 | var tok = input.peek(); 954 | return tok && tok.type == "op" && (!op || tok.value == op) && tok; 955 | } 956 | 957 | function is_comment() { 958 | var tok = input.peek(); 959 | return tok && tok.type == "comment"; 960 | } 961 | 962 | function is_reg() { 963 | var tok = input.peek(); 964 | return tok && tok.type == "reg"; 965 | } 966 | 967 | function skip_punc(ch) { 968 | if (is_punc(ch)) input.next(); 969 | else input.croak("Expecting punctuation: \"" + ch + "\""); 970 | } 971 | 972 | function skip_comment(ch) { 973 | if (is_comment()) parse_comment(); 974 | input.next(); 975 | } 976 | 977 | function skip_kw(kw) { 978 | if (is_kw(kw)) input.next(); 979 | else input.croak("Expecting keyword: \"" + kw + "\""); 980 | } 981 | 982 | function skip_op(op) { 983 | if (is_op(op)) input.next(); 984 | else input.croak("Expecting operator: \"" + op + "\""); 985 | } 986 | 987 | function skip_comma() { 988 | if (is_punc(",")) { 989 | input.next(); 990 | return { 991 | type: "comma" 992 | }; 993 | } else input.croak("Expecting comma: \"" + JSON.stringify(input.peek()) + "\"") 994 | } 995 | 996 | function unexpected() { 997 | input.croak("Unexpected token: " + JSON.stringify(input.peek())); 998 | } 999 | // Check whether or not to parse this through binary operations 1000 | function maybe_binary(left, my_prec) { 1001 | var tok = is_op(); 1002 | if (tok) { 1003 | var his_prec = PRECEDENCE[tok.value]; 1004 | if (his_prec > my_prec) { 1005 | input.next(); 1006 | return maybe_binary({ 1007 | type: tok.value == "=" ? "assign" : "binary", 1008 | operator: tok.value, 1009 | left: left, 1010 | right: maybe_binary(parse_atom(), his_prec) 1011 | }, my_prec); 1012 | } 1013 | } 1014 | return left; 1015 | } 1016 | // Parses through anything between start and stop, with separator, using the given parser 1017 | function delimited(start, stop, separator, parser) { 1018 | var a = [], 1019 | first = true; 1020 | skip_punc(start); 1021 | while (!input.eof()) { 1022 | if (is_punc(stop)) break; 1023 | if (first) first = false; 1024 | else if (check_last()) skip_punc(separator); 1025 | if (is_punc(stop)) break; 1026 | a.push(parser()); 1027 | } 1028 | skip_punc(stop); 1029 | return a; 1030 | } 1031 | 1032 | function parse_call(func) { 1033 | return { 1034 | type: "call", 1035 | func: func, 1036 | args: delimited("(", ")", ",", parse_expression), 1037 | }; 1038 | } 1039 | // Variable names can't be ivar nor keyword, check that it's a reg 1040 | function parse_varname() { 1041 | var name = input.next(); 1042 | if (name.type != "ivar") input.croak("Expecting variable name"); 1043 | return name.value; 1044 | } 1045 | // Parse if statements, add elseif if there are some, and add else if there is one 1046 | function parse_if() { 1047 | skip_kw("if"); 1048 | var cond = parse_expression(); 1049 | var then = parse_expression(); 1050 | var ret = { 1051 | type: "if", 1052 | cond: cond, 1053 | then: then, 1054 | }; 1055 | if (is_kw("else")) { 1056 | input.next(); 1057 | ret.else = parse_expression(); 1058 | } 1059 | return ret; 1060 | } 1061 | // Parse a var declaration 1062 | function parse_var() { 1063 | skip_kw("var"); 1064 | return { 1065 | type: 'var', 1066 | value: parse_varname() 1067 | }; 1068 | } 1069 | // Parse a for loop 1070 | function parse_for() { 1071 | //input.croak("For loops are currently not supported"); 1072 | skip_kw("for"); 1073 | var params = delimited("(", ")", ";", parse_expression); 1074 | var then = parse_expression(); 1075 | return { 1076 | type: "for", 1077 | params: params, 1078 | then: then, 1079 | }; 1080 | } 1081 | // Parse a foreach loop 1082 | function parse_foreach() { 1083 | skip_kw("foreach"); 1084 | skip_punc("("); 1085 | skip_kw("var"); 1086 | var varName = parse_ivar(); 1087 | skip_kw("in"); 1088 | var param = parse_expression(); 1089 | skip_punc(")"); 1090 | var then = parse_expression(); 1091 | return { 1092 | type: "foreach", 1093 | variable: varName, 1094 | param: param, 1095 | then: then 1096 | }; 1097 | 1098 | } 1099 | /* Parse a function 1100 | This can be taken in two ways: 1101 | 1. actual function declaration ( function name { } ) 1102 | 2. minecraft function command ( function name [if/unless...] ) 1103 | 1104 | Therefore, testing if there are reg arguments following it, if so, it's Option 2. 1105 | */ 1106 | function parse_function() { 1107 | // Skip the function keyword 1108 | input.next(); 1109 | // Get the name of the function 1110 | var name = input.next(); 1111 | // Check if what's afterwards is a call 1112 | if (input.peek().type == 'colon') { 1113 | var obj = { 1114 | type: "command", 1115 | value: [{ 1116 | type: "reg", 1117 | value: "function" 1118 | }, 1119 | name 1120 | ] 1121 | }; 1122 | // Loop through it to add all of the arguments 1123 | while (!input.eof()) { 1124 | if (input.peek().type == "kw") 1125 | obj.value.push(input.next()); 1126 | else 1127 | obj.value.push(parse_expression()); 1128 | 1129 | 1130 | if (is_punc(';')) break; 1131 | else if (input.eof()) skip_punc(';'); 1132 | } 1133 | return obj; 1134 | } else { 1135 | // It's not a call to a function, parse it as a normal function 1136 | return { 1137 | type: "function", 1138 | name: name.value, 1139 | body: parse_expression() 1140 | }; 1141 | } 1142 | } 1143 | // Parsing a group (sub namespaces/folders) 1144 | function parse_group() { 1145 | input.next(); 1146 | return { 1147 | type: "group", 1148 | name: input.next().value, 1149 | body: parse_expression() 1150 | }; 1151 | } 1152 | // Parse a macro, basically a function with parameters 1153 | function parse_macro() { 1154 | input.next(); 1155 | return { 1156 | type: "macro", 1157 | name: input.next().value, 1158 | vars: delimited("(", ")", ",", parse_varname), 1159 | body: parse_expression() 1160 | }; 1161 | } 1162 | // Return statements 1163 | function parse_return() { 1164 | input.next(); 1165 | return { 1166 | type: "return", 1167 | value: parse_expression() 1168 | }; 1169 | } 1170 | 1171 | function parse_execute() { 1172 | input.next(); 1173 | var final = { 1174 | type: 'execute', 1175 | selector: '', 1176 | pos: [] 1177 | }; 1178 | var tokenCount = 0; 1179 | while (!input.eof()) { 1180 | if (tokenCount == 5) { 1181 | break; 1182 | } else { 1183 | var expr = parse_expression(); 1184 | if (tokenCount == 0) final.selector = expr; 1185 | else if (tokenCount > 0 && tokenCount < 4) final.pos.push(expr); 1186 | else if (tokenCount == 4) final.prog = expr; 1187 | tokenCount++; 1188 | } 1189 | } 1190 | return final; 1191 | } 1192 | // Bool just checks if the value is "true" 1193 | function parse_bool() { 1194 | return { 1195 | type: "bool", 1196 | value: input.next().value == "true" 1197 | }; 1198 | } 1199 | // Parse an ivar and detect whether or not it's asking for an index of an array 1200 | function parse_ivar() { 1201 | var ivar = input.next(); 1202 | if (is_punc("[")) { 1203 | skip_punc("["); 1204 | while (!input.eof()) { 1205 | if (is_punc("]")) break; 1206 | if (input.peek().type == "kw") unexpected(); 1207 | ivar.index = parse_expression(); 1208 | if (is_punc("]")) break; 1209 | else input.croak('Expecting punctuation: "]"'); 1210 | } 1211 | skip_punc("]"); 1212 | } 1213 | return ivar; 1214 | } 1215 | // Parsing an array declaration [5, "string", false] 1216 | function parse_array() { 1217 | return { 1218 | type: "array", 1219 | value: delimited("[", "]", ",", function custom_parser() { 1220 | var i = input.peek(); 1221 | // Don't allow keywords such as function, var... inside an array declaration 1222 | // Macro is allowed to allow for variable functions (lambdas) ($fn = macro call () { something; }) 1223 | if (i.type == "kw" && i.value != "false" && i.value != "true" && i.value != "macro") unexpected(); 1224 | else return parse_expression(); 1225 | }) 1226 | }; 1227 | } 1228 | // Parsing a comment as an actual comment 1229 | function parse_comment() { 1230 | return { 1231 | type: "comment", 1232 | value: input.next().value 1233 | }; 1234 | } 1235 | 1236 | function parse_setting() { 1237 | var setting = input.next().value.trim(); 1238 | var indexSeparator = setting.indexOf(':'); 1239 | if (indexSeparator == -1) input.croak('Expecting separator: ":"'); 1240 | return { 1241 | type: 'setting', 1242 | name: setting.substring(setting.indexOf('!') + 1, indexSeparator).trim(), 1243 | value: setting.substring(indexSeparator + 1).trim() 1244 | }; 1245 | } 1246 | // Relatives need to check for variables inside 1247 | function parse_relative() { 1248 | // Parse the relative's content 1249 | var quickInput = TokenStream(InputStream(input.next().value.substring(1))); 1250 | var final = []; 1251 | while (!quickInput.eof()) { 1252 | final.push(quickInput.next()); 1253 | } 1254 | return { 1255 | type: 'relative', 1256 | value: final 1257 | }; 1258 | } 1259 | // Selectors need to check for variables inside 1260 | function parse_selector() { 1261 | var next = input.next(); 1262 | var final = []; 1263 | // If the selector has arguments, parse them 1264 | if (next.value.indexOf("[") > -1) { 1265 | var valueToParse = next.value.substring(3, next.value.length - 1); 1266 | var quickInput = TokenStream(InputStream(valueToParse)); 1267 | while (!quickInput.eof()) { 1268 | final.push(quickInput.next()); 1269 | } 1270 | } 1271 | return { 1272 | type: 'selector', 1273 | prefix: next.value.substring(0, 2), 1274 | value: final 1275 | }; 1276 | } 1277 | /* Parsing reg is complicated 1278 | 1279 | Most of the time, a reg is simply a minecraft command and its arguments, it checks if it's an actual command, and if so, it returns the full command 1280 | Sometimes, it's the name of a macro or other function calling, if so return the exact token 1281 | If it's neither of those, then it's unexpected 1282 | */ 1283 | var lala; 1284 | 1285 | function parse_reg() { 1286 | // Regs are commands and command arguments 1287 | var final = { 1288 | type: "command", 1289 | value: [] 1290 | }; 1291 | if (availableCommands.includes(input.peek().value)) { 1292 | final.value.push(input.next()); 1293 | while (!input.eof()) { 1294 | var next = parse_expression(); 1295 | final.value.push(next); 1296 | 1297 | if (is_punc(';')) break; 1298 | else if (input.eof()) skip_punc(';'); 1299 | } 1300 | return final; 1301 | } else { 1302 | return input.next(); 1303 | //unexpected(); 1304 | } 1305 | } 1306 | // Check whether or not the given token is a JSON or a program 1307 | function json_or_prog() { 1308 | skip_punc("{"); 1309 | var a = {}, 1310 | first = true; 1311 | // Check if the next item is a string 1312 | if (input.peek().type == "str") { 1313 | a = { 1314 | type: "json", 1315 | value: [] 1316 | }; 1317 | while (!input.eof()) { 1318 | if (is_punc("}")) break; 1319 | if (first) first = false; 1320 | else if (input.peek().type == "colon") first = true; 1321 | else a.value.push(skip_comma());; 1322 | if (is_punc("}")) break; 1323 | a.value.push(parse_expression()); 1324 | } 1325 | skip_punc("}"); 1326 | return a; 1327 | } 1328 | // Regular program 1329 | else { 1330 | a = { 1331 | type: "prog", 1332 | prog: [] 1333 | }; 1334 | while (!input.eof()) { 1335 | if (is_punc("}")) break; 1336 | if (first) first = false; 1337 | else if (check_last()) skip_punc(";"); 1338 | if (is_punc("}")) break; 1339 | a.prog.push(parse_expression()); 1340 | } 1341 | skip_punc("}"); 1342 | if (a.prog.length == 0) return FALSE; 1343 | if (a.prog.length == 1) return a.prog[0]; 1344 | return a; 1345 | } 1346 | } 1347 | 1348 | function maybe_call(expr) { 1349 | expr = expr(); 1350 | return is_punc("(") ? parse_call(expr) : expr; 1351 | } 1352 | // Major parser, checks what the token is an tells it to how to parse it 1353 | function parse_atom() { 1354 | return maybe_call(function() { 1355 | if (is_punc("(")) { 1356 | input.next(); 1357 | var exp = parse_expression(); 1358 | skip_punc(")"); 1359 | return exp; 1360 | } 1361 | if (is_punc("{")) return json_or_prog(); 1362 | if (is_punc("[")) return parse_array(); 1363 | if (is_kw("if")) return parse_if(); 1364 | if (is_kw("var")) return parse_var(); 1365 | if (is_kw("true") || is_kw("false")) return parse_bool(); 1366 | if (is_kw("for")) return parse_for(); 1367 | if (is_kw("foreach")) return parse_foreach(); 1368 | if (is_kw("function")) return parse_function(); 1369 | if (is_kw("group")) return parse_group(); 1370 | if (is_kw("execute")) return parse_execute(); 1371 | if (is_kw("macro")) return parse_macro(); 1372 | 1373 | if (is_kw("return")) return parse_return(); 1374 | if (is_comment()) return parse_comment(); 1375 | if (input.peek().type == 'reg') return parse_reg(); 1376 | if (input.peek().type == 'setting') return parse_setting(); 1377 | if (input.peek().type == "ivar") return parse_ivar(); 1378 | if (input.peek().type == 'relative') return parse_relative(); 1379 | if (input.peek().type == 'selector') return parse_selector(); 1380 | 1381 | var tok = input.next(); 1382 | if (tok.type == 'colon' || tok.type == "num" || tok.type == "str") 1383 | return tok; 1384 | unexpected(); 1385 | }); 1386 | } 1387 | // Utility to check whether or not the last one was a comment or a setting (use it to prevent requirement of semicolon) 1388 | function check_last() { 1389 | return (!input.last() || (input.last() && input.last().type != "comment" && input.last().type != "setting")); 1390 | } 1391 | // Parsing a program/top level 1392 | function parse_toplevel() { 1393 | var prog = []; 1394 | while (!input.eof()) { 1395 | prog.push(parse_expression()); 1396 | // Comments are special because they don't require a ; at the end, so we need to check that it's not a comment 1397 | if (is_comment()) { 1398 | prog.push(parse_comment()); 1399 | } else if (!input.eof() && check_last()) skip_punc(";"); 1400 | } 1401 | return { 1402 | type: "prog", 1403 | prog: prog 1404 | }; 1405 | } 1406 | /* UNUSED but keep for clarity 1407 | // Parse through a full program 1408 | function parse_prog() { 1409 | var prog = delimited("{", "}", ";", parse_expression); 1410 | if (prog.length == 0) return FALSE; 1411 | if (prog.length == 1) return prog[0]; 1412 | return { type: "prog", prog: prog };}*/ 1413 | // Parse through everything, parse binary and calls just in case 1414 | function parse_expression() { 1415 | return maybe_call(function() { 1416 | return maybe_binary(parse_atom(), 0); 1417 | }); 1418 | } 1419 | } 1420 | 1421 | function range_macro(min, max, delta) { 1422 | // range(n) creates a array from 1 to n, including n. 1423 | // range(m,n) creates a array from m to n, including n. 1424 | // range(n,m,delta) creates a array from n to m, by step of delta. May not include max 1425 | 1426 | var arr = []; 1427 | var myStepCount; 1428 | 1429 | if (arguments.length === 1) { 1430 | for (var ii = 0; ii < min; ii++) { 1431 | arr[ii] = ii + 1; 1432 | }; 1433 | } else { 1434 | if (arguments.length === 2) { 1435 | myStepCount = (max - min); 1436 | for (var ii = 0; ii <= myStepCount; ii++) { 1437 | arr.push(ii + min); 1438 | }; 1439 | } else { 1440 | myStepCount = Math.floor((max - min) / delta); 1441 | for (var ii = 0; ii <= myStepCount; ii++) { 1442 | arr.push(ii * delta + min); 1443 | }; 1444 | } 1445 | } 1446 | 1447 | return arr; 1448 | 1449 | } 1450 | 1451 | /* This will appear at the bottom/end of the final dist file */ 1452 | 1453 | // Get the abstract syntax tree from the input 1454 | var ast = Parser(TokenStream(InputStream(input))); 1455 | 1456 | 1457 | // Evaluate and Compile 1458 | var output = Compiler(ast); 1459 | 1460 | return output; 1461 | }; 1462 | 1463 | mcs.help = function help() { 1464 | console.log([ 1465 | ' mcs', 1466 | ' A pre-processor to write Minecraft Functions more efficiently', 1467 | '', 1468 | ' Usage:', 1469 | ' var output = mcs(input) - Converts MCS language into an object of function files', 1470 | '', 1471 | ' Check the GitHub repository for more info: https://github.com/PandawanFr/mcs' 1472 | ]); 1473 | 1474 | } 1475 | 1476 | return mcs; 1477 | })(); 1478 | 1479 | /* Browser/AMD/NodeJS handling */ 1480 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 1481 | module.exports = mcs; 1482 | } else { 1483 | if (typeof define === 'function' && define.amd) { 1484 | define([], function() { 1485 | return mcs; 1486 | }); 1487 | } else { 1488 | window.mcs = mcs; 1489 | } 1490 | } 1491 | 1492 | })(); -------------------------------------------------------------------------------- /dist/mcs.min.js: -------------------------------------------------------------------------------- 1 | "use strict";(function(){var e=function(){var e=function e(r){if(!r||typeof r!=="string"){var n="Error: Object to parse is not a valid string or does not exist.";console.error(n);return n}function t(e){var r=true,n=true,t="",a=false,u=[],i="",o="",f=[];function s(e){this.vars=Object.create(e?e.vars:null);this.parent=e}s.prototype={extend:function e(){return new s(this)},lookup:function e(r){var n=this;while(n){if(Object.prototype.hasOwnProperty.call(n.vars,r))return n;n=n.parent}},get:function e(r){if(r in this.vars)return this.vars[r];p("Undefined variable "+r)},set:function e(r,n){var t=this.lookup(r);if(!t&&this.parent)p("Undefined variable "+r);return(t||this).vars[r]=n},def:function e(r,n){return this.vars[r]=n}};var c=new s;var v={};L(e,c);return v;function p(e){if(r)console.error(e);throw new Error(e)}function y(e,r,n){function t(e){if(typeof e!="number")p("Expected number but got "+e);return e}function a(e){if(t(e)==0)p("Divide by zero");return e}if(e=="+"&&(typeof r=="string"||typeof n=="string")){return r+n}switch(e){case"+":return t(r)+t(n);case"-":return t(r)-t(n);case"*":return t(r)*t(n);case"/":return t(r)/a(n);case"%":return t(r)%a(n);case"^":return Math.pow(t(r),t(n));case"&&":return r!==false&&n;case"||":return r!==false?r:n;case"<":return t(r)":return t(r)>t(n);case"<=":return t(r)<=t(n);case">=":return t(r)>=t(n);case"==":return r===n;case"!=":return r!==n}p("Can't apply operator "+e)}function h(e,r){function n(e){if(e.type=="command")_(r,e);else if(e.type=="return"||e.type=="if"){return L(e,r)}else L(e,r)}if(e.type=="prog"){for(var t=0;t0){var n="~";for(var t=0;t0){var n=r.prefix+"[";for(var t=0;t0?f.join(""):"";M(i,o+n+"\n");return n}function P(e,r){var n="";r.prog.forEach(function(r){if(r.type=="command"){var t=L(r,e);n+=t+"\n"}else if(r.type=="return"){var a=L(r,e);n+=a}else L(r,e)});return n}function F(e,r){if(a)p("Cannot declare a function inside another");a=true;i=r.name;L(r.body,e.extend());i="";a=false}function J(e,r){if(a)p("Groups cannot be inside functions");u.push(r.name);L(r.body,e.extend());u.pop()}function U(e,r){if(e.parent!=null)p("Settings must be declared in the root");if(r.name=="namespace"){if(o&&o!="namespace")p("Cannot declare namespace more than once");if(o&&o=="_namespace")p("Please declare the namespace BEFORE writing any functions");o=r.value}else{p("No setting found with the name "+r.name)}}function M(e,r){if(!o)o="_namespace";if(!v[o])v[o]={_type:"namespace"};var n=v[o];if(u){u.forEach(function(e){if(!n[e])n[e]={_type:"group"};n=n[e]});if(n.hasOwnProperty(e)){n[e].value+=r}else{n[e]={_type:"function",value:r}}}else{if(n.hasOwnProperty(e)){n[e]+=r}else{n[e]={_type:"function",value:r}}}}function z(e){for(var r in e){if(e.hasOwnProperty(r))return false}return JSON.stringify(e)===JSON.stringify({})}function L(e,r){switch(e.type){case"num":case"bool":case"kw":return e.value;case"str":return w(r,e);case"eval":return L(e.value,r.extend());case"colon":return":";case"relative":return O(r,e);case"selector":return E(r,e);case"comma":return",";case"json":return A(r,e);case"reg":return d(r,e);case"comment":return S(r,e);case"command":return _(r,e);case"array":return C(r,e);case"ivar":return N(r,e);case"assign":return j(r,e);case"binary":return y(e.operator,L(e.left,r),L(e.right,r));case"macro":return g(r,e);case"return":return L(e.value,r);case"if":return m(r,e);case"for":return x(r,e);case"foreach":return k(r,e);case"execute":return b(r,e);case"function":return F(r,e);case"group":return J(r,e);case"setting":return U(r,e);case"prog":return P(r,e);case"call":var n=L(e.func,r);return n.apply(null,e.args.map(function(e){return L(e,r)}));default:p("Unable to evaluate "+e.type)}}}var a=["advancement","ban","blockdata","clear","clone","debug","defaultgamemode","deop","difficulty","effect","enchant","entitydata","execute","fill","function","gamemode","gamerule","give","help","kick","kill","list","locate","me","op","pardon","particle","playsound","publish","recipe","reload","replaceitem","save","say","scoreboard","seed","setblock","setidletimeout","setmaxplayers","setworldspawn","spawnpoint","spreadplayers","stats","stop","stopsound","summon","teleport","tell","tellraw","testfor","testforblock","testforblocks","time","title","toggledownfall","tp","transferserver","trigger","weather","whitelist","worldborder","wsserver","xp"];function u(e){var r=0,n=1,t=0,a=null,u=true;return{next:i,peek:l,eof:s,croak:c,last:o,lastWasNewLine:f};function i(){if(l()=="\n")u=true;else if("\r\t ".indexOf(l())==-1)u=false;a=l();var i=e.charAt(r++);if(i=="\n")n++,t=0;else t++;return i}function o(){return a}function f(){return u}function l(){return e.charAt(r)}function s(){return l()==""}function c(e){var r=e+" at ("+n+":"+t+")";console.error(r);throw new Error(r)}}function i(e){var r=null;var n=" function macro group if elseif else return execute true false var for foreach in ";var t=null;return{next:J,peek:P,eof:U,croak:e.croak,last:F};function a(e){return n.indexOf(" "+e+" ")>=0}function o(e){return/[0-9]/i.test(e)}function l(e){return/[a-z0-9_\$]/i.test(e)}function s(e){return l(e)}function c(e){return/\$[a-z0-9-_]/i.test(e)}function v(e){return"+-*/%^=&|<>!".indexOf(e)>=0}function p(e){return",;(){}[]".indexOf(e)>=0}function y(e){return" \t\n\r".indexOf(e)>=0}function h(r){var n="";while(!e.eof()&&r(e.peek())){n+=e.next()}return n}function g(){e.next();if(o(e.peek())){var r=d();r.value*=-1;return r}e.croak("Can't handle character: "+e.peek())}function d(){var e=false;var r=h(function(r){if(r=="."){if(e)return false;e=true;return true}return o(r)});return{type:"num",value:parseFloat(r)}}function m(){var e=h(s);var r;if(a(e))r="kw";else if(c(e))r="ivar";else r="reg";return{type:r,value:e}}function x(r){var n=false,t="";e.next();while(!e.eof()){var a=e.next();if(n){t+=a;n=false}else if(a=="\\"){n=true}else if(a==r){break}else{t+=a}}return t}function k(r){if(r.indexOf("`")>=0){var n=false,t=[],a="";var o=r.split("");for(var l=0;l":7,"<=":7,">=":7,"==":7,"!=":7,"+":10,"-":10,"*":20,"/":20,"%":20,"^":30};return D();function n(r){var n=e.peek();return n&&n.type=="punc"&&(!r||n.value==r)&&n}function t(r){var n=e.peek();return n&&n.type=="kw"&&(!r||n.value==r)&&n}function f(r){var n=e.peek();return n&&n.type=="op"&&(!r||n.value==r)&&n}function l(){var r=e.peek();return r&&r.type=="comment"}function s(){var r=e.peek();return r&&r.type=="reg"}function c(r){if(n(r))e.next();else e.croak('Expecting punctuation: "'+r+'"')}function v(r){if(l())J();e.next()}function p(r){if(t(r))e.next();else e.croak('Expecting keyword: "'+r+'"')}function y(r){if(f(r))e.next();else e.croak('Expecting operator: "'+r+'"')}function h(){if(n(",")){e.next();return{type:"comma"}}else e.croak('Expecting comma: "'+JSON.stringify(e.peek())+'"')}function g(){e.croak("Unexpected token: "+JSON.stringify(e.peek()))}function d(n,t){var a=f();if(a){var u=r[a.value];if(u>t){e.next();return d({type:a.value=="="?"assign":"binary",operator:a.value,left:n,right:d($(),u)},t)}}return n}function m(r,t,a,u){var i=[],o=true;c(r);while(!e.eof()){if(n(t))break;if(o)o=false;else if(B())c(a);if(n(t))break;i.push(u())}c(t);return i}function x(e){return{type:"call",func:e,args:m("(",")",",",H)}}function k(){var r=e.next();if(r.type!="ivar")e.croak("Expecting variable name");return r.value}function b(){p("if");var r=H();var n=H();var a={type:"if",cond:r,then:n};if(t("else")){e.next();a.else=H()}return a}function w(){p("var");return{type:"var",value:k()}}function O(){p("for");var e=m("(",")",";",H);var r=H();return{type:"for",params:e,then:r}}function E(){p("foreach");c("(");p("var");var e=P();p("in");var r=H();c(")");var n=H();return{type:"foreach",variable:e,param:r,then:n}}function C(){e.next();var r=e.next();if(e.peek().type=="colon"){var t={type:"command",value:[{type:"reg",value:"function"},r]};while(!e.eof()){if(e.peek().type=="kw")t.value.push(e.next());else t.value.push(H());if(n(";"))break;else if(e.eof())c(";")}return t}else{return{type:"function",name:r.value,body:H()}}}function N(){e.next();return{type:"group",name:e.next().value,body:H()}}function j(){e.next();return{type:"macro",name:e.next().value,vars:m("(",")",",",k),body:H()}}function A(){e.next();return{type:"return",value:H()}}function S(){e.next();var r={type:"execute",selector:"",pos:[]};var n=0;while(!e.eof()){if(n==5){break}else{var t=H();if(n==0)r.selector=t;else if(n>0&&n<4)r.pos.push(t);else if(n==4)r.prog=t;n++}}return r}function _(){return{type:"bool",value:e.next().value=="true"}}function P(){var r=e.next();if(n("[")){c("[");while(!e.eof()){if(n("]"))break;if(e.peek().type=="kw")g();r.index=H();if(n("]"))break;else e.croak('Expecting punctuation: "]"')}c("]")}return r}function F(){return{type:"array",value:m("[","]",",",function r(){var n=e.peek();if(n.type=="kw"&&n.value!="false"&&n.value!="true"&&n.value!="macro")g();else return H()})}}function J(){return{type:"comment",value:e.next().value}}function U(){var r=e.next().value.trim();var n=r.indexOf(":");if(n==-1)e.croak('Expecting separator: ":"');return{type:"setting",name:r.substring(r.indexOf("!")+1,n).trim(),value:r.substring(n+1).trim()}}function M(){var r=i(u(e.next().value.substring(1)));var n=[];while(!r.eof()){n.push(r.next())}return{type:"relative",value:n}}function z(){var r=e.next();var n=[];if(r.value.indexOf("[")>-1){var t=r.value.substring(3,r.value.length-1);var a=i(u(t));while(!a.eof()){n.push(a.next())}}return{type:"selector",prefix:r.value.substring(0,2),value:n}}var L;function W(){var r={type:"command",value:[]};if(a.includes(e.peek().value)){r.value.push(e.next());while(!e.eof()){var t=H();r.value.push(t);if(n(";"))break;else if(e.eof())c(";")}return r}else{return e.next()}}function G(){c("{");var r={},t=true;if(e.peek().type=="str"){r={type:"json",value:[]};while(!e.eof()){if(n("}"))break;if(t)t=false;else if(e.peek().type=="colon")t=true;else r.value.push(h());if(n("}"))break;r.value.push(H())}c("}");return r}else{r={type:"prog",prog:[]};while(!e.eof()){if(n("}"))break;if(t)t=false;else if(B())c(";");if(n("}"))break;r.prog.push(H())}c("}");if(r.prog.length==0)return o;if(r.prog.length==1)return r.prog[0];return r}}function R(e){e=e();return n("(")?x(e):e}function $(){return R(function(){if(n("(")){e.next();var r=H();c(")");return r}if(n("{"))return G();if(n("["))return F();if(t("if"))return b();if(t("var"))return w();if(t("true")||t("false"))return _();if(t("for"))return O();if(t("foreach"))return E();if(t("function"))return C();if(t("group"))return N();if(t("execute"))return S();if(t("macro"))return j();if(t("return"))return A();if(l())return J();if(e.peek().type=="reg")return W();if(e.peek().type=="setting")return U();if(e.peek().type=="ivar")return P();if(e.peek().type=="relative")return M();if(e.peek().type=="selector")return z();var a=e.next();if(a.type=="colon"||a.type=="num"||a.type=="str")return a;g()})}function B(){return!e.last()||e.last()&&e.last().type!="comment"&&e.last().type!="setting"}function D(){var r=[];while(!e.eof()){r.push(H());if(l()){r.push(J())}else if(!e.eof()&&B())c(";")}return{type:"prog",prog:r}}function H(){return R(function(){return d($(),0)})}}function l(e,r,n){var t=[];var a;if(arguments.length===1){for(var u=0;u [output (optional)] - Convert input file to output directory', 20 | ' mcs --debug [output (opt)] - Convert input file to output directory and an output.json', 21 | ' mcs --version - Displays the version number', 22 | ' mcs --help - Display this help menu', 23 | ' Examples:', 24 | ' Convert file.mcs in the current directory', 25 | ' mcs ./file.mcs', 26 | ' Convert file.mcs in the ./output directory', 27 | ' mcs ./file.mcs ./output', 28 | ' ', 29 | ' Check the GitHub repository for more info: https://github.com/PandawanFr/mcs' 30 | ].join('\n')); 31 | } else if (process.argv[2] == '--version' || process.argv[2] == '-v') { 32 | console.log([ 33 | 'mcs v' + module.exports.version 34 | ].join('\n')); 35 | } else if (process.argv[2] == '--debug' || process.argv[2] == '-d') { 36 | run(true); 37 | } else { 38 | run(false); 39 | } 40 | 41 | function run(debug) { 42 | var extraArg = debug ? 1 : 0; 43 | 44 | if (process.argv[2 + extraArg] && /.*(.mcs)$/.test(process.argv[2 + extraArg])) { 45 | input = path.normalize(process.argv[2 + extraArg]); 46 | } else { 47 | console.log(chalk.red('Error: ') + 'No valid input file (.mcs) given!'); 48 | return; 49 | } 50 | 51 | if (process.argv[3 + extraArg]) { 52 | outputDir = path.normalize(process.argv[3 + extraArg]); 53 | } 54 | 55 | fs.readFile(input, 'utf8', function(err, data) { 56 | if (err) { 57 | console.log(err); 58 | return; 59 | } 60 | var output = mcs(data); 61 | if (output) { 62 | // Debug also adds the output.json for what would be a call to mcs directly (rather than using CLI/wrapper) 63 | if (debug) { 64 | writeFile(path.join(outputDir, 'output.json'), JSON.stringify(output, null, '\t')); 65 | } 66 | 67 | var namespace = Object.keys(output)[0]; 68 | recursiveOutput(output[namespace], namespace); 69 | } 70 | }); 71 | } 72 | var currentFinalDir = []; 73 | 74 | function recursiveOutput(data, name) { 75 | // If it's a namespace or a group, add it 76 | if (data._type == "namespace" || data._type == "group") { 77 | // Add name to subdir list 78 | currentFinalDir.push(name); 79 | // Check that it exists, create dir if not 80 | if (!fs.existsSync(path.join(outputDir, currentFinalDir.join('/')))) { 81 | fs.mkdirSync(path.join(outputDir, currentFinalDir.join('/'))); 82 | } 83 | // Recursive for each value 84 | Object.keys(data).forEach(function(element) { 85 | if (element != "_type") recursiveOutput(data[element], element); 86 | }); 87 | // Pop the dir 88 | currentFinalDir.pop(); 89 | } else if (data._type == "function") { 90 | // Write file 91 | writeFile(path.join(outputDir, currentFinalDir.join('/'), name + '.mcfunction'), data.value); 92 | } 93 | } 94 | 95 | function writeFile(file, data) { 96 | fs.writeFile(file, data, 'utf8', function(err) { 97 | if (err) { 98 | console.log(err); 99 | return; 100 | } 101 | 102 | console.log(chalk.green('Success: ') + 'Created file ' + file); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcs", 3 | "version": "2.2.0", 4 | "description": "A pre-processor to write Minecraft Functions more efficiently", 5 | "main": "dist/mcs.min.js", 6 | "scripts": { 7 | "test": "node index.js --debug ./test/new_syntax.mcs ./test/syntax_output/", 8 | "build": "gulp build-all", 9 | "bnt": "npm run build && npm test", 10 | "watch": "gulp" 11 | }, 12 | "keywords": [ 13 | "Minecraft", 14 | "MC", 15 | "Script", 16 | "JS" 17 | ], 18 | "author": "Pandawan", 19 | "license": "MIT", 20 | "repository": "git+https://github.com/PandawanFr/mcs.git", 21 | "bugs": { 22 | "url": "https://github.com/PandawanFr/mcs/issues" 23 | }, 24 | "homepage": "https://github.com/PandawanFr/mcs#readme", 25 | "dependencies": { 26 | "chalk": "^2.1.0", 27 | "pkginfo": "^0.4.1" 28 | }, 29 | "bin": { 30 | "mcs": "./index.js" 31 | }, 32 | "devDependencies": { 33 | "babel-preset-es2015": "^6.24.1", 34 | "gulp": "^3.9.1", 35 | "gulp-babel": "^6.1.2", 36 | "gulp-concat": "^2.6.1", 37 | "gulp-jsbeautifier": "^2.1.1", 38 | "gulp-order": "^1.1.1", 39 | "gulp-rename": "^1.2.2", 40 | "gulp-uglify": "^3.0.0", 41 | "pump": "^1.0.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/back.js: -------------------------------------------------------------------------------- 1 | /* This will appear at the bottom/end of the final dist file */ 2 | 3 | // Get the abstract syntax tree from the input 4 | var ast = Parser(TokenStream(InputStream(input))); 5 | 6 | 7 | // Evaluate and Compile 8 | var output = Compiler(ast); 9 | 10 | return output; 11 | }; 12 | 13 | mcs.help = function help() { 14 | console.log([ 15 | ' mcs', 16 | ' A pre-processor to write Minecraft Functions more efficiently', 17 | '', 18 | ' Usage:', 19 | ' var output = mcs(input) - Converts MCS language into an object of function files', 20 | '', 21 | ' Check the GitHub repository for more info: https://github.com/PandawanFr/mcs' 22 | ]); 23 | 24 | } 25 | 26 | return mcs; 27 | })(); 28 | 29 | /* Browser/AMD/NodeJS handling */ 30 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 31 | module.exports = mcs; 32 | } else { 33 | if (typeof define === 'function' && define.amd) { 34 | define([], function() { 35 | return mcs; 36 | }); 37 | } else { 38 | window.mcs = mcs; 39 | } 40 | } 41 | 42 | })(); 43 | -------------------------------------------------------------------------------- /src/compiler.js: -------------------------------------------------------------------------------- 1 | /* Compile the AST into a final JSON object */ 2 | 3 | function Compiler(exp) { 4 | 5 | var debug = true, 6 | oldDebug = true, 7 | addTop = "", 8 | inFunc = false, 9 | current = [], 10 | currentFunc = '', 11 | namespace = '', 12 | prefix = []; 13 | 14 | // Environment is used to remember/manage the scope 15 | function Environment(parent) { 16 | this.vars = Object.create(parent ? parent.vars : null); 17 | this.parent = parent; 18 | } 19 | Environment.prototype = { 20 | extend: function() { 21 | return new Environment(this); 22 | }, 23 | lookup: function(name) { 24 | var scope = this; 25 | while (scope) { 26 | if (Object.prototype.hasOwnProperty.call(scope.vars, name)) 27 | return scope; 28 | scope = scope.parent; 29 | } 30 | }, 31 | get: function(name) { 32 | if (name in this.vars) 33 | return this.vars[name]; 34 | err("Undefined variable " + name); 35 | }, 36 | set: function(name, value) { 37 | var scope = this.lookup(name); 38 | // let's not allow defining globals from a nested environment 39 | if (!scope && this.parent) 40 | err("Undefined variable " + name); 41 | return (scope || this).vars[name] = value; 42 | }, 43 | def: function(name, value) { 44 | return this.vars[name] = value; 45 | } 46 | }; 47 | 48 | // Declare global/root scope 49 | var env = new Environment(); 50 | 51 | var output = {}; 52 | evaluate(exp, env); 53 | 54 | return output; 55 | 56 | 57 | // Actual logic 58 | 59 | function err(msg) { 60 | if (debug) console.error(msg); 61 | throw new Error(msg); 62 | } 63 | 64 | // Apply operator 65 | function apply_op(op, a, b) { 66 | function num(x) { 67 | if (typeof x != "number") 68 | err("Expected number but got " + x); 69 | return x; 70 | } 71 | 72 | function div(x) { 73 | if (num(x) == 0) 74 | err("Divide by zero"); 75 | return x; 76 | } 77 | if (op == "+" && (typeof a == "string" || typeof b == "string")) { 78 | return a + b; 79 | } 80 | switch (op) { 81 | case "+": 82 | return num(a) + num(b); 83 | case "-": 84 | return num(a) - num(b); 85 | case "*": 86 | return num(a) * num(b); 87 | case "/": 88 | return num(a) / div(b); 89 | case "%": 90 | return num(a) % div(b); 91 | case "^": 92 | return Math.pow(num(a),num(b)); 93 | case "&&": 94 | return a !== false && b; 95 | case "||": 96 | return a !== false ? a : b; 97 | case "<": 98 | return num(a) < num(b); 99 | case ">": 100 | return num(a) > num(b); 101 | case "<=": 102 | return num(a) <= num(b); 103 | case ">=": 104 | return num(a) >= num(b); 105 | case "==": 106 | return a === b; 107 | case "!=": 108 | return a !== b; 109 | } 110 | err("Can't apply operator " + op); 111 | } 112 | // Evaluates things quickly 113 | // Evaluates things normally except that it doesn't return anything except when the return keyword is used. 114 | // Also evaluates commands differently as they are added to the output 115 | function quickEvaler(exp, env) { 116 | function quickEval(element) { 117 | // Don't need to return commands, they get added automatically 118 | if (element.type == "command") make_command(env, element); 119 | else if (element.type == "return" || element.type == "if") { 120 | return evaluate(element, env); 121 | } else evaluate(element, env); 122 | } 123 | 124 | // If its more than one line 125 | if (exp.type == "prog") { 126 | // Loops through every exp 127 | for (var i = 0; i < exp.prog.length; i++) { 128 | // Get its value, return if needed 129 | var x = quickEval(exp.prog[i]); 130 | if (x) return x; 131 | } 132 | } 133 | // If its only one line 134 | else { 135 | // Get its value, return if needed 136 | var x = quickEval(exp); 137 | if (x) return x; 138 | } 139 | } 140 | 141 | // Create a JS macro to evaluate when called 142 | function make_macro(env, exp) { 143 | if (exp.name == "range") err("Range is a pre-defined macro, please use another name"); 144 | 145 | function macro() { 146 | var names = exp.vars; 147 | var scope = env.extend(); 148 | for (var i = 0; i < names.length; ++i) 149 | scope.def(names[i], i < arguments.length ? arguments[i] : false); 150 | 151 | var x = quickEvaler(exp.body, scope); 152 | return x; 153 | } 154 | return env.set(exp.name, macro); 155 | } 156 | 157 | // Macros have their name as a reg, so need to separate macros from actual regs 158 | function reg_or_macro(env, exp) { 159 | oldDebug = debug; 160 | try { 161 | debug = false; 162 | if (exp.value == "range") return range_macro; 163 | var possible = env.get(exp.value); 164 | debug = oldDebug; 165 | return possible; 166 | } catch (e) { 167 | debug = oldDebug; 168 | return exp.value; 169 | } 170 | } 171 | 172 | // If/else if/else statements 173 | function make_if(env, exp) { 174 | var cond = evaluate(exp.cond, env); 175 | if (cond !== false) { 176 | var x = quickEvaler(exp.then, env.extend()); 177 | if (x) return x; 178 | } else if (exp.else) { 179 | var y = quickEvaler(exp.else, env.extend()); 180 | if (y) return y; 181 | } 182 | return false; 183 | } 184 | // For loops 185 | function make_for(env, exp) { 186 | // Create a new env 187 | var newEnv = env.extend(); 188 | // Evaluate the first param (declaring variable) 189 | evaluate(exp.params[0], newEnv); 190 | // While the second param is valid 191 | while (evaluate(exp.params[1], newEnv)) { 192 | // Evaluate the for content 193 | evaluate(exp.then, newEnv); 194 | // Evaluate the last param (setting/modifying the variable) 195 | evaluate(exp.params[2], newEnv); 196 | } 197 | } 198 | // Foreach loops 199 | function make_foreach(env, exp) { 200 | // Create a new env 201 | var newEnv = env.extend(); 202 | // Get the array 203 | var arr = evaluate(exp.param, env); 204 | // Define the variable 205 | newEnv.def(exp.variable.value, 0); 206 | // Loop through all of them 207 | for (var i = 0; i < arr.length; i++) { 208 | // Set the variable to the correct value 209 | newEnv.set(exp.variable.value, arr[i]); 210 | // Evaluate 211 | evaluate(exp.then, newEnv); 212 | } 213 | } 214 | // Execute blocks 215 | function make_execute(env, exp) { 216 | // Evaluate all the values 217 | var selector = evaluate(exp.selector, env); 218 | var pos1 = evaluate(exp.pos[0], env); 219 | var pos2 = evaluate(exp.pos[1], env); 220 | var pos3 = evaluate(exp.pos[2], env); 221 | // Add prefix 222 | prefix.push("execute " + selector + " " + pos1 + " " + pos2 + " " + pos3 + " "); 223 | // Evaluate content 224 | evaluate(exp.prog, env.extend()); 225 | // pop 226 | prefix.pop(); 227 | } 228 | // Strings can also have evals inside them 229 | function make_string(env, exp) { 230 | // If it's an array, then it contains evals 231 | if (Array.isArray(exp.value)) { 232 | var final = ""; 233 | for (var i = 0; i < exp.value.length; i++) { 234 | if (exp.value[i].type == "prog") { 235 | var x = evaluate(exp.value[i], env.extend()); 236 | final += x; 237 | } else final += exp.value[i].value; 238 | } 239 | return final; 240 | } 241 | // If it's a basic string 242 | else { 243 | return exp.value; 244 | } 245 | } 246 | 247 | // Relatives can be evaluated 248 | function make_relative(env, exp) { 249 | if (exp.value && exp.value.length > 0) { 250 | var final = "~"; 251 | for (var i = 0; i < exp.value.length; i++) { 252 | final += evaluate(exp.value[i], env); 253 | } 254 | return final; 255 | } else { 256 | return "~"; 257 | } 258 | } 259 | 260 | // Selectors can be evaluated 261 | function make_selector(env, exp) { 262 | // If the selector's value is an array 263 | if (Array.isArray(exp.value)) { 264 | if (exp.value && exp.value.length > 0) { 265 | var final = exp.prefix + "["; 266 | for (var i = 0; i < exp.value.length; i++) { 267 | // Only compile ivars (already created variables/calls) 268 | if (exp.value[i].type == "ivar") { 269 | var x = evaluate(exp.value[i], env.extend()); 270 | final += x; 271 | } else final += exp.value[i].value; 272 | } 273 | return final + "]"; 274 | } else { 275 | return exp.prefix; 276 | } 277 | } else { 278 | return exp.value; 279 | } 280 | } 281 | 282 | // Create an array 283 | function make_array(env, exp) { 284 | var arr = {}; 285 | var index = 0; 286 | exp.value.forEach(function(element) { 287 | var obj = evaluate(element, env); 288 | arr[index] = obj; 289 | index++; 290 | }); 291 | return arr; 292 | } 293 | // Get the ivar's value 294 | function get_ivar(env, exp) { 295 | var ivar = env.get(exp.value); 296 | if (exp.index) { 297 | var index = evaluate(exp.index, env); 298 | if (typeof(index) != "number") err("Array index must be a number"); 299 | return ivar[index]; 300 | } else return ivar; 301 | } 302 | // Assign can either be a declaration or a modification 303 | function make_assign(env, exp) { 304 | // Modify a current variable, use set 305 | if (exp.left.type == "ivar") return env.set(exp.left.value, evaluate(exp.right, env)); 306 | // Declare a new variable, define (def) 307 | else if (exp.left.type == "var") { 308 | return env.def(exp.left.value, evaluate(exp.right, env)); 309 | } 310 | } 311 | // Need to compile JSON the way that MC would accept it 312 | function make_json(env, exp) { 313 | var json = "{"; 314 | for (var i = 0; i < exp.value.length; i++) { 315 | var jsonToAdd = ""; 316 | // if it's a string, add quotes around it 317 | if (exp.value[i].type == "str") { 318 | jsonToAdd = "\"" + evaluate(exp.value[i], env) + "\""; 319 | } 320 | // If it's an array, JSONinfy it 321 | else if (exp.value[i].type == "array") { 322 | var temp = evaluate(exp.value[i], env); 323 | jsonToAdd = JSON.stringify(Object.keys(temp).map(function(k) { 324 | return temp[k]; 325 | })); 326 | } 327 | // if it's something else, evaluate it, it might be something interesting 328 | else { 329 | jsonToAdd = evaluate(exp.value[i], env); 330 | } 331 | json += jsonToAdd; 332 | } 333 | json += "}"; 334 | return json; 335 | } 336 | // Create a comment and add it 337 | function make_comment(env, exp) { 338 | addToOutput(currentFunc, exp.value + "\n"); 339 | } 340 | // Create a command 341 | function make_command(env, exp) { 342 | var cmd = ""; 343 | var lastVal = ""; 344 | if (env.parent == null) err("Commands cannot be used in root"); 345 | for (var i = 0; i < exp.value.length; i++) { 346 | var valueToAdd = evaluate(exp.value[i], env); 347 | if (valueToAdd != ":" && lastVal != "" & lastVal != ":") cmd += " "; 348 | cmd += valueToAdd; 349 | 350 | lastVal = valueToAdd; 351 | } 352 | // Whenever a command is read, add it to the output 353 | var prefixToAdd = (prefix && prefix.length > 0) ? prefix.join('') : ''; 354 | addToOutput(currentFunc, prefixToAdd + cmd + "\n"); 355 | return cmd; 356 | } 357 | // Programs are anything inside a {} with more than one statement 358 | function make_prog(env, exp) { 359 | var final = ""; 360 | exp.prog.forEach(function(exp) { 361 | if (exp.type == "command") { 362 | var cmd = evaluate(exp, env); 363 | final += cmd + "\n"; 364 | } 365 | // Need to add returned items because of eval blocks 366 | else if (exp.type == "return") { 367 | var output = evaluate(exp, env); 368 | final += output; 369 | } else evaluate(exp, env); 370 | }); 371 | return final; 372 | } 373 | // Make a function, evaluate, get out of function 374 | function make_func(env, exp) { 375 | if (inFunc) err("Cannot declare a function inside another"); 376 | inFunc = true; 377 | currentFunc = exp.name; 378 | evaluate(exp.body, env.extend()); 379 | // No need to add anything to the output here, whenever a command is found, it adds it when read 380 | currentFunc = ''; 381 | inFunc = false; 382 | } 383 | // Make a group, evaluate inside, get out of group 384 | function make_group(env, exp) { 385 | if (inFunc) err("Groups cannot be inside functions"); 386 | current.push(exp.name); 387 | evaluate(exp.body, env.extend()); 388 | current.pop(); 389 | } 390 | 391 | function make_setting(env, exp) { 392 | if (env.parent != null) err("Settings must be declared in the root"); 393 | if (exp.name == "namespace") { 394 | if (namespace && namespace != "namespace") err("Cannot declare namespace more than once"); 395 | if (namespace && namespace == "_namespace") err("Please declare the namespace BEFORE writing any functions"); 396 | namespace = exp.value; 397 | } else { 398 | err("No setting found with the name " + exp.name); 399 | } 400 | } 401 | 402 | // Add the given key-value pair to the output 403 | function addToOutput(name, value) { 404 | 405 | // We need the namespace now, if it doesn't exist, set it! (Use _namespace so that there's less chance of conflict) 406 | if (!namespace) namespace = "_namespace"; 407 | if (!output[namespace]) output[namespace] = { 408 | _type: "namespace" 409 | }; 410 | // Get the current position to setup our group 411 | var curOutput = output[namespace]; 412 | 413 | // Check whether or not we are in a group 414 | if (current) { 415 | // Get the current group 416 | current.forEach(function(element) { 417 | if (!curOutput[element]) curOutput[element] = { 418 | _type: "group" 419 | }; 420 | curOutput = curOutput[element]; 421 | }); 422 | 423 | // If it doesn't exist yet, set instead of add (or else it says undefined at the start) 424 | if (curOutput.hasOwnProperty(name)) { 425 | curOutput[name].value += value; 426 | } else { 427 | curOutput[name] = { 428 | _type: "function", 429 | value: value 430 | }; 431 | } 432 | } 433 | // No groups 434 | else { 435 | // If it doesn't exist yet, set instead of add (or else it says undefined at the start) 436 | if (curOutput.hasOwnProperty(name)) { 437 | curOutput[name] += value; 438 | } else { 439 | curOutput[name] = { 440 | _type: "function", 441 | value: value 442 | }; 443 | } 444 | } 445 | } 446 | 447 | // Whether or not a JSON object is empty 448 | function isJSONEmpty(obj) { 449 | for (var prop in obj) { 450 | if (obj.hasOwnProperty(prop)) 451 | return false; 452 | } 453 | return JSON.stringify(obj) === JSON.stringify({}); 454 | } 455 | 456 | // Evaluates all the tokens and compiles commands 457 | function evaluate(exp, env) { 458 | switch (exp.type) { 459 | case "num": 460 | case "bool": 461 | case "kw": 462 | return exp.value; 463 | case "str": 464 | return make_string(env, exp); 465 | case "eval": 466 | return evaluate(exp.value, env.extend()); 467 | case "colon": 468 | return ":"; 469 | case "relative": 470 | return make_relative(env, exp); 471 | case "selector": 472 | return make_selector(env, exp); 473 | case "comma": 474 | return ","; 475 | case "json": 476 | return make_json(env, exp); 477 | case "reg": 478 | return reg_or_macro(env, exp); 479 | case "comment": 480 | return make_comment(env, exp); 481 | case "command": 482 | return make_command(env, exp); 483 | case "array": 484 | return make_array(env, exp); 485 | case "ivar": 486 | return get_ivar(env, exp); 487 | case "assign": 488 | return make_assign(env, exp); 489 | case "binary": 490 | return apply_op(exp.operator, evaluate(exp.left, env), evaluate(exp.right, env)); 491 | case "macro": 492 | return make_macro(env, exp); 493 | case "return": 494 | return evaluate(exp.value, env); 495 | case "if": 496 | return make_if(env, exp); 497 | case "for": 498 | return make_for(env, exp); 499 | case "foreach": 500 | return make_foreach(env, exp); 501 | case "execute": 502 | return make_execute(env, exp); 503 | case "function": 504 | return make_func(env, exp); 505 | case "group": 506 | return make_group(env, exp); 507 | case "setting": 508 | return make_setting(env, exp); 509 | case "prog": 510 | return make_prog(env, exp); 511 | case "call": 512 | var macro = evaluate(exp.func, env); 513 | return macro.apply(null, exp.args.map(function(arg) { 514 | return evaluate(arg, env); 515 | })); 516 | default: 517 | err("Unable to evaluate " + exp.type); 518 | } 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/front.js: -------------------------------------------------------------------------------- 1 | /* This will appear at the top/start of the final dist file */ 2 | (function () { 3 | 4 | var mcs = (function () { 5 | 6 | var mcs = function(input) { 7 | if (!input || typeof(input) !== 'string'){ 8 | var err = 'Error: Object to parse is not a valid string or does not exist.'; 9 | console.error(err); 10 | return err; 11 | } 12 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | /* Advanced Parser 2 | InputStream reads characters 3 | TokenStream is the lexer (converts everything into tokens) 4 | Parser tries to create node structures (AST) out of the tokens 5 | 6 | Parser based on: http://lisperator.net/pltut/ 7 | */ 8 | 9 | // List of all commands that exist in mc 10 | var availableCommands = ["advancement", "ban", "blockdata", "clear", "clone", "debug", "defaultgamemode", "deop", "difficulty", "effect", "enchant", "entitydata", "execute", "fill", "function", "gamemode", "gamerule", "give", "help", "kick", "kill", "list", "locate", "me", "op", "pardon", "particle", "playsound", "publish", "recipe", "reload", "replaceitem", "save", "say", "scoreboard", "seed", "setblock", "setidletimeout", "setmaxplayers", "setworldspawn", "spawnpoint", "spreadplayers", "stats", "stop", "stopsound", "summon", "teleport", "tell", "tellraw", "testfor", "testforblock", "testforblocks", "time", "title", "toggledownfall", "tp", "transferserver", "trigger", "weather", "whitelist", "worldborder", "wsserver", "xp"]; 11 | 12 | // InputStream (Read input character by character) 13 | function InputStream(input) { 14 | var pos = 0, 15 | line = 1, 16 | col = 0, 17 | lastVal = null, 18 | lastWasNewLineVal = true; 19 | return { 20 | next: next, 21 | peek: peek, 22 | eof: eof, 23 | croak: croak, 24 | last: last, 25 | lastWasNewLine: lastWasNewLine 26 | }; 27 | 28 | function next() { 29 | // Knows whether or not we switched to a new line 30 | if (peek() == "\n") lastWasNewLineVal = true; 31 | else if ("\r\t ".indexOf(peek()) == -1) lastWasNewLineVal = false; 32 | // Get the last character 33 | lastVal = peek(); 34 | 35 | var ch = input.charAt(pos++); 36 | if (ch == "\n") line++, col = 0; 37 | else col++; 38 | return ch; 39 | } 40 | 41 | function last() { 42 | return lastVal; 43 | } 44 | 45 | function lastWasNewLine() { 46 | return lastWasNewLineVal; 47 | } 48 | 49 | function peek() { 50 | return input.charAt(pos); 51 | } 52 | 53 | function eof() { 54 | return peek() == ""; 55 | } 56 | 57 | function croak(msg) { 58 | var err = msg + ' at (' + line + ':' + col + ')'; 59 | console.error(err); 60 | throw new Error(err); 61 | } 62 | } 63 | 64 | // Lexer (converts everything into tokens) 65 | function TokenStream(input) { 66 | var current = null; 67 | // List of all keywords that are available 68 | var keywords = " function macro group if elseif else return execute true false var for foreach in "; 69 | var lastVal = null; 70 | return { 71 | next: next, 72 | peek: peek, 73 | eof: eof, 74 | croak: input.croak, 75 | last: last 76 | }; 77 | 78 | function is_keyword(x) { 79 | return keywords.indexOf(" " + x + " ") >= 0; 80 | } 81 | 82 | function is_digit(ch) { 83 | return /[0-9]/i.test(ch); 84 | } 85 | 86 | function is_id_start(ch) { 87 | return /[a-z0-9_\$]/i.test(ch); 88 | } 89 | 90 | function is_id(ch) { 91 | return is_id_start(ch); 92 | } 93 | 94 | function is_ivar(ch) { 95 | return /\$[a-z0-9-_]/i.test(ch); 96 | } 97 | 98 | function is_op_char(ch) { 99 | return "+-*/%^=&|<>!".indexOf(ch) >= 0; 100 | } 101 | 102 | function is_punc(ch) { 103 | return ",;(){}[]".indexOf(ch) >= 0; 104 | } 105 | 106 | function is_whitespace(ch) { 107 | return " \t\n\r".indexOf(ch) >= 0; 108 | } 109 | // Read until the given predicate returns false 110 | function read_while(predicate) { 111 | var str = ""; 112 | while (!input.eof() && predicate(input.peek())) 113 | str += input.next(); 114 | return str; 115 | } 116 | 117 | function try_number() { 118 | input.next(); 119 | if (is_digit(input.peek())) { 120 | var num = read_number(); 121 | num.value *= -1; 122 | return num; 123 | } 124 | input.croak("Can't handle character: " + input.peek()); 125 | } 126 | 127 | function read_number() { 128 | var has_dot = false; 129 | var number = read_while(function(ch) { 130 | if (ch == ".") { 131 | if (has_dot) return false; 132 | has_dot = true; 133 | return true; 134 | } 135 | return is_digit(ch); 136 | }); 137 | return { 138 | type: "num", 139 | value: parseFloat(number) 140 | }; 141 | } 142 | // Read identifiers, can return a keyword, an ivar ($variable), or reg (anything else) 143 | function read_ident() { 144 | var id = read_while(is_id); 145 | var type; 146 | if (is_keyword(id)) type = "kw"; 147 | else if (is_ivar(id)) type = "ivar" 148 | else type = "reg"; 149 | 150 | return { 151 | type: type, 152 | value: id 153 | }; 154 | } 155 | 156 | function read_escaped(end) { 157 | var escaped = false, 158 | str = ""; 159 | input.next(); 160 | while (!input.eof()) { 161 | var ch = input.next(); 162 | if (escaped) { 163 | str += ch; 164 | escaped = false; 165 | } else if (ch == "\\") { 166 | escaped = true; 167 | } else if (ch == end) { 168 | break; 169 | } else { 170 | str += ch; 171 | } 172 | } 173 | return str; 174 | } 175 | /* Evaluation blocks 176 | Inside a string, content inside `` will be parsed as if it was normal syntax. 177 | This allows for easier variable/macro integration: "math result: `math(1,2) + 2`" rather than "math result: " + (math(1,2) + 2) 178 | (Although the second option is still available if you need it). 179 | */ 180 | function read_evaled(val) { 181 | // Don't do it if it doesn't need evaluation 182 | if (val.indexOf("`") >= 0) { 183 | 184 | var evalBlock = false, 185 | final = [], 186 | str = ""; 187 | var arr = val.split(''); 188 | 189 | for (var i = 0; i < arr.length; i++) { 190 | var ch = arr[i]; 191 | 192 | // Currently in an eval block 193 | if (evalBlock) { 194 | if (ch == '`') { 195 | evalBlock = false; 196 | // Parse the whole thing as if it was a full code block 197 | var parsedEval = Parser(TokenStream(InputStream(str))); 198 | if (parsedEval.prog.length != 0) { 199 | for (var x = 0; x < parsedEval.prog.length; x++) { 200 | if (parsedEval.prog[x].type == "comment") { 201 | input.croak("Comments are not allowed in evaluation blocks"); 202 | } else if (parsedEval.prog[x].type == "function") { 203 | input.croak("Functions are not allowed in evaluation blocks"); 204 | } else if (parsedEval.prog[x].type == "macro") { 205 | input.croak("Creating macros is not allowed in evaluation blocks"); 206 | } 207 | } 208 | final.push(parsedEval); 209 | } 210 | str = ""; 211 | } else { 212 | str += ch; 213 | 214 | if (i == arr.length - 1) { 215 | if (str) final.push({ 216 | type: "str", 217 | value: str 218 | }); 219 | } 220 | } 221 | } 222 | // Don't evalBlock 223 | else { 224 | if (ch == '`') { 225 | evalBlock = true; 226 | if (str) final.push({ 227 | type: "str", 228 | value: str 229 | }); 230 | str = ""; 231 | } else { 232 | str += ch; 233 | if (i == arr.length - 1) { 234 | if (str) final.push({ 235 | type: "str", 236 | value: str 237 | }); 238 | } 239 | } 240 | } 241 | } 242 | return final; 243 | } else { 244 | return val; 245 | } 246 | } 247 | 248 | function read_string() { 249 | return { 250 | type: "str", 251 | value: read_evaled(read_escaped('"')) 252 | }; 253 | } 254 | 255 | function selector_or_setting() { 256 | var lastNewLine = input.lastWasNewLine(); 257 | input.next(); 258 | if (input.peek() == '!') { 259 | if (!lastNewLine) input.croak('Settings with "@!" need to start at the begining of a line'); 260 | return read_settings(); 261 | } else { 262 | return read_selector(); 263 | } 264 | } 265 | 266 | function read_selector() { 267 | var output = read_while(function(ch) { 268 | return (!is_whitespace(ch) && ch != ';') 269 | }); 270 | 271 | return { 272 | type: 'selector', 273 | value: '@' + output 274 | }; 275 | } 276 | 277 | function read_settings() { 278 | var output = read_while(function(ch) { 279 | return ch != "\n" 280 | }); 281 | return { 282 | type: "setting", 283 | value: output.replace('\r', '') 284 | }; 285 | } 286 | 287 | function read_relative() { 288 | var val = read_while(function(ch) { 289 | return (!is_whitespace(ch) && ch != ";"); 290 | }); 291 | return { 292 | type: 'relative', 293 | value: val 294 | }; 295 | } 296 | 297 | function read_colon() { 298 | input.next(); 299 | return { 300 | type: 'colon' 301 | }; 302 | } 303 | // Read comments that need to be added (#) 304 | function read_comment() { 305 | if (!input.lastWasNewLine()) input.croak('Comments with "#" need to start at the begining of a line'); 306 | var output = read_while(function(ch) { 307 | return ch != "\n" 308 | }); 309 | return { 310 | type: "comment", 311 | value: output.replace('\r', '') 312 | }; 313 | } 314 | 315 | function skip_comment() { 316 | read_while(function(ch) { 317 | return ch != "\n" 318 | }); 319 | input.next(); 320 | } 321 | // Check whether or not the line is a // comment, skip it if so 322 | function check_comment() { 323 | var output = read_while(function(ch) { 324 | return ch == "/" 325 | }); 326 | if (output == '//') { 327 | skip_comment(); 328 | } else { 329 | input.next(); 330 | } 331 | } 332 | // Read the next character, assign tokens 333 | function read_next() { 334 | read_while(is_whitespace); 335 | if (input.eof()) return null; 336 | var ch = input.peek(); 337 | if (ch == "#") { 338 | return read_comment(); 339 | } 340 | if (ch == "/") { 341 | check_comment(); 342 | return read_next(); 343 | } 344 | if (ch == "@") return selector_or_setting(); 345 | if (ch == '"') return read_string(); 346 | if (ch == "~") return read_relative(); 347 | if (ch == ":") return read_colon(); 348 | if (is_digit(ch)) return read_number(); 349 | if (is_id_start(ch)) return read_ident(); 350 | if (is_punc(ch)) return { 351 | type: "punc", 352 | value: input.next() 353 | }; 354 | if (is_op_char(ch)) return { 355 | type: "op", 356 | value: read_while(is_op_char) 357 | }; 358 | 359 | input.croak("Can't handle character: " + ch); 360 | } 361 | 362 | function peek() { 363 | return current || (current = read_next()); 364 | } 365 | 366 | function last() { 367 | return lastVal; 368 | } 369 | 370 | function next() { 371 | lastVal = peek(); 372 | var tok = current; 373 | current = null; 374 | return tok || read_next(); 375 | } 376 | 377 | function eof() { 378 | return peek() == null; 379 | } 380 | } 381 | 382 | // Parser (actually parses data) 383 | var FALSE = { 384 | type: "bool", 385 | value: false 386 | }; 387 | 388 | function Parser(input) { 389 | // Order of math operations, greater means included first 390 | var PRECEDENCE = { 391 | "=": 1, 392 | "||": 2, 393 | "&&": 3, 394 | "<": 7, 395 | ">": 7, 396 | "<=": 7, 397 | ">=": 7, 398 | "==": 7, 399 | "!=": 7, 400 | "+": 10, 401 | "-": 10, 402 | "*": 20, 403 | "/": 20, 404 | "%": 20, 405 | "^": 30, 406 | }; 407 | return parse_toplevel(); 408 | 409 | function is_punc(ch) { 410 | var tok = input.peek(); 411 | return tok && tok.type == "punc" && (!ch || tok.value == ch) && tok; 412 | } 413 | 414 | function is_kw(kw) { 415 | var tok = input.peek(); 416 | return tok && tok.type == "kw" && (!kw || tok.value == kw) && tok; 417 | } 418 | 419 | function is_op(op) { 420 | var tok = input.peek(); 421 | return tok && tok.type == "op" && (!op || tok.value == op) && tok; 422 | } 423 | 424 | function is_comment() { 425 | var tok = input.peek(); 426 | return tok && tok.type == "comment"; 427 | } 428 | 429 | function is_reg() { 430 | var tok = input.peek(); 431 | return tok && tok.type == "reg"; 432 | } 433 | 434 | function skip_punc(ch) { 435 | if (is_punc(ch)) input.next(); 436 | else input.croak("Expecting punctuation: \"" + ch + "\""); 437 | } 438 | 439 | function skip_comment(ch) { 440 | if (is_comment()) parse_comment(); 441 | input.next(); 442 | } 443 | 444 | function skip_kw(kw) { 445 | if (is_kw(kw)) input.next(); 446 | else input.croak("Expecting keyword: \"" + kw + "\""); 447 | } 448 | 449 | function skip_op(op) { 450 | if (is_op(op)) input.next(); 451 | else input.croak("Expecting operator: \"" + op + "\""); 452 | } 453 | 454 | function skip_comma() { 455 | if (is_punc(",")) { 456 | input.next(); 457 | return { 458 | type: "comma" 459 | }; 460 | } else input.croak("Expecting comma: \"" + JSON.stringify(input.peek()) + "\"") 461 | } 462 | 463 | function unexpected() { 464 | input.croak("Unexpected token: " + JSON.stringify(input.peek())); 465 | } 466 | // Check whether or not to parse this through binary operations 467 | function maybe_binary(left, my_prec) { 468 | var tok = is_op(); 469 | if (tok) { 470 | var his_prec = PRECEDENCE[tok.value]; 471 | if (his_prec > my_prec) { 472 | input.next(); 473 | return maybe_binary({ 474 | type: tok.value == "=" ? "assign" : "binary", 475 | operator: tok.value, 476 | left: left, 477 | right: maybe_binary(parse_atom(), his_prec) 478 | }, my_prec); 479 | } 480 | } 481 | return left; 482 | } 483 | // Parses through anything between start and stop, with separator, using the given parser 484 | function delimited(start, stop, separator, parser) { 485 | var a = [], 486 | first = true; 487 | skip_punc(start); 488 | while (!input.eof()) { 489 | if (is_punc(stop)) break; 490 | if (first) first = false; 491 | else if (check_last()) skip_punc(separator); 492 | if (is_punc(stop)) break; 493 | a.push(parser()); 494 | } 495 | skip_punc(stop); 496 | return a; 497 | } 498 | 499 | function parse_call(func) { 500 | return { 501 | type: "call", 502 | func: func, 503 | args: delimited("(", ")", ",", parse_expression), 504 | }; 505 | } 506 | // Variable names can't be ivar nor keyword, check that it's a reg 507 | function parse_varname() { 508 | var name = input.next(); 509 | if (name.type != "ivar") input.croak("Expecting variable name"); 510 | return name.value; 511 | } 512 | // Parse if statements, add elseif if there are some, and add else if there is one 513 | function parse_if() { 514 | skip_kw("if"); 515 | var cond = parse_expression(); 516 | var then = parse_expression(); 517 | var ret = { 518 | type: "if", 519 | cond: cond, 520 | then: then, 521 | }; 522 | if (is_kw("else")) { 523 | input.next(); 524 | ret.else = parse_expression(); 525 | } 526 | return ret; 527 | } 528 | // Parse a var declaration 529 | function parse_var() { 530 | skip_kw("var"); 531 | return { 532 | type: 'var', 533 | value: parse_varname() 534 | }; 535 | } 536 | // Parse a for loop 537 | function parse_for() { 538 | //input.croak("For loops are currently not supported"); 539 | skip_kw("for"); 540 | var params = delimited("(", ")", ";", parse_expression); 541 | var then = parse_expression(); 542 | return { 543 | type: "for", 544 | params: params, 545 | then: then, 546 | }; 547 | } 548 | // Parse a foreach loop 549 | function parse_foreach() { 550 | skip_kw("foreach"); 551 | skip_punc("("); 552 | skip_kw("var"); 553 | var varName = parse_ivar(); 554 | skip_kw("in"); 555 | var param = parse_expression(); 556 | skip_punc(")"); 557 | var then = parse_expression(); 558 | return { 559 | type: "foreach", 560 | variable: varName, 561 | param: param, 562 | then: then 563 | }; 564 | 565 | } 566 | /* Parse a function 567 | This can be taken in two ways: 568 | 1. actual function declaration ( function name { } ) 569 | 2. minecraft function command ( function name [if/unless...] ) 570 | 571 | Therefore, testing if there are reg arguments following it, if so, it's Option 2. 572 | */ 573 | function parse_function() { 574 | // Skip the function keyword 575 | input.next(); 576 | // Get the name of the function 577 | var name = input.next(); 578 | // Check if what's afterwards is a call 579 | if (input.peek().type == 'colon') { 580 | var obj = { 581 | type: "command", 582 | value: [{ 583 | type: "reg", 584 | value: "function" 585 | }, 586 | name 587 | ] 588 | }; 589 | // Loop through it to add all of the arguments 590 | while (!input.eof()) { 591 | if (input.peek().type == "kw") 592 | obj.value.push(input.next()); 593 | else 594 | obj.value.push(parse_expression()); 595 | 596 | 597 | if (is_punc(';')) break; 598 | else if (input.eof()) skip_punc(';'); 599 | } 600 | return obj; 601 | } else { 602 | // It's not a call to a function, parse it as a normal function 603 | return { 604 | type: "function", 605 | name: name.value, 606 | body: parse_expression() 607 | }; 608 | } 609 | } 610 | // Parsing a group (sub namespaces/folders) 611 | function parse_group() { 612 | input.next(); 613 | return { 614 | type: "group", 615 | name: input.next().value, 616 | body: parse_expression() 617 | }; 618 | } 619 | // Parse a macro, basically a function with parameters 620 | function parse_macro() { 621 | input.next(); 622 | return { 623 | type: "macro", 624 | name: input.next().value, 625 | vars: delimited("(", ")", ",", parse_varname), 626 | body: parse_expression() 627 | }; 628 | } 629 | // Return statements 630 | function parse_return() { 631 | input.next(); 632 | return { 633 | type: "return", 634 | value: parse_expression() 635 | }; 636 | } 637 | 638 | function parse_execute() { 639 | input.next(); 640 | var final = { 641 | type: 'execute', 642 | selector: '', 643 | pos: [] 644 | }; 645 | var tokenCount = 0; 646 | while (!input.eof()) { 647 | if (tokenCount == 5) { 648 | break; 649 | } else { 650 | var expr = parse_expression(); 651 | if (tokenCount == 0) final.selector = expr; 652 | else if (tokenCount > 0 && tokenCount < 4) final.pos.push(expr); 653 | else if (tokenCount == 4) final.prog = expr; 654 | tokenCount++; 655 | } 656 | } 657 | return final; 658 | } 659 | // Bool just checks if the value is "true" 660 | function parse_bool() { 661 | return { 662 | type: "bool", 663 | value: input.next().value == "true" 664 | }; 665 | } 666 | // Parse an ivar and detect whether or not it's asking for an index of an array 667 | function parse_ivar() { 668 | var ivar = input.next(); 669 | if (is_punc("[")) { 670 | skip_punc("["); 671 | while (!input.eof()) { 672 | if (is_punc("]")) break; 673 | if (input.peek().type == "kw") unexpected(); 674 | ivar.index = parse_expression(); 675 | if (is_punc("]")) break; 676 | else input.croak('Expecting punctuation: "]"'); 677 | } 678 | skip_punc("]"); 679 | } 680 | return ivar; 681 | } 682 | // Parsing an array declaration [5, "string", false] 683 | function parse_array() { 684 | return { 685 | type: "array", 686 | value: delimited("[", "]", ",", function custom_parser() { 687 | var i = input.peek(); 688 | // Don't allow keywords such as function, var... inside an array declaration 689 | // Macro is allowed to allow for variable functions (lambdas) ($fn = macro call () { something; }) 690 | if (i.type == "kw" && i.value != "false" && i.value != "true" && i.value != "macro") unexpected(); 691 | else return parse_expression(); 692 | }) 693 | }; 694 | } 695 | // Parsing a comment as an actual comment 696 | function parse_comment() { 697 | return { 698 | type: "comment", 699 | value: input.next().value 700 | }; 701 | } 702 | 703 | function parse_setting() { 704 | var setting = input.next().value.trim(); 705 | var indexSeparator = setting.indexOf(':'); 706 | if (indexSeparator == -1) input.croak('Expecting separator: ":"'); 707 | return { 708 | type: 'setting', 709 | name: setting.substring(setting.indexOf('!') + 1, indexSeparator).trim(), 710 | value: setting.substring(indexSeparator + 1).trim() 711 | }; 712 | } 713 | // Relatives need to check for variables inside 714 | function parse_relative() { 715 | // Parse the relative's content 716 | var quickInput = TokenStream(InputStream(input.next().value.substring(1))); 717 | var final = []; 718 | while (!quickInput.eof()) { 719 | final.push(quickInput.next()); 720 | } 721 | return { 722 | type: 'relative', 723 | value: final 724 | }; 725 | } 726 | // Selectors need to check for variables inside 727 | function parse_selector() { 728 | var next = input.next(); 729 | var final = []; 730 | // If the selector has arguments, parse them 731 | if (next.value.indexOf("[") > -1) { 732 | var valueToParse = next.value.substring(3, next.value.length - 1); 733 | var quickInput = TokenStream(InputStream(valueToParse)); 734 | while (!quickInput.eof()) { 735 | final.push(quickInput.next()); 736 | } 737 | } 738 | return { 739 | type: 'selector', 740 | prefix: next.value.substring(0, 2), 741 | value: final 742 | }; 743 | } 744 | /* Parsing reg is complicated 745 | 746 | Most of the time, a reg is simply a minecraft command and its arguments, it checks if it's an actual command, and if so, it returns the full command 747 | Sometimes, it's the name of a macro or other function calling, if so return the exact token 748 | If it's neither of those, then it's unexpected 749 | */ 750 | var lala; 751 | 752 | function parse_reg() { 753 | // Regs are commands and command arguments 754 | var final = { 755 | type: "command", 756 | value: [] 757 | }; 758 | if (availableCommands.includes(input.peek().value)) { 759 | final.value.push(input.next()); 760 | while (!input.eof()) { 761 | var next = parse_expression(); 762 | final.value.push(next); 763 | 764 | if (is_punc(';')) break; 765 | else if (input.eof()) skip_punc(';'); 766 | } 767 | return final; 768 | } else { 769 | return input.next(); 770 | //unexpected(); 771 | } 772 | } 773 | // Check whether or not the given token is a JSON or a program 774 | function json_or_prog() { 775 | skip_punc("{"); 776 | var a = {}, 777 | first = true; 778 | // Check if the next item is a string 779 | if (input.peek().type == "str") { 780 | a = { 781 | type: "json", 782 | value: [] 783 | }; 784 | while (!input.eof()) { 785 | if (is_punc("}")) break; 786 | if (first) first = false; 787 | else if (input.peek().type == "colon") first = true; 788 | else a.value.push(skip_comma());; 789 | if (is_punc("}")) break; 790 | a.value.push(parse_expression()); 791 | } 792 | skip_punc("}"); 793 | return a; 794 | } 795 | // Regular program 796 | else { 797 | a = { 798 | type: "prog", 799 | prog: [] 800 | }; 801 | while (!input.eof()) { 802 | if (is_punc("}")) break; 803 | if (first) first = false; 804 | else if (check_last()) skip_punc(";"); 805 | if (is_punc("}")) break; 806 | a.prog.push(parse_expression()); 807 | } 808 | skip_punc("}"); 809 | if (a.prog.length == 0) return FALSE; 810 | if (a.prog.length == 1) return a.prog[0]; 811 | return a; 812 | } 813 | } 814 | 815 | function maybe_call(expr) { 816 | expr = expr(); 817 | return is_punc("(") ? parse_call(expr) : expr; 818 | } 819 | // Major parser, checks what the token is an tells it to how to parse it 820 | function parse_atom() { 821 | return maybe_call(function() { 822 | if (is_punc("(")) { 823 | input.next(); 824 | var exp = parse_expression(); 825 | skip_punc(")"); 826 | return exp; 827 | } 828 | if (is_punc("{")) return json_or_prog(); 829 | if (is_punc("[")) return parse_array(); 830 | if (is_kw("if")) return parse_if(); 831 | if (is_kw("var")) return parse_var(); 832 | if (is_kw("true") || is_kw("false")) return parse_bool(); 833 | if (is_kw("for")) return parse_for(); 834 | if (is_kw("foreach")) return parse_foreach(); 835 | if (is_kw("function")) return parse_function(); 836 | if (is_kw("group")) return parse_group(); 837 | if (is_kw("execute")) return parse_execute(); 838 | if (is_kw("macro")) return parse_macro(); 839 | 840 | if (is_kw("return")) return parse_return(); 841 | if (is_comment()) return parse_comment(); 842 | if (input.peek().type == 'reg') return parse_reg(); 843 | if (input.peek().type == 'setting') return parse_setting(); 844 | if (input.peek().type == "ivar") return parse_ivar(); 845 | if (input.peek().type == 'relative') return parse_relative(); 846 | if (input.peek().type == 'selector') return parse_selector(); 847 | 848 | var tok = input.next(); 849 | if (tok.type == 'colon' || tok.type == "num" || tok.type == "str") 850 | return tok; 851 | unexpected(); 852 | }); 853 | } 854 | // Utility to check whether or not the last one was a comment or a setting (use it to prevent requirement of semicolon) 855 | function check_last() { 856 | return (!input.last() || (input.last() && input.last().type != "comment" && input.last().type != "setting")); 857 | } 858 | // Parsing a program/top level 859 | function parse_toplevel() { 860 | var prog = []; 861 | while (!input.eof()) { 862 | prog.push(parse_expression()); 863 | // Comments are special because they don't require a ; at the end, so we need to check that it's not a comment 864 | if (is_comment()) { 865 | prog.push(parse_comment()); 866 | } else if (!input.eof() && check_last()) skip_punc(";"); 867 | } 868 | return { 869 | type: "prog", 870 | prog: prog 871 | }; 872 | } 873 | /* UNUSED but keep for clarity 874 | // Parse through a full program 875 | function parse_prog() { 876 | var prog = delimited("{", "}", ";", parse_expression); 877 | if (prog.length == 0) return FALSE; 878 | if (prog.length == 1) return prog[0]; 879 | return { type: "prog", prog: prog };}*/ 880 | // Parse through everything, parse binary and calls just in case 881 | function parse_expression() { 882 | return maybe_call(function() { 883 | return maybe_binary(parse_atom(), 0); 884 | }); 885 | } 886 | } 887 | -------------------------------------------------------------------------------- /src/pre-defined-macros.js: -------------------------------------------------------------------------------- 1 | function range_macro(min, max, delta) { 2 | // range(n) creates a array from 1 to n, including n. 3 | // range(m,n) creates a array from m to n, including n. 4 | // range(n,m,delta) creates a array from n to m, by step of delta. May not include max 5 | 6 | var arr = []; 7 | var myStepCount; 8 | 9 | if (arguments.length === 1) { 10 | for (var ii = 0; ii < min; ii++) { 11 | arr[ii] = ii + 1; 12 | }; 13 | } else { 14 | if (arguments.length === 2) { 15 | myStepCount = (max - min); 16 | for (var ii = 0; ii <= myStepCount; ii++) { 17 | arr.push(ii + min); 18 | }; 19 | } else { 20 | myStepCount = Math.floor((max - min) / delta); 21 | for (var ii = 0; ii <= myStepCount; ii++) { 22 | arr.push(ii * delta + min); 23 | }; 24 | } 25 | } 26 | 27 | return arr; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /test/new_syntax.mcs: -------------------------------------------------------------------------------- 1 | // This is a quick demo/tutorial file 2 | 3 | // Declare a namespace 4 | @!namespace: myNamespace 5 | 6 | // This is the root, it's not inside any group or function 7 | 8 | // Groups are used to create folders in the path (namespace:group/function) 9 | group myFolder { 10 | 11 | // Functions get separated into files, this would be myFunction.mcfunction 12 | function myFunction { 13 | 14 | // Unparsed comments (get removed) can be anywhere 15 | # Parsed comments (get added to final function file) cannot be in root 16 | 17 | // Normal minecraft commands 18 | say hello world; 19 | 20 | // Variables (contains a string) 21 | var $stringVar = "a string var"; 22 | 23 | // Variable with a number 24 | var $numberVar = 3.1415; 25 | 26 | // Variable with a boolean 27 | var $boolVar = true; 28 | 29 | // Variable with arrays (of any type) 30 | var $arrVar = [15, "test", false]; 31 | 32 | // Use a variable by preceding its name with $ 33 | $numberVar = 3; 34 | 35 | // Arrays can be accessed like so 36 | $arrVar[1]; // Returns "test" (index starts at 0) 37 | 38 | // You can add or set index elements like so 39 | $arrVar[3] = 20; 40 | 41 | // You can concatenate (add) strings 42 | var $another = $stringVar + " something else"; 43 | 44 | // You can also use an eval block, to use variables inside strings/quotation marks using `` 45 | tellraw @a {"text":"this is an eval block -> `return $stringVar`"}; 46 | // will become tellraw @a {"text":"this is an eval block -> a string var"} 47 | 48 | // If statements 49 | if ($numberVar < 4) { 50 | say Number is less than 4; 51 | } 52 | // Else if statements 53 | else if ($numberVar == 4) { 54 | say Number is equal to 4; 55 | } 56 | // Else 57 | else { 58 | say Number is greater than 4; 59 | }; 60 | 61 | // If you want to use a reserved keyword in your commands, put quotes around it 62 | say "for" loop; 63 | // will become say for loop 64 | 65 | // For loops, from 0->3 inclusive 66 | for(var $i = 0; $i < 4; $i = $i + 1) { 67 | // This will say "Num 0", "Num 1", "Num 2", "Num 3" 68 | say Num $i; 69 | }; 70 | 71 | say "foreach" loop; 72 | 73 | // Or you can do the same thing with a foreach loop 74 | foreach (var $j in range(0,3)) { 75 | // This will say "Num 0", "Num 1", "Num 2", "Num3" 76 | say Num $j; 77 | }; 78 | 79 | // Execute block (adds the execute prefix to any command inside its { } ) 80 | execute @a ~ ~ ~ { 81 | // Will become "execute @a ~ ~ ~ say Hello World" 82 | say Hello World; 83 | }; 84 | 85 | // Calling another function (just like you would with commands) 86 | function myNamespace:callAMacro if @a[score_hello=1]; 87 | }; 88 | }; 89 | 90 | // Macros allow you to write similar commands more quickly 91 | macro aMacro ($parameter1, $parameter2) { 92 | 93 | // This will prepend normal commands in the line before the call to "aMacro()" 94 | tellraw $parameter1 {"text":"Hello, my name is `return $parameter2`."}; 95 | 96 | 97 | // This is the value that will replace any call to "aMacro()" 98 | return 5; 99 | }; 100 | 101 | // A function to demonstrate macros 102 | function callAMacro { 103 | 104 | // You can also make variables with selectors or relative positions 105 | var $selector = @a[score_hello_min=1]; 106 | 107 | // Call a macro and add its return value as a new line 108 | var $hey = aMacro($selector, "John"); 109 | say $hey; 110 | 111 | // This will compile into 112 | // tellraw @a[score_hello_min=1] {"text":"Hello, my name is John."} 113 | // say 5; 114 | 115 | }; 116 | 117 | // This function shows the changes from the latest version 118 | function changes { 119 | // Changes from v2.2.0 120 | 121 | // New Exponent/Power operator 122 | var $exponent = 3; 123 | var $base = 2; 124 | 125 | // 2 to the power of 3 is 8 126 | var $result = 2 ^ 3; 127 | 128 | // say 8 129 | say $result; 130 | 131 | // Changes from v2.1.0 132 | 133 | // Execute as normal command 134 | execute @a ~ ~ ~ say hello world; 135 | 136 | var $someVal = 5; 137 | 138 | // Selectors can evaluate their content 139 | var $selector = @a[some_score=$someVal]; 140 | 141 | // Relatives can evaluate their content 142 | var $rel = ~$someVal; 143 | 144 | say $selector; 145 | 146 | say $rel; 147 | } 148 | -------------------------------------------------------------------------------- /test/syntax_output/myNamespace/callAMacro.mcfunction: -------------------------------------------------------------------------------- 1 | tellraw @a[score_hello_min=1] {"text":"Hello, my name is John."} 2 | say 5 3 | -------------------------------------------------------------------------------- /test/syntax_output/myNamespace/changes.mcfunction: -------------------------------------------------------------------------------- 1 | say 8 2 | execute @a ~ ~ ~ say hello world 3 | say @a[some_score=5] 4 | say ~5 5 | -------------------------------------------------------------------------------- /test/syntax_output/myNamespace/myFolder/myFunction.mcfunction: -------------------------------------------------------------------------------- 1 | # Parsed comments (get added to final function file) cannot be in root 2 | say hello world 3 | tellraw @a {"text":"this is an eval block -> a string var"} 4 | say Number is less than 4 5 | say for loop 6 | say Num 0 7 | say Num 1 8 | say Num 2 9 | say Num 3 10 | say foreach loop 11 | say Num 0 12 | say Num 1 13 | say Num 2 14 | say Num 3 15 | execute @a ~ ~ ~ say Hello World 16 | function myNamespace:callAMacro if @a[score_hello=1] 17 | -------------------------------------------------------------------------------- /test/syntax_output/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "myNamespace": { 3 | "_type": "namespace", 4 | "myFolder": { 5 | "_type": "group", 6 | "myFunction": { 7 | "_type": "function", 8 | "value": "# Parsed comments (get added to final function file) cannot be in root\nsay hello world\ntellraw @a {\"text\":\"this is an eval block -> a string var\"}\nsay Number is less than 4\nsay for loop\nsay Num 0\nsay Num 1\nsay Num 2\nsay Num 3\nsay foreach loop\nsay Num 0\nsay Num 1\nsay Num 2\nsay Num 3\nexecute @a ~ ~ ~ say Hello World\nfunction myNamespace:callAMacro if @a[score_hello=1]\n" 9 | } 10 | }, 11 | "callAMacro": { 12 | "_type": "function", 13 | "value": "tellraw @a[score_hello_min=1] {\"text\":\"Hello, my name is John.\"}\nsay 5\n" 14 | }, 15 | "changes": { 16 | "_type": "function", 17 | "value": "say 8\nexecute @a ~ ~ ~ say hello world\nsay @a[some_score=5]\nsay ~5\n" 18 | } 19 | } 20 | } --------------------------------------------------------------------------------