├── .gitignore ├── README.md ├── dub.sdl ├── license.md ├── source ├── app.d └── qscript │ ├── ast.d │ ├── base │ ├── parser.d │ └── tokens.d │ ├── compiler.d │ ├── parser.d │ └── tokens.d ├── spec ├── coding-conventions.md └── spec.md └── topuml /.gitignore: -------------------------------------------------------------------------------- 1 | .dub/ 2 | .vscode/ 3 | ast 4 | demo 5 | dub_platform* 6 | dub.selections.json 7 | lib*.a 8 | *.bcode 9 | *-test-default 10 | *.pdf 11 | *.svg 12 | sample 13 | /ast.json 14 | /qscript 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QScript 2 | 3 | A Simple Scripting Language. 4 | 5 | **For last stable version, see v0.7.4** 6 | 7 | **QScript is currently under development, and being redesigned under a new 8 | specification, see spec/spec.md.** 9 | 10 | ## Setting it up 11 | 12 | To add QScript to your dub package or project, run this in your dub package's 13 | directory: 14 | 15 | ```bash 16 | dub add qscript 17 | ``` 18 | 19 | Look at the `source/demo.d` to see how to use QScript 20 | 21 | --- 22 | 23 | ## Getting Started 24 | 25 | To get started on using QScript, see the following documents: 26 | 27 | * `spec/syntax.md` - language specification 28 | * `examples/` - Contains some scripts showing how to write scripts. // TODO 29 | write these 30 | 31 | ### Building 32 | 33 | QScript comes with a standalone configuration, so it can be used without being 34 | first integrated into some program as a library. 35 | You can build it using: 36 | 37 | ```bash 38 | dub build qscript -c=bin -b=release 39 | ``` 40 | 41 | You can use this executable to run scripts, inspect the generated AST for script, or the generated NaVM bytecode: 42 | 43 | ```bash 44 | ./qscript /path/to/script # execute a script 45 | 46 | ./qscript --ast /path/to/script # pretty print AST for script 47 | 48 | ./qscript --bcode /path/to/script # print NaVM bytecode for script 49 | ``` 50 | 51 | ## Features 52 | 53 | // Most of these are not yet implemented 54 | 55 | 1. Static typed 56 | 1. Dynamic arrays 57 | 1. Templates 58 | 1. Conditional Compilation 59 | 1. Compile Time Function Execution 60 | 1. First class Functions 61 | 1. Lambda Functions 62 | 1. Function overloading 63 | 1. Operator overloading 64 | 1. Reference Data Types 65 | 1. Structs 66 | 1. Enums 67 | 68 | --- 69 | 70 | ## Hello World 71 | 72 | This is how a hello world would look like in QScript. For more examples, see 73 | `examples/` // TODO write examples 74 | 75 | ``` 76 | load(stdio); 77 | 78 | fn main(){ 79 | writeln("Hello World!"); 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "qscript" 2 | description "A Simple Scripting Language" 3 | authors "Nafees Hassan" 4 | copyright "Copyright © 2016-2024, Nafees Hassan" 5 | license "MIT" 6 | dependency "utils" version="~>0.8.0" 7 | dependency "navm" version="~>2.0.2" 8 | configuration "library" { 9 | targetType "library" 10 | targetName "qscript" 11 | } 12 | configuration "bin" { 13 | targetType "executable" 14 | targetName "qscript" 15 | versions "standalone" 16 | } 17 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2024 Nafees Hassan 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 | -------------------------------------------------------------------------------- /source/app.d: -------------------------------------------------------------------------------- 1 | module app; 2 | 3 | import std.stdio, 4 | std.file, 5 | std.algorithm.iteration, 6 | std.datetime.stopwatch, 7 | std.format, 8 | std.conv; 9 | 10 | import utils.ds; 11 | 12 | import qscript.parser, 13 | qscript.tokens, 14 | qscript.compiler; 15 | 16 | version(unittest){}else 17 | int main(string[] args){ 18 | // tokenize file 19 | Flags!TokenType ignore; 20 | ignore |= TokenType.Whitespace; 21 | ignore |= TokenType.Comment; 22 | ignore |= TokenType.CommentMultiline; 23 | auto range = Tokenizer(cast(string)read("sample"), ignore); 24 | 25 | /*foreach (tok; range) 26 | stderr.writeln(tok);*/ 27 | 28 | if (args.length > 1 && args[1] == "time" ){ 29 | timeIt(range, args.length > 2 ? args[2].to!uint : 1); 30 | return 0; 31 | } 32 | 33 | auto result = parseScript(range); 34 | if (!result){ 35 | stderr.writeln("error: ", result.error.msg); 36 | return 1; 37 | } 38 | Node rootNode = result.value; 39 | if (rootNode) 40 | writeln(rootNode.toJSON.toPrettyString); 41 | else 42 | stderr.writeln("root is null"); 43 | return 0; 44 | } 45 | 46 | void timeIt(Tokenizer toks, uint times){ 47 | void benchmark(ref StopWatch sw){ 48 | auto branch = toks; 49 | sw.start; 50 | parseScript(branch); 51 | sw.stop; 52 | } 53 | bench(&benchmark, times).writeln; 54 | } 55 | 56 | struct Times{ 57 | ulong min = ulong.max; 58 | ulong max = 0; 59 | ulong total = 0; 60 | ulong avg = 0; 61 | string toString() const @safe pure{ 62 | return format!"min\tmax\tavg\ttotal\t/msecs\n%d\t%d\t%d\t%d"( 63 | min, max, avg, total); 64 | } 65 | } 66 | 67 | Times bench(void delegate(ref StopWatch sw) func, ulong runs = 100_000){ 68 | Times time; 69 | StopWatch sw = StopWatch(AutoStart.no); 70 | foreach (i; 0 .. runs){ 71 | func(sw); 72 | immutable ulong currentTime = sw.peek.total!"msecs" - time.total; 73 | time.min = currentTime < time.min ? currentTime : time.min; 74 | time.max = currentTime > time.max ? currentTime : time.max; 75 | time.total = sw.peek.total!"msecs"; 76 | } 77 | time.avg = time.total / runs; 78 | return time; 79 | } 80 | -------------------------------------------------------------------------------- /source/qscript/ast.d: -------------------------------------------------------------------------------- 1 | module qscript.ast; 2 | 3 | import std.json; 4 | 5 | import qscript.compiler, 6 | qscript.tokens; 7 | 8 | import utils.ds; 9 | 10 | /// Generic AST Node 11 | public abstract class Node{ 12 | public: 13 | /// location in source 14 | uint line, col; 15 | /// parent node 16 | Node parent; 17 | 18 | /*abstract JSONValue toJSON() const; 19 | final override string toString() const { 20 | return toJSON.toString; 21 | }*/ 22 | } 23 | 24 | public class ScriptNode : Node{ 25 | public: 26 | /// declarations 27 | DeclNode[] decls; 28 | } 29 | 30 | public abstract class DeclNode : Node{ 31 | public: 32 | /// name 33 | string name; 34 | } 35 | 36 | public class PubNode : DeclNode{ 37 | public: 38 | /// declaration 39 | DeclNode decl; 40 | } 41 | 42 | public class TemplateNode : DeclNode{ 43 | public: 44 | /// Template parameters 45 | TemplateParamNode[] params; 46 | /// declarations 47 | DeclNode[] decls; 48 | } 49 | 50 | public class FnNode : DeclNode{ 51 | public: 52 | /// Return type (can be null) 53 | ExpressionNode retType; 54 | /// Parameters 55 | ParamNode[] parameters; 56 | /// body 57 | StatementNode statement; 58 | } 59 | 60 | public class VarNode : DeclNode{ 61 | public: 62 | /// Data type 63 | ExpressionNode type; 64 | /// variable name to value map (value can be null) 65 | ExpressionNode[string] vars; 66 | } 67 | 68 | public class StructNode : DeclNode{ 69 | public: 70 | /// declarations 71 | DeclNode[] decls; 72 | } 73 | 74 | public class EnumNode : DeclNode{ 75 | public: 76 | /// data type 77 | ExpressionNode type; 78 | /// members to values map (value can be null) 79 | StatementNode[string] members; 80 | } 81 | 82 | public class AliasNode : DeclNode{ 83 | public: 84 | /// Expression used to identify what is being aliased 85 | ExpressionNode expAliasTo; 86 | } 87 | 88 | public class ParamNode : Node{ 89 | public: 90 | /// data type 91 | ExpressionNode dataType; 92 | /// name, can be null 93 | string name; 94 | } 95 | 96 | public class TemplateParamNode : Node{ 97 | public: 98 | /// Data Type (can be null) 99 | ExpressionNode dataType; 100 | /// name 101 | string name; 102 | } 103 | 104 | public abstract class StatementNode : Node{} 105 | 106 | public class ReturnStatement : StatementNode{ 107 | public: 108 | /// return value 109 | ExpressionNode value; 110 | } 111 | 112 | public class IfNode : StatementNode{ 113 | public: 114 | /// condition 115 | ExpressionNode condition; 116 | /// statement 117 | StatementNode statement; 118 | /// else statement 119 | StatementNode elseStatement; 120 | } 121 | 122 | public class StaticIfNode : StatementNode{ 123 | public: 124 | /// condition 125 | ExpressionNode condition; 126 | /// statement 127 | StatementNode statement; 128 | /// else statement 129 | StatementNode elseStatement; 130 | } 131 | 132 | public class WhileNode : StatementNode{ 133 | public: 134 | /// condition 135 | ExpressionNode condition; 136 | /// statement 137 | StatementNode statement; 138 | } 139 | 140 | public class DoWhileNode : StatementNode{ 141 | public: 142 | /// condition 143 | ExpressionNode condition; 144 | /// statement 145 | StatementNode statement; 146 | } 147 | 148 | public class ForNode : StatementNode{ 149 | public: 150 | /// iteration counter name, can be null 151 | string counter; 152 | /// iterator name 153 | string iterator; 154 | /// range 155 | ExpressionNode range; 156 | } 157 | 158 | public class StaticForNode : StatementNode{ 159 | public: 160 | /// iteration counter name, can be null 161 | string counter; 162 | /// iterator name 163 | string iterator; 164 | /// range 165 | ExpressionNode range; 166 | } 167 | 168 | public class BreakNode : StatementNode{ 169 | public: 170 | } 171 | 172 | public class ContinueNode : StatementNode{ 173 | public: 174 | } 175 | 176 | public class BlockNode : StatementNode{ 177 | public: 178 | /// statements 179 | StatementNode[] statements; 180 | } 181 | 182 | public abstract class ExpressionNode : Node{} 183 | 184 | public class LiteralIntNode : ExpressionNode{ 185 | public: 186 | /// integer value 187 | ptrdiff_t value; 188 | } 189 | 190 | public class LiteralFloatNode : ExpressionNode{ 191 | public: 192 | /// floating point value 193 | double value; 194 | } 195 | 196 | public class LiteralStringNode : ExpressionNode{ 197 | public: 198 | /// string value 199 | string value; 200 | } 201 | 202 | public class LiteralCharNode : ExpressionNode{ 203 | public: 204 | /// character value 205 | char value; 206 | } 207 | 208 | public class LiteralNullNode : ExpressionNode{ 209 | public: 210 | // no members 211 | } 212 | 213 | public class LiteralBoolNode : ExpressionNode{ 214 | public: 215 | /// boolean value 216 | bool value; 217 | } 218 | 219 | public class TraitNode : ExpressionNode{ 220 | public: 221 | /// trait name 222 | string name; 223 | /// trait parameters 224 | ExpressionNode params; 225 | } 226 | 227 | public class LoadNode : ExpressionNode{ 228 | public: 229 | /// module path (a.b.c will be [a,b,c]) 230 | string[] modulePath; 231 | } 232 | 233 | public class ArrowParamNode : Node{ 234 | public: 235 | /// Data type (can be null) 236 | ExpressionNode dataType; 237 | /// name 238 | string name; 239 | } 240 | 241 | public class ArrowFuncNode : ExpressionNode{ 242 | public: 243 | /// parameters 244 | ArrowParamNode[] params; 245 | } 246 | 247 | public class BinOp : ExpressionNode{ 248 | public: 249 | } 250 | 251 | public class UnaryOp : ExpressionNode{ 252 | public: 253 | } 254 | 255 | public class OpDot : BinOp{ 256 | public: 257 | } 258 | 259 | public class OpIndex : BinOp{ 260 | public: 261 | } 262 | 263 | public class OpCall : BinOp{ 264 | public: 265 | } 266 | 267 | public class OpPostInc : UnaryOp{ 268 | public: 269 | } 270 | 271 | public class OpPostDec : UnaryOp{ 272 | public: 273 | } 274 | 275 | public class OpPreInc : UnaryOp{ 276 | public: 277 | } 278 | 279 | public class OpPreDec : UnaryOp{ 280 | public: 281 | } 282 | 283 | public class OpNot : UnaryOp{ 284 | public: 285 | } 286 | 287 | public class OpMul : BinOp{ 288 | public: 289 | } 290 | 291 | public class OpDiv : BinOp{ 292 | public: 293 | } 294 | 295 | public class OpMod : BinOp{ 296 | public: 297 | } 298 | 299 | public class OpAdd : BinOp{ 300 | public: 301 | } 302 | 303 | public class OpSub : BinOp{ 304 | public: 305 | } 306 | 307 | public class OpLShift : BinOp{ 308 | public: 309 | } 310 | 311 | public class OpRShift : BinOp{ 312 | public: 313 | } 314 | 315 | public class OpEquals : BinOp{ 316 | public: 317 | } 318 | 319 | public class OpNotEquals : BinOp{ 320 | public: 321 | } 322 | 323 | public class OpGreaterEquals : BinOp{ 324 | public: 325 | } 326 | 327 | public class OpLesserEquals : BinOp{ 328 | public: 329 | } 330 | 331 | public class OpGreater : BinOp{ 332 | public: 333 | } 334 | 335 | public class OpLesser : BinOp{ 336 | public: 337 | } 338 | 339 | public class OpIs : BinOp{ 340 | public: 341 | } 342 | 343 | public class OpNotIs : BinOp{ 344 | public: 345 | } 346 | 347 | public class OpBinAnd : BinOp{ 348 | public: 349 | } 350 | 351 | public class OpBinOr : BinOp{ 352 | public: 353 | } 354 | 355 | public class OpBinXor : BinOp{ 356 | public: 357 | } 358 | 359 | public class OpBoolAnd : BinOp{ 360 | public: 361 | } 362 | 363 | public class OpBoolOr : BinOp{ 364 | public: 365 | } 366 | 367 | public class OpAssign : BinOp{ 368 | public: 369 | } 370 | 371 | public class OpAddAssign : BinOp{ 372 | public: 373 | } 374 | 375 | public class OpSubAssign : BinOp{ 376 | public: 377 | } 378 | 379 | public class ObMulAssign : BinOp{ 380 | public: 381 | } 382 | 383 | public class OpDivAssign : BinOp{ 384 | public: 385 | } 386 | 387 | public class OpModAssign : BinOp{ 388 | public: 389 | } 390 | 391 | public class OpBinAndAssign : BinOp{ 392 | public: 393 | } 394 | 395 | public class OpBinOrAssign : BinOp{ 396 | public: 397 | } 398 | 399 | public class OpBinXorAssign : BinOp{ 400 | public: 401 | } 402 | -------------------------------------------------------------------------------- /source/qscript/base/parser.d: -------------------------------------------------------------------------------- 1 | module qscript.base.parser; 2 | 3 | import qscript.base.tokens; 4 | 5 | import std.traits; 6 | import std.json; 7 | import std.string; 8 | import std.array; 9 | import std.algorithm; 10 | import std.uni : isWhite; 11 | import std.stdio; 12 | import std.conv : to; 13 | 14 | /// an AST Node 15 | // initialise with T = Token Type enum, M = Match Type enum 16 | public class Node(T, M) if (is(T == enum) && is(M == enum)){ 17 | public: 18 | Token!T token; 19 | Node[] childNodes; 20 | M match; 21 | 22 | JSONValue toJSON(){ 23 | JSONValue ret; 24 | ret["token"] = token.token; 25 | ret["match"] = match.to!string; 26 | string type; 27 | foreach (member; EnumMembers!T){ 28 | if (!token.type[member]) 29 | continue; 30 | type ~= member.to!string ~ ", "; 31 | } 32 | if (type.length) 33 | ret["type"] = type.chomp(", "); 34 | JSONValue[] sub; 35 | foreach (child; childNodes) 36 | sub ~= child.toJSON; 37 | if (sub.length) 38 | ret["childNodes"] = sub; 39 | return ret; 40 | } 41 | } 42 | 43 | /// parses tokens using a M match type enum. 44 | /// 45 | /// Returns: Node, or null 46 | public Node!T match(T, M)(ref Token!T[] tokens) if ( 47 | is(T == enum) && is(M == enum)){ 48 | Node!T ret; 49 | 50 | return ret; 51 | } 52 | 53 | /// MatchExp[] associated with an enum M 54 | private template Matchers(T, M) if (is(M == enum) && is(T == enum)){ 55 | enum auto Matchers = readMatchers(); 56 | private MatchExp!(T, M)[] readMatchers() pure { 57 | MatchExp!(T, M)[] ret; 58 | static foreach (sym; getSymbolsByUDA!(M, string)){ 59 | static foreach (match; getUDAs!(sym, string)){ 60 | ret ~= parseMatchExp!(T, M, sym, match); 61 | static if (hasUDA!(sym, KeyMatch)) 62 | ret[$ - 1].isKey = true; 63 | } 64 | } 65 | return ret; 66 | } 67 | } 68 | 69 | /// MatchExp[] associated with a specific member in enum M 70 | private template Matchers(T, M, M sym) if (is(M == enum) && is(T == enum)){ 71 | enum auto Matchers = readMatchers(); 72 | private MatchExp!(T, M)[] readMatchers() pure { 73 | MatchExp!(T, M)[] ret; 74 | static foreach (matcher; Matchers!(T, M)){ 75 | static if (matcher.type == sym){ 76 | ret ~= matcher; 77 | static if (hasUDA!(sym, KeyMatch)) 78 | ret[$ - 1].isKey = true; 79 | } 80 | } 81 | return ret; 82 | } 83 | } 84 | 85 | /// Matching expression for a single match type 86 | private struct MatchExp(T, M){ 87 | struct Unit{ 88 | bool terminal = false; 89 | bool loop = false; 90 | bool or = false; 91 | union{ 92 | T tok; 93 | M mat; 94 | Unit[] units; 95 | } 96 | 97 | this(M mat){ 98 | this.terminal = this.loop = this.or = false; 99 | this.mat = mat; 100 | } 101 | this(T tok){ 102 | this.terminal = this.loop = this.or = false; 103 | this.tok = tok; 104 | } 105 | this(Unit[] units, bool isLoop = true){ 106 | this.loop = isLoop; 107 | this.or = !isLoop; 108 | this.units = units; 109 | } 110 | string toString() const { 111 | return isTok ? tok.to!string : '/' ~ mat.to!string; 112 | } 113 | } 114 | 115 | Unit[] units; 116 | M type; 117 | 118 | this(Unit[] units, M type){ 119 | this.units = units; 120 | this.type = type; 121 | } 122 | 123 | string toString() const { 124 | string ret = (isKey ? "#/" : "/") ~ type.to!string ~ " ->"; 125 | foreach (i, unit; units) 126 | ret ~= " " ~ unit.toString; 127 | return ret; 128 | } 129 | } 130 | 131 | /// parses string into MatchExp[] 132 | private MatchExp!(T, M) parseMatchExp(T, M, M type, string str)(){ 133 | struct Unit{ 134 | string name; 135 | bool terminal = true; 136 | bool ignore = false; 137 | } 138 | static Matcher parseIndividual(string str){ 139 | Matcher ret; 140 | if (str.length && str[0] == '-'){ 141 | ret.isRoot = true; 142 | str = str[1 .. $]; 143 | } 144 | if (str.length && str[0] == '/'){ 145 | ret.isMatched = true; 146 | str = str[1 .. $]; 147 | } 148 | ret.name = str; 149 | return ret; 150 | } 151 | alias Unit = MatchExp!(T, M).Unit; 152 | 153 | Unit[] units; 154 | uint root; 155 | static foreach (i, str; str.split!isWhite){{ 156 | enum auto matcher = parseIndividual(str); 157 | static if (matcher.isRoot) 158 | root = i; 159 | static if (matcher.isMatched){ 160 | static assert(__traits(hasMember, M, matcher.name), 161 | matcher.name ~ " not found in " ~ M.stringof); 162 | units ~= Unit(__traits(getMember, M, matcher.name)); 163 | }else{ 164 | static assert(__traits(hasMember, T, matcher.name), 165 | matcher.name ~ " not found in " ~ T.stringof); 166 | units ~= Unit(__traits(getMember, T, matcher.name)); 167 | } 168 | }} 169 | return MatchExp!(T, M)(units, type, root); 170 | } 171 | -------------------------------------------------------------------------------- /source/qscript/base/tokens.d: -------------------------------------------------------------------------------- 1 | module qscript.base.tokens; 2 | 3 | import utils.ds; 4 | 5 | import std.algorithm, 6 | std.traits, 7 | std.conv, 8 | std.meta; 9 | 10 | debug import std.stdio; 11 | 12 | /// A token 13 | public struct Token(T) if (is (T == enum)){ 14 | /// line number and column number 15 | uint lineno, colno; 16 | /// token type(s) 17 | Flags!T type; 18 | /// token 19 | string token; 20 | alias token this; 21 | 22 | /// constructor 23 | this(Flags!T type, string token){ 24 | this.type = type; 25 | this.token = token; 26 | } 27 | 28 | string toString() const { 29 | string ret = "{"; 30 | static foreach (member; EnumMembers!T){ 31 | if (type[member]) 32 | ret ~= member.to!string ~ ", "; 33 | } 34 | ret ~= "`" ~ token ~ "`}"; 35 | return ret; 36 | } 37 | 38 | /// == operator 39 | bool opBinary(string op : "==")(const Token rhs) const{ 40 | return type == rhs.type && token == rhs.token; 41 | } 42 | /// ditto 43 | bool opBinary(string op : "==")(const T rhs) const{ 44 | return type.get(rhs); 45 | } 46 | /// ditto 47 | bool opBinary(string op : "==")(const string rhs) const{ 48 | return token == rhs; 49 | } 50 | /// != operator 51 | bool opBinary(string op : "!=")(const Token rhs) const{ 52 | return type != rhs.type || token != rhs.token; 53 | } 54 | /// ditto 55 | bool opBinary(string op : "!=")(const T rhs) const{ 56 | return !type.get(rhs); 57 | } 58 | /// ditto 59 | bool opBinary(string op : "!=")(const string rhs) const{ 60 | return token != rhs; 61 | } 62 | } 63 | 64 | /// Match("..") for exact match 65 | /// Match(&func, "ab") for custom function, where it only need to trigger 66 | /// if string starts with a or b 67 | public struct Match{ 68 | private bool exactMatch; 69 | string matchStr; 70 | uint function(string) funcMatch; 71 | 72 | this (string match){ 73 | this.exactMatch = true; 74 | this.matchStr = match; 75 | } 76 | 77 | this(uint function(string) match, string matchStr){ 78 | this.funcMatch = match; 79 | this.exactMatch = false; 80 | this.matchStr = matchStr; 81 | } 82 | } 83 | 84 | /// Tokenizer Exception 85 | public class TokenizerException : Exception{ 86 | private: 87 | uint _lineno, _colno; 88 | 89 | public: 90 | this(uint lineno, uint colno){ 91 | _lineno = lineno; 92 | _colno = colno; 93 | super(_lineno.to!string ~ "," ~ _colno.to!string ~ ": Unexpected token"); 94 | } 95 | 96 | /// Line number where error occurred 97 | @property uint line() const { 98 | return _lineno; 99 | } 100 | 101 | /// Column number wher error occurred 102 | @property uint col() const { 103 | return _colno; 104 | } 105 | } 106 | 107 | /// Matches that have given initial character 108 | /// Match alternates with Enum Member 109 | template MatchesWithInitChar(T, char C){ 110 | alias MatchesWithInitChar = AliasSeq!(); 111 | static foreach (member; EnumMembers!T){ 112 | static foreach (matcher; getUDAs!(member, Match)){ 113 | static if (matcher.exactMatch){ 114 | static if (matcher.matchStr.length && matcher.matchStr[0] == C) 115 | MatchesWithInitChar = AliasSeq!(MatchesWithInitChar, matcher, 116 | member); 117 | }else{ 118 | static foreach (char ch; matcher.matchStr){ 119 | static if (ch == C){ 120 | MatchesWithInitChar = AliasSeq!(MatchesWithInitChar, matcher, 121 | member); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | private Token!T match(T)(string str){ 130 | Flags!T matches; 131 | uint maxLen; 132 | Switch: switch (str[0]){ 133 | static foreach (char C; char.min .. char.max){ 134 | static if (MatchesWithInitChar!(T, C).length){ 135 | case C: 136 | static foreach (i; 0 .. MatchesWithInitChar!(T, C).length / 2){{ 137 | alias matcher = MatchesWithInitChar!(T, C)[i * 2]; 138 | alias member = MatchesWithInitChar!(T, C)[i * 2 + 1]; 139 | uint localMaxLen = 0; 140 | static if (matcher.exactMatch){ 141 | if (str.length >= matcher.matchStr.length && 142 | (str[0 .. matcher.matchStr.length] == matcher.matchStr)){ 143 | localMaxLen = matcher.matchStr.length; 144 | } 145 | }else{ 146 | localMaxLen = matcher.funcMatch(str); 147 | } 148 | if (localMaxLen > maxLen){ 149 | maxLen = localMaxLen; 150 | matches.set(false); 151 | matches.set!member(true); 152 | }else if (maxLen && localMaxLen == maxLen){ 153 | matches.set!member(true); 154 | } 155 | }} 156 | break Switch; 157 | } 158 | } 159 | default: break; 160 | } 161 | return Token!T(matches, str[0 .. maxLen]); 162 | } 163 | 164 | /// Fancy string exploder 165 | public struct Tokenizer(T) if (is (T == enum)){ 166 | private: 167 | string _source; 168 | uint _seek; 169 | uint _lineno; 170 | uint _lastNewlineIndex; 171 | 172 | Flags!T _ignore; 173 | Token!T _next; 174 | bool _empty; 175 | 176 | 177 | /// match a token 178 | Token!T _getToken(){ 179 | Token!T ret = match!T(_source[_seek .. $]); 180 | if (!ret.token.length) 181 | throw new TokenizerException(_lineno + 1, _seek - _lastNewlineIndex); 182 | 183 | // figure out line number etc 184 | ret.lineno = _lineno + 1; 185 | ret.colno = _seek - _lastNewlineIndex; 186 | // increment _lineno if needed 187 | foreach (i, c; _source[_seek .. _seek + ret.length]){ 188 | if (c == '\n'){ 189 | _lastNewlineIndex = cast(uint)i + _seek; 190 | _lineno ++; 191 | } 192 | } 193 | _seek += ret.length; 194 | return ret; 195 | } 196 | 197 | /// Parses next token. This will throw if called after all tokens been read 198 | /// 199 | /// Returns: Token 200 | /// 201 | /// Throws: TokenizerException 202 | void _parseNext(){ 203 | while (_seek < _source.length){ 204 | auto prev = _seek; 205 | _next = _getToken; 206 | if (!(_next.type & _ignore)) 207 | break; 208 | if (prev == _seek) 209 | throw new TokenizerException(_next.lineno, _next.colno); 210 | } 211 | if (_next.type & _ignore) 212 | _empty = true; 213 | } 214 | 215 | public: 216 | @disable this(); 217 | this (string source, Flags!T ignore){ 218 | this._source = source; 219 | this._ignore = ignore; 220 | _empty = _source.length == 0; 221 | _parseNext; 222 | } 223 | 224 | bool empty(){ 225 | return _empty; 226 | } 227 | 228 | void popFront(){ 229 | if (_seek >= _source.length) 230 | _empty = true; 231 | _parseNext; 232 | } 233 | 234 | Token!T front(){ 235 | return _next; 236 | } 237 | 238 | } 239 | 240 | /// 241 | unittest{ 242 | static uint identifyWhitespace(string str){ 243 | uint ret = 0; 244 | while (ret < str.length && (str[ret] == ' ' || str[ret] == '\n')) 245 | ret ++; 246 | return ret; 247 | } 248 | 249 | static uint identifyWord(string str){ 250 | foreach (i, c; str){ 251 | if (c < 'a' || c > 'z') 252 | return cast(uint)i; 253 | if (i + 1 == str.length) 254 | return cast(uint)i + 1; 255 | } 256 | return 0; 257 | } 258 | 259 | enum Type{ 260 | @Match(`keyword`) Keyword, 261 | @Match(&identifyWhitespace) Whitespace, 262 | @Match(&identifyWord) Word 263 | } 264 | 265 | Token!Type[] tokens; 266 | auto range = Tokenizer!Type(` keyword word`, Flags!Type()); 267 | foreach (token; range) 268 | tokens ~= token; 269 | 270 | assert (tokens.length == 4); 271 | assert(tokens[0].type.get!(Type.Whitespace)); 272 | 273 | assert(tokens[1].type.get!(Type.Keyword)); 274 | assert(tokens[1].type.get!(Type.Word)); 275 | 276 | assert(tokens[2].type.get!(Type.Whitespace)); 277 | 278 | assert(tokens[3].type.get!(Type.Word)); 279 | } 280 | -------------------------------------------------------------------------------- /source/qscript/compiler.d: -------------------------------------------------------------------------------- 1 | module qscript.compiler; 2 | 3 | import utils.misc; 4 | import utils.ds; 5 | 6 | import std.conv : to; 7 | import std.traits; 8 | 9 | import qscript.tokens; 10 | 11 | debug import std.stdio; 12 | 13 | /// default script name 14 | package const string DEFAULT_SCRIPT_NAME = "QSCRIPT_SCRIPT"; 15 | /// default namespace name 16 | package const string DEFAULT_NAMESPACE = "this"; 17 | /// maximum number of errors 18 | package const uint ERRORS_MAX = 20; 19 | 20 | /// Data type names 21 | package enum TYPENAME : string{ 22 | INT = "int", 23 | FLOAT = "float", 24 | CHAR = "char", 25 | BOOL = "bool", 26 | } 27 | 28 | /// unescapes a string. the string must be provided with surrounding quotes stripped 29 | /// 30 | /// Returns: unescaped string 31 | package char[] strUnescape(string str){ 32 | uint i, shift; 33 | char[] r; 34 | r.length = str.length; 35 | while (i + shift < str.length && i < r.length){ 36 | if (str[i + shift] == '\\'){ 37 | shift ++; 38 | if (i + shift < str.length){ 39 | r[i] = charUnescape(str[i + shift]); 40 | r.length --; 41 | } 42 | }else 43 | r[i] = str[i + shift]; 44 | i ++; 45 | } 46 | return r; 47 | } 48 | /// 49 | unittest{ 50 | string s = "t\\\"bcd\\\"\\t\\\\"; 51 | assert(strUnescape(s) == "t\"bcd\"\t\\", strUnescape(s)); 52 | } 53 | 54 | /// Returns: unescaped character, for a character `c` when used as `\c` 55 | package char charUnescape(char c){ 56 | switch (c){ 57 | case 't': return '\t'; 58 | case 'n': return '\n'; 59 | case 'b': return '\b'; 60 | default: return c; 61 | } 62 | } 63 | 64 | /// compilation error 65 | /// Possible types of errors 66 | public enum ErrorType{ 67 | @("") None, 68 | @("unidentified token, cannot read further") TokenInvalid, 69 | @("unexpected token, cannot read further") TokenUnexpected, 70 | @("Expected $") Expected, 71 | @("Expected $ found $") ExpectedAFoundB, 72 | @("Expected 1 character inside ''") CharLengthInvalid, 73 | @("Unexpected end of file") UnexpectedEOF, 74 | @("Syntax Error") SyntaxError, 75 | } 76 | 77 | /// error explain format string 78 | private string errorExplainFormat(ErrorType type){ 79 | switch (type){ 80 | static foreach (member; EnumMembers!ErrorType){ 81 | case member: 82 | return getUDAs!(member, string)[0]; 83 | } 84 | default: 85 | return null; 86 | } 87 | } 88 | 89 | /// construct error explanation string 90 | private string errorExplainStr(ErrorType type, uint line, uint col, string[] details){ 91 | string format = errorExplainFormat(type); 92 | if (format is null) 93 | return null; 94 | char[] errStr; 95 | errStr = cast(char[]) line.to!string ~ ',' ~ col.to!string ~ ": "; 96 | uint i = cast(uint)errStr.length; 97 | errStr ~= format; 98 | 99 | foreach (detail; details){ 100 | while (i < errStr.length){ 101 | if (errStr[i] == '$'){ 102 | errStr = errStr[0 .. i] ~ detail ~ 103 | (i + 1 < errStr.length ? errStr[i + 1 .. $] : []); 104 | i += detail.length; 105 | break; 106 | } 107 | i++; 108 | } 109 | } 110 | return cast(string)errStr; 111 | } 112 | 113 | /// A value or a compiler error 114 | public struct CompileResult(T){ 115 | bool isError; 116 | union{ 117 | T value; 118 | CompileError error; 119 | } 120 | @disable this(); 121 | this(T value){ 122 | this.value = value; 123 | this.isError = false; 124 | } 125 | this(CompileError error){ 126 | this.error = error; 127 | this.isError = true; 128 | } 129 | bool opCast(To : bool)() const pure { 130 | return !isError; 131 | } 132 | } 133 | 134 | /// A compiler error 135 | public struct CompileError{ 136 | ErrorType type = ErrorType.None; 137 | uint line, col; 138 | string msg; 139 | 140 | /// constructor 141 | this(ErrorType type, uint line, uint col, string[] details = []){ 142 | this.type = type; 143 | this.line = line; 144 | this.col = col; 145 | msg = errorExplainStr(type, line, col, details); 146 | } 147 | 148 | /// ditto 149 | this(ErrorType type, Token tok, string[] details = []){ 150 | this.type = type; 151 | this.line = tok.lineno; 152 | this.col = tok.colno; 153 | msg = errorExplainStr(type, line, col, details); 154 | } 155 | 156 | /// ditto 157 | /// syntax error 158 | this(Token tok){ 159 | this.type = ErrorType.SyntaxError; 160 | this.line = tok.lineno; 161 | this.col = tok.colno; 162 | msg = errorExplainStr(type, line, col, []); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /source/qscript/parser.d: -------------------------------------------------------------------------------- 1 | module qscript.parser; 2 | 3 | import std.json, 4 | std.traits, 5 | std.conv, 6 | std.string, 7 | std.stdio, 8 | std.functional, 9 | std.algorithm, 10 | std.meta, 11 | std.array; 12 | 13 | import qscript.compiler, 14 | qscript.tokens; 15 | 16 | import utils.ds; 17 | 18 | /// A Node 19 | public class Node{ 20 | public: 21 | Token token; 22 | NodeType type; 23 | Node[] children; 24 | 25 | this(Token token, Node[] children = null){ 26 | this.token = token; 27 | this.children = children; 28 | } 29 | 30 | this(Node[] children = null){ 31 | this.children = children; 32 | } 33 | 34 | JSONValue toJSON(){ 35 | JSONValue ret; 36 | ret["token"] = token.token; 37 | ret["type"] = type.to!string; 38 | string typeStr; 39 | foreach (member; EnumMembers!TokenType){ 40 | if (!token.type[member]) 41 | continue; 42 | typeStr ~= member.to!string ~ ", "; 43 | } 44 | if (typeStr.length) 45 | ret["tokentype"] = typeStr.chomp(", "); 46 | JSONValue[] sub; 47 | foreach (child; children){ 48 | if (child is null) 49 | sub ~= JSONValue("null"); 50 | else 51 | sub ~= child.toJSON; 52 | } 53 | if (sub.length) 54 | ret["children"] = sub; 55 | return ret; 56 | } 57 | } 58 | 59 | /// Returns: Flags constructed from AliasSeq 60 | private template ToFlags(Vals...) if ( 61 | Vals.length > 0 && 62 | is(typeof(Vals[0]) == enum)){ 63 | alias T = typeof(Vals[0]); 64 | enum ToFlags = getFlags; 65 | Flags!T getFlags(){ 66 | Flags!T ret; 67 | static foreach (val; Vals){ 68 | static if (!is(typeof(val) == T)) 69 | static assert(false, "Vals not all of same type: " ~ 70 | typeof(val).stringof ~ " != " ~ T.stringof); 71 | ret |= val; 72 | } 73 | return ret; 74 | } 75 | } 76 | 77 | /// stringof's all other template parameters, and joins with a string 78 | private template JoinStringOf(string Jstr, Vals...){ 79 | enum JoinStringOf = getJoinStringOf; 80 | private string getJoinStringOf(){ 81 | string ret; 82 | static foreach (i, val; Vals){ 83 | ret ~= val.to!string; 84 | static if (i + 1 < Vals.length) 85 | ret ~= Jstr; 86 | } 87 | return ret; 88 | } 89 | } 90 | 91 | /// Precedence (if none found, default is 0) 92 | private template PrecedenceOf(alias type){ 93 | enum PrecedenceOf = getPrecedenceOf(); 94 | private uint getPrecedenceOf(){ 95 | static if (hasUDA!(type, Precedence)) 96 | return getUDAs!(type, Precedence)[0].precedence; 97 | else 98 | return 0; 99 | } 100 | } 101 | 102 | /// Returns: precedence of a NodeType, or 0 103 | private uint precedenceOf(NodeType type){ 104 | switch (type){ 105 | static foreach(member; EnumMembers!NodeType){ 106 | case member: 107 | return PrecedenceOf!member; 108 | } 109 | default: return 0; 110 | } 111 | } 112 | 113 | /// All precedences 114 | private template Precedences(){ 115 | alias Precedences = AliasSeq!(); 116 | static foreach (member; EnumMembers!NodeType) 117 | Precedences = AliasSeq!(Precedences, PrecedenceOf!member); 118 | Precedences = NoDuplicates!Precedences; 119 | } 120 | 121 | /// subset that have a precedence equal to or greater 122 | private template HigherPreced(uint P, Ops...){ 123 | alias HigherPreced = AliasSeq!(); 124 | static foreach (node; Ops){ 125 | static if (PrecedenceOf!node >= P) 126 | HigherPreced = AliasSeq!(HigherPreced, node); 127 | } 128 | } 129 | 130 | /// AliasSeq of TokenType of Hooks for given NodeTypes AliasSeq 131 | private template Hooks(Types...){ 132 | alias Hooks = AliasSeq!(); 133 | static foreach (node; Types){ 134 | static foreach (tokType; getUDAs!(node, TokenType)) 135 | Hooks = AliasSeq!(Hooks, tokType); 136 | } 137 | } 138 | 139 | /// Binary Operators NodeTypes with Precedence >= P 140 | private template BinOps(uint P = 0){ 141 | alias BinOps = AliasSeq!(); 142 | static foreach (type; EnumMembers!NodeType){ 143 | static if (hasUDA!(type, BinOp) && PrecedenceOf!type >= P) 144 | BinOps = AliasSeq!(BinOps, type); 145 | } 146 | } 147 | 148 | /// Prefix Operators with Precedence >= P 149 | private template PreOps(uint P = 0){ 150 | alias PreOps = AliasSeq!(); 151 | static foreach (type; EnumMembers!NodeType){ 152 | static if (hasUDA!(type, PreOp) && PrecedenceOf!type >= P) 153 | PreOps = AliasSeq!(PreOps, type); 154 | } 155 | } 156 | 157 | /// Postfix Operators with Precedence >= P 158 | private template PostOps(uint P = 0){ 159 | alias PostOps = AliasSeq!(); 160 | static foreach (type; EnumMembers!NodeType){ 161 | static if ((hasUDA!(type, PostOp) || hasUDA!(type, BinOp)) && 162 | PrecedenceOf!type >= P) 163 | PostOps = AliasSeq!(PostOps, type); 164 | } 165 | } 166 | 167 | /// Postfix Operators excluding Binary Operators, with Precedence >= P 168 | private template PostNoBinOps(uint P = 0){ 169 | alias PostNoBinOps = AliasSeq!(); 170 | static foreach (type; EnumMembers!NodeType){ 171 | static if (hasUDA!(type, PostOp) && !hasUDA!(type, BinOp) && 172 | PrecedenceOf!type >= P) 173 | PostNoBinOps = AliasSeq!(PostNoBinOps, type); 174 | } 175 | } 176 | 177 | /// Checks if token at front is matching type. 178 | /// 179 | /// Returns: true if matched 180 | private bool expect(Types...)(ref Tokenizer toks){ 181 | if (toks.empty) 182 | return false; 183 | enum Flags!TokenType match = ToFlags!Types; 184 | return cast(bool)(match & toks.front.type); 185 | } 186 | 187 | /// Checks if tokens at front are matching types. 188 | /// 189 | /// Returns: true if matched 190 | private bool expectSeqPop(Types...)(ref Tokenizer toks){ 191 | auto branch = toks; 192 | static foreach (type; Types){{ 193 | if (!branch.expectPop!(type)) 194 | return false; 195 | }} 196 | toks = branch; 197 | return true; 198 | } 199 | 200 | /// Checks if token at front is matching type. Pops it if matching 201 | /// 202 | /// Returns: true if matched 203 | private bool expectPop(Types...)(ref Tokenizer toks){ 204 | if (toks.expect!Types){ 205 | toks.popFront; 206 | return true; 207 | } 208 | return false; 209 | } 210 | 211 | /// current context (NodeType of calling function) 212 | private NodeType currContext, prevContext; 213 | 214 | /// Builder function 215 | private template Builder(alias Type){ 216 | static if (exists){ 217 | alias Builder = __traits(getMember, mixin(__MODULE__), 218 | "read" ~ Type.stringof); 219 | }else static if (hasUDA!(Type, BinOp)){ 220 | alias Builder = readBinOp!Type; 221 | }else static if (hasUDA!(Type, PreOp)){ 222 | alias Builder = readPreOp!Type; 223 | }else static if (hasUDA!(Type, PostOp)){ 224 | alias Builder = readPostOp!Type; 225 | } 226 | 227 | private bool exists(){ 228 | bool ret = false; 229 | static foreach (member; __traits(allMembers, mixin(__MODULE__))){ 230 | static if (member.to!string == "read" ~ Type.to!string){ 231 | ret = true; 232 | } 233 | } 234 | return ret; 235 | } 236 | } 237 | 238 | /// Tries to read a specific type(s) by matching hooks 239 | /// 240 | /// Returns: Node or null 241 | private template read(Types...){ 242 | static if (Types.length == 0){ 243 | alias read = read!(EnumMembers!NodeType); 244 | }else static if (Types.length == 1){ 245 | Node read(ref Tokenizer toks, Node preceeding = null){ 246 | // save state 247 | NodeType displaced = prevContext; 248 | prevContext = currContext; 249 | currContext = Types[0]; 250 | 251 | alias func = Builder!(Types[0]); 252 | Node ret = func(toks, preceeding); 253 | if (ret !is null) 254 | ret.type = Types[0]; 255 | // restore state 256 | currContext = prevContext; 257 | prevContext = displaced; 258 | return ret; 259 | } 260 | 261 | }else{ 262 | Node read(ref Tokenizer toks, Node preceeding = null){ 263 | const auto type = toks.front.type; 264 | static foreach (member; Types){ 265 | static foreach (tokType; getUDAs!(member, TokenType)){{ 266 | if (type.get!(tokType)){ 267 | auto branch = toks; 268 | auto ret = branch.read!member(preceeding); 269 | if (ret !is null){ 270 | toks = branch; 271 | return ret; 272 | } 273 | } 274 | }} 275 | } 276 | static foreach (member; Types){ 277 | static if (!hasUDA!(member, TokenType)){{ 278 | auto branch = toks; 279 | auto ret = branch.read!member(preceeding); 280 | if (ret !is null){ 281 | toks = branch; 282 | return ret; 283 | } 284 | }} 285 | } 286 | return null; 287 | } 288 | } 289 | } 290 | 291 | /// Reads a sequence of Nodes 292 | /// 293 | /// Returns: Node[] or empty array in case either one was null 294 | private Node[] readSeq(Types...)(ref Tokenizer toks, Node preceeding = null){ 295 | enum N = Types.length; 296 | Node[] ret; 297 | ret.length = N; 298 | static foreach (i; 0 .. N){{ 299 | auto branch = toks; 300 | ret[i] = branch.read!(Types[i])(preceeding); 301 | if (ret[i] is null) 302 | return null; 303 | preceeding = ret[i]; 304 | toks = branch; 305 | }} 306 | return ret; 307 | } 308 | 309 | /// Tries reading Types that are of Precedence >= P 310 | /// 311 | /// Returns: Node or null 312 | private Node readWithPrecedence(uint P, Types...)(ref Tokenizer toks, Node a){ 313 | return toks.read!(HigherPreced!(P, Types))(a); 314 | } 315 | 316 | /// ditto 317 | private Node readWithPrecedence(Types...)(ref Tokenizer toks, Node a, uint p){ 318 | switch (p){ 319 | static foreach (precedence; Precedences!()){ 320 | case precedence: 321 | return toks.readWithPrecedence!(precedence, Types)(a); 322 | } 323 | default: 324 | return null; 325 | } 326 | } 327 | 328 | /// UDA for precedence. greater number is first 329 | private struct Precedence{ 330 | uint precedence; 331 | this(uint precedence){ 332 | this.precedence = precedence; 333 | } 334 | } 335 | 336 | /// UDA for Binary Operator 337 | enum BinOp; 338 | /// UDA for Pre Operator 339 | enum PreOp; 340 | /// UDA for Post Operator 341 | enum PostOp; 342 | 343 | /// Node types 344 | public enum NodeType{ 345 | Script, 346 | @(TokenType.Pub) Pub, 347 | Declaration, 348 | @(TokenType.Template) Template, 349 | @(TokenType.TemplateFn) TemplateFn, 350 | @(TokenType.TemplateEnum) TemplateEnum, 351 | @(TokenType.TemplateStruct) TemplateStruct, 352 | @(TokenType.TemplateVar) TemplateVar, 353 | @(TokenType.TemplateAlias) TemplateAlias, 354 | @(TokenType.Fn) Fn, 355 | @(TokenType.Var) Var, 356 | @(TokenType.Struct) Struct, 357 | @(TokenType.Enum) Enum, 358 | @(TokenType.Alias) Alias, 359 | Param, 360 | TemplateParam, 361 | ParamList, 362 | TemplateParamList, 363 | NamedValue, /// used in enum for key/val pair 364 | Statement, 365 | @(TokenType.Return) ReturnStatement, 366 | @(TokenType.If) IfStatement, 367 | @(TokenType.StaticIf) StaticIfStatement, 368 | @(TokenType.While) WhileStatement, 369 | @(TokenType.Do) DoWhileStatement, 370 | @(TokenType.For) ForStatement, 371 | @(TokenType.StaticFor) StaticForStatement, 372 | @(TokenType.Break) BreakStatement, 373 | @(TokenType.Continue) ContinueStatement, 374 | @(TokenType.CurlyOpen) Block, 375 | ExprUnit, 376 | @(TokenType.Identifier) Identifier, 377 | @(TokenType.LiteralInt) 378 | @(TokenType.LiteralHexadecimal) 379 | @(TokenType.LiteralBinary) LiteralInt, 380 | @(TokenType.LiteralFloat) LiteralFloat, 381 | @(TokenType.LiteralString) LiteralString, 382 | @(TokenType.LiteralChar) LiteralChar, 383 | @(TokenType.Null) LiteralNull, 384 | @(TokenType.True) 385 | @(TokenType.False) LiteralBool, 386 | ArgList, 387 | 388 | @(TokenType.Trait) Trait, 389 | 390 | Expression, 391 | 392 | @PreOp 393 | @(TokenType.BracketOpen) ArrowFunc, 394 | @PreOp 395 | @(TokenType.BracketOpen) 396 | @(TokenType.IndexOpen) BracketExpr, 397 | @PreOp 398 | @(TokenType.Load) LoadExpr, 399 | 400 | @Precedence(130) 401 | @BinOp 402 | @(TokenType.OpDot) OpDot, 403 | 404 | @Precedence(120) 405 | @BinOp 406 | @(TokenType.OpIndex) OpIndex, 407 | 408 | @Precedence(110) 409 | @PreOp 410 | @(TokenType.Ref) OpRef, 411 | @Precedence(110) 412 | @PreOp 413 | @(TokenType.Fn) FnDataType, 414 | 415 | @Precedence(100) 416 | @BinOp 417 | @(TokenType.OpFnCall) OpCall, 418 | 419 | @Precedence(90) 420 | @PostOp 421 | @(TokenType.OpInc) OpPostInc, 422 | @Precedence(90) 423 | @PostOp 424 | @(TokenType.OpDec) OpPostDec, 425 | 426 | @Precedence(80) 427 | @PreOp 428 | @(TokenType.OpInc) OpPreInc, 429 | @Precedence(80) 430 | @PreOp 431 | @(TokenType.OpDec) OpPreDec, 432 | @Precedence(80) 433 | @PreOp 434 | @(TokenType.OpNot) OpNot, 435 | 436 | @Precedence(70) 437 | @BinOp 438 | @(TokenType.OpMul) OpMul, 439 | @Precedence(70) 440 | @BinOp 441 | @(TokenType.OpDiv) OpDiv, 442 | @Precedence(70) 443 | @BinOp 444 | @(TokenType.OpMod) OpMod, 445 | 446 | @Precedence(60) 447 | @BinOp 448 | @(TokenType.OpAdd) OpAdd, 449 | @Precedence(60) 450 | @BinOp 451 | @(TokenType.OpSub) OpSub, 452 | 453 | @Precedence(50) 454 | @BinOp 455 | @(TokenType.OpLShift) OpLShift, 456 | @Precedence(50) 457 | @BinOp 458 | @(TokenType.OpRShift) OpRShift, 459 | 460 | @Precedence(40) 461 | @BinOp 462 | @(TokenType.OpEquals) OpEquals, 463 | @Precedence(40) 464 | @BinOp 465 | @(TokenType.OpNotEquals) OpNotEquals, 466 | @Precedence(40) 467 | @BinOp 468 | @(TokenType.OpGreaterEquals) OpGreaterEquals, 469 | @Precedence(40) 470 | @BinOp 471 | @(TokenType.OpLesserEquals) OpLesserEquals, 472 | @Precedence(40) 473 | @BinOp 474 | @(TokenType.OpGreater) OpGreater, 475 | @Precedence(40) 476 | @BinOp 477 | @(TokenType.OpLesser) OpLesser, 478 | @Precedence(40) 479 | @BinOp 480 | @(TokenType.OpIs) OpIs, 481 | @Precedence(40) 482 | @BinOp 483 | @(TokenType.OpNotIs) OpNotIs, 484 | 485 | @Precedence(30) 486 | @BinOp 487 | @(TokenType.OpBinAnd) OpBinAnd, 488 | @Precedence(30) 489 | @BinOp 490 | @(TokenType.OpBinOr) OpBinOr, 491 | @Precedence(30) 492 | @BinOp 493 | @(TokenType.OpBinXor) OpBinXor, 494 | 495 | @Precedence(20) 496 | @BinOp 497 | @(TokenType.OpBoolAnd) OpBoolAnd, 498 | @Precedence(20) 499 | @BinOp 500 | @(TokenType.OpBoolOr) OpBoolOr, 501 | 502 | @Precedence(10) 503 | @BinOp 504 | @(TokenType.OpAssign) OpAssign, 505 | @Precedence(10) 506 | @BinOp 507 | @(TokenType.OpAddAssign) OpAddAssign, 508 | @Precedence(10) 509 | @BinOp 510 | @(TokenType.OpSubAssign) OpSubAssign, 511 | @Precedence(10) 512 | @BinOp 513 | @(TokenType.OpMulAssign) OpMulAssign, 514 | @Precedence(10) 515 | @BinOp 516 | @(TokenType.OpDivAssign) OpDivAssign, 517 | @Precedence(10) 518 | @BinOp 519 | @(TokenType.OpModAssign) OpModAssign, 520 | @Precedence(10) 521 | @BinOp 522 | @(TokenType.OpSubAssign) OpBinAndAssign, 523 | @Precedence(10) 524 | @BinOp 525 | @(TokenType.OpBinOrAssign) OpBinOrAssign, 526 | @Precedence(10) 527 | @BinOp 528 | @(TokenType.OpBinXorAssign) OpBinXorAssign, 529 | } 530 | 531 | public CompileResult!Node parseScript(ref Tokenizer toks){ 532 | auto ret = readScript(toks, null); 533 | if (ret) 534 | return CompileResult!Node(ret); 535 | return CompileResult!Node(CompileError(ErrorType.SyntaxError, toks.front)); 536 | } 537 | 538 | private Node readScript(ref Tokenizer toks, Node){ 539 | Node ret = new Node; 540 | while (!toks.empty){ 541 | if (auto val = toks.read!(NodeType.Declaration)) 542 | ret.children ~= val; 543 | else 544 | return null; 545 | } 546 | return ret; 547 | } 548 | 549 | private Node readPub(ref Tokenizer toks, Node){ 550 | if (!toks.expect!(TokenType.Pub)) 551 | return null; 552 | auto token = toks.front; 553 | toks.popFront; 554 | 555 | if (auto vals = toks.readSeq!(NodeType.Declaration)) 556 | return new Node(token, vals); 557 | return null; 558 | } 559 | 560 | private Node readDeclaration(ref Tokenizer toks, Node){ 561 | if (auto val = toks.read!( 562 | NodeType.Pub, 563 | NodeType.LoadExpr, 564 | NodeType.Template, 565 | NodeType.TemplateFn, 566 | NodeType.TemplateEnum, 567 | NodeType.TemplateStruct, 568 | NodeType.TemplateVar, 569 | NodeType.TemplateAlias, 570 | NodeType.Fn, 571 | NodeType.Enum, 572 | NodeType.Struct, 573 | NodeType.Var, 574 | NodeType.Alias)){ 575 | if (val.type == NodeType.LoadExpr){ 576 | if (!toks.expectPop!(TokenType.Semicolon)) 577 | return null; 578 | } 579 | return new Node([val]); 580 | } 581 | return null; 582 | } 583 | 584 | private Node readTemplate(ref Tokenizer toks, Node){ 585 | if (!toks.expect!(TokenType.Template)) 586 | return null; 587 | auto token = toks.front; 588 | toks.popFront; 589 | 590 | if (auto vals = toks.readSeq!( 591 | NodeType.TemplateParamList, 592 | NodeType.Statement)) 593 | return new Node(token, vals); 594 | return null; 595 | } 596 | 597 | private Node readTemplateFn(ref Tokenizer toks, Node){ 598 | if (!toks.expect!(TokenType.TemplateFn)) 599 | return null; 600 | auto token = toks.front; 601 | toks.popFront; 602 | 603 | auto branch = toks; 604 | if (auto vals = branch.readSeq!( 605 | NodeType.Identifier, 606 | NodeType.TemplateParamList, 607 | NodeType.ParamList, 608 | NodeType.Statement)){ 609 | toks = branch; 610 | return new Node(token, vals); 611 | } 612 | 613 | if (auto vals = toks.readSeq!( 614 | NodeType.Expression, 615 | NodeType.Identifier, 616 | NodeType.TemplateParamList, 617 | NodeType.ParamList, 618 | NodeType.Statement)){ 619 | return new Node(token, vals); 620 | } 621 | return null; 622 | } 623 | 624 | private Node readTemplateEnum(ref Tokenizer toks, Node){ 625 | if (!toks.expect!(TokenType.TemplateEnum)) 626 | return null; 627 | Node ret = new Node(toks.front); 628 | toks.popFront; 629 | 630 | if (auto vals = toks.readSeq!( 631 | NodeType.Expression, 632 | NodeType.Identifier, 633 | NodeType.TemplateParamList)) 634 | ret.children = vals; 635 | else 636 | return null; 637 | 638 | // is it just a simple define? 639 | if (toks.expectPop!(TokenType.Semicolon)) 640 | return ret; 641 | 642 | // from here on, there can be a block, or a single OpAssign 643 | if (toks.expectPop!(TokenType.OpAssign)){ 644 | // single OpAssign 645 | auto val = toks.read!(NodeType.Expression); 646 | if (val is null) 647 | return null; 648 | ret.children ~= val; 649 | // expect a semicolon 650 | if (!toks.expectPop!(TokenType.Semicolon)) 651 | return null; 652 | return ret; 653 | } 654 | 655 | // multiple values 656 | if (!toks.expectPop!(TokenType.CurlyOpen)) 657 | return null; 658 | 659 | while (true){ 660 | auto val = toks.read!(NodeType.NamedValue); 661 | if (val is null) 662 | return null; 663 | ret.children ~= val; 664 | if (toks.expectPop!(TokenType.CurlyClose)) 665 | break; 666 | if (!toks.expectPop!(TokenType.Comma)) 667 | return null; 668 | } 669 | return ret; 670 | } 671 | 672 | private Node readTemplateStruct(ref Tokenizer toks, Node){ 673 | if (!toks.expect!(TokenType.TemplateStruct)) 674 | return null; 675 | Node ret = new Node(toks.front); 676 | toks.popFront; 677 | 678 | if (auto vals = toks.readSeq!( 679 | NodeType.Identifier, 680 | NodeType.TemplateParamList)) 681 | ret.children = vals; 682 | else 683 | return null; 684 | 685 | if (!toks.expectPop!(TokenType.CurlyOpen)) 686 | return null; 687 | while (true){ 688 | if (auto val = toks.read!(NodeType.Declaration)) 689 | ret.children ~= val; 690 | else 691 | return null; 692 | if (toks.expectPop!(TokenType.CurlyClose)) 693 | break; 694 | } 695 | return ret; 696 | } 697 | 698 | private Node readTemplateVar(ref Tokenizer toks, Node){ 699 | if (!toks.expect!(TokenType.TemplateVar)) 700 | return null; 701 | Node ret = new Node(toks.front); 702 | toks.popFront; 703 | 704 | if (auto val = toks.read!(NodeType.Expression)) 705 | ret.children = [val]; 706 | else 707 | return null; 708 | 709 | // now read ident paramlist optional( OpAssign Expr) [Comma or Semicolon] 710 | while (true){ 711 | Node varNode = toks.read!(NodeType.Identifier); 712 | if (varNode is null) 713 | return null; 714 | if (auto val = toks.read!(NodeType.TemplateParamList)) 715 | varNode.children = [val]; 716 | else 717 | return null; 718 | 719 | if (toks.expectPop!(TokenType.OpAssign)){ 720 | if (auto val = toks.read!(NodeType.Identifier)) 721 | varNode.children ~= val; 722 | else 723 | return null; 724 | } 725 | 726 | if (toks.expectPop!(TokenType.Comma)) 727 | continue; 728 | if (toks.expectPop!(TokenType.Semicolon)) 729 | break; 730 | } 731 | return ret; 732 | } 733 | 734 | private Node readTemplateAlias(ref Tokenizer toks, Node){ 735 | if (!toks.expect!(TokenType.TemplateAlias)) 736 | return null; 737 | auto token = toks.front; 738 | toks.popFront; 739 | 740 | Node ret; 741 | if (auto vals = toks.readSeq!( 742 | NodeType.Identifier, 743 | NodeType.TemplateParamList)) 744 | ret = new Node(token, vals); 745 | else 746 | return null; 747 | if (!toks.expectPop!(TokenType.OpAssign)) 748 | return null; 749 | if (auto val = toks.read!()) 750 | ret.children ~= val; 751 | else 752 | return null; 753 | if (!toks.expectPop!(TokenType.Semicolon)) 754 | return null; 755 | return ret; 756 | } 757 | 758 | private Node readFn(ref Tokenizer toks, Node){ 759 | if (!toks.expect!(TokenType.Fn)) 760 | return null; 761 | auto token = toks.front; 762 | toks.popFront; 763 | 764 | auto branch = toks; 765 | if (auto vals = branch.readSeq!( 766 | NodeType.Identifier, 767 | NodeType.ParamList, 768 | NodeType.Statement)){ 769 | toks = branch; 770 | return new Node(token, vals); 771 | } 772 | 773 | if (auto vals = toks.readSeq!( 774 | NodeType.Expression, 775 | NodeType.Identifier, 776 | NodeType.ParamList, 777 | NodeType.Statement)){ 778 | return new Node(token, vals); 779 | } 780 | return null; 781 | } 782 | 783 | private Node readVar(ref Tokenizer toks, Node){ 784 | if (!toks.expect!(TokenType.Var)) 785 | return null; 786 | Node ret = new Node(toks.front); 787 | toks.popFront; 788 | 789 | if (auto val = toks.read!(NodeType.Expression)) 790 | ret.children = [val]; 791 | else 792 | return null; 793 | 794 | // now read ident optional( OpAssign Expr) [Comma or Semicolon] 795 | while (true){ 796 | Node varNode = toks.read!(NodeType.Identifier); 797 | if (varNode is null) 798 | return null; 799 | 800 | if (toks.expectPop!(TokenType.OpAssign)){ 801 | if (auto val = toks.read!(NodeType.Expression)) 802 | varNode.children ~= val; 803 | else 804 | return null; 805 | } 806 | ret.children ~= varNode; 807 | 808 | if (toks.expectPop!(TokenType.Comma)) 809 | continue; 810 | if (toks.expectPop!(TokenType.Semicolon)) 811 | break; 812 | } 813 | return ret; 814 | } 815 | 816 | private Node readStruct(ref Tokenizer toks, Node){ 817 | if (!toks.expect!(TokenType.Struct)) 818 | return null; 819 | Node ret = new Node(toks.front); 820 | toks.popFront; 821 | 822 | if (auto val = toks.read!(NodeType.Identifier)) 823 | ret.children = [val]; 824 | else 825 | return null; 826 | 827 | if (!toks.expectPop!(TokenType.CurlyOpen)) 828 | return null; 829 | while (true){ 830 | if (auto val = toks.read!(NodeType.Declaration)) 831 | ret.children ~= val; 832 | else 833 | return null; 834 | if (toks.expectPop!(TokenType.CurlyClose)) 835 | break; 836 | } 837 | return ret; 838 | } 839 | 840 | private Node readEnum(ref Tokenizer toks, Node){ 841 | if (!toks.expect!(TokenType.Enum)) 842 | return null; 843 | Node ret = new Node(toks.front); 844 | toks.popFront; 845 | 846 | if (auto vals = toks.readSeq!( 847 | NodeType.Expression, 848 | NodeType.Identifier)) 849 | ret.children = vals; 850 | else 851 | return null; 852 | 853 | // from here on, there can be a block, or a single OpAssign 854 | if (toks.expectPop!(TokenType.OpAssign)){ 855 | // single OpAssign 856 | auto val = toks.read!(NodeType.Expression); 857 | if (val is null) 858 | return null; 859 | ret.children ~= val; 860 | // expect a semicolon 861 | if (!toks.expectPop!(TokenType.Semicolon)) 862 | return null; 863 | return ret; 864 | } 865 | 866 | // multiple values 867 | if (!toks.expectPop!(TokenType.CurlyOpen)) 868 | return null; 869 | 870 | while (true){ 871 | auto val = toks.read!(NodeType.NamedValue); 872 | if (val is null) 873 | return null; 874 | ret.children ~= val; 875 | if (toks.expectPop!(TokenType.CurlyClose)) 876 | break; 877 | if (!toks.expectPop!(TokenType.Comma)) 878 | return null; 879 | } 880 | return ret; 881 | } 882 | 883 | private Node readAlias(ref Tokenizer toks, Node){ 884 | if (!toks.expect!(TokenType.Alias)) 885 | return null; 886 | auto token = toks.front; 887 | toks.popFront; 888 | 889 | Node ret; 890 | if (auto val = toks.read!(NodeType.Identifier)) 891 | ret = new Node(token, [val]); 892 | else 893 | return null; 894 | if (!toks.expectPop!(TokenType.OpAssign)) 895 | return null; 896 | if (auto val = toks.read!()) 897 | ret.children ~= val; 898 | else 899 | return null; 900 | if (!toks.expectPop!(TokenType.Semicolon)) 901 | return null; 902 | return ret; 903 | } 904 | 905 | private Node readIndexBracketPair(ref Tokenizer toks, Node){ 906 | auto token = toks.front; 907 | if (auto vals = toks.expectSeqPop!( 908 | TokenType.IndexOpen, 909 | TokenType.IndexClose)) 910 | return new Node(token); 911 | return null; 912 | } 913 | 914 | private Node readTemplateParamList(ref Tokenizer toks, Node){ 915 | if (!toks.expect!(TokenType.BracketOpen)) 916 | return null; 917 | Node ret = new Node(toks.front); 918 | toks.popFront; 919 | while (!toks.expectPop!(TokenType.BracketClose)){ 920 | if (auto val = toks.read!(NodeType.TemplateParam)) 921 | ret.children ~= val; 922 | else 923 | return null; 924 | } 925 | return ret; 926 | } 927 | 928 | private Node readTemplateParam(ref Tokenizer toks, Node){ 929 | // can be: 930 | // DataType Identifier 931 | // Identifier 932 | auto branch = toks; 933 | Node ret; 934 | if (auto vals = branch.readSeq!( 935 | NodeType.Expression, 936 | NodeType.Identifier)){ 937 | ret = new Node(vals); 938 | }else{ 939 | branch = toks; 940 | if (auto val = branch.read!(NodeType.Identifier)) 941 | ret = new Node([val]); 942 | } 943 | 944 | toks = branch; 945 | if (!toks.expect!(TokenType.BracketClose)){ 946 | if (!toks.expectPop!(TokenType.Comma)) 947 | return null; 948 | } 949 | return ret; 950 | } 951 | 952 | private Node readParamList(ref Tokenizer toks, Node){ 953 | if (!toks.expect!(TokenType.BracketOpen)) 954 | return null; 955 | Node ret = new Node(toks.front); 956 | toks.popFront; 957 | while (!toks.expectPop!(TokenType.BracketClose)){ 958 | if (auto val = toks.read!(NodeType.Param)) 959 | ret.children ~= val; 960 | else 961 | return null; 962 | } 963 | return ret; 964 | } 965 | 966 | private Node readParam(ref Tokenizer toks, Node){ 967 | // can be: 968 | // DataType Identifier = Expression 969 | // DataType Identifier 970 | // DataType 971 | Node ret; 972 | if (auto val = toks.read!(NodeType.Expression)) 973 | ret = new Node([val]); 974 | else 975 | return null; 976 | auto branch = toks; 977 | if (auto val = branch.read!(NodeType.Identifier)){ 978 | ret.children ~= val; 979 | toks = branch; 980 | if (toks.expectPop!(TokenType.OpAssign)){ 981 | if (auto exp = toks.read!(NodeType.Expression)) 982 | ret.children ~= exp; 983 | else 984 | return null; 985 | } 986 | } 987 | if (!toks.expect!(TokenType.BracketClose)){ 988 | if (!toks.expectPop!(TokenType.Comma)) 989 | return null; 990 | } 991 | return ret; 992 | } 993 | 994 | /// reads `foo = bar`, the `= bar` being optional 995 | private Node readNamedValue(ref Tokenizer toks, Node){ 996 | Node ret; 997 | if (auto val = toks.read!(NodeType.Identifier)) 998 | ret = new Node([val]); 999 | else 1000 | return null; 1001 | if (!toks.expect!(TokenType.OpAssign)) 1002 | return ret; 1003 | 1004 | ret.token = toks.front; 1005 | toks.popFront; 1006 | if (auto val = toks.read!(NodeType.Expression)) 1007 | ret.children ~= val; 1008 | else 1009 | return null; 1010 | return ret; 1011 | } 1012 | 1013 | private Node readStatement(ref Tokenizer toks, Node){ 1014 | if (auto val = toks.read!( 1015 | NodeType.Declaration, 1016 | NodeType.ReturnStatement, 1017 | NodeType.IfStatement, 1018 | NodeType.StaticIfStatement, 1019 | NodeType.WhileStatement, 1020 | NodeType.DoWhileStatement, 1021 | NodeType.ForStatement, 1022 | NodeType.StaticForStatement, 1023 | NodeType.BreakStatement, 1024 | NodeType.ContinueStatement, 1025 | NodeType.Block)){ 1026 | return new Node([val]); 1027 | } 1028 | 1029 | Node ret; 1030 | if (auto val = toks.read!(NodeType.Expression)) 1031 | ret = new Node([val]); 1032 | if (!toks.expectPop!(TokenType.Semicolon)) 1033 | return null; 1034 | return ret; 1035 | } 1036 | 1037 | private Node readReturnStatement(ref Tokenizer toks, Node){ 1038 | if (!toks.expect!(TokenType.Return)) 1039 | return null; 1040 | Node ret = new Node(toks.front); 1041 | toks.popFront; 1042 | // the return value is optional 1043 | if (auto val = toks.read!(NodeType.Expression)) 1044 | ret.children = [val]; 1045 | // but the semicolon is must 1046 | if (!toks.expectPop!(TokenType.Semicolon)) 1047 | return null; 1048 | return ret; 1049 | } 1050 | 1051 | private Node readIfStatement(ref Tokenizer toks, Node){ 1052 | if (!toks.expect!(TokenType.If)) 1053 | return null; 1054 | Node ret = new Node(toks.front); 1055 | toks.popFront; 1056 | if (auto vals = toks.readSeq!( 1057 | NodeType.Expression, 1058 | NodeType.Statement)) 1059 | ret.children = vals; 1060 | else 1061 | return null; 1062 | if (toks.expectPop!(TokenType.Else)){ 1063 | if (auto val = toks.read!(NodeType.Statement)) 1064 | ret.children ~= val; 1065 | else 1066 | return null; 1067 | } 1068 | return ret; 1069 | } 1070 | 1071 | private Node readStaticIfStatement(ref Tokenizer toks, Node){ 1072 | if (!toks.expect!(TokenType.StaticIf)) 1073 | return null; 1074 | Node ret = new Node(toks.front); 1075 | toks.popFront; 1076 | if (auto vals = toks.readSeq!( 1077 | NodeType.Expression, 1078 | NodeType.Statement)) 1079 | ret.children = vals; 1080 | else 1081 | return null; 1082 | if (toks.expectPop!(TokenType.Else)){ 1083 | if (auto val = toks.read!(NodeType.Statement)) 1084 | ret.children ~= val; 1085 | else 1086 | return null; 1087 | } 1088 | return ret; 1089 | } 1090 | 1091 | private Node readWhileStatement(ref Tokenizer toks, Node){ 1092 | if (!toks.expect!(TokenType.While)) 1093 | return null; 1094 | auto token = toks.front; 1095 | toks.popFront; 1096 | if (auto vals = toks.readSeq!( 1097 | NodeType.Expression, 1098 | NodeType.Statement)) 1099 | return new Node(token, vals); 1100 | return null; 1101 | } 1102 | 1103 | private Node readDoWhileStatement(ref Tokenizer toks, Node){ 1104 | if (!toks.expect!(TokenType.Do)) 1105 | return null; 1106 | Node ret = new Node(toks.front); 1107 | toks.popFront; 1108 | if (auto val = toks.read!(NodeType.Statement)) 1109 | ret.children = [val, null]; 1110 | else 1111 | return null; 1112 | if (!toks.expectPop!(TokenType.While)) 1113 | return null; 1114 | if (auto val = toks.read!(NodeType.Expression)) 1115 | ret.children[1] = val; 1116 | else 1117 | return null; 1118 | if (!toks.expectPop!(TokenType.Semicolon)) 1119 | return null; 1120 | return ret; 1121 | } 1122 | 1123 | private Node readForStatement(ref Tokenizer toks, Node){ 1124 | if (!toks.expect!(TokenType.For)) 1125 | return null; 1126 | auto ret = new Node(toks.front); 1127 | toks.popFront; 1128 | 1129 | if (!toks.expectPop!(TokenType.BracketOpen)) 1130 | return null; 1131 | Node node = toks.read!(NodeType.Identifier); 1132 | if (node is null) 1133 | return null; 1134 | if (toks.expectPop!(TokenType.Comma)){ 1135 | ret.children ~= node; // iteration counter 1136 | node = toks.read!(NodeType.Identifier); 1137 | if (node is null) 1138 | return null; 1139 | } 1140 | ret.children ~= node; // iteration element 1141 | 1142 | if (!toks.expectPop!(TokenType.Semicolon)) 1143 | return null; 1144 | if (auto val = toks.read!(NodeType.Expression)) 1145 | ret.children ~= val; 1146 | else 1147 | return null; 1148 | 1149 | if (!toks.expectPop!(TokenType.BracketClose)) 1150 | return null; 1151 | if (auto val = toks.read!(NodeType.Statement)) 1152 | ret.children ~= val; 1153 | else 1154 | return null; 1155 | return ret; 1156 | } 1157 | 1158 | private Node readStaticForStatement(ref Tokenizer toks, Node){ 1159 | if (!toks.expect!(TokenType.StaticFor)) 1160 | return null; 1161 | auto ret = new Node(toks.front); 1162 | toks.popFront; 1163 | 1164 | if (!toks.expectPop!(TokenType.BracketOpen)) 1165 | return null; 1166 | Node node = toks.read!(NodeType.Identifier); 1167 | if (node is null) 1168 | return null; 1169 | if (toks.expectPop!(TokenType.Comma)){ 1170 | ret.children ~= node; // iteration counter 1171 | node = toks.read!(NodeType.Identifier); 1172 | if (node is null) 1173 | return null; 1174 | } 1175 | ret.children ~= node; // iteration element 1176 | 1177 | if (!toks.expectPop!(TokenType.Semicolon)) 1178 | return null; 1179 | if (auto val = toks.read!(NodeType.Expression)) 1180 | ret.children ~= val; 1181 | else 1182 | return null; 1183 | 1184 | if (!toks.expectPop!(TokenType.BracketClose)) 1185 | return null; 1186 | if (auto val = toks.read!(NodeType.Statement)) 1187 | ret.children ~= val; 1188 | else 1189 | return null; 1190 | return ret; 1191 | } 1192 | 1193 | private Node readBreakStatement(ref Tokenizer toks, Node){ 1194 | if (!toks.expect!(TokenType.Break)) 1195 | return null; 1196 | Node ret = new Node(toks.front); 1197 | toks.popFront; 1198 | if (!toks.expectPop!(TokenType.Semicolon)) 1199 | return null; 1200 | return ret; 1201 | } 1202 | 1203 | private Node readContinueStatement(ref Tokenizer toks, Node){ 1204 | if (!toks.expect!(TokenType.Continue)) 1205 | return null; 1206 | Node ret = new Node(toks.front); 1207 | toks.popFront; 1208 | if (!toks.expectPop!(TokenType.Semicolon)) 1209 | return null; 1210 | return ret; 1211 | } 1212 | 1213 | private Node readBlock(ref Tokenizer toks, Node){ 1214 | if (!toks.expect!(TokenType.CurlyOpen)) 1215 | return null; 1216 | Node ret = new Node(toks.front); 1217 | ret.token = toks.front; 1218 | toks.popFront; 1219 | 1220 | while (!toks.expectPop!(TokenType.CurlyClose)){ 1221 | if (auto val = toks.read!(NodeType.Statement)) 1222 | ret.children ~= val; 1223 | else 1224 | return null; 1225 | } 1226 | return ret; 1227 | } 1228 | 1229 | private Node readExprUnit(ref Tokenizer toks, Node){ 1230 | if (auto val = toks.read!( 1231 | NodeType.Identifier, 1232 | NodeType.LiteralInt, 1233 | NodeType.LiteralFloat, 1234 | NodeType.LiteralString, 1235 | NodeType.LiteralChar, 1236 | NodeType.LiteralNull, 1237 | NodeType.LiteralBool, 1238 | NodeType.Trait)) 1239 | return new Node([val]); 1240 | return null; 1241 | } 1242 | 1243 | private Node readIdentifier(ref Tokenizer toks, Node){ 1244 | if (!toks.expect!(TokenType.Identifier)) 1245 | return null; 1246 | Node ret = new Node(toks.front); 1247 | toks.popFront; 1248 | return ret; 1249 | } 1250 | 1251 | private Node readLiteralInt(ref Tokenizer toks, Node){ 1252 | if (!toks.expect!( 1253 | TokenType.LiteralInt, 1254 | TokenType.LiteralBinary, 1255 | TokenType.LiteralHexadecimal)) 1256 | return null; 1257 | Node ret = new Node(toks.front); 1258 | toks.popFront; 1259 | return ret; 1260 | } 1261 | 1262 | private Node readLiteralFloat(ref Tokenizer toks, Node){ 1263 | if (!toks.expect!(TokenType.LiteralFloat)) 1264 | return null; 1265 | Node ret = new Node(toks.front); 1266 | toks.popFront; 1267 | return ret; 1268 | } 1269 | 1270 | private Node readLiteralString(ref Tokenizer toks, Node){ 1271 | if (!toks.expect!(TokenType.LiteralString)) 1272 | return null; 1273 | Node ret = new Node(toks.front); 1274 | toks.popFront; 1275 | return ret; 1276 | } 1277 | 1278 | private Node readLiteralChar(ref Tokenizer toks, Node){ 1279 | if (!toks.expect!(TokenType.LiteralChar)) 1280 | return null; 1281 | Node ret = new Node(toks.front); 1282 | toks.popFront; 1283 | return ret; 1284 | } 1285 | 1286 | private Node readLiteralNull(ref Tokenizer toks, Node){ 1287 | if (!toks.expect!(TokenType.Null)) 1288 | return null; 1289 | Node ret = new Node(toks.front); 1290 | toks.popFront; 1291 | return ret; 1292 | } 1293 | 1294 | private Node readLiteralBool(ref Tokenizer toks, Node){ 1295 | if (!toks.expect!( 1296 | TokenType.True, 1297 | TokenType.False)) 1298 | return null; 1299 | Node ret = new Node(toks.front); 1300 | toks.popFront; 1301 | return ret; 1302 | } 1303 | 1304 | private Node readArgList(ref Tokenizer toks, Node){ 1305 | if (!toks.expect!(TokenType.BracketOpen)) 1306 | return null; 1307 | Node ret = new Node(toks.front); 1308 | toks.popFront; 1309 | if (toks.expectPop!(TokenType.BracketClose)) 1310 | return ret; 1311 | 1312 | while (true){ 1313 | Node arg = null; 1314 | // if not a comma or bracket end, expect expression 1315 | if (!toks.expect!(TokenType.Comma, TokenType.BracketClose)){ 1316 | arg = toks.read!(NodeType.Expression); 1317 | if (arg is null) 1318 | return null; 1319 | } 1320 | if (toks.expectPop!(TokenType.BracketClose)){ 1321 | ret.children ~= arg; 1322 | break; 1323 | } 1324 | if (toks.expectPop!(TokenType.Comma)){ 1325 | ret.children ~= arg; 1326 | }else{ 1327 | return null; 1328 | } 1329 | } 1330 | return ret; 1331 | } 1332 | 1333 | private Node readTrait(ref Tokenizer toks, Node){ 1334 | if (!toks.expect!(TokenType.Trait)) 1335 | return null; 1336 | auto token = toks.front; 1337 | toks.popFront; 1338 | if (auto val = toks.read!(NodeType.ArgList)) 1339 | return new Node(token, [val]); 1340 | return null; 1341 | } 1342 | 1343 | private Node readExpression(ref Tokenizer toks, Node){ 1344 | const uint precedence = precedenceOf(prevContext); 1345 | bool createContainer = false; 1346 | auto branch = toks; 1347 | Node expr; 1348 | if (auto val = branch.read!(PreOps!())){ 1349 | toks = branch; 1350 | expr = val; 1351 | createContainer = true; 1352 | }else if (auto val = toks.read!(NodeType.ExprUnit)){ 1353 | expr = val; 1354 | }else{ 1355 | return null; 1356 | } 1357 | 1358 | while (true){ 1359 | branch = toks; 1360 | if (auto val = branch.readWithPrecedence!(PostOps!()) 1361 | (expr, precedence)){ 1362 | toks = branch; 1363 | createContainer = true; 1364 | expr = val; 1365 | }else{ 1366 | break; 1367 | } 1368 | } 1369 | if (!createContainer) 1370 | return expr; 1371 | return new Node([expr]); 1372 | } 1373 | 1374 | private Node readArrowFunc(ref Tokenizer toks, Node){ 1375 | if (!toks.expect!(TokenType.BracketOpen)) 1376 | return null; 1377 | Node ret = new Node; 1378 | if (auto val = toks.read!(NodeType.ParamList)) 1379 | ret.children = [val]; 1380 | else 1381 | return null; 1382 | // now the arrow 1383 | if (!toks.expect!(TokenType.Arrow)) 1384 | return null; 1385 | ret.token = toks.front; 1386 | toks.popFront; 1387 | 1388 | // read data type, or the expression itself 1389 | if (auto val = toks.read!(NodeType.Expression)) 1390 | ret.children ~= val; 1391 | else 1392 | return null; 1393 | 1394 | // and now if the expression above was data type maybe, read the block if any 1395 | if (auto val = toks.read!(NodeType.Block)) 1396 | ret.children ~= val; 1397 | return ret; 1398 | } 1399 | 1400 | private Node readBracketExpr(ref Tokenizer toks, Node){ 1401 | if (!toks.expect!(TokenType.BracketOpen, TokenType.IndexOpen)) 1402 | return null; 1403 | bool isIndexBracket = toks.front.type.get!(TokenType.IndexOpen); 1404 | auto token = toks.front; 1405 | toks.popFront; 1406 | auto val = toks.read!(NodeType.Expression); 1407 | if (val is null) 1408 | return null; 1409 | if ((isIndexBracket && !toks.expectPop!(TokenType.IndexClose)) || 1410 | (!isIndexBracket && !toks.expectPop!(TokenType.BracketClose))) 1411 | return null; 1412 | return new Node(token, [val]); 1413 | } 1414 | 1415 | private Node readLoadExpr(ref Tokenizer toks, Node){ 1416 | if (!toks.expect!(TokenType.Load)) 1417 | return null; 1418 | Node ret = new Node(toks.front); 1419 | toks.popFront; 1420 | 1421 | if (!toks.expectPop!(TokenType.BracketOpen)) 1422 | return null; 1423 | while (true){ 1424 | if (auto val = toks.read!(NodeType.Identifier)) 1425 | ret.children ~= val; 1426 | else 1427 | return null; 1428 | if (toks.expectPop!(TokenType.BracketClose)) 1429 | break; 1430 | if (!toks.expectPop!(TokenType.BracketClose)) 1431 | return null; 1432 | } 1433 | return ret; 1434 | } 1435 | 1436 | private Node readOpIndex(ref Tokenizer toks, Node a){ 1437 | if (!toks.expect!(TokenType.IndexOpen)) 1438 | return null; 1439 | auto branch = toks; 1440 | branch.popFront; 1441 | if (branch.expectPop!(TokenType.IndexClose)){ 1442 | toks = branch; 1443 | return new Node([a]); 1444 | } 1445 | if (auto val = toks.read!(NodeType.BracketExpr)) 1446 | return new Node([a, val]); 1447 | return null; 1448 | } 1449 | 1450 | private Node readFnDataType(ref Tokenizer toks, Node){ 1451 | if (!toks.expect!(TokenType.Fn)) 1452 | return null; 1453 | auto token = toks.front; 1454 | toks.popFront; 1455 | // if no return type: 1456 | if (toks.expect!(TokenType.BracketOpen)){ 1457 | if (auto val = toks.read!(NodeType.ParamList)) 1458 | return new Node(token, [val]); 1459 | return null; 1460 | } 1461 | if (auto type = toks.read!(NodeType.Expression)){ 1462 | if (auto params = toks.read!(NodeType.ParamList)) 1463 | return new Node(token, [type, params]); 1464 | } 1465 | return null; 1466 | } 1467 | 1468 | private Node readOpCall(ref Tokenizer toks, Node a){ 1469 | if (!toks.expect!(TokenType.BracketOpen)) 1470 | return null; 1471 | if (auto val = toks.read!(NodeType.ArgList)) 1472 | return new Node([a, val]); 1473 | return null; 1474 | } 1475 | 1476 | private Node readBinOp(Types...)(ref Tokenizer toks, Node a){ 1477 | if (!toks.expect!(Hooks!(Types))) 1478 | return null; 1479 | auto token = toks.front; 1480 | toks.popFront; 1481 | if (auto val = toks.read!(NodeType.Expression)) 1482 | return new Node(token, [a, val]); 1483 | return null; 1484 | } 1485 | 1486 | private Node readPreOp(Types...)(ref Tokenizer toks, Node){ 1487 | if (!toks.expect!(Hooks!(Types))) 1488 | return null; 1489 | auto token = toks.front; 1490 | toks.popFront; 1491 | if (auto val = toks.read!(NodeType.Expression)) 1492 | return new Node(token, [val]); 1493 | return null; 1494 | } 1495 | 1496 | private Node readPostOp(Types...)(ref Tokenizer toks, Node a){ 1497 | if (!toks.expect!(Hooks!(Types))) 1498 | return null; 1499 | auto token = toks.front; 1500 | toks.popFront; 1501 | return new Node(token, [a]); 1502 | } 1503 | -------------------------------------------------------------------------------- /source/qscript/tokens.d: -------------------------------------------------------------------------------- 1 | module qscript.tokens; 2 | 3 | import qscript.base.tokens; 4 | 5 | import utils.ds; 6 | 7 | debug import std.stdio; 8 | 9 | import std.algorithm, 10 | std.range; 11 | 12 | /// A Token 13 | public alias Token = qscript.base.tokens.Token!TokenType; 14 | /// Tokenizer 15 | public alias Tokenizer = qscript.base.tokens.Tokenizer!TokenType; 16 | 17 | /// removes whitespace tokens 18 | void whitespaceRemove(ref Token[] tokens){ 19 | uint i = 0, shift = 0; 20 | while (i + shift < tokens.length){ 21 | auto tok = tokens[i + shift]; 22 | if (tok.type.get!(TokenType.Whitespace) || 23 | tok.type.get!(TokenType.Comment) || 24 | tok.type.get!(TokenType.CommentMultiline)){ 25 | ++ shift; 26 | continue; 27 | } 28 | 29 | if (shift) 30 | tokens[i] = tok; 31 | ++ i; 32 | } 33 | tokens.length -= shift; 34 | } 35 | 36 | /// possible token types 37 | enum TokenType{ 38 | @Match(&identifyWhitespace, "\t \r\n") Whitespace, 39 | @Match(&identifyComment, "/#") Comment, 40 | @Match(&identifyCommentMultiline, "/") CommentMultiline, 41 | @Match(&identifyLiteralInt, "0123456789") 42 | LiteralInt, 43 | @Match(&identifyLiteralFloat, "0123456789") 44 | LiteralFloat, 45 | @Match(&identifyLiteralString, "\"") LiteralString, 46 | @Match(&identifyLiteralChar, "'") LiteralChar, 47 | @Match(&identifyLiteralHexadecimal, "0") 48 | LiteralHexadecimal, 49 | @Match(&identifyLiteralBinary, "0") LiteralBinary, 50 | @Match(`template`) Template, 51 | @Match(`load`) Load, 52 | @Match(`alias`) Alias, 53 | @Match(`fn`) Fn, 54 | @Match(`var`) Var, 55 | @Match(`ref`) Ref, 56 | @Match(`enum`) Enum, 57 | @Match(`struct`) Struct, 58 | @Match(`pub`) Pub, 59 | @Match(`return`) Return, 60 | @Match(`this`) This, 61 | @Match(`auto`) Auto, 62 | @Match(`int`) Int, 63 | @Match(`float`) Float, 64 | @Match(`char`) Char, 65 | @Match(`string`) String, 66 | @Match(`bool`) Bool, 67 | @Match(`true`) True, 68 | @Match(`false`) False, 69 | @Match(`if`) If, 70 | @Match(`else`) Else, 71 | @Match(`while`) While, 72 | @Match(`do`) Do, 73 | @Match(`for`) For, 74 | @Match(`break`) Break, 75 | @Match(`continue`) Continue, 76 | @Match(`is`) Is, 77 | @Match(`!is`) NotIs, 78 | @Match(`null`) Null, 79 | @Match(&identifyIdentifier, cast(string) 80 | iota('a', 'z' + 1). 81 | chain(iota('A', 'Z' + 1)). 82 | map!(a => cast(char)a).array ~ '_') Identifier, 83 | @Match(`;`) Semicolon, 84 | @Match(`->`) Arrow, 85 | @Match(`,`) Comma, 86 | @Match(`.`) OpDot, 87 | @Match(`[`) OpIndex, 88 | @Match(`(`) OpFnCall, 89 | @Match(`++`) OpInc, 90 | @Match(`--`) OpDec, 91 | @Match(`!`) OpNot, 92 | @Match(`*`) OpMul, 93 | @Match(`/`) OpDiv, 94 | @Match(`%`) OpMod, 95 | @Match(`+`) OpAdd, 96 | @Match(`-`) OpSub, 97 | @Match(`<<`) OpLShift, 98 | @Match(`>>`) OpRShift, 99 | @Match(`==`) OpEquals, 100 | @Match(`!=`) OpNotEquals, 101 | @Match(`>=`) OpGreaterEquals, 102 | @Match(`<=`) OpLesserEquals, 103 | @Match(`>`) OpGreater, 104 | @Match(`<`) OpLesser, 105 | @Match(`is`) OpIs, 106 | @Match(`!is`) OpNotIs, 107 | @Match(`&`) OpBinAnd, 108 | @Match(`|`) OpBinOr, 109 | @Match(`^`) OpBinXor, 110 | @Match(`&&`) OpBoolAnd, 111 | @Match(`||`) OpBoolOr, 112 | @Match(`=`) OpAssign, 113 | @Match(`+=`) OpAddAssign, 114 | @Match(`-=`) OpSubAssign, 115 | @Match(`*=`) OpMulAssign, 116 | @Match(`/=`) OpDivAssign, 117 | @Match(`%=`) OpModAssign, 118 | @Match(`&=`) OpBinAndAssign, 119 | @Match(`|=`) OpBinOrAssign, 120 | @Match(`^=`) OpBinXorAssign, 121 | @Match(`(`) BracketOpen, 122 | @Match(`)`) BracketClose, 123 | @Match(`[`) IndexOpen, 124 | @Match(`]`) IndexClose, 125 | @Match(`{`) CurlyOpen, 126 | @Match(`}`) CurlyClose, 127 | @Match(&identifyTrait, "$") Trait, 128 | @Match(`$if`) StaticIf, 129 | @Match(`$for`) StaticFor, 130 | @Match(`$fn`) TemplateFn, 131 | @Match(`$struct`) TemplateStruct, 132 | @Match(`$enum`) TemplateEnum, 133 | @Match(`$var`) TemplateVar, 134 | @Match(`$alias`) TemplateAlias 135 | } 136 | 137 | private uint identifyWhitespace(string str){ 138 | foreach (index, ch; str){ 139 | if (ch != ' ' && ch != '\t' && ch != '\n') 140 | return cast(uint)index; 141 | } 142 | return cast(uint)(str.length); 143 | } 144 | 145 | private uint identifyComment(string str){ 146 | if ((!str.length || str[0] != '#') && 147 | (str.length < 2 || str[0 .. 2] != "//")) 148 | return cast(uint)0; 149 | foreach (index, ch; str[1 .. $]){ 150 | if (ch == '\n') 151 | return cast(uint)index + 1; 152 | } 153 | return cast(uint)(str.length); 154 | } 155 | 156 | private uint identifyCommentMultiline(string str){ 157 | if (str.length < 2 || str[0 .. 2] != "/*") 158 | return 0; 159 | for (uint i = 4; i <= str.length; i ++){ 160 | if (str[i - 2 .. i] == "*/") 161 | return i; 162 | } 163 | return 0; 164 | } 165 | 166 | private uint identifyLiteralInt(string str){ 167 | foreach (index, ch; str){ 168 | if (ch < '0' || ch > '9') 169 | return cast(uint)index; 170 | } 171 | return cast(uint)str.length; 172 | } 173 | 174 | private uint identifyLiteralFloat(string str){ 175 | int charsAfterDot = -1; 176 | foreach (index, ch; str){ 177 | if (ch == '.' && charsAfterDot == -1){ 178 | charsAfterDot = 0; 179 | continue; 180 | } 181 | if (ch < '0' || ch > '9') 182 | return (charsAfterDot > 0) * cast(uint)index; 183 | charsAfterDot += charsAfterDot != -1; 184 | } 185 | return (charsAfterDot > 0) * cast(uint)str.length; 186 | } 187 | 188 | private uint identifyLiteralString(string str){ 189 | if (str.length < 2 || str[0] != '"') 190 | return 0; 191 | for (uint i = 1; i < str.length; i ++){ 192 | if (str[i] == '\\'){ 193 | i ++; 194 | continue; 195 | } 196 | if (str[i] == '"') 197 | return i + 1; 198 | } 199 | return 0; 200 | } 201 | 202 | private uint identifyLiteralChar(string str){ 203 | if (str.length < 3 || str[0] != '\'') 204 | return 0; 205 | if (str[1] == '\\' && str.length > 3 && str[3] == '\'') 206 | return 4; 207 | if (str[1] != '\'' && str[2] == '\'') 208 | return 3; 209 | return 0; 210 | } 211 | 212 | private uint identifyLiteralHexadecimal(string str){ 213 | if (str.length < 3 || str[0] != '0' || (str[1] != 'x' && str[1] != 'X')) 214 | return 0; 215 | foreach (index, ch; str[2 .. $]){ 216 | if ((ch < '0' || ch > '9') 217 | && (ch < 'A' || ch > 'F') 218 | && (ch < 'a' || ch > 'f')) 219 | return cast(uint)index + 2; 220 | } 221 | return cast(uint)str.length; 222 | } 223 | 224 | private uint identifyLiteralBinary(string str){ 225 | if (str.length < 3 || str[0] != '0' || (str[1] != 'b' && str[1] != 'B')) 226 | return 0; 227 | foreach (index, ch; str[2 .. $]){ 228 | if (ch != '0' && ch != '1') 229 | return cast(uint)index + 2; 230 | } 231 | return cast(uint)str.length; 232 | } 233 | 234 | private uint identifyIdentifier(string str){ 235 | uint len; 236 | while (len < str.length && str[len] == '_') 237 | len ++; 238 | if (len == 0 && 239 | (str[len] < 'a' || str[len] > 'z') && 240 | (str[len] < 'A' || str[len] > 'Z')) 241 | return 0; 242 | for (; len < str.length; len ++){ 243 | const char ch = str[len]; 244 | if ((ch < '0' || ch > '9') && 245 | (ch < 'a' || ch > 'z') && 246 | (ch < 'A' || ch > 'Z') && ch != '_') 247 | return len; 248 | } 249 | return cast(uint)str.length; 250 | } 251 | 252 | private uint identifyTrait(string str){ 253 | if (str.length < 2 || str[0] != '$') 254 | return 0; 255 | const uint len = identifyIdentifier(str[1 .. $]); 256 | if (!len) 257 | return 0; 258 | return 1 + len; 259 | } 260 | 261 | unittest{ 262 | import std.conv : to; 263 | 264 | string source = 265 | `fn main(){ # comment 266 | return 2 + 0B10; #another comment 267 | voidNot 268 | }`; 269 | Token[] tokens; 270 | auto tokenizer = Tokenizer(source, Flags!TokenType()); 271 | 272 | foreach (token; tokenizer) 273 | tokens ~= token; 274 | 275 | const string[] expectedStr = [ 276 | "fn", " ", "main", "(", ")", "{", " ", "# comment", 277 | "\n", "return", " ", "2", " ", "+", " ", "0B10", ";", " ", 278 | "#another comment", "\n", "voidNot", "\n", "}" 279 | ]; 280 | const TokenType[] expectedType = [ 281 | TokenType.Fn, TokenType.Whitespace, 282 | TokenType.Identifier, TokenType.BracketOpen, 283 | TokenType.BracketClose, TokenType.CurlyOpen, TokenType.Whitespace, 284 | TokenType.Comment, TokenType.Whitespace, TokenType.Return, 285 | TokenType.Whitespace, TokenType.LiteralInt, TokenType.Whitespace, 286 | TokenType.OpAdd, TokenType.Whitespace, TokenType.LiteralBinary, 287 | TokenType.Semicolon, TokenType.Whitespace, TokenType.Comment, 288 | TokenType.Whitespace, TokenType.Identifier, TokenType.Whitespace, 289 | TokenType.CurlyClose 290 | ]; 291 | foreach (i, token; tokens){ 292 | assert (token.token == expectedStr[i] && token.type[expectedType[i]], 293 | "failed to match at index " ~ i.to!string); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /spec/coding-conventions.md: -------------------------------------------------------------------------------- 1 | # QScript Coding Style 2 | 3 | * Tabs for indents, width = 2 spaces wide. 4 | * 1 optional space character after indents, for alignment 5 | * 80 characters maximum width per each line 6 | * opening curly brace on same line as if/while/else/whatever 7 | * else on same line as closing curly brace 8 | * 1 empty line between every function, or in case of group of small function, 9 | 1 empty line after them. 10 | * space or newline after comma 11 | * space between binary operator and operands 12 | -------------------------------------------------------------------------------- /spec/spec.md: -------------------------------------------------------------------------------- 1 | // TODO: 2 | * struct static functions? 3 | * module import 4 | * unions tag based function call 5 | 6 | **Work In Progress, not finalised** 7 | 8 | # QScript Language Reference 9 | 10 | # Comments 11 | 12 | Comments can be added using the `#` character or `//`. 13 | Anything following a `#` or `//` is ignored by compiler as a comment. 14 | 15 | For example: 16 | 17 | ``` 18 | # a comment 19 | // another comment 20 | fn main(){ # This is a comment 21 | # This also is a comment 22 | } 23 | ``` 24 | 25 | Multi line comments can be written by enclosing them in between `/*` and `*/` 26 | like: 27 | 28 | ``` 29 | /* 30 | this 31 | is 32 | a 33 | multiline 34 | comment 35 | */ 36 | ``` 37 | 38 | Use `///` comments for writing documentation. 39 | 40 | --- 41 | 42 | # Functions 43 | 44 | QScript has first class functions. Functions can be assigned to a variable 45 | (of appropriate type), or passed as an argument, they behave as normal 46 | variables. 47 | 48 | ## Function Definition 49 | 50 | ``` 51 | [pub] fn FUNCTION_NAME( 52 | [@]arg0_type arg0, 53 | [@]arg1_type arg1) [-> RETURN_TYPE]{ 54 | # function body 55 | } 56 | ``` 57 | 58 | * `pub` (optional) will make the function public 59 | * `@` (optional) for pass by reference 60 | * `FUNCTION_NAME` is the name of the function 61 | * `arg0_type` is the type for first argument 62 | * `arg0` is the "name" for first argument 63 | * more arguments can be added, and are to be separated by a comma. 64 | * `RETURN_TYPE` (optional) is the return type of this function. 65 | 66 | A function without any arguments would be defined like: 67 | 68 | ``` 69 | fn FUNCTION_NAME() -> RETURN_TYPE{ 70 | # function body 71 | } 72 | ``` 73 | 74 | Without any arguments and no return type: 75 | ``` 76 | fn FUNCTION_NAME(){ 77 | # function body 78 | } 79 | ``` 80 | 81 | ## Default parameters 82 | 83 | Default values for parameters can be written as: 84 | 85 | ``` 86 | fn sum(int a = 0, int b = 1) -> int{...} // valid 87 | fn sum(int a, int b = 1) -> int{...} // valid 88 | fn sum(int a = 0, int b) -> int{...} // invalid 89 | ``` 90 | 91 | While calling a function, to use the default value for a parameter: 92 | ``` 93 | sum(, 1); // default value for a is used 94 | sum(0, 1); // no default value used 95 | sum(0); // default value for b used 96 | ``` 97 | 98 | ## Anonymous Functions 99 | 100 | ``` 101 | fn sumXTimes(int x, fn int(int) func) -> int{ 102 | var int i = 0; 103 | var int sum; 104 | while (i < x) 105 | sum += func(i ++); 106 | return sum; 107 | } 108 | sumXTimes(5, num -> num * 5); 109 | 110 | # is equivalent to: 111 | sumXTimes(5, (int num) -> {return num * 5;}); 112 | 113 | # is equivalent to: 114 | fn mul5(int num) -> int{ 115 | return num * 5; 116 | } 117 | sumXTimes(5, @mul5); 118 | ``` 119 | 120 | ## Returning From Functions 121 | 122 | A return statement can be used to return a value from the function. The type of 123 | the return value, and the return type of the function must match. 124 | A return statement is written as following: 125 | 126 | ``` 127 | return RETURN_VALUE; 128 | ``` 129 | 130 | where `RETURN_VALUE` is the value to return. As soon as this statement is 131 | executed, the function execution quits, meaning that in the following code, 132 | writeln will not be called. 133 | 134 | ``` 135 | fn someFunction() -> int{ 136 | return 0; 137 | writeln("this won't be written"); # because return terminates the execution 138 | } 139 | ``` 140 | 141 | In case of functions that return nothing, `return;` can be used to terminate 142 | execution, like: 143 | 144 | ``` 145 | fn main(){ 146 | # .. 147 | return; 148 | # anything below wont be executed 149 | } 150 | ``` 151 | 152 | ## Function Calls 153 | 154 | Function calls are made through the `()` operator like: 155 | 156 | ``` 157 | fnName(funcArg0, funcArg1, ...); 158 | ``` 159 | 160 | or in case of no arguments: 161 | 162 | ``` 163 | fnName(); 164 | ``` 165 | 166 | The first argument(s) to a function call can also be passed using the `.` 167 | operator: 168 | 169 | ``` 170 | fn sum(int a, int b) -> int{ 171 | return a + b; 172 | } 173 | 174 | fn main(){ 175 | writeln(sum(5, 5)); # is same as following 176 | writeln(5.sum(5)); # is same as following 177 | writeln((5, 5).sum()); # is same as following 178 | writeln((5, 5).sum); # writes 25 179 | } 180 | ``` 181 | 182 | Alternatively, just writing the name of a function will call it, if it has 183 | either no arguments, or all the arguments were provided using `.` operator: 184 | 185 | ``` 186 | fn func(int a, int b){ 187 | # do stuff 188 | } 189 | fn bar(){ ... } 190 | bar; // will call 191 | (5, 6).func; // will call 192 | 5.func(6); // is the same as above 193 | ``` 194 | 195 | The one case where this will not result in a function call: 196 | 197 | ``` 198 | var @fn() fref; 199 | fref @= bar; // bar will not be called 200 | ``` 201 | 202 | --- 203 | 204 | # Data Types 205 | 206 | QScript has these basic data types: 207 | 208 | * `int` - largest supported signed integer (usually 64 or 32 bits) 209 | * `uint` - largest supported unsigned integer (usually 64 or 32 bits) 210 | * `i8` - 8 bit signed integer 211 | * `i16` - 16 bit signed integer 212 | * `i32` - 32 bit signed integer 213 | * `i64` - 64 bit signed integer 214 | * `u8` - 8 bit unsigned integer 215 | * `u16` - 16 bit unsigned integer 216 | * `u32` - 32 bit unsigned integer 217 | * `u64` - 64 bit unsigned integer 218 | * `f64` - a 64 bit floating point (double) 219 | * `f32` - 32 bit bit floating point 220 | * `char` - an 8 bit character 221 | * `bool` - a `true` or `false` (Dlang `bool`) 222 | * `auto` - in above examples, `auto` can be substituted for `X` in cases where 223 | inline assignment occers and compiler is able to detect intended type. 224 | * `@X` - reference to any of the above (behaves the same as `X` alone would) 225 | * `@fn ...(...)` - reference to a function 226 | 227 | ## `int` (and other integers) 228 | 229 | This can be written as series (or just one) digit(s). 230 | 231 | Can be written as: 232 | 233 | * Binary - `0B1100` or `0b1100` - for `12` 234 | * Hexadecimal - `0xFF` or `0XfF` - for `15` 235 | * Series of `[0-9]+` digits 236 | 237 | `int` is initialised as `0`. 238 | 239 | ## floating points 240 | 241 | Digits with a single `.` between them is read as a double. 242 | `5` is an int, but `5.0` is a double. 243 | 244 | initialised as `0.0` 245 | 246 | ## `char` 247 | 248 | This is written by enclosing a single character within a pair of apostrophes: 249 | 250 | `'c'` for example, or `'\t'` tab character. 251 | 252 | initialised as ascii code `0` 253 | 254 | ## `bool` 255 | 256 | A `true` (1) or a `false` (0). 257 | 258 | While casting from `int`, a non zero value is read as a `true`, zero as `false`. 259 | 260 | initialised as `false` 261 | 262 | ## `@X` references 263 | 264 | These are pointers to data. A reference must always be pointing to a valid data, 265 | there is no concept of null in qscript. 266 | 267 | ``` 268 | int i = 0; 269 | var @int r; // error, not initialised 270 | var @int r @= i; 271 | r = 2; 272 | i.writeln; // 2 273 | r.writeln; // 2 274 | 275 | int j = 0; 276 | r @= j; // reassign is fine 277 | r = 1; 278 | r.writeln; // 1 279 | j.writeln; // 1 280 | i.writeln; // 2 281 | ``` 282 | 283 | References cannot refer to lower-scoped data: 284 | 285 | ``` 286 | var int i; 287 | var @int r @= i; 288 | { 289 | var int j; 290 | r @= j; // error, j has lower scope than r 291 | } 292 | ``` 293 | 294 | ## `auto` variables 295 | 296 | The `auto` keyword can be used to infer types: 297 | 298 | ``` 299 | var auto x = something; 300 | var auto y @= something; 301 | ``` 302 | 303 | Where it is not possible for qscript to detect whether `auto` should resolve to 304 | `T` or `@T`, it will prefer `T`. To override this behavior, use `@auto` 305 | 306 | --- 307 | 308 | # Structs 309 | 310 | These can be used to store multiple values of varying or same data types. 311 | They also act as namespaces. 312 | 313 | They are defined like: 314 | 315 | ``` 316 | struct STRUCT_NAME{ 317 | [pub] TYPE NAME [ = INIT_VALUE]; 318 | [pub] TYPE NAME [ = INIT_VALUE] , NAME_2 [ = INIT_VALUE]; 319 | [pub] TYPE; // for when TYPE is a struct or union 320 | [pub] alias [X] = [Y]; 321 | } 322 | ``` 323 | 324 | By default, all members inside a struct are private. 325 | 326 | Members of a struct can be: 327 | 328 | * variables - `TYPE NAME;` 329 | * static variables - `static TYPE NAME;` 330 | 331 | ## Anonymous Structs 332 | 333 | A struct can be created, and immediately used, without being given a type name: 334 | 335 | ``` 336 | var struct{ int i; string s; } data; 337 | data.i = 5; 338 | data.s = "foo"; 339 | ``` 340 | 341 | `struct{ int i; string s; }` is treated as an expression. 342 | 343 | As such, structs can be defined as: 344 | 345 | ``` 346 | alias T = struct{ int i; string s; }; 347 | // or 348 | struct T{ int i; string s; } 349 | ``` 350 | 351 | ## Equivalence 352 | 353 | Two different structs will always be treated as different types, regardless of 354 | their members: 355 | 356 | ``` 357 | struct T{ int i; string s; } 358 | 359 | alias TT = struct { int i; string s; }; 360 | 361 | struct TTT{ int i; string s; } 362 | ``` 363 | 364 | Each of these (`T`, `TT`, and `TTT`) are a different type. 365 | 366 | Aliases to a defined type are equivalent: 367 | 368 | ``` 369 | struct Foo{ int i; string s; } 370 | alias Bar = Foo; 371 | // bar is equivalent to Foo 372 | 373 | alias A = struct{ int i; string s; }; 374 | alias B = struct{ int i; string s; }; 375 | // A and B are not equivalent 376 | // nor are they equivalent to either Foo or Bar 377 | ``` 378 | 379 | ## `this` member in struct 380 | 381 | Having `X.this` defined, where X is a struct, will add a fallback member to do 382 | operations on rather than the struct itself: 383 | 384 | ``` 385 | struct Length{ 386 | int len = 0; 387 | string unit = "cm"; 388 | alias this = len; 389 | } 390 | var Length len = 0; // Length = int is error, so assigns to the int member 391 | len = 1; 392 | writeln(len + 5); // Length + int is not implemented, evaluates to len.len + 5 393 | writeln(len.unit); // prints "cm" 394 | ``` 395 | 396 | ## Constructing Structs 397 | 398 | Structs can be constructed as: 399 | 400 | ``` 401 | auto s = StructName(member1Value, member2Value, member3Value ...); 402 | ``` 403 | 404 | Providing values for all members is not necessary. 405 | 406 | Example: 407 | 408 | ``` 409 | struct Foo{ 410 | int i; 411 | string s; 412 | f32 f = 10.5; 413 | char c; 414 | } 415 | 416 | var Foo f = Foo(5, "hello"); 417 | $assert(f.i == 5); 418 | $assert(f.s == "hello"); 419 | $assert(f.f == 10.5); 420 | $assert(f.c == '\0'); 421 | ``` 422 | 423 | Only members that are accessible from the current scope can be passed to 424 | constructor. 425 | 426 | ``` 427 | # file module.qs: 428 | pub struct Foo{ 429 | int i; 430 | pub string s = "a"; 431 | } 432 | var Foo f = Foo(5, "hello"); # is fine 433 | 434 | # file main.qs 435 | load(module); 436 | var Foo f = Foo(5, "hello"); # will not compile, 5 is not accessible 437 | var Foo f = Foo("hello"); # is fine 438 | ``` 439 | 440 | --- 441 | 442 | # Unions 443 | 444 | Unions store one of the members at a time, along with a tag, indicating which 445 | member is currently stored: 446 | 447 | ``` 448 | union Name{ 449 | [pub] TYPE NAME; 450 | [pub] TYPE; // for when type is struct or union 451 | [pub] alias X = Y; 452 | } 453 | ``` 454 | 455 | Example: 456 | 457 | ``` 458 | union Val{ 459 | int i = 0; 460 | float f; 461 | string s; 462 | } 463 | ``` 464 | 465 | Example: 466 | 467 | ``` 468 | struct User{ 469 | string username; 470 | union{ 471 | struct {} admin; 472 | struct {} moderator; 473 | struct {} user; 474 | struct { 475 | int loginAttempts = int.max; 476 | string ipAddr = "127.0.0.1"; 477 | } loggedOut; 478 | }; 479 | } 480 | ``` 481 | 482 | This is a User struct, where the username is always stored, along with one of: 483 | 484 | * nothing - in case of `admin` 485 | * nothing - in case of `moderator` 486 | * nothing - in case of `user` 487 | * `loginAttempts` and `ipAddr` - in case of `loggedOut` 488 | 489 | Similar to structs, anonymous unions can also be created. 490 | 491 | Same equivalence rules as structs apply. 492 | 493 | Same rules regarding `this` member as struct apply. 494 | 495 | ## Default Member 496 | 497 | A union must at all times have a valid member. At initialization, the default 498 | member can be denoted by assigning a default value to it. For example: 499 | 500 | ``` 501 | union Foo{ 502 | struct {} bar = void; // bar is default 503 | int baz; 504 | } 505 | 506 | union Num{ 507 | int i; 508 | float f = float.init; // f is default 509 | } 510 | ``` 511 | 512 | ## Reading tag 513 | 514 | The `unionIs(U, T)` can be used to check if a union currently stores a tag: 515 | 516 | ``` 517 | union Foo{ 518 | struct {} bar = void; 519 | int baz; 520 | } 521 | var Foo f; 522 | $assert($unionIs(f, bar) == true); 523 | $assert($unionIs(f, baz) == false); 524 | f.baz = 5; 525 | $assert($unionIs(f, bar) == false); 526 | $assert($unionIs(f, baz) == true); 527 | ``` 528 | 529 | In case of `this` members: 530 | 531 | ``` 532 | union Foo{ 533 | struct {} bar = void; 534 | int baz; 535 | alias this = bar; 536 | } 537 | var Foo f; 538 | $assert($unionIs(f) == $unionIs(f, bar)); 539 | ``` 540 | 541 | Alternatively, the prefix `is` operator can be used: 542 | 543 | ``` 544 | union Foo{ 545 | struct {} bar = void; 546 | alias this = bar; 547 | int baz; 548 | } 549 | var Foo f; 550 | $assert(is f); 551 | $assert(is f.bar); 552 | $assert(is f.baz == false); 553 | ``` 554 | 555 | It is a compiler error to read a union member where it is not clear if that 556 | member is stored: 557 | 558 | ``` 559 | union Foo{ int i = 0; string s; f32 f; } 560 | fn bar(){ 561 | var Foo f = # get Foo from somewhere 562 | f.s.writeln; # error 563 | if (is f.s) 564 | f.s.writeln; # no error 565 | 566 | f.i.writeln; # error 567 | if (is f.s || is f.f) 568 | return; 569 | f.i.writeln; # no error 570 | } 571 | ``` 572 | 573 | ## Constructing Unions 574 | 575 | Unions have multiple constructors, for each member. However if for some type 576 | `T`, there are multiple members, there will be no constructor. 577 | 578 | Example: 579 | 580 | ``` 581 | # file module.qs 582 | union Foo{ 583 | pub int i = 0; // default 584 | pub string s; 585 | pub struct { 586 | f64 x, y; 587 | } pos; 588 | int k; 589 | } 590 | 591 | Foo(); // fine, default i = 0 592 | Foo(0); // error: int matches for i and k 593 | Foo("hello"); // fine, string matches only s 594 | Foo(5, 6); // fine, ints casted to floats, match pos.x pos.y 595 | 596 | # file main.qs 597 | load(module) 598 | Foo(5); // fine, k is inaccessible, leaving i the only int 599 | ``` 600 | 601 | --- 602 | 603 | # Enums 604 | 605 | Enum can be used to group together constants of the same base data type. 606 | 607 | Enums are defined like: 608 | 609 | ``` 610 | enum int EnumName{ 611 | member0 = 1, 612 | member1 = 3, 613 | member2 = 3, # same value multiple times is allowed 614 | } 615 | ``` 616 | 617 | * `int` is the base data type 618 | * `EnumName` is the name for this enum 619 | 620 | Example: 621 | 622 | ``` 623 | enum int ErrorType{ 624 | FileNotFound = 1, # default value is first one 625 | InvalidPath = 1 << 1, # constant expression is allowed 626 | PermissionDenied = 4 627 | } 628 | ``` 629 | 630 | An enum's member's value can be read as: `EnumName.MemberName`, using the 631 | member selector operator. 632 | 633 | Enums act as data types: 634 | 635 | ``` 636 | var ErrorType err; # initialised to FileNotFound 637 | // or 638 | var auto err = ErrorType.FileNotFound; 639 | ``` 640 | 641 | ## Compile Time Constants 642 | 643 | The `enum` keyword can be used to create constants that are evaluated compile 644 | time as follows: 645 | 646 | ``` 647 | enum int U8MASK = (1 << 4) - 1; # evaluated at compile time 648 | ``` 649 | 650 | --- 651 | 652 | # Aliases 653 | 654 | `alias` keyword can be used to enable using alternative identifiers to access 655 | something: 656 | 657 | ``` 658 | struct Position{ # struct is private 659 | pub int x, y; 660 | } 661 | 662 | pub alias Coordinate = Position; # Coordinate is publically accessible 663 | ``` 664 | 665 | --- 666 | 667 | # Variables 668 | 669 | ## Variable Declaration 670 | 671 | Variables can be declared like: 672 | 673 | ``` 674 | var [static] TYPE var0, var1, var2; 675 | ``` 676 | 677 | * `TYPE` is the data type of the variables, it can be a `char`, `int`, 678 | `float`, `bool`, or an array of those types: `int[]`, or `int[][]`... 679 | * `var0`, `var1`, `var2` are the names of the variables. There can be more/less 680 | than 3, and are to be separated by a comma. 681 | 682 | Value assignment can also be done in the variable declaration statement like: 683 | 684 | ``` 685 | var int var0 = 12, var1 = 24; 686 | ``` 687 | 688 | ## Variable Assignment 689 | 690 | Variables can be assigned a value like: 691 | 692 | ``` 693 | VAR = VALUE; 694 | ``` 695 | 696 | the data type of `VAR` and `VALUE` must match, or be implicitle castable. 697 | 698 | In case the `VAR` is an array, then it's individual elements can be modified: 699 | 700 | ``` 701 | VAR[ INDEX ] = VALUE; 702 | ``` 703 | 704 | * `VAR` is the name of the var/array 705 | * `INDEX` , in case `VAR` is an array, is the index of the element to assign a 706 | value to 707 | * `VALUE` is the value to assign 708 | 709 | And in a case like this, `VAR[INDEX]` must have the same data type as `VALUE`. 710 | 711 | In case you want to modify the whole array, it can be done like: 712 | 713 | ``` 714 | var char someChar = 'a'; 715 | var string someString; 716 | someString = [someChar, 'b', 'c'] ; # you could've also done someChar + "bc" 717 | ``` 718 | 719 | ## Variable Scope 720 | 721 | Variables are only available inside the scope they are declared in. 722 | In the code below: 723 | 724 | ``` 725 | var int someGlobalVar; 726 | pub fn main(int count){ 727 | var int i = 0; 728 | while (i < count){ 729 | var int j; 730 | # some other code 731 | i = i + 1; 732 | } 733 | } 734 | ``` 735 | 736 | Varible `i` and `count` are accessible throughout the function. Variable `j` is 737 | accessible only inside the `while` block. Variable `someGlobalVar` is declared 738 | outside any function, so it is available to all functions defined inside the 739 | module, as it is `private`. 740 | 741 | --- 742 | 743 | # `_` identifiers 744 | 745 | `_` can be used as a "discard" identifier. Anything named `_` will be created, 746 | but inaccessible. For example: 747 | 748 | ``` 749 | var int _; // create an int, that cannot be accessed 750 | var int _; // create another such int 751 | 752 | fn main(string[] _){ 753 | # receive string[] parameter, do not use it 754 | } 755 | 756 | for (i; _; range){ 757 | // iterate range, ignoring the values, just keep the counter 758 | } 759 | ``` 760 | 761 | --- 762 | 763 | # Modules 764 | 765 | Each file is a module. 766 | 767 | The `load` keyword can be used to instantiate a module. 768 | A module is similar to a struct, except: 769 | 770 | 1. only 1 instance of a module can exist, created the first time `load` is 771 | called 772 | 2. private members inside a module are not accessible outside 773 | 774 | ``` 775 | # file debug.qs 776 | var string prefix = "debug: "; 777 | pub var auto log = (string msg) -> { ... }; 778 | 779 | # file main.qs: 780 | var auto logger = load(debug); 781 | pub fn this(){ 782 | logger.log("started"); 783 | logger.prefix.writeln; # compiler error, prefix not accessible 784 | }; 785 | ``` 786 | 787 | is equivalent to: 788 | 789 | ``` 790 | # file main.qs: 791 | module Logger{ 792 | var string prefix = "debug: "; 793 | pub var auto log = (string msg) -> { ... }; 794 | } 795 | 796 | var Logger logger; 797 | fn this(){ 798 | logger.log("started"); 799 | logger.prefix.writeln; # compiler error, prefix not accessible 800 | } 801 | ``` 802 | 803 | In the first case, the `load(debug)` statement creates an instance of the 804 | `debug.qs` module, and returns it. 805 | 806 | assigning a "namespace" of sorts as shown above is not necessary, a library can 807 | be loaded so that its symbols become accessible as global symbols: 808 | 809 | ``` 810 | load(debug); 811 | ``` 812 | 813 | This will create an instance of debug and merge it's public symbols with 814 | current module. 815 | 816 | Similarly, a module can be loaded and all it's public symbols made public: 817 | 818 | ``` 819 | pub load(debug); 820 | ``` 821 | 822 | if a module is `load`-ed multiple times, instance is created only once, 823 | succeeding `load`s return the same instance. 824 | 825 | # Visibility 826 | 827 | All members are by default private, and only accessible inside current scope. 828 | 829 | The `pub` keyword can be prefixed to make a member public: 830 | 831 | ``` 832 | pub struct SomeStruct{ 833 | int someInt, someOtherInt; # these data members are private 834 | pub int x; # this is public 835 | } 836 | ``` 837 | 838 | private applies at module level. For example: 839 | 840 | ``` 841 | # file coord.qs 842 | pub struct Coord{ 843 | var int x, y; # private 844 | this (int x, int y){ 845 | this.x = x; 846 | this.y = y; 847 | } 848 | } 849 | pub Coord right(Coord c){ 850 | return Coord(c.x + 1, c.y); # Coord.x Coord.y accessible inside module 851 | } 852 | 853 | # file app.qs 854 | load(coord); 855 | pub fn main(){ 856 | var Coord c; 857 | c.x = 5; # compiler error, Coord.x is private 858 | } 859 | ``` 860 | 861 | --- 862 | 863 | # Shadowing 864 | 865 | In case an identifier matches with a library, and with one defined in module, 866 | the one declared in the module will be preferred. 867 | 868 | --- 869 | 870 | # If Statements 871 | 872 | If statements are written like: 873 | 874 | ``` 875 | if CONDITION_EXPR 876 | STATEMENT_ON_TRUE 877 | ``` 878 | 879 | or: 880 | 881 | ``` 882 | if CONDITION_EXPR 883 | STATEMENT_ON_TRUE 884 | else 885 | STATEMENT_ON_FALSE 886 | ``` 887 | 888 | --- 889 | 890 | # Loops 891 | 892 | ## While: 893 | 894 | While loops are written like: 895 | 896 | ``` 897 | while (CONDITION) { 898 | # some code in a loop 899 | } 900 | ``` 901 | 902 | As long as `CONDITION` is `true`, `# some code in a loop` is executed. 903 | 904 | ## Do While: 905 | 906 | Do while loops are writen like: 907 | 908 | ``` 909 | do { 910 | # some code in a loop 911 | } while (CONDITION); 912 | ``` 913 | 914 | First the code is executed, then if the condition is `true`, it's executed 915 | again. 916 | 917 | ## For: 918 | 919 | For loops uses `Ranges` to iterate over members of a value. 920 | 921 | A for loop can be written as: 922 | 923 | ``` 924 | for (counter; value; range) 925 | writeln(counter + 1, "th value is ", value); 926 | // or 927 | for (value; range) 928 | writeln(value); 929 | ``` 930 | 931 | Example: 932 | 933 | ``` 934 | var auto data = getStuff(); 935 | for (val; data) 936 | writeln(val); 937 | 938 | // is equivalent to: 939 | for (val; data.rangeOf) 940 | writeln(val); 941 | 942 | // is equivalent to: 943 | auto __range = data.rangeOf; 944 | while (!__range.empty()){ 945 | var auto val = __range.front(); 946 | writeln(val); 947 | __range.popFront(); 948 | } 949 | ``` 950 | 951 | ### Ranges: 952 | 953 | A data type `T` can qualify as a range if the following functions can be called 954 | on it: 955 | 956 | ``` 957 | T.empty(); // should return true if there are no more values left to pop 958 | T.front(); // current value 959 | T.popFront(); // pop current element 960 | ``` 961 | 962 | To make a data type `X` iterable, `X.rangeOf` should return a range, for 963 | example: 964 | 965 | ``` 966 | fn rangeOf(X) -> RangeObjectOfX{ 967 | return RangeObjectOfX(X); 968 | } 969 | ``` 970 | 971 | Or in case `X` itself is a range, you can use the `alias .. = this`: 972 | 973 | ``` 974 | struct X{ 975 | # ... 976 | alias rangeOf = this; 977 | } 978 | ``` 979 | 980 | ## Break & Continue 981 | 982 | A `break;` statement can be used to exit a loop at any point, and a `continue;` 983 | will jump to the end of current iteration. 984 | 985 | --- 986 | 987 | # Operators 988 | 989 | Operators are read in this order (higher = evaluated first): 990 | 991 | 1. `.` 992 | 1. `[` 993 | 1. `@`, `fn` 994 | 1. `(` 995 | 1. `a?`, `a!`, `a++`, `a--` 996 | 1. `is a`, `!a`, `++a`, `--a` 997 | 1. `*`, `/`, `%` 998 | 1. `+`, `-` 999 | 1. `<<`, `>>` 1000 | 1. `==`, `!=`, `>=`, `<=`, `>`, `<`, `is`, `!is` 1001 | 1. `&`, `|`, `^` 1002 | 1. `&&`, `||` 1003 | 1. `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `@=` 1004 | 1005 | ## Operator Overloading 1006 | 1007 | Following operators cannot be overloaded: 1008 | 1009 | * `.` 1010 | * `is` 1011 | * `!is` 1012 | 1013 | `a op b` Binary operator is translated to each, in this order, whichever 1014 | compiles, is used: 1015 | 1016 | 1. `opBin("op", a, b)`- aliases to `a`, `b` are passed 1017 | 1. `opBin("op")(a, b)` 1018 | 1019 | `op a` Unary operator is translated: 1020 | 1021 | 1. `opPre("op", a)` - alias to `a` is passed 1022 | 1. `opPre("op")(a)` 1023 | 1024 | `a op` Unary operator is translated: 1025 | 1026 | 1. `opPost("op", a)` - alias to `a` is passed 1027 | 1. `opPost("op")(a)` 1028 | 1029 | ### `()`, `[]` Operators Overloading 1030 | 1031 | `a(x, y, z)` is translated to: 1032 | 1033 | 1. `opBin("()", a, x, y, z)` 1034 | 1. `opBin("()", a, typeof(x), typeof(y), typeof(z))(x, y, z)` 1035 | 1. `opBin("()", typeof(x), typeof(y), typeof(z))(a, x, y, z)` 1036 | 1037 | `a[x, y, z]` is translated to: 1038 | 1039 | 1. `opBin("[]", a, x, y, z)` 1040 | 1. `opBin("[]", a, typeof(x), typeof(y), typeof(z))(x, y, z)` 1041 | 1. `opBin("[]", typeof(x), typeof(y), typeof(z))(a, x, y, z)` 1042 | 1043 | ### Example 1044 | 1045 | // TODO: operator overloading example 1046 | 1047 | --- 1048 | 1049 | # Type Casting 1050 | 1051 | QScript does explicit, and implicit casting. Implicit casting is implemented 1052 | through `T opCast(To)(val)`. 1053 | 1054 | It is not necessary that the `opCast(To)` return type be `To`, for example: 1055 | 1056 | ``` 1057 | union Optional(T){ 1058 | pub T this; 1059 | pub struct{} none = void; 1060 | } 1061 | 1062 | /// An integer that is >=5 and <= 10 1063 | struct WeirdInt{ 1064 | pub int this; 1065 | } 1066 | 1067 | Optional(WeirdInt) opCast(To : WeirdInt)(int i){ 1068 | OptionalInt ret; 1069 | if (i < 5 || i > 10) 1070 | return ret; 1071 | ret = i; 1072 | return ret; 1073 | } 1074 | ``` 1075 | 1076 | --- 1077 | 1078 | # Conditional Compilation 1079 | 1080 | ## `$if` 1081 | 1082 | `$if` can be used to determine which branch of code to compile: 1083 | 1084 | ``` 1085 | $if (platformIsLinux){ 1086 | enum string PlatformName = "Linux"; 1087 | }else{ 1088 | enum string PlatformName = "Other"; 1089 | } 1090 | ``` 1091 | 1092 | ## `$for` 1093 | 1094 | `$for` iterates over a container that is available at compile time, 1095 | and copies its body for each element: 1096 | 1097 | ``` 1098 | # this loop will not exist at runtime 1099 | $for (num; [0, 5, 4, 2]){ 1100 | writeln(num.stringOf); 1101 | } 1102 | ``` 1103 | 1104 | Since a `$for` will copy it's body, it will result in redefinition 1105 | errors if any definitions are made inside. To avoid, do: 1106 | 1107 | ``` 1108 | $for (num; [0, 5, 4, 2]){{ 1109 | enum square = num * num; 1110 | writeln(square.stringOf); 1111 | }} 1112 | ``` 1113 | 1114 | Since `$for` is evaluated at compile time, it can iterate over sequnces: 1115 | 1116 | ``` 1117 | $for (num; (0, 5, 4, 2)){ 1118 | num.stringOf.writeln; 1119 | } 1120 | ``` 1121 | 1122 | --- 1123 | 1124 | # Templates 1125 | 1126 | A template can be declared using the `template` keyword followed by name and a 1127 | tuple of template parameters: 1128 | 1129 | ``` 1130 | // an overly complicated way to create global variables of a type 1131 | template globVar(T){ 1132 | var T this; 1133 | } 1134 | 1135 | template square(int x){ 1136 | enum int this = getSquare(x); # function call will be made at compile time 1137 | fn getSquare(int x) -> int{ return x * x; } 1138 | } 1139 | 1140 | template sum(T x, T y, T){ 1141 | enum T this = x + y; 1142 | } 1143 | ``` 1144 | 1145 | Anything within a template is private. 1146 | 1147 | Since the enums inside the template are named `this`, the resulting enum 1148 | will replace the template whereever initialised: 1149 | 1150 | ``` 1151 | globVar(int) = 5; 1152 | writeln(globVar(int)); # prints 5 1153 | writeln(square(5)); # prints 25 1154 | writeln(sum(5.5, 2)); # prints 7.5 1155 | ``` 1156 | 1157 | As shown above, templates are initialised similar to a function call 1158 | 1159 | ## Conditions 1160 | 1161 | Templates can be tagged with an if condition block, to only initialize if 1162 | it evaluates to true: 1163 | 1164 | ``` 1165 | template foo(T) if (someCondition(T)){ 1166 | enum string this = "bar"; 1167 | } 1168 | ``` 1169 | 1170 | Another way to condition a template is through the `:` operator in template 1171 | paramters list: 1172 | 1173 | ``` 1174 | template typeStr(T : int){ 1175 | enum string this = "int"; 1176 | } 1177 | template typeStr(T : double){ 1178 | enum string this = "double" 1179 | } 1180 | template number(T : (int, double)){ 1181 | alias this = T; 1182 | } 1183 | ``` 1184 | 1185 | ## Mixin 1186 | 1187 | A template can be mixin'd; initialized in the context where it is called. This 1188 | can be done by following any template definition by the `mixin` keyword: 1189 | 1190 | ``` 1191 | mixin template xTimes(F, uint times) if ($isFn(F)){ 1192 | $for (i; range(0, times)){ 1193 | F(); 1194 | } 1195 | } 1196 | fn main(){ 1197 | foo.xTimes(5); // equivalent to calling foo 5 times 1198 | } 1199 | ``` 1200 | 1201 | ## Functions 1202 | 1203 | Function templates are defined as regular functions with an extra tuple for 1204 | template parameters: 1205 | 1206 | ``` 1207 | $fn sum(T)(T a, T b) if (someCondition) -> T{ 1208 | return a + b; 1209 | } 1210 | // or 1211 | $fn sum(T)(T a, T b) -> T{ 1212 | return a + b; 1213 | } 1214 | // or 1215 | template sum(T){ 1216 | fn this(T a, T b) -> T{ 1217 | return a + b; 1218 | } 1219 | } 1220 | ``` 1221 | 1222 | Calling a function template can be done as: 1223 | 1224 | ``` 1225 | var int c = sum(int)(5, 10); 1226 | // or 1227 | var int c = sum(5, 10); // T is inferred 1228 | ``` 1229 | 1230 | QScript is able to determine what value to use for `T`, only if the former 1231 | declaration (`$fn sym(T)(T a, T b) -> T`) is used. 1232 | 1233 | ## Enums 1234 | 1235 | Templates can be used to create compile time constants: 1236 | 1237 | ``` 1238 | template TypeName(T) if ($isType){ 1239 | $if (T is int) 1240 | enum string this = "int"; 1241 | else $if (T is double) 1242 | enum string this = "double"; 1243 | else $if (T is string) 1244 | enum string this = "string"; 1245 | else 1246 | enum string this = "weird type"; 1247 | } 1248 | // or 1249 | $enum string TypeName(T) if ($isType(T)) = doSomethingAtCompileTime(); 1250 | 1251 | $fn typeIsSupported(T)() -> bool{ 1252 | return TypeName(T) != "weird type"; 1253 | } 1254 | ``` 1255 | 1256 | ## Structs 1257 | 1258 | ``` 1259 | $struct Position(T) if ($isNumber(T)){ 1260 | var T x, y; 1261 | } 1262 | // or 1263 | template Position(T){ 1264 | struct this{ 1265 | var T x, y; 1266 | } 1267 | } 1268 | alias PositionDiscrete = Position(int); 1269 | alias PositionContinuous = Position(double); 1270 | ``` 1271 | 1272 | ## Unions 1273 | 1274 | ``` 1275 | $union Optional(T) if ($isType(T)){ 1276 | pub T this; 1277 | pub struct {} none; 1278 | } 1279 | ``` 1280 | 1281 | ## Variables 1282 | 1283 | ``` 1284 | $var T foo(T), bar(T); 1285 | fn setFoo(){ 1286 | foo(int) = 5; 1287 | foo(string) = "hello"; 1288 | } 1289 | 1290 | fn setBar(){ 1291 | bar(int) = 10; 1292 | bar(string) = " world"; 1293 | } 1294 | 1295 | fn main(){ 1296 | setFoo(); 1297 | setBar(); 1298 | writeln(foo(string), bar(string)); # "hello world" 1299 | writeln(foo(int)); # 5 1300 | writeln(bar(int)); # 10 1301 | } 1302 | ``` 1303 | 1304 | ## Aliases 1305 | 1306 | ``` 1307 | $alias X(T) = T; 1308 | $var X(int) i = 5; 1309 | ``` 1310 | 1311 | Alias templates can be used to create type qualifiers. When an alias template 1312 | `foo(...)` is used as a Data Type, QScript will check if `foo(T)` evaluates to 1313 | `T`. If it does, `T` is a `foo` qualified type: 1314 | 1315 | ``` 1316 | $alias Number(T) if (T is int || T is double) = T; 1317 | 1318 | fn square(Number x) -> double{ 1319 | return x * x; 1320 | } 1321 | 1322 | square(5); // 25.0 1323 | square(5.5); // 30.25 1324 | square("a"); // compiler error 1325 | ``` 1326 | 1327 | Another example: 1328 | 1329 | ``` 1330 | $alias Number(T) if (T is int || T is double) = T; 1331 | template Iterable(BaseType){ 1332 | $alias this(T) if ($isRange(T) && $rangeType(T) is BaseType) = T; 1333 | } 1334 | 1335 | fn sum(Iterable(Number) numbers) -> double{ 1336 | double ret; 1337 | for (num; numbers) 1338 | ret += num; 1339 | return ret; 1340 | } 1341 | ``` 1342 | 1343 | ## Sequences 1344 | 1345 | Sequences are compile time "lists" of aliases and/or values (can be mixed). 1346 | 1347 | A sequence can be defined as: `(a, b, c)` 1348 | 1349 | A template can receive a sequence as: 1350 | 1351 | ``` 1352 | template foo(T...){} 1353 | ``` 1354 | 1355 | A sequence containing aliases to types can be used to declare a sequence of 1356 | variables: 1357 | 1358 | ``` 1359 | $fn sum(T...)(T params) -> T[0]{ 1360 | var T[0] ret; 1361 | $for (val; params) 1362 | ret += val; 1363 | return ret; 1364 | } 1365 | ``` 1366 | 1367 | Length of a sequence can be found through the `length` template: 1368 | 1369 | ``` 1370 | fn printLength(T...)(){ 1371 | length(T).writeln; 1372 | } 1373 | ``` 1374 | 1375 | --- 1376 | 1377 | # Macros 1378 | 1379 | Compiler macros, written as: 1380 | 1381 | ``` 1382 | $macroName(args...) 1383 | ``` 1384 | 1385 | _Note: list is incomplete_ 1386 | 1387 | * `assert(condition, error)` - Emits error as a compiler error if !condition 1388 | * `unionIs(unionInstance, tagSymbolName)` - true if `tagSymbolName` is stored 1389 | * `unionIs(unionInstance)` - true if `this` is stored 1390 | * `fnRetType(symbol)` - return type of a function 1391 | * `fnArgs(symbol)` - array of names of function arguments 1392 | * `fnArgType(symbol, name)` - argument type of argument with name in function 1393 | * `members(symbol)` - string array, names of accessible members inside of a 1394 | symbol. Works for structs, unions, and enums. 1395 | * `member(symbol, nameStr)` - returns member with name=nameStr in symbol. 1396 | Works for structs, unions, and enums. 1397 | * `canCast(s1, s2)` - whether s1 can be implicitly casted to s2 (via `opCast`), 1398 | s1, s2 can be data types, or values. 1399 | * `typeOf(symbol)` - data type of a symbol 1400 | * `isStatic(symbol)` - true if a symbol is static 1401 | * `isRef(symbol)` - true if a data type is reference 1402 | * `isTemplate(symbol)` - true if a symbol is a template 1403 | * `isFn(symbol)` - true if a symbol is a function 1404 | * `isFnTemplate(symbol)` - true if a symbol is a function template 1405 | * `isStruct(symbol)` - true if a symbol is a struct 1406 | * `isStructTemplate(symbol)` - true if a symbol is a struct template 1407 | * `isEnum(symbol)` - true if a symbol is an enum 1408 | * `isEnumTemplate(symbol)` - true if a symbol is an enum template 1409 | * `isVar(symbol)` - true if a symbol is a variable 1410 | * `isVarTemplate(symbol)` - true if a symbol is a variable template 1411 | * `isAlias(symbol)` - true if a symbol is an alias 1412 | * `isAliasTemplate(symbol)` - true if a symbol is an alias template 1413 | * `isPub(symbol)` - true if a symbol is public 1414 | -------------------------------------------------------------------------------- /topuml: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | touch sample.json 3 | echo "@startjson" >> sample.json 4 | ./qscript >> sample.json 5 | echo "@endjson" >> sample.json 6 | plantuml -tsvg sample.json 7 | rm sample.json 8 | --------------------------------------------------------------------------------