From 3867ef499160b2846209d92ac17e17f9afbab95f Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Mon, 2 Oct 2023 14:00:23 +0200 Subject: [PATCH] :pencil: Document language (renamed to minbasic), add vscode extension for syntax highlighting --- GUIDE.md | 160 ++++++++++++++++++ LICENSE | 19 +++ README.md | 35 +++- examples/{counter.basic => counter.mbas} | 0 examples/{fizzbuzz.basic => fizzbuzz.mbas} | 0 examples/{prime.basic => prime.mbas} | 0 ...ural-waves.basic => procedural-waves.mbas} | 0 minbasic-vscode/.gitignore | 5 + minbasic-vscode/.vscodeignore | 7 + minbasic-vscode/LICENSE | 19 +++ minbasic-vscode/README.md | 22 +++ minbasic-vscode/language-configuration.json | 30 ++++ minbasic-vscode/package.json | 31 ++++ .../syntaxes/minbasic.tmLanguage.json | 140 +++++++++++++++ tests/examples.rs | 2 +- 15 files changed, 467 insertions(+), 3 deletions(-) create mode 100644 GUIDE.md create mode 100644 LICENSE rename examples/{counter.basic => counter.mbas} (100%) rename examples/{fizzbuzz.basic => fizzbuzz.mbas} (100%) rename examples/{prime.basic => prime.mbas} (100%) rename examples/{procedural-waves.basic => procedural-waves.mbas} (100%) create mode 100644 minbasic-vscode/.gitignore create mode 100644 minbasic-vscode/.vscodeignore create mode 100644 minbasic-vscode/LICENSE create mode 100644 minbasic-vscode/README.md create mode 100644 minbasic-vscode/language-configuration.json create mode 100644 minbasic-vscode/package.json create mode 100644 minbasic-vscode/syntaxes/minbasic.tmLanguage.json diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..4adcdc1 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,160 @@ +# MinBasic guide + +## Keywords and case sensitivity + +Most things in MinBasic are described with keywords. +These keywords are *not* case-sensitive, meaning that `if x > 0 then` is equivalent to `IF x > 0 THEN`. + +User-defined things, like functions and variables, *are case-sensitive*. So `myVariable`, `MYVARIABLE` and `myvariable` all refer to different things. + +We recommend to prefer uppercase for keywords and lowercase for variable names. + +## Comments + +Comments are prefixed with the `REM` keyword. + +## Variables and assignments + + + +To assign a value to a variable, use the `variable = value` syntax, where `value` can be any expression. +Optionally, you can prefix the assignment with the `LET` keyword. + +To use a variable, simply put its name in an expression. + +*Note: multi-line expressions are not yet supported.* + +Variables don't need to be declared, and values can be assigned to them at any point in time. +Using a variable before it was assigned any value will yield `null` instead. + +### Examples + +```basic +REM Sets the variable "answer" to 42: +answer = 42 + +REM Also sets the variable "answer" to 42: +LET answer = 42 + +REM Sets the variable "x" to 21: +x = answer / 2 + +REM Increments "x" by one: +x = x + 1 +``` + + +## Expressions + +The following binary operators are supported: + +- Addition: `a + b` +- Subtraction: `a - b` +- Multiplication: `a * b` +- Division: `a / b` +- Modulo: `a % b` +- Less than: `a < b` +- Greater than: `a > b` +- Less than or equal: `a <= b` +- Greater than or equal: `a >= b` +- Equal: `a == b` +- Not equal: `a != b` + +Multiplication and division have a greater precedence than addition and subtraction. +Comparisons have the lowest precedence. +You can wrap sub-expressions in parentheses to override precedence. + +Some additional operators are only available by calling builtin functions, which are case-insensitive: + +- Maximum: `MAX(a, b)` +- Minimum: `MIN(a, b)` +- Square root: `SQRT(a)` +- Floor: `FLOOR(a)` +- Ceil: `CEIL(a)` +- Round: `ROUND(a)` +- Rand: `RAND(a)`, generates a random number between `0` and `a` + +### Examples + +```basic +REM Picks a random integer between 0 and 63 +n = FLOOR(RAND(64)) + +REM Sets x to the remainder of n by 8, and y by the integer part of n / 8 +x = n % 8 +y = FLOOR(n / 8) + +REM Sets dist to the euclidean distance between (0, 0) and (x, y) +dist = SQRT(x * x + y * y) +``` + +## Jumps and labels + +Jumping allows you to interrupt the regular flow of instruction to go to another point in the program. + +To perform a jump, you will first need to define where you want to jump to. +You have two options: prefixing a line with a number, or writing a named label. + +Then, use the `GOTO` statement to jump to either a line number, or a label. + +### Using line numbers + +```basic +REM The following lines have been numbered. The numbers chosen are arbitrary, but they are commonly increasing multiples of 10, +REM which allows you to squeeze in debugging statements when needed. +10 PRINT "Hello, world" +20 PRINT "This is line 20" + +REM We then jump back to line 20, which will cause an infinite loop printing "This is line 20" +30 GOTO 20 +``` + +### Using labels + +```basic +REM We define here the "start" label +start: +PRINT "Hello, world" + +REM We then jump to the "start" label, causing an infinite loop printing "Hello, world" +GOTO start +``` + +## Conditions + +The `IF` keyword allows you to execute different parts of the code depending on whether a condition is met or not. +The syntax for `IF` is as follows: + +```basic +IF condition THEN + REM Code to be executed if "condition" is true +ELSE + REM Code to be executed if "condition" is false +END IF +``` + +If you do not need to execute code when the condition is false, then you can omit the `ELSE` keyword. + +### Example + +```basic +REM This is a condition without an ELSE block: +IF age < 0 THEN + PRINT "It seems like you weren't born yet..." +END IF + +IF age < 18 THEN + PRINT "You are underaged" +ELSE + REM We can nest conditions within other conditions: + IF age == 18 THEN + PRINT "You just turned 18!" + ELSE + PRINT "You're over 18" + END IF +END IF +``` + +## Loops + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..423fadc --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Emilie BURGUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e44ed44..fd3d78d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ # BASIC to Mindustry logic -This is a small transpiler from the [BASIC](https://en.wikipedia.org/wiki/BASIC) language to [Mindustry](https://github.com/Anuken/Mindustry/)'s [logic system](https://www.reddit.com/r/Mindustry/comments/kfea1e/an_overly_indepth_logic_guide/) (also known as `mlog`). +This is a small transpiler from a dialect of the [BASIC](https://en.wikipedia.org/wiki/BASIC) language, "MinBasic" (also known as `mbas`), to [Mindustry](https://github.com/Anuken/Mindustry/)'s [logic system](https://www.reddit.com/r/Mindustry/comments/kfea1e/an_overly_indepth_logic_guide/) (also known as `mlog`). Basic is chosen as the source language as it already contains jumps (which mindustry heavily relies on), while allowing for some higher-order constructs like conditions, loops and functions. -For now this is a heavily work-in-progress project. +## Installation and running + +To use this project, start by cloning this git repository: + +```sh +git clone https://git.shadamethyst.xyz/amethyst/basic-to-mindustry/ +cd basic-to-mindustry +``` + +You will then need an installation of the Rust compiler, which you can quickly get from [rustup.rs](https://rustup.rs/). + +```sh +# To build the source code: +cargo build + +# To run the binary: +./target/debug/basic-to-mindustry examples/prime.mbas + +# You can do both of these with the following command (note the --): +cargo run -- examples/prime.mbas +``` + + + +## VSCode syntax highlighting + +Any language support extension for QuickBasic (the dialect MinBasic is based on) will work, +but if you would like an extension that was tailored to support MinBasic, you can have a look at [the one bundled with this project](./minbasic-vscode/README.md). + +## Language features + +The [GUIDE.md](./GUIDE.md) file describes how to write programs in MinBasic. diff --git a/examples/counter.basic b/examples/counter.mbas similarity index 100% rename from examples/counter.basic rename to examples/counter.mbas diff --git a/examples/fizzbuzz.basic b/examples/fizzbuzz.mbas similarity index 100% rename from examples/fizzbuzz.basic rename to examples/fizzbuzz.mbas diff --git a/examples/prime.basic b/examples/prime.mbas similarity index 100% rename from examples/prime.basic rename to examples/prime.mbas diff --git a/examples/procedural-waves.basic b/examples/procedural-waves.mbas similarity index 100% rename from examples/procedural-waves.basic rename to examples/procedural-waves.mbas diff --git a/minbasic-vscode/.gitignore b/minbasic-vscode/.gitignore new file mode 100644 index 0000000..1c074c4 --- /dev/null +++ b/minbasic-vscode/.gitignore @@ -0,0 +1,5 @@ +node_modules +*.vsix +.vscode +yarn.lock +target diff --git a/minbasic-vscode/.vscodeignore b/minbasic-vscode/.vscodeignore new file mode 100644 index 0000000..7e188b0 --- /dev/null +++ b/minbasic-vscode/.vscodeignore @@ -0,0 +1,7 @@ +.gitattributes +.gitignore +yarn.lock +node_modules +.vscode +*.vsix +target diff --git a/minbasic-vscode/LICENSE b/minbasic-vscode/LICENSE new file mode 100644 index 0000000..423fadc --- /dev/null +++ b/minbasic-vscode/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Emilie BURGUN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/minbasic-vscode/README.md b/minbasic-vscode/README.md new file mode 100644 index 0000000..1c06c6b --- /dev/null +++ b/minbasic-vscode/README.md @@ -0,0 +1,22 @@ +# Language support for the MinBASIC language + +This small vscode extension provides language support for the MinBASIC language. + +To build it, run the following: + +```sh +npm i -g @vscode/vsce +vsce package + +# Alternatively, +npx @vscode/vsce package +``` + +Then, install it by running: + +```sh +vscodium --install-extension ./minbasic-vscode-*.vsix + +# If you're using the proprietary builds of VSCode: +vscode --install-extension ./minbasic-vscode-*.vsix +``` diff --git a/minbasic-vscode/language-configuration.json b/minbasic-vscode/language-configuration.json new file mode 100644 index 0000000..8f162a0 --- /dev/null +++ b/minbasic-vscode/language-configuration.json @@ -0,0 +1,30 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//", + // symbols used for start and end a block comment. Remove this entry if your language does not support block comments + "blockComment": [ "/*", "*/" ] + }, + // symbols used as brackets + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + // symbols that can be used to surround a selection + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} \ No newline at end of file diff --git a/minbasic-vscode/package.json b/minbasic-vscode/package.json new file mode 100644 index 0000000..9534cff --- /dev/null +++ b/minbasic-vscode/package.json @@ -0,0 +1,31 @@ +{ + "name": "minbasic-vscode", + "displayName": "MinBasic Language Support", + "description": "Language support for the MinBasic language, which is an extension of QuickBasic for compilation to Mindustry Logic", + "version": "0.0.1", + "engines": { + "vscode": "^1.79.0" + }, + "categories": [ + "Programming Languages" + ], + "repository": { + "type": "git", + "url": "https://git.shadamethyst.xyz/amethyst/basic-to-mindustry" + }, + "author": "Shad Amethyst", + "license": "MIT", + "contributes": { + "languages": [{ + "id": "minbasic", + "aliases": ["MinBasic", "minbasic"], + "extensions": ["mbas"], + "configuration": "./language-configuration.json" + }], + "grammars": [{ + "language": "minbasic", + "scopeName": "source.mbas", + "path": "./syntaxes/minbasic.tmLanguage.json" + }] + } +} diff --git a/minbasic-vscode/syntaxes/minbasic.tmLanguage.json b/minbasic-vscode/syntaxes/minbasic.tmLanguage.json new file mode 100644 index 0000000..a81e426 --- /dev/null +++ b/minbasic-vscode/syntaxes/minbasic.tmLanguage.json @@ -0,0 +1,140 @@ +{ + "scopeName": "source.mbas", + "fileTypes": [ + "mbas", + "minbasic" + ], + "name": "MinBasic", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.comment.minbasic" + } + }, + "comment": "Comment", + "match": "^ *(REM\\b|').*", + "name": "comment.line.minbasic" + }, + { + "comment": "Delimiter", + "match": "[,:;]", + "name": "meta.delimiter.object.minbasic" + }, + { + "comment": "Keyword", + "match": "(?i)(\\b((END ?)?IF|(END )?SELECT|(RESUME )?NEXT|CASE|CLOSE|DO|ELSE|FOR|GOSUB|GOTO|LOOP|ON|OPEN|RETURN|THEN|TO|UNTIL|WHILE)\\b)", + "name": "keyword.control.minbasic" + }, + { + "comment": "Function", + "match": "(?i)(\\b(PRINT)\\b)", + "name": "support.function.minbasic" + }, + { + "comment": "Operator", + "match": "(?i)((\\+|=|<|>|<>|AND|OR))", + "name": "keyword.operator.minbasic" + }, + { + "comment": "Numeric", + "match": "\\b(\\d(\\.\\d)?)+", + "name": "constant.numeric.minbasic" + }, + { + "comment": "Mindustry builtins", + "match": "(@(?:time|tick|unit|counter|this[xy]?|ipt|links|map[wh]))\\b", + "name": "constant.global.minbasic" + }, + { + "captures": { + "1": { + "name": "entity.name.function.minbasic" + } + }, + "comment": "SUB", + "match": "(?i)(^ *(\\w+):)", + "name": "meta.function.minbasic" + }, + { + "name": "meta.assignment.minbasic", + "match": "^ *((?i)LET +)([a-zA-Z_@#][a-zA-Z0-9_@#]*) *(=)", + "captures": { + "1": { + "name": "keyword.other.minbasic" + }, + "2": { + "name": "variable.other.minbasic" + }, + "3": { + "name": "keyword.operator.assignment.minbasic" + } + } + }, + { + "comment": "Brace, round", + "match": "[\\(\\)]", + "name": "meta.brace.round.minbasic" + }, + { + "comment": "Brace, curly", + "match": "[\\{\\}]", + "name": "meta.brace.curly.minbasic" + }, + { + "begin": "(\\w+)(\\()", + "beginCaptures": { + "1": { + "name": "entity.name.function.minbasic" + }, + "2": { + "name": "meta.brace.round.minbasic" + } + }, + "comment": "Function call", + "end": "(\\))", + "endCaptures": { + "1": { + "name": "meta.brace.round.minbasic" + } + }, + "name": "meta.function.call.minbasic", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "(\")", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.minbasic" + } + }, + "comment": "String, double-quoted", + "end": "(\")", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.minbasic" + } + }, + "name": "string.quoted.double.minbasic", + "patterns": [ + { + "comment": "Escaped double-quote inside double-quoted string", + "match": "(\\\")", + "name": "constant.character.escape.minbasic" + }, + { + "comment": "Single quote inside double-quoted string", + "match": "(')", + "name": "other.minbasic" + }, + { + "include": "$self" + } + ] + } + ] +} diff --git a/tests/examples.rs b/tests/examples.rs index 29e86c8..3d7a77b 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -19,7 +19,7 @@ fn read_basic_examples() -> impl Iterator { if entry .file_name() .into_string() - .map(|name| name.ends_with(".basic")) + .map(|name| name.ends_with(".mbas")) .unwrap_or(false) { let file_name = entry.file_name().into_string().unwrap();