├── .editorconfig ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SFLK.sublime-syntax ├── TODO.md ├── lines.sh ├── logo ├── sflk-logo-black-square.png ├── sflk-logo-black-square.svg ├── sflk-logo-black.png ├── sflk-logo-black.svg ├── sflk-logo-color-square.png ├── sflk-logo-color-square.svg ├── sflk-logo-color.png ├── sflk-logo-color.svg ├── sflk-logo-white-square.png ├── sflk-logo-white-square.svg ├── sflk-logo-white.png └── sflk-logo-white.svg ├── rustfmt.toml ├── sflk-lang ├── Cargo.toml ├── repl.sflk └── src │ ├── ast.rs │ ├── bignums.rs │ ├── log.rs │ ├── log_indent.rs │ ├── main.rs │ ├── object.rs │ ├── parser.rs │ ├── scu.rs │ ├── settings.rs │ ├── sir.rs │ ├── stringtree.rs │ ├── tokenizer.rs │ └── utils.rs ├── sflk-lsp ├── Cargo.toml └── src │ ├── completion.rs │ ├── document.rs │ └── main.rs ├── sflk-std └── std.sflk ├── tests ├── README.md ├── h.sflk ├── rt.sflk ├── test.sflk ├── test3.sflk ├── test4.sflk ├── test5.sflk ├── test6.sflk ├── test7.sflk ├── test9.sflk ├── test91.sflk ├── test92.sflk ├── test93.sflk └── test94.sflk └── tree.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # IDE stuff 3 | .DS_Store 4 | **/*.DS_Store 5 | .vscode/ 6 | .markdownlint.jsonc 7 | 8 | # Cargo stuff 9 | debug/ 10 | target/ 11 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 12 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 13 | #Cargo.lock 14 | 15 | # rustfmt stuff 16 | **/*.rs.bk 17 | 18 | # Whatever you want 19 | local/ 20 | 21 | # Some test programs want to interact with the file system and might require or produce files. 22 | # These files should remain in this directory and not pollute the repo. 23 | test-output/ 24 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "sflk" 7 | version = "0.3.0" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "sflk-lang", 4 | #"sflk-lsp" 5 | # The LSP crate is not currently under development and has 6 | # dependencies that are a bit annoying (and are not of any use currently) 7 | # so it is unlisted from here for now. 8 | ] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Anima Libera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SFLK logo 2 | 3 | # SFLK programming language 4 | 5 | ## Introduction 6 | 7 | *SFLK* is an interpreted programming language. This repository contains the reference implementation. 8 | SFLK doesn't focus on being fast, a lot of languages are fast already. Instead, it tries new things, in the hope of being interesting. 20% serious, 80% weird stuff (this ratio is an estimation). 9 | Enjoy! 10 | 11 | ## Presentation 12 | 13 | The language is very unstable as it is still in very early development. 14 | Many many essential features are still missing. 15 | What is written here may not have beed updated properly (but all that is described here works or have worked in somepast commit, SFLK is not vaporware). 16 | 17 | ### Syntax 18 | 19 | #### The basics 20 | 21 | ```sflk 22 | pr "Hello world!" nl 23 | ``` 24 | 25 | This SFLK program contains two statements: a print statement and a newline statement. The whitespace is not significant in any way, except for separating tokens when necessary. There is no statement separator, which makes understanding poorly formatted SFLK programs 26 | pretty hard for one unknowing of all the statement syntaxes. 27 | 28 | An SFLK program is basically a sequence of statements, some of which may contain other statements or even generate code at run-time to allow for rich control flow. Most statements begin with a keyword (such as `pr`), most keywords are 2 characters long. 29 | 30 | Some statements expect stuff to follow some keywords, such as the print statement that expects an expression after its `pr` keyword. The newline statement does not. 31 | 32 | #### Expressions 33 | 34 | ```sflk 35 | pr 6 + 2 nl 36 | ``` 37 | 38 | This piece of code prints `8` as the expression `6 + 2` is evaluated during the execution of the statement `pr 6 + 2`. 39 | 40 | Binary operators such as `+` do not have a precedence, expressions such as `1 + 2 * 3 / 4 - 5` are really understood as `((((1 + 2) * 3) / 4) - 5)` and this is why a good formatting practice is to write these as `1 +2 *3 /4 -5`. Parenthesis are supported to allow some freedom on the shape of expressions. 41 | 42 | #### Comments 43 | 44 | ```sflk 45 | # Comments are blocks delimited by hashes # 46 | #### Or by any number 47 | of successive #s #### 48 | #! Hash+bang starts a line comment (unix shebang compatible) 49 | ``` 50 | 51 | #### Variables 52 | 53 | ```sflk 54 | x! < "So long" 55 | x < x + " gay " 56 | pr x + "Bowser" nl 57 | ``` 58 | 59 | Here, `x` is a variable that is being assigned a string object, and again, to finally be used in an expression that is printed. 60 | 61 | The `name < expression` syntax is the one of the assignment statement. The expression is evaluated and the result is stored in the variable of the given name. The `name! < expression` (note the `!`) declares the variable before doing the assignment, which is convenient for a first assignment since variables must be declared before being assigned values. 62 | 63 | To discard the expression final value instead of storing it, the evaluation statement can be used instead with the syntax `ev expression`. 64 | 65 | #### Code blocks 66 | 67 | ```sflk 68 | do {pr "uwu" nl} 69 | ``` 70 | 71 | This piece of code prints `uwu` as one might expect. The code block `{pr "uwu" nl}` is actually a code block literal that is an expression, as code blocks are objects just as integers and strings. 72 | 73 | ```sflk 74 | x! < {pr "uwu"} 75 | x < x + {nl} 76 | do x 77 | ``` 78 | 79 | This also prints `uwu` followed by a newline, as the code block executed in the do statement is the concatenation of the two literals `{pr "uwu"}` and `{nl}`. 80 | 81 | It is to be noted that `{pr "uwu"} + {nl}` is valid, but `{pr} + {"uwu" nl}` is not. `{pr}` is invalid syntax as a print statement expects an expression, and `{"uwu" nl}` is invalid syntax as `"uwu"` is not a statement. To be able to concatenate partial invalid code pieces as if they were strings, then just concatenate strings like so: `"pr" + "\"uwu\" nl"`, the resulting sting `"pr\"uwu\" nl"` happens to be valid SFLK code and can be executed as code when given to a do statement (or in any other place where a code block can be given). Beware of potential token concatenation though (`"pr x" + "nl"` evaluates into `"pr xnl"`, you see the issue). 82 | 83 | One might think of code blocks as functions or procedures or whatever. They are just pieces of code really. SFLK does not care (that much) about debugability or readability and intends to allow as much dynamic bullshit as possible, as demonstrated in the following section: 84 | 85 | #### `>` operator 86 | 87 | ```sflk 88 | double! < {v < v *2} 89 | ``` 90 | 91 | Here, `double` is a code block that only consists of the statement `v < v *2`. The variable `v` is special: it is the variable used to pass arguments to a code block, as well as to return a value. 92 | 93 | ```sflk 94 | pr 4 >double nl 95 | ``` 96 | 97 | The evaluation of `4 >double` goes like this: the value `double` is evaluated, as well as the value `4`. A new context (more on that later) is created for the execution of the code block. In this context, the variable `v` is initialized to `4` before the code block is executed. After the code block is executed, the value of `v` (here `8`) is extracted from the that context (that is discarded) to be the value to which the expression `4 >double` evaluates to. So in the end `8` is printed. 98 | 99 | Now for a demonstration of how deranged is SFLK's dynamism, consider the following: 100 | 101 | ```sflk 102 | quad! < double >double 103 | pr 4 >quad nl 104 | ``` 105 | 106 | Yes, that is right, `16` is printed. `double >double` takes advantage of the fact that `*2` is a valid operation on a code block (simply duplicating its sequence of statements). That produces a new code block that performs the action of `double` two times. Thanks to the design choice of making all the input and output of a block pass via the `v` variable, block concatenation has the same effect as composition, and the block assigned to `quad` makes sense: it doubles doubly (i.e. it quadruples). 107 | 108 | #### Unary operators 109 | 110 | ```sflk 111 | pr -1 nl # -1 # 112 | pr -1+1 +1 nl # -3 # 113 | pr -1+1.+1 nl # -1 # 114 | ``` 115 | 116 | Although `-` can be a binary operator, it can also be a unary operator. Since it only takes one argument (the expression on its right) then nothing is expected on its left. However, when does the expression it takes stops? The second line of the above examples shows that it does not stop until there is no more expression to take (the expression here could also be written `-(1 +1 +1)`). 117 | 118 | This could be controlled by writing expressions like `(-1 +1) +1`, but there is a more sugary syntax for this: `-1 +1. +1`. The `.` here makes the parser terminate the expression it is parsing, and this makes `1 +1` the argument of the `-` unary operator. The expression required by the print statement is not over though (the argument of `-` is a sub expression) and its parsing continues to include the last `+1`. 119 | 120 | `-1.` is not to be thought as some kind of negative floating point literal. There is no such thing in SFLK. 121 | 122 | #### Some other stuff 123 | 124 | ##### Numbers are arbitrarly precise fractions 125 | 126 | ```sflk 127 | pr 333333333333333333333333333 / 111111111111111111111111111 nl # 3 # 128 | pr 333 / 111111111111111111111111111 nl # 1/333667000333667000333667 # 129 | # It works, you can check with the `fractions` module from the Python standard library. # 130 | ``` 131 | 132 | There is no place for floating point rounding errors in SFLK. Every number is a fraction that is as precise as the RAM allows. 133 | 134 | ##### How to include other scripts 135 | 136 | ```sflk 137 | dh fi "file.sflk" 138 | ``` 139 | 140 | `fi` is a unary operator that takes a file path as its argument and evaluates into the content of the said file as a string. `dh` is the keyword of the do-here statement (which is like the do statement, but instead of executing code in a new context, code is executed in the current context, here). This piece of code executes the content of the given file in the current context. 141 | 142 | ##### What are contexts 143 | 144 | (*TODO: Some explainations here are redundent with the "Signals" section. This redundency should be factored out.*) 145 | 146 | A context is a place where variables are defined. When doing `x! < 8`, then a new variable named `x` is defined in the current context (if it was not already defined in that context). A simple assignment statement like `x < 8` does not define `x` (note the missing `!`), it will just assign `8` to an already defined `x` variable. Which one ? 147 | 148 | Constexts are arranged as a tree. The context in which the execution takes place (the current context) is always a leaf. `x < 8` will search for an `x` variable starting from the current context and going toward the root (ignoring the other branches) and will write `8` to the first it finds (if any). The same logic applies to reading values from variables, a statement like `pr x + y` will search an `x` variable to read a value from, and then the same goes for `y`, in a manner similar to a search performed by an assignment. 149 | 150 | ```sflk 151 | x! < "uwu" 152 | pr x nl # A # 153 | do { 154 | pr x nl # B # 155 | x! < "owo" 156 | pr x nl # C # 157 | } 158 | pr x nl # D # 159 | ``` 160 | 161 | In this example, the print statement A prints `uwu` as it reads the variable `x` of the current context. Then, the do statement creates a new context that is a child (in the context tree) of the current context, then this child becomes the current context for the execution of the code block given to the do statement. Then, the print statement B is executed, and this time there is no `x` variable in the current context, but as said earlier, this is not a problem. The parent context has an `x` variable, so it is read, and the print statement B prints `uwu` like A. Then, the print statement C prints `owo` as now there is an `x` variable in the current context. Then, the code block executed by the do statement comes to an end, and that brings the second context (in which `x` has the value `"owo"`) to be discarded, the first context (in which `x` has the value `"uwu"`) becomes the current context again. Then, the print statement D prints `uwu` because we are back in the first context where the `x` variable is untouched. 162 | 163 | Should the `x! < "owo"` statement in the do statement block be replaced with `x < "owo"`, it would mean something else entierly. No `!` means that we are not defining a new variable, we are just assigning to an existing `x` variable, and here that would be the one that was initialized to `"uwu"`. So the print statement D would print `owo` instead of `uwu`. 164 | 165 | ##### Lists 166 | 167 | ```sflk 168 | ev (), 3, 8, 18 169 | ev 3,, 8, 18 170 | ``` 171 | 172 | The `()` literal evaluates to an object called nothing (every language has one of those). The `,` operator appends its operand to the list given on its left, and `()` acts as an empty list for `,`, so the first expression evaluates into a list containing `3`, `8` and `18`. 173 | 174 | The `,,` operator is sugar that makes `a,, b` evaluate like `(), a, b` would. 175 | 176 | ```sflk 177 | x < "a",, "us", {pr "mog"} 178 | pr x ix 0 do x ix 2 pr x ix 1 nl 179 | ``` 180 | 181 | The `ix` binary operator allow access to a list element via its 0-based index. The above example also demonstrates that lists are not bound to contain only one type (this is a dynamic language after all, all the footguns of dynamic languages are to be supported by SFLK, it would be no fun otherwise). 182 | 183 | ##### Nop 184 | 185 | ```sflk 186 | np 187 | ``` 188 | 189 | Does nothing on a Sunday morning. Also does nothing any other day. 190 | 191 | ##### If then else 192 | 193 | ```sflk 194 | if x 195 | th pr "then" 196 | el pr "else" 197 | ``` 198 | 199 | Here goes your if-then-else statement. Except SFLK wants to be special, so then and else branches are optional, there can be multiples of them, and they can be given in any order and even interleaved. The condition `x` is required though. 200 | 201 | ##### Loop 202 | 203 | ```sflk 204 | lp 205 | wh x 206 | bd dh {pr x x < x-1} 207 | sp pr ", " 208 | ``` 209 | 210 | Loop, while `x`, execute the body (`bd`) statement. In between executions of the body, execute the separator (`sp`) statement. 211 | Similar to the if statement, all these extensions are optional and can be given in any quantity and in any order. 212 | 213 | The separator extension to loop statements are imho a very cool feature that more languages should have. It does not cost a lot in terms of language design but allows removing a code duplication pattern that occurs in pieces of code such as the one featured in the above example. Usually the `pr x` part of the code is duplicated (one instance in the loop body, and one instance before or after the loop) to account for the fact that *n* elements are printed but only *n-1* commas, but the loop body runs either *n-1* or *n* times. Granted, duplication could be avoided in other ways, but still. 214 | 215 | ##### Signals 216 | 217 | (*TODO: Some explainations here are redundent with the "What are contexts" section. This redundency should be factored out.*) 218 | 219 | ```sflk 220 | pr "h" nl 221 | ``` 222 | 223 | This actually does not *just* print stuff. Printing to the console is a side effect. Such side effects are frowned upon in some religious comunities. SFLK empowers the programmer to have complete control over these thanks to the following mechanism: 224 | 225 | `pr "h"` actually sends a signal that travels through the context tree toward the root, and finally exits the isolated bubble where the execution takes place to reach the interpreter and ask for an interaction with the rest of the universe (here, the console). The interpreter then performs the required action, and potentially returns a result (but not in the case of a print statement, as there is nothing to answer from it). The execution then continues. 226 | 227 | Wtf? Indeed, here are more details: When something like a do statement is executed, the new context that is created to run a piece of code in is a sub-context of the current context. Thus all the contexts that exist at a given time are organized in a tree. If the do statement registers an interceptor with the `wi` (With Interceptor) extention, then the interceptor will then intercept all the signals that come from sub-contexts as they travel towards the root. The interceptor may examine the signal, let it pass, send another signal, discard it, whatever. 228 | 229 | There is no side effect that a context can do without all its parent contexts agreeing to it. 230 | 231 | The details of how this works are still pretty unstable, but to give an idea, here is an example: 232 | 233 | ```sflk 234 | do { 235 | pr "life in yellow~" nl 236 | } wi { 237 | cy v 238 | if name - "print" 239 | el pr "\e[33m" + value + "\e[39m" 240 | th em v rs v 241 | } 242 | ``` 243 | 244 | The with-interceptor extention (`wi`) takes a code block that is registered in the sub-context as the interceptor. Intercepted signals are available for inspection in the `v` variable. The final value of the variable `v` will be what the intercepted signal returns. 245 | 246 | In the above example, only the print statements are tinkered with, and the other signals are handled by the statement `em v rs v` which re-emits them and forwards their result, so that everything happens as it they were not intercepted. 247 | 248 | Another use case for this feature: 249 | 250 | ```sflk 251 | do fi "something.sflk" 252 | wi { 253 | cy v 254 | if name - "input" 255 | el v < "Morbius" 256 | th em v rs v 257 | } 258 | ``` 259 | 260 | Here, we suppose that the file `something.sflk` contains an SFLK script that may at some point ask the user about its favorite movie (via the input expression `in` that evaluates into what the user types in the console). But here for some reason you don't want to have to type the name of your favorite movie every time you run the script (but you also don't want to modify the script). So you can write another script that executes `something.sflk` and makes the subscript behave as if you typed `Morbius` in response to every request, even though you are never requested to type anything anymore. 261 | 262 | Both these examples are pretty dumb but imho this is a very nice feature with truly useful use cases! 263 | 264 | ##### Coming soon 265 | 266 | Coming soon! 267 | 268 | ### Binary operator behaviors 269 | 270 | #### Plus `+` 271 | 272 | | left type | right type | behavior 273 | |:----------:|:----------:| -------- 274 | | number | number | Arithmetic addition 275 | | string | string | String concatenation 276 | | code block | code block | Code block concatenation 277 | 278 | #### Minus `-` 279 | 280 | | left type | right type | behavior 281 | |:----------:|:----------:| -------- 282 | | number | number | Arithmetic subtraction 283 | | string | string | String comparison (0 iff equal) 284 | 285 | #### Star `*` 286 | 287 | | left type | right type | behavior 288 | |:----------:|:----------:| -------- 289 | | number | number | Arithmetic multiplication 290 | | string | number | String repetition 291 | | code block | number | Code block repetition 292 | 293 | #### Slash `/` 294 | 295 | | left type | right type | behavior 296 | |:----------:|:----------:| -------- 297 | | number | number | Arithmetic division 298 | | string | string | Count non-overlapping occurrences of right in left 299 | 300 | #### To right `>` 301 | 302 | | left type | right type | behavior 303 | |:----------:|:----------:| -------- 304 | | any type | code block | Execute right with left as `v`, evaluates to `v` 305 | | number | list | Same as `right ix left` 306 | 307 | #### Comma `,` 308 | 309 | | left type | right type | result 310 | |:----------:|:----------:| ------ 311 | | nothing | any type | A list with right in it 312 | | list | any type | Left to which is appended right 313 | 314 | #### Double comma `,,` 315 | 316 | | left type | right type | result 317 | |:----------:|:----------:| ------ 318 | | any type | any type | A list with left and right in it 319 | 320 | #### Index `ix` 321 | 322 | | left type | right type | result 323 | |:----------:|:----------:| ------ 324 | | list | number | The right-th element of left 325 | | string | number | The right-th character of string 326 | 327 | ### Unary operators behaviors 328 | 329 | #### Unary minus `-` 330 | 331 | | right type | behavior 332 | |:----------:| -------- 333 | | number | Arithmetic negation 334 | 335 | #### File `fi` 336 | 337 | | right type | behavior 338 | |:----------:| -------- 339 | | string | Read file at path right 340 | 341 | #### Ordered `od` 342 | 343 | | right type | result 344 | |:----------:| ------ 345 | | list | if the list is ordered then `1` else `0` 346 | 347 | #### Ordered but strictly `os` 348 | 349 | | right type | result 350 | |:----------:| ------ 351 | | list | if the list is strictly ordered then `1` else `0` 352 | 353 | #### Length `ln` 354 | 355 | | right type | result 356 | |:----------:| ------ 357 | | list | number of elements 358 | | string | number of characters 359 | 360 | ## Contribute 361 | 362 | If you want to contribute in any way, please feel free to do so ^^. 363 | 364 | Note that there is a Discord server dedicated to the *development and use* of SFLK (how to get in there? we don't know haha, maybe ask for an invite in one way or another). 365 | 366 | ## FAQ 367 | 368 | ### What does SFLK means? 369 | 370 | Nothing haha. Similarly to LLVM, it is an acronym-like name without more meaning. 371 | 372 | ### What is it that this language tries to achieve? 373 | 374 | Well... You know... 375 | 376 | Ok, to be honest here, I have no idea where this is going. Designing a programming language is a common exercise that programmers are supposed to be capable of at some point, and it is plenty fun! However, it takes time, and life is short enough already, so I don't want to spend months to implement a clone of a successful language that will differentiate itself by just being less good than the original. I want to have fun programming in SFLK! This is all that matters, this is the only SFLK development principle. 377 | 378 | This is not an [esolang](https://esolangs.org), but it is not a serious C/Python-wannabe that has "Safe, blazing fast🚀 system programming language" as its description. There are thousands of new languages like this every month, most of which never really make it. Instead, this is an unsafe, slow scripting programming language that hopes to provide an interesting programming experience to people who are lost in life and end up trying it. 379 | 380 | ### Implemented in Rust huh? 381 | 382 | Rust blah blah you know already. 383 | 384 | ### Is there a roadmap? 385 | 386 | Mmm, there is a [TODO](TODO.md) list, which is better than nothing. 387 | -------------------------------------------------------------------------------- /SFLK.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: SFLK 4 | file_extensions: [sflk, Sflk, SFLK] 5 | scope: source.sflk 6 | 7 | variables: 8 | name: '\b([a-zA-Z]|([a-zA-Z]{3,}))\b' 9 | 10 | contexts: 11 | 12 | main: 13 | - match: '#####' 14 | scope: punctuation.definition.comment.begin.sflk 15 | push: comment_5 16 | - match: '####' 17 | scope: punctuation.definition.comment.begin.sflk 18 | push: comment_4 19 | - match: '###' 20 | scope: punctuation.definition.comment.begin.sflk 21 | push: comment_3 22 | - match: '##' 23 | scope: punctuation.definition.comment.begin.sflk 24 | push: comment_2 25 | - match: '(#\!)(.*)$' 26 | captures: 27 | 0: comment.line.sflk 28 | 1: punctuation.definition.comment.begin.sflk 29 | - match: '#' 30 | scope: punctuation.definition.comment.begin.sflk 31 | push: comment_1 32 | - match: '\(\s*\)' 33 | scope: constant.language.null.sflk 34 | - match: '\(' 35 | scope: punctuation.section.parens.begin.sflk 36 | push: paren 37 | - match: '\)' 38 | scope: invalid.illegal.sflk 39 | - match: '\{' 40 | scope: punctuation.section.braces.begin.sflk 41 | push: curly 42 | - match: '\}' 43 | scope: invalid.illegal.sflk 44 | - match: '"' 45 | scope: punctuation.definition.string.begin.sflk 46 | push: string_litteral 47 | - match: '[0-9]+' 48 | scope: constant.numeric.integer.decimal.sflk 49 | - match: '(<)' 50 | scope: keyword.other.sflk 51 | - match: '\+|\-|\*|/' 52 | scope: keyword.operator.arithmetic.sflk 53 | - match: '(>)\s*({{name}})' 54 | captures: 55 | 1: keyword.operator.sflk 56 | 2: variable.function.sflk 57 | - match: '\b(do|wi|dh|ri)\b\s*({{name}})' 58 | captures: 59 | 1: keyword.other.sflk 60 | 2: variable.function.sflk 61 | - match: '\,\,|\,' 62 | scope: keyword.operator.sflk 63 | - match: '(\.)({{name}})' 64 | captures: 65 | 1: punctuation.definition.string.begin.sflk string.other.sflk 66 | 2: string.other.sflk 67 | - match: '\.' 68 | scope: keyword.operator.sflk 69 | - match: '>' 70 | scope: keyword.operator.sflk 71 | - match: '\b(ix)\b' 72 | scope: punctuation.accessor.sflk 73 | - match: '\b(if|th|el)\b' 74 | scope: keyword.control.conditional.if.sflk 75 | - match: '\b(lp|wh|bd|sp|ao)\b' 76 | scope: keyword.control.loop.sflk 77 | - match: '\b(fi)\b' 78 | scope: keyword.import.sflk 79 | - match: '\b(np|pr|nl|do|dh|ev|ri|em|rs|in|cx|cy|wi|od|os|ln|gs|ag)\b' 80 | scope: keyword.other.sflk 81 | - match: '\b(v)\b' 82 | scope: variable.parameter.sflk 83 | - match: '({{name}})\s*(\!)' 84 | captures: 85 | 1: entity.name.function.sflk 86 | 2: punctuation.definition.variable.end.sflk 87 | - match: '{{name}}' 88 | scope: variable.sflk 89 | 90 | comment_1: 91 | - meta_scope: comment.block.sflk 92 | - match: '##' 93 | - match: '#' 94 | scope: punctuation.definition.comment.end.sflk 95 | pop: true 96 | 97 | comment_2: 98 | - meta_scope: comment.block.sflk 99 | - match: '###' 100 | - match: '##' 101 | scope: punctuation.definition.comment.end.sflk 102 | pop: true 103 | 104 | comment_3: 105 | - meta_scope: comment.block.sflk 106 | - match: '####' 107 | - match: '###' 108 | scope: punctuation.definition.comment.end.sflk 109 | pop: true 110 | 111 | comment_4: 112 | - meta_scope: comment.block.sflk 113 | - match: '#####' 114 | - match: '####' 115 | scope: punctuation.definition.comment.end.sflk 116 | pop: true 117 | 118 | comment_5: 119 | - meta_scope: comment.block.sflk 120 | - match: '######' 121 | - match: '#####' 122 | scope: punctuation.definition.comment.end.sflk 123 | pop: true 124 | 125 | paren: 126 | - match: '\)' 127 | scope: punctuation.section.parens.end.sflk 128 | pop: true 129 | - include: main 130 | 131 | curly: 132 | - match: '\}' 133 | scope: punctuation.section.braces.end.sflk 134 | pop: true 135 | - include: main 136 | 137 | string_litteral: 138 | - meta_scope: string.quoted.double.sflk 139 | - match: '\\$' 140 | scope: constant.character.escape.sflk 141 | - match: '\\x\([0-9a-fA-F]*\)' 142 | scope: constant.character.escape.sflk 143 | - match: '\\d\([0-9]*\)' 144 | scope: constant.character.escape.sflk 145 | - match: '\\(\\|\"|\?|n|t|e|a|b|v|f|r|(x[0-9a-fA-F]{2})|(d[0-9]{2}))' 146 | scope: constant.character.escape.sflk 147 | - match: '"' 148 | scope: punctuation.definition.string.end.sflk 149 | pop: true 150 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | # SFLK development TODO list and ideas 3 | 4 | Here are listed ideas of ameliorations to SFLK. 5 | 6 | ## Big language features 7 | 8 | ### "Lambda captures" 9 | 10 | Here is the problem: 11 | 12 | ```sflk 13 | x! < 8 14 | block! < { 15 | x! < 42 16 | v < { 17 | v < x 18 | } 19 | } 20 | pr () > (() > block) nl 21 | ``` 22 | 23 | Here, `block` returns a block (let it be **B**) that returns the value of `x` (which will depend on the contexts at the moment **B** is executed). But what if we wanted `block` to return a block that returns an "hardcoded" `42` (or whatever the "inner" x is), or even an "hardcoded" `8` (or whatever the "outer" x is at the time `block` is set, regardless of what it is at the moment it is run later)? 24 | 25 | To provide a way to do this, the following syntax could be added to SFLK: 26 | 27 | ```sflk 28 | x! < 8 29 | block! < { 30 | x! < 42 31 | marker! < () 32 | v < { 33 | v < x~marker 34 | } 35 | } 36 | pr () > (() > block) nl 37 | ``` 38 | 39 | The new thing here would be the `~name` syntax. Here is what that would mean and do: 40 | 41 | First, it would terminate an expression the same way `.` does. But it would also mark the exception it terminates in a special way. Mm it is quite annoying to explain. 42 | 43 | In the above example, there are 2 block literals: 44 | ```sflk 45 | { 46 | x! < 42 47 | marker! < () 48 | v < { 49 | v < x~marker 50 | } 51 | } 52 | ``` 53 | and 54 | ```sflk 55 | { 56 | v < x~marker 57 | } 58 | ``` 59 | 60 | Lets call the big one **A** and the small one **B**. **A** contains **B**. 61 | 62 | Usually, a literal is an expression that evaluates to a constant whose value is what the literal represent. Here, `~marker` messes up the concept of literal by making **B** *directly incomplete* (as it contains an expression that is marked by a `~name` thing) (**B** is also *incomplete*). **A** is just *incomplete* as it contains an expression that contains a block literal that is *incomplete*. 63 | 64 | Each time an incomplete block literal expression is evaluated, all the `~name`-marked expressions contained in itself recursively (thus also in the block literals it contains, etc.) are attempted to be replaced by normal expressions. For an expression `E~name`, its evaluation is attempted as follows: 65 | 66 | - We search a context in which `name` is a defined variable, starting in the current context and going towards the root until it is found. 67 | - If such context is not found (i.e. the search reaches the interpreter past the root), then the replacement fails (the expression remains `E~name`). 68 | - If such context is found, then `E` is evaluated in that context, and the result of this evaluation is what replaces `E~name`. 69 | 70 | So, in the example with **A** and **B**, it would go as follows: 71 | 72 | - `x` is declared and is assigned the value `8`, whatever. 73 | - `block` is declared and is assigned the value of the evaluation of the expression **A** that goes as follows: 74 | - **A** contains **B** that contains `x~marker`, we will this attempt to replace `x~marker` by a normal expression. 75 | - We search a context in which `marker` is declared. 76 | - We don't find such context (there is only one context and it only contains `x` and maybe `block` so far). 77 | - The replacement attempt fails. 78 | - Very well, then `pr () > (() > block) nl` is executed. It runs `block` (**A**) to get the block **B** that it returns, then it runs **B** to get the value that it returns, and print that with a newline. 79 | - While it runs **A** (in a sub-context): 80 | - `v` is declared and is assigned the value `()` (that is the doing of executing **A** with `() >`). 81 | - `x` is declared and is assigned the value `42`. 82 | - `marker` is declared and is assigned the value `()`. 83 | - `v` is assigned the result of the evaluation of `{v < x~marker}` (**B**), here goes its evaluation: 84 | - This is an incomplete block literal as it still contains `x~marker`, so we attempt to replace it. 85 | - We search a context in which `marker` is declared. 86 | - Oh, we find one (the current context)! 87 | - We evaluate the expression `x` in this context. 88 | - It evaluates to `42`. 89 | - `x~marker` is thus replaced by an expression that evaluates to the contant `42`. 90 | - There are no more things to replace, the final block is `{v < 42}`. 91 | - As `v` is `{v < 42}`, this is what is returned. 92 | - The returned block, `{v < 42}`, is run, and the value it returned, `42`, is printed, followed by a newline. 93 | 94 | With this, the initial problem is solved. Even with 50 levels of nesting of block literals, these wierd expressions can capture whatever we want in arbitrary outer layers (by placing declarations like `marker! < ()` in the layers of the contexts we want to capture stuff from). Even more: the expressions marked with `~name` things can be more rich than just variable names (although stuff like `(1+2)~marker` kind of misses the point). 95 | 96 | In the example, if `marker! < ()` was out of the big block literal, then the replacing attempt during the evaluation of the big block literal would have been successful and the final printed value would have been `8` instead of `42`. 97 | 98 | #### Note about the evaluation of a non-replaced thing 99 | 100 | ```sflk 101 | do { 102 | ev 8~owo 103 | } 104 | ``` 105 | 106 | The above piece of code produces an error if executed as-is, as the evaluation of the block literal cannot replace its `8~owo`, which is then evaluated without having been replaced by a normal expression (and that is illegal for now). 107 | 108 | #### Note about the implementation 109 | 110 | If could make things simpler if a code block object would separate code from data, for example the literals could be like *Constant 17*, and there would be a table of constants coming with the code. 111 | 112 | An entry in this table could either be a normal SFLK object, or a marked expression (like the SIR code that evaluates the expression, no need to store the AST in there) coupled with the marker name. 113 | 114 | #### Note on a simpler alternative 115 | 116 | Instead of `expression~marker`, we could simply make `variable~` read the variable of the given name in the first context to contain it. 117 | 118 | Maybe it is not a good idea, the marker seems important to help be more explicit about what context is used for the capture. 119 | 120 | #### Note on more explicit handling of this feature 121 | 122 | Instead of attempting replacements implicitely like proposed previously, we could provide an unary operator that attemps the replacements. 123 | 124 | ##### Even better 125 | 126 | ```sflk 127 | a! < { 128 | x! < .jej > v 129 | v < v 130 | + {pr "uwu"} 131 | + { 132 | pr " " 133 | pr x~k 134 | pr " " 135 | } 136 | + {nl} 137 | v ? .k 138 | } 139 | ``` 140 | 141 | Consider how in this example the `?` operator (can be an other syntax) (with `.k` as its right operand) makes it clear when `x~k` is replaced. 142 | 143 | **This looks like it is the better decision.** 144 | 145 | ### Contexts vs general maps 146 | 147 | It could also be nice to have maps from anything to anything, it is highly probable that SFLK will get some in one way or another. To avoid confusion between these types, we could call the type discussed in this entry by the name of *context*, and the for futur general-purpose maps *dictionaries*. 148 | 149 | ### Real numbers 150 | 151 | Have the ability to define numbers with stuff like code that returns the i-th decimal in base b or something, so that they can be used up to any precision. Then make them behave like numbers (stuff like be printed, be multiplied by 2, etc.). Then make a variable like `e` (for error) be used as a parameter for their use that can indicate that their use is only guarenteed up to some precision with the error being less than the content of `e` or something (and of course if `e` is something like `()` then their use must be of infinite precision and printing them hould take an infinite amount of time to terminate for example). 152 | 153 | BETTER IDEA: Define a real number by two infinite sequences of rationals (one always below the other, they must converge toward the same number)! 154 | 155 | ### Even better signals 156 | 157 | A print signal does nothing as it goes through contexts and travels toward the root, but a variable writing signal does stuff at each signal it encounters. The way this is made possible by the interpreter is that the name of some signals (e.g. variable writing) is considered a special case by the interpreter. Not very elegant and general. 158 | 159 | Not sure how to make it better tho.. Making each signal having blocks of code to run in every contexts it passes through may let interceptors a bit clueless as (for now) there is no way to study what a block of code does or do stuff with it; so a signal could name itself "readvar" but its block of code may actually do more stuff than just reading a variable or something. 160 | 161 | TODO: Think about it. 162 | 163 | ## Everything is a function, a list and a context 164 | 165 | ### Everything is a function 166 | 167 | A list is kind of a function of indices to objects. A context is a function of names to objects. A string is a list of characters so kind of a function there. A number is a list of digits (moreon that later) so also a function. 168 | 169 | ### Everything is a list 170 | 171 | A block is a list of statements. 172 | 173 | A number is a list of digits. The base to use can be the number stored in the variable `b`. On that, numbers like *tau* can have an infinite amount of digits, which is not a problem. 174 | 175 | ### Everything is a context 176 | 177 | The idea here is to be able to `cy` anything and get useful information. 178 | 179 | For example, `cy`ing a list can put the first element in `f` (front), the last in `b` (back), the length in `l`, etc. `cy`ing a number can put the numerator in `n` and the denominator in `d`, the integral part in `i`, etc. etc. 180 | 181 | ## Lazy operations when necessary 182 | 183 | Doing something like `{pr "a"} * n` with `n` being too large will fail because the star operation actually constructs a block that is the concatenation of `n` copies of the left operand block. This was easy to program, but it makes this operation limited. 184 | 185 | A solution would be to make `{pr "a"} * n` construct an object that just contains the left opereand block and the value of `n` and it being a variant form of a block. Like, a block can be a sequence of instruction or a repetition (n times) of a sub-block. 186 | 187 | However, this makes everything more complicated. What about `{pr "a"} * n + {pr "b"}`? To handle this case, now a block must also have a variant for a lazy plus (because the left operand given to `+` here is not a sequence of instructions to which the right sequence of instruction can be concatenated to). 188 | 189 | This should also apply to strings, lists, etc. Handle indexing correctly. Handle execution of such blocks correctly. And all the other issues that I have not thought about yet. 190 | 191 | ## Easy loading bar handling 192 | 193 | Make it very easy (somehow) to make a long operation or whatever communicate its progress in the form of a loading bar. 194 | 195 | This may be doable by introducing a new signal type that an operation emits regularly to communicate its progress, and some interceptor is free to use these to redraw a loading bar. Maybe? Would it be easy to use such thing? 196 | 197 | ## Syntax cool stuff 198 | 199 | ### Everything should be doable with only alphabetical characters and whitespace 200 | 201 | Imagine only keywords and variable names. Huge potential for polyglots. 202 | 203 | ### Everything should be doable with only non-alphanumerc characters 204 | 205 | Imagine bs like `&~ $..!: ++, $, ?, ??&--'-().;;<>` etc. and it actually means something. It would be so cool, huge potential for obfuscation. 206 | 207 | ## Small language features 208 | 209 | ### Run before and run after 210 | 211 | ```sflk 212 | pr "a" 213 | bf {pr "b"} 214 | pr "c" 215 | af {pr "d"} 216 | pr "e" 217 | ``` 218 | 219 | This shall print "baced". 220 | 221 | The idea is that before running a block, it is scanned for `bf` (before) statements that are collected and run (in some order that is left to specify), and only then it is executed normally (`bf` and `af` statements are ignored during this phase), and then it is scanned for `af` (after) statements that are collected and run (in a similar way to `bf` statements). 222 | 223 | This could be so cool if done well. It may need more thoughts. 224 | 225 | ### Goto and labels 226 | 227 | Goto and labels. 228 | 229 | ### Create parent context with interceptor 230 | 231 | The idea is to be able to create a new context inbetween the current context and the parent of the current context (thus the created context will be a child of our old parent, and out new parent), and with an interceptor that will intercept signals from the current context (the interceptor will run in the created context). 232 | 233 | ```sflk 234 | A... 235 | newstatement context C interceptor I 236 | B... 237 | ``` 238 | 239 | will be *somewhat* equivalent to 240 | 241 | ```sflk 242 | A... 243 | do { 244 | cy C 245 | do B wi I 246 | } 247 | ``` 248 | 249 | but not really because what `A...` does to its current context should end up in the context in which `B` is executed... 250 | 251 | ### Context list 252 | 253 | The way to mutate a context object is to `cy` it into a context, change the context, and then `cx` it back into an object. Why not make the same thing with lists? Like every context has a list in it, there are a few ways to mutate that list and interact with it, and a few ways to make it into a list and to make a list into the context list or something. 254 | 255 | This needs more thoughts tho... 256 | 257 | ### Bools (yes/no) 258 | 259 | Boolean type. True is `ye` and false is `no`. Instead of stating about the truthiness of some fact, it awnsers a yes/no question. 260 | 261 | ### Is defined expressions 262 | 263 | - Expression `x?!` that is true iff `x` is defined in the current context. 264 | - Other forms of such expression ? 265 | 266 | ### Block statements 267 | 268 | New token `.{` (no whitespace between the `.` and the `{`) that opens a block that is closed with `}` (just like the regular `{`) but instead of producing a block of code literal that is an expression, it produces a block statement that is a statement. When executed, its effect is to execute the sequence of statements it contains. It would allow to replace stuff like `if x th dh { ... }` by `if x th .{ ... }` which is clearly more elegant. 269 | 270 | Note: For some time I thought about using `( ... )` for block statements, but now I don't think this is a good idea. It seems better to stick to the implicit rules that say "parenthesis are for expressions" and "code is in curly-braces". 271 | 272 | ### Chop-like assign 273 | 274 | New token `.>` (no whitespace between the `.` and the `>`) that behaves *similarly* to a chop (chain operator) and does what `<` allows to do. Consider `ev 2 + 6 .> x .> y`, well this should assign 8 to `x` and then to `y`. `.>` is not a chop tho (as what is on the right of it is not an expression but a *target expression*). Note that `ev 2 + 6 .> x! .> y!` should be valid and do what we expect it to do (declare `x` and assign 8 to `x` then declare `y` and assign 8 to `y`). 275 | 276 | ### `rs` in the `em` statement should accept `x!` 277 | 278 | This. All target expressions should be accepted where a target expression is expected. The fact that it is currently not the case is kinda a bug. 279 | 280 | ### Some equivalent of `repr` 281 | 282 | In Python there is a `repr` function (or whatever calls `__repr__`) that is different from `str` (that calls `__str__`). `repr` is supposed to return a string that is a Python expression that evaluates into the object. SFLK should have something like that. 283 | 284 | ### Send error signals instead of panicking 285 | 286 | Do that. Also, use a dedicated signal type instead of just print signal. Use the difference in signal type to print them in red in stderr or something. 287 | 288 | ## Interpreter 289 | 290 | ### Object stack 291 | 292 | Replace the object stack by a stack of something else. The goal here is to make the stack better represent the stack of nested scopes of operations that we are in, and contain relevant operation information in a better way. For example, a loop will push a loop scope or somthing that holds the loop counter and all the loop information that are needed to make it work (no more loop counter pushed as an SFLK number object). 293 | 294 | More generally, when an operation involves having an instruction pushing some information on the stack, and having an other instruction pop this information later (with every instruction inbetween expected to not interact with this information) then making this information into a dedicated type and type checking every pop makes sense (easier debgging when poping too much or not enough, etc.). 295 | 296 | ### Execution time debugging 297 | 298 | With a verbosity similar to the parsing in debug mode, the execution in debug mode should log everything worthy of notice, and everything else, in a colorful and indented way. 299 | 300 | ### Rust tests 301 | 302 | Rust-level tests should cover most of the interpreter logic. 303 | 304 | ### Reduce the number of warnings to 0 305 | 306 | The 50+ warnings at each `cargo run` are pretty annoying. 307 | 308 | ### Code readability 309 | 310 | The code shoud be made more readable and more commented. 311 | 312 | ### Error messages in panics 313 | 314 | No more empty `unimplemented!`s and dangerous `unwrap`s. When the interpreted explodes for some reason, the reason should try its best to be clear to whoever is using the interpreter. 315 | 316 | ## Built-in cool features 317 | 318 | ### Assembly and machine-code generation and execution 319 | 320 | Allow an SFLK progam to produce machine code (with the help of some assembly pieces provided by the standard library or something) and to run the produced machine code by directly jumping in it or something. 321 | 322 | This would be very unsafe, but very fun as it will bring very low level considerations into funcky SFLK code! 323 | 324 | ### Graphics window 325 | 326 | Allow an SFLK program to create windows, recieve events and draw on the surface, etc. 327 | 328 | Beware the dependencies! Maybe make it opt-out with cargo feature and `#[cfg(stuff)]` attributes. Also, avoid the `sdl2` crate (as it requires SDL2 dev lib and is a mess to make it link statically). The dependency MUST link statically and not require any setup from someone who just want to clone and compile SFLK with all the features (thus a standalone crate is required). 329 | 330 | How about [notan](https://crates.io/crates/notan)? It seem to be the first crate in the #windowing cathegory that does not says that installing it requires anything else than adding it via cargo like a normal simple crate. See some [examples](https://nazariglez.github.io/notan-web/) maybe. 331 | 332 | ### Sound 333 | 334 | Allow an SFLK program to play sound. Something that could be really cool would be to allow to give a list of bytes and give it directly to the system mixer. Then the stdlib and the programs can synthesize sound and mix and all. 335 | 336 | Beware the dependencies! Maybe make it opt-out with cargo feature and `#[cfg(stuff)]` attributes. Also, avoid the `sdl2` crate (as it requires SDL2 dev lib and is a mess to make it link statically). The dependency MUST link statically and not require any setup from someone who just want to clone and compile SFLK with all the features (thus a standalone crate is required). 337 | 338 | ## Standard library 339 | 340 | ### PDF creation 341 | 342 | A PDF creation feature should be provided as an interceptor that intercepts print statements and adapt them to alter an output PDF document containing the formated content of the intercepted print signals. 343 | 344 | Not that it should replace LaTeX, but it could be kinda cool to have that in the stdlib, like this is so random yet so facinating. 345 | 346 | ## Logo 347 | 348 | GitHub project embeds seem to work best when given a 1280×640px image to illustrate the repo. One should be drawn with Inkscape or something to fit the size requirement while still being vector graphics. Why not putting the SFLK logo on the left, and writing SFLK on the center and right ? 349 | 350 | ## Architecture 351 | 352 | Take inspiration from cool stuff like [Rome](https://github.com/rome/tools) to refractor the parsing stage into something really solid (and maybe even usable by a language server). 353 | 354 | ## "Marketing" 355 | 356 | Add SFLK to [this list of languages written in Rust](https://github.com/alilleybrinker/langs-in-rust) when it is ready. (When should SFLK be considered ready? Idk.) 357 | -------------------------------------------------------------------------------- /lines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Use the `wc` command to count the lines of code of the 4 | # SFLK interpreter. This serves no purpose but it feels 5 | # good sometimes to remember that I can work on a passion 6 | # project without dropping it 3 weeks later for an other one. 7 | # This is a superficial yet satisfying statistic, and it 8 | # reveals how superficial I am, experiencing joy from 9 | # the subjectively high number of lines of code I wrote. 10 | 11 | # Come on, print it. Again. Again! Harder! Come on! 12 | # How many lines again? Huh? That many!? Oahh! So much code! 13 | 14 | cat sflk-lang/src/*.rs | wc -l 15 | -------------------------------------------------------------------------------- /logo/sflk-logo-black-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/logo/sflk-logo-black-square.png -------------------------------------------------------------------------------- /logo/sflk-logo-black-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 36 | 49 | 60 | 73 | 83 | 94 | 107 | 112 | 118 | 119 | 150 | 155 | 156 | 158 | 159 | 161 | image/svg+xml 162 | 164 | 165 | 166 | 167 | 168 | 174 | 176 | 180 | 185 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /logo/sflk-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/logo/sflk-logo-black.png -------------------------------------------------------------------------------- /logo/sflk-logo-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 36 | 49 | 60 | 73 | 83 | 94 | 107 | 112 | 118 | 119 | 149 | 154 | 155 | 157 | 158 | 160 | image/svg+xml 161 | 163 | 164 | 165 | 166 | 167 | 173 | 175 | 179 | 184 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /logo/sflk-logo-color-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/logo/sflk-logo-color-square.png -------------------------------------------------------------------------------- /logo/sflk-logo-color-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 36 | 49 | 60 | 73 | 83 | 94 | 107 | 112 | 118 | 119 | 149 | 154 | 155 | 157 | 158 | 160 | image/svg+xml 161 | 163 | 164 | 165 | 166 | 167 | 173 | 175 | 178 | 183 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /logo/sflk-logo-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/logo/sflk-logo-color.png -------------------------------------------------------------------------------- /logo/sflk-logo-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 36 | 49 | 60 | 73 | 83 | 94 | 107 | 112 | 118 | 126 | 130 | 131 | 132 | 162 | 167 | 168 | 170 | 171 | 173 | image/svg+xml 174 | 176 | 177 | 178 | 179 | 180 | 186 | 188 | 191 | 196 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /logo/sflk-logo-white-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/logo/sflk-logo-white-square.png -------------------------------------------------------------------------------- /logo/sflk-logo-white-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 36 | 49 | 60 | 73 | 83 | 94 | 107 | 112 | 118 | 119 | 149 | 154 | 155 | 157 | 158 | 160 | image/svg+xml 161 | 163 | 164 | 165 | 166 | 167 | 173 | 175 | 181 | 186 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /logo/sflk-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/logo/sflk-logo-white.png -------------------------------------------------------------------------------- /logo/sflk-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 36 | 49 | 60 | 73 | 83 | 94 | 107 | 112 | 118 | 119 | 149 | 154 | 155 | 157 | 158 | 160 | image/svg+xml 161 | 163 | 164 | 165 | 166 | 167 | 173 | 175 | 179 | 184 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # See https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | # for actual up-to-date config options. 3 | 4 | edition = "2018" 5 | 6 | binop_separator = "Back" 7 | 8 | match_arm_blocks = false 9 | match_block_trailing_comma = true 10 | 11 | hard_tabs = true 12 | tab_spaces = 4 13 | 14 | blank_lines_upper_bound = 1 15 | 16 | use_field_init_shorthand = true 17 | 18 | # TODO: fix this (it doesn't work) 19 | # Try to mess with width_heuristics 20 | # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#width_heuristics 21 | struct_lit_width = 50 22 | struct_lit_single_line = true 23 | struct_variant_width = 50 24 | 25 | wrap_comments = true 26 | 27 | # Not supported anymore, 28 | # and "Block" seems better since it doesn't align with spaces 29 | # 30 | # struct_lit_style = "Visual" 31 | # struct_lit_multiline_style = "PreferSingle" 32 | -------------------------------------------------------------------------------- /sflk-lang/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sflk" 3 | version = "0.3.0" 4 | authors = ["anima-libera "] 5 | edition = "2021" 6 | readme = "README.md" 7 | repository = "https://github.com/sflk-lang/sflk" 8 | description = "SFLK programming language reference interpreter" 9 | default-run = "sflk" 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /sflk-lang/repl.sflk: -------------------------------------------------------------------------------- 1 | 2 | pr "\e[35m 3 | *** SFLK REPL *** 4 | 5 | Each input line should be one or more statements. 6 | Try to type `pr 8 + 34` to see if it works. 7 | To exit, enter `do quit` or an empty line.\e[39m 8 | 9 | " 10 | 11 | run! < 1 12 | quit! < {run < 0} 13 | lp wh run bd dh { 14 | pr "\e[33m$\e[39m " 15 | line! < in 16 | if line - "\n" 17 | el do quit 18 | th dh line th nl 19 | } 20 | -------------------------------------------------------------------------------- /sflk-lang/src/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | log::IndentedLog, 3 | parser::ParsingWarning, 4 | scu::Loc, 5 | stringtree::StringTree, 6 | utils::{escape_string, styles}, 7 | }; 8 | 9 | pub(crate) struct Node { 10 | content: T, 11 | loc: Loc, 12 | comments: Comments, 13 | warnings: Vec, 14 | } 15 | 16 | impl Node { 17 | pub(crate) fn from(content: T, loc: Loc) -> Node { 18 | Node { 19 | content, 20 | loc, 21 | comments: Comments::new(), 22 | warnings: Vec::new(), 23 | } 24 | } 25 | 26 | pub(crate) fn unwrap(self) -> T { 27 | self.content 28 | } 29 | 30 | pub(crate) fn unwrap_ref(&self) -> &T { 31 | &self.content 32 | } 33 | } 34 | 35 | #[derive(Debug)] 36 | pub(crate) struct Comment { 37 | _content: String, 38 | _delimitation_thickness: usize, 39 | } 40 | 41 | #[derive(Debug)] 42 | struct Comments { 43 | _left_comments: Vec, 44 | _internal_comments: Vec, 45 | } 46 | 47 | impl Comments { 48 | fn new() -> Comments { 49 | Comments { 50 | _left_comments: Vec::new(), 51 | _internal_comments: Vec::new(), 52 | } 53 | } 54 | } 55 | 56 | impl Node { 57 | pub(crate) fn loc(&self) -> &Loc { 58 | &self.loc 59 | } 60 | } 61 | 62 | impl Node { 63 | pub(crate) fn map(self, func: impl FnOnce(T) -> U) -> Node { 64 | Node { 65 | content: func(self.content), 66 | loc: self.loc, 67 | comments: self.comments, 68 | warnings: self.warnings, 69 | } 70 | } 71 | } 72 | 73 | pub(crate) struct Program { 74 | pub(crate) stmts: Vec>, 75 | } 76 | 77 | pub(crate) enum Stmt { 78 | Nop, 79 | Print { 80 | expr: Node, 81 | }, 82 | Newline, 83 | Assign { 84 | target: Node, 85 | expr: Node, 86 | }, 87 | Evaluate { 88 | expr: Node, 89 | }, 90 | Do { 91 | expr: Node, 92 | wi_expr: Option>, 93 | }, 94 | DoHere { 95 | expr: Node, 96 | }, 97 | If { 98 | cond_expr: Node, 99 | th_stmts: Vec>, 100 | el_stmts: Vec>, 101 | }, 102 | Loop { 103 | wh_exprs: Vec>, 104 | bd_stmts: Vec>, 105 | sp_stmts: Vec>, 106 | ao_flag: Option>, 107 | }, 108 | RegisterInterceptor { 109 | expr: Node, 110 | }, 111 | Emit { 112 | expr: Node, 113 | target: Option>, 114 | }, 115 | DeployContext { 116 | expr: Node, 117 | }, 118 | GenericSyntax { 119 | expr: Node, 120 | ar_exprs: Vec>, 121 | target: Option>, 122 | }, 123 | Invalid { 124 | error_expr: Node, 125 | }, 126 | } 127 | 128 | #[derive(Debug)] 129 | pub(crate) enum TargetExpr { 130 | VariableName(String), 131 | DeclVariableName(String), 132 | Invalid, // TODO: Add error details 133 | } 134 | 135 | pub(crate) enum Expr { 136 | VariableName(String), 137 | NothingLiteral, 138 | IntegerLiteral(String), 139 | StringLiteral(String), 140 | BlockLiteral(Vec>), 141 | Input, 142 | Context, 143 | Unop(Unop), 144 | Chain { init: Box>, chops: Vec> }, 145 | Invalid { error_expr: Box> }, 146 | } 147 | 148 | pub(crate) enum Unop { 149 | Negate(Box>), 150 | ReadFile(Box>), 151 | Ordered(Box>), 152 | OrderedStrictly(Box>), 153 | Length(Box>), 154 | } 155 | 156 | pub(crate) enum Chop { 157 | Plus(Node), 158 | Minus(Node), 159 | Star(Node), 160 | Slash(Node), 161 | Comma(Node), 162 | DoubleComma(Node), 163 | Index(Node), 164 | ToRight(Node), 165 | } 166 | 167 | pub(crate) trait Treeable { 168 | fn tree(&self, loc: &Loc) -> StringTree; 169 | } 170 | 171 | impl From<&Node> for StringTree 172 | where 173 | T: Treeable, 174 | { 175 | fn from(node: &Node) -> StringTree { 176 | node.content.tree(node.loc()) 177 | } 178 | } 179 | 180 | impl Treeable for Chop { 181 | fn tree(&self, _loc: &Loc) -> StringTree { 182 | match self { 183 | Chop::Plus(expr_node) => StringTree::new_node( 184 | "chop plus".to_string(), 185 | styles::NORMAL, 186 | vec![StringTree::from(expr_node)], 187 | ), 188 | Chop::Minus(expr_node) => StringTree::new_node( 189 | "chop minus".to_string(), 190 | styles::NORMAL, 191 | vec![StringTree::from(expr_node)], 192 | ), 193 | Chop::Star(expr_node) => StringTree::new_node( 194 | "chop star".to_string(), 195 | styles::NORMAL, 196 | vec![StringTree::from(expr_node)], 197 | ), 198 | Chop::Slash(expr_node) => StringTree::new_node( 199 | "chop slash".to_string(), 200 | styles::NORMAL, 201 | vec![StringTree::from(expr_node)], 202 | ), 203 | Chop::ToRight(expr_node) => StringTree::new_node( 204 | "chop to_right".to_string(), 205 | styles::NORMAL, 206 | vec![StringTree::from(expr_node)], 207 | ), 208 | Chop::Comma(expr_node) => StringTree::new_node( 209 | "chop comma".to_string(), 210 | styles::NORMAL, 211 | vec![StringTree::from(expr_node)], 212 | ), 213 | Chop::DoubleComma(expr_node) => StringTree::new_node( 214 | "chop double comma".to_string(), 215 | styles::NORMAL, 216 | vec![StringTree::from(expr_node)], 217 | ), 218 | Chop::Index(expr_node) => StringTree::new_node( 219 | "chop dot".to_string(), 220 | styles::NORMAL, 221 | vec![StringTree::from(expr_node)], 222 | ), 223 | } 224 | } 225 | } 226 | 227 | impl Treeable for Expr { 228 | fn tree(&self, _loc: &Loc) -> StringTree { 229 | match self { 230 | Expr::VariableName(name) => { 231 | StringTree::new_leaf(format!("variable {}", name), styles::NORMAL) 232 | }, 233 | Expr::NothingLiteral => StringTree::new_leaf("nothing".to_string(), styles::NORMAL), 234 | Expr::IntegerLiteral(integer) => { 235 | StringTree::new_leaf(format!("integer {}", integer), styles::NORMAL) 236 | }, 237 | Expr::StringLiteral(string) => StringTree::new_leaf( 238 | format!("string \"{}\"", escape_string(string, &styles::UNDERLINE)), 239 | styles::NORMAL, 240 | ), 241 | Expr::BlockLiteral(stmts) => StringTree::new_node( 242 | "block".to_string(), 243 | styles::CYAN, 244 | stmts.iter().map(StringTree::from).collect(), 245 | ), 246 | Expr::Input => StringTree::new_leaf("input".to_string(), styles::NORMAL), 247 | Expr::Context => StringTree::new_leaf("context".to_string(), styles::NORMAL), 248 | Expr::Unop(Unop::Negate(expr)) => StringTree::new_node( 249 | "unary minus".to_string(), 250 | styles::NORMAL, 251 | vec![StringTree::from(&**expr)], 252 | ), 253 | Expr::Unop(Unop::ReadFile(expr)) => StringTree::new_node( 254 | "unary read file".to_string(), 255 | styles::NORMAL, 256 | vec![StringTree::from(&**expr)], 257 | ), 258 | Expr::Unop(Unop::Ordered(expr)) => StringTree::new_node( 259 | "unary ordered".to_string(), 260 | styles::NORMAL, 261 | vec![StringTree::from(&**expr)], 262 | ), 263 | Expr::Unop(Unop::OrderedStrictly(expr)) => StringTree::new_node( 264 | "unary ordered strictly".to_string(), 265 | styles::NORMAL, 266 | vec![StringTree::from(&**expr)], 267 | ), 268 | Expr::Unop(Unop::Length(expr)) => StringTree::new_node( 269 | "unary length".to_string(), 270 | styles::NORMAL, 271 | vec![StringTree::from(&**expr)], 272 | ), 273 | Expr::Chain { init, chops } => StringTree::new_node( 274 | "chain".to_string(), 275 | styles::BLUE, 276 | std::iter::once(StringTree::from(&**init)) 277 | .chain(chops.iter().map(StringTree::from)) 278 | .collect(), 279 | ), 280 | Expr::Invalid { error_expr } => StringTree::new_node( 281 | "invalid".to_string(), 282 | styles::BOLD_LIGHT_RED, 283 | vec![StringTree::from(&**error_expr)], 284 | ), 285 | } 286 | } 287 | } 288 | 289 | impl Treeable for TargetExpr { 290 | fn tree(&self, _loc: &Loc) -> StringTree { 291 | match self { 292 | TargetExpr::VariableName(name) => { 293 | StringTree::new_leaf(format!("target variable {}", name), styles::NORMAL) 294 | }, 295 | TargetExpr::DeclVariableName(name) => { 296 | StringTree::new_leaf(format!("target declare variable {}", name), styles::NORMAL) 297 | }, 298 | TargetExpr::Invalid => { 299 | StringTree::new_leaf("invalid".to_string(), styles::BOLD_LIGHT_RED) 300 | }, // TODO 301 | } 302 | } 303 | } 304 | 305 | impl Treeable for Stmt { 306 | fn tree(&self, _loc: &Loc) -> StringTree { 307 | match self { 308 | Stmt::Nop => StringTree::new_leaf("nop".to_string(), styles::NORMAL), 309 | Stmt::Print { expr } => StringTree::new_node( 310 | "print".to_string(), 311 | styles::NORMAL, 312 | vec![StringTree::from(expr)], 313 | ), 314 | Stmt::Newline => StringTree::new_leaf("newline".to_string(), styles::NORMAL), 315 | Stmt::Assign { target, expr } => StringTree::new_node( 316 | "assign".to_string(), 317 | styles::NORMAL, 318 | vec![StringTree::from(target), StringTree::from(expr)], 319 | ), 320 | Stmt::Evaluate { expr } => StringTree::new_node( 321 | "evaluate".to_string(), 322 | styles::NORMAL, 323 | vec![StringTree::from(expr)], 324 | ), 325 | Stmt::Do { expr, wi_expr } => StringTree::new_node( 326 | "do".to_string(), 327 | styles::NORMAL, 328 | vec![ 329 | StringTree::from(expr), 330 | if let Some(wi_expr) = wi_expr { 331 | StringTree::from(wi_expr) 332 | } else { 333 | StringTree::new_leaf("no interceptor".to_string(), styles::NORMAL) 334 | }, 335 | ], 336 | ), 337 | Stmt::DoHere { expr } => StringTree::new_node( 338 | "do here".to_string(), 339 | styles::NORMAL, 340 | vec![StringTree::from(expr)], 341 | ), 342 | Stmt::If { cond_expr, th_stmts, el_stmts } => StringTree::new_node( 343 | "if".to_string(), 344 | styles::NORMAL, 345 | vec![ 346 | StringTree::from(cond_expr), 347 | if !th_stmts.is_empty() { 348 | StringTree::new_node( 349 | "then branch".to_string(), 350 | styles::NORMAL, 351 | th_stmts.iter().map(StringTree::from).collect(), 352 | ) 353 | } else { 354 | StringTree::new_leaf("no then branch".to_string(), styles::NORMAL) 355 | }, 356 | if !el_stmts.is_empty() { 357 | StringTree::new_node( 358 | "else branch".to_string(), 359 | styles::NORMAL, 360 | el_stmts.iter().map(StringTree::from).collect(), 361 | ) 362 | } else { 363 | StringTree::new_leaf("no else branch".to_string(), styles::NORMAL) 364 | }, 365 | ], 366 | ), 367 | Stmt::Loop { wh_exprs, bd_stmts, sp_stmts, ao_flag } => StringTree::new_node( 368 | "loop".to_string(), 369 | styles::NORMAL, 370 | [ 371 | if !wh_exprs.is_empty() { 372 | StringTree::new_node( 373 | "while condition".to_string(), 374 | styles::NORMAL, 375 | wh_exprs.iter().map(StringTree::from).collect(), 376 | ) 377 | } else { 378 | StringTree::new_leaf("no while condition".to_string(), styles::NORMAL) 379 | }, 380 | if !bd_stmts.is_empty() { 381 | StringTree::new_node( 382 | "body".to_string(), 383 | styles::NORMAL, 384 | bd_stmts.iter().map(StringTree::from).collect(), 385 | ) 386 | } else { 387 | StringTree::new_leaf("no body".to_string(), styles::NORMAL) 388 | }, 389 | if !sp_stmts.is_empty() { 390 | StringTree::new_node( 391 | "separator".to_string(), 392 | styles::NORMAL, 393 | sp_stmts.iter().map(StringTree::from).collect(), 394 | ) 395 | } else { 396 | StringTree::new_leaf("no separator".to_string(), styles::NORMAL) 397 | }, 398 | ] 399 | .into_iter() 400 | .chain(if ao_flag.is_some() { 401 | Some(StringTree::new_leaf( 402 | "at least once".to_string(), 403 | styles::NORMAL, 404 | )) 405 | } else { 406 | None 407 | }) 408 | .collect(), 409 | ), 410 | Stmt::RegisterInterceptor { expr } => StringTree::new_node( 411 | "register interceptor".to_string(), 412 | styles::NORMAL, 413 | vec![StringTree::from(expr)], 414 | ), 415 | Stmt::Emit { expr, target } => StringTree::new_node( 416 | "emit".to_string(), 417 | styles::NORMAL, 418 | vec![ 419 | StringTree::from(expr), 420 | if let Some(target_expr) = target { 421 | StringTree::from(target_expr) 422 | } else { 423 | StringTree::new_leaf("no target".to_string(), styles::NORMAL) 424 | }, 425 | ], 426 | ), 427 | Stmt::DeployContext { expr } => StringTree::new_node( 428 | "deploy context".to_string(), 429 | styles::NORMAL, 430 | vec![StringTree::from(expr)], 431 | ), 432 | Stmt::GenericSyntax { expr, ar_exprs, target } => StringTree::new_node( 433 | "generic syntax".to_string(), 434 | styles::NORMAL, 435 | vec![ 436 | StringTree::from(expr), 437 | if !ar_exprs.is_empty() { 438 | StringTree::new_node( 439 | "arguments".to_string(), 440 | styles::NORMAL, 441 | ar_exprs.iter().map(StringTree::from).collect(), 442 | ) 443 | } else { 444 | StringTree::new_leaf("no arguments".to_string(), styles::NORMAL) 445 | }, 446 | if let Some(target_expr) = target { 447 | StringTree::from(target_expr) 448 | } else { 449 | StringTree::new_leaf("no target".to_string(), styles::NORMAL) 450 | }, 451 | ], 452 | ), 453 | Stmt::Invalid { error_expr } => StringTree::new_node( 454 | "invalid".to_string(), 455 | styles::BOLD_LIGHT_RED, 456 | vec![StringTree::from(error_expr)], 457 | ), 458 | } 459 | } 460 | } 461 | 462 | impl Treeable for Program { 463 | fn tree(&self, _loc: &Loc) -> StringTree { 464 | StringTree::new_node( 465 | "program".to_string(), 466 | styles::CYAN, 467 | self.stmts.iter().map(StringTree::from).collect(), 468 | ) 469 | } 470 | } 471 | 472 | impl Node { 473 | pub(crate) fn print(&self) { 474 | // TODO: Clean this old wird stuff. 475 | 476 | pub(crate) struct DebugMem { 477 | pub(crate) log: IndentedLog, 478 | } 479 | 480 | impl DebugMem { 481 | pub(crate) fn new() -> DebugMem { 482 | DebugMem { log: IndentedLog::new() } 483 | } 484 | } 485 | 486 | let mut debug_mem = DebugMem::new(); 487 | debug_mem 488 | .log 489 | .log_line("Program tree".to_string(), crate::utils::styles::NEGATIVE); 490 | crate::stringtree::StringTree::from(self).print(&mut debug_mem.log); 491 | debug_mem.log.print_to_stdout(); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /sflk-lang/src/log.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{styles, StdoutWriter, Style}; 2 | 3 | pub(crate) struct IndentedLog { 4 | items: Vec, 5 | } 6 | 7 | impl IndentedLog { 8 | pub(crate) fn new() -> IndentedLog { 9 | IndentedLog { items: Vec::new() } 10 | } 11 | 12 | fn push(&mut self, item: Item) { 13 | self.items.push(item); 14 | } 15 | 16 | #[allow(unused)] 17 | pub(crate) fn indent(&mut self, string: String, is_context: bool, style: Style) { 18 | assert!(!string.contains('\n')); 19 | self.push(Item::IndentAdd { string, indent: Indent { is_context, style } }); 20 | } 21 | 22 | #[allow(unused)] 23 | pub(crate) fn deindent(&mut self) { 24 | self.push(Item::IndentRemove); 25 | } 26 | 27 | pub(crate) fn log_line(&mut self, string: String, style: Style) { 28 | assert!(!string.contains('\n')); 29 | self.push(Item::String { string, is_line: true, style }); 30 | } 31 | 32 | pub(crate) fn log_string(&mut self, string: String, style: Style) { 33 | self.push(Item::String { string, is_line: false, style }); 34 | } 35 | } 36 | 37 | impl std::fmt::Write for IndentedLog { 38 | fn write_str(&mut self, string: &str) -> Result<(), std::fmt::Error> { 39 | self.log_string(string.to_string(), styles::NORMAL); 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl IndentedLog { 45 | pub(crate) fn print_to_stdout(&self) { 46 | self.print(&mut StdoutWriter::new()); 47 | } 48 | } 49 | 50 | #[derive(Debug)] 51 | enum Item { 52 | IndentAdd { string: String, indent: Indent }, 53 | IndentRemove, 54 | String { string: String, is_line: bool, style: Style }, 55 | } 56 | 57 | #[derive(Debug, Clone)] 58 | struct Indent { 59 | is_context: bool, 60 | style: Style, 61 | } 62 | 63 | const INDENT_START: &str = "┌"; 64 | const INDENT_NORMAL: &str = "│"; 65 | const INDENT_WEAK: &str = "╎"; 66 | 67 | impl IndentedLog { 68 | pub(crate) fn print(&self, writer: &mut impl std::fmt::Write) { 69 | let mut indents: Vec = Vec::new(); 70 | let mut is_newline: bool = true; 71 | for item in &self.items { 72 | match item { 73 | Item::IndentAdd { string, indent } => { 74 | print_indents(writer, &indents, Some(indent)); 75 | writeln!(writer, "{}{}{}", indent.style.0, string, indent.style.1) 76 | .expect("TODO"); 77 | is_newline = true; 78 | indents.push(indent.clone()); 79 | }, 80 | Item::IndentRemove => { 81 | indents.pop().expect("bug"); 82 | }, 83 | Item::String { string, is_line: true, style } => { 84 | if is_newline { 85 | print_indents(writer, &indents, None); 86 | } 87 | writeln!(writer, "{}{}{}", style.0, string, style.1).expect("TODO"); 88 | is_newline = true; 89 | }, 90 | Item::String { string, is_line: false, style } => { 91 | let formatted_string = string.to_string(); 92 | let fragments: Vec<&str> = formatted_string.split('\n').collect(); 93 | if let Some((end, lines)) = fragments.split_last() { 94 | for line in lines { 95 | if is_newline { 96 | print_indents(writer, &indents, None); 97 | } 98 | writeln!(writer, "{}{}{}", style.0, line, style.1).expect("TODO"); 99 | is_newline = true; 100 | } 101 | if !end.is_empty() { 102 | if is_newline { 103 | print_indents(writer, &indents, None); 104 | } 105 | write!(writer, "{}{}{}", style.0, end, style.1).expect("TODO"); 106 | is_newline = false; 107 | } 108 | } 109 | }, 110 | } 111 | } 112 | } 113 | } 114 | 115 | fn print_indents( 116 | writer: &mut impl std::fmt::Write, 117 | indents: &[Indent], 118 | add_start: Option<&Indent>, 119 | ) { 120 | let last_cx_index = match add_start { 121 | Some(indent) if indent.is_context => indents.len(), 122 | _ => indents 123 | .iter() 124 | .rposition(|indent| indent.is_context) 125 | .unwrap_or(0), 126 | }; 127 | for indent in indents[..last_cx_index].iter() { 128 | write!( 129 | writer, 130 | "{}{}{}", 131 | indent.style.0, 132 | if indent.is_context { 133 | INDENT_NORMAL 134 | } else { 135 | INDENT_WEAK 136 | }, 137 | indent.style.1 138 | ) 139 | .expect("TODO"); 140 | } 141 | for indent in indents[last_cx_index..].iter() { 142 | write!( 143 | writer, 144 | "{}{}{}", 145 | indent.style.0, INDENT_NORMAL, indent.style.1 146 | ) 147 | .expect("TODO"); 148 | } 149 | if let Some(indent) = add_start { 150 | write!( 151 | writer, 152 | "{}{}{}", 153 | indent.style.0, INDENT_START, indent.style.1 154 | ) 155 | .expect("TODO"); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /sflk-lang/src/log_indent.rs: -------------------------------------------------------------------------------- 1 | //! Logs text in a way that supports nested messages with indentation. 2 | //! 3 | //! Example: 4 | //! ```text 5 | //! ┌─Text (indent) 6 | //! │ Text (normal) 7 | //! │ ┌─Text (indent) 8 | //! │ │ Text (normal) 9 | //! │ └─Text (deindent) 10 | //! └─Text (deindent) 11 | //! ``` 12 | 13 | use crate::utils::{styles, StdoutWriter, Style}; 14 | 15 | pub(crate) struct IndentedLogger { 16 | indents: Vec, 17 | writer: Box, 18 | } 19 | 20 | impl IndentedLogger { 21 | pub(crate) fn new() -> IndentedLogger { 22 | IndentedLogger { 23 | indents: Vec::new(), 24 | writer: Box::new(StdoutWriter::new()), 25 | } 26 | } 27 | 28 | pub(crate) fn indent(&mut self, string: &str, is_important: bool, style: Style) { 29 | // TODO: Make this more readable or something. 30 | let new_indent = Indent { is_important, style }; 31 | let mut lines = string.lines(); 32 | let first_line = lines.next().unwrap_or(""); 33 | self.print_indents(Some((new_indent.clone(), StartOrEnd::Start))); 34 | writeln!(self.writer, "{}{}{}", style.0, first_line, style.1).unwrap(); 35 | self.indents.push(new_indent); 36 | for line in lines { 37 | self.log_line(line, style); 38 | } 39 | } 40 | 41 | pub(crate) fn deindent(&mut self, string: &str) { 42 | // TODO: Make this more readable or something. 43 | assert!(!self.indents.is_empty()); 44 | let style = self.indents.last().unwrap().style; 45 | let lines: Vec<_> = string.lines().collect(); 46 | let (last_line, lines) = lines.split_last().unwrap_or((&"", &[])); 47 | for line in lines { 48 | self.log_line(line, style); 49 | } 50 | let ended_indent = self.indents.pop().unwrap(); 51 | self.print_indents(Some((ended_indent, StartOrEnd::End))); 52 | writeln!(self.writer, "{}{}{}", style.0, last_line, style.1).unwrap(); 53 | } 54 | 55 | pub(crate) fn log_line(&mut self, line: &str, style: Style) { 56 | assert!(!line.contains('\n')); 57 | self.print_indents(None); 58 | writeln!(self.writer, "{}{}{}", style.0, line, style.1).unwrap(); 59 | } 60 | 61 | pub(crate) fn log_string(&mut self, string: &str, style: Style) { 62 | for line in string.lines() { 63 | self.log_line(line, style); 64 | } 65 | } 66 | } 67 | 68 | impl std::fmt::Write for IndentedLogger { 69 | fn write_str(&mut self, string: &str) -> Result<(), std::fmt::Error> { 70 | self.log_string(string, styles::NORMAL); 71 | Ok(()) 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | struct Indent { 77 | is_important: bool, 78 | style: Style, 79 | } 80 | 81 | const INDENT_START: &str = "┌"; 82 | const INDENT_END: &str = "└"; 83 | const INDENT_NORMAL: &str = "│"; 84 | const INDENT_WEAK: &str = "╎"; 85 | const INDENT_TAIL: &str = "─"; 86 | 87 | enum StartOrEnd { 88 | Start, 89 | End, 90 | } 91 | 92 | impl StartOrEnd { 93 | fn indent_text(&self) -> &str { 94 | match self { 95 | StartOrEnd::Start => INDENT_START, 96 | StartOrEnd::End => INDENT_END, 97 | } 98 | } 99 | } 100 | 101 | impl IndentedLogger { 102 | fn print_indents(&mut self, added_indent: Option<(Indent, StartOrEnd)>) { 103 | let last_important_index = match &added_indent { 104 | Some((indent, _)) if indent.is_important => self.indents.len(), 105 | _ => self 106 | .indents 107 | .iter() 108 | .rposition(|indent| indent.is_important) 109 | .unwrap_or(0), 110 | }; 111 | for indent in self.indents[..last_important_index].iter() { 112 | write!( 113 | self.writer, 114 | "{}{} {}", 115 | indent.style.0, 116 | if indent.is_important { 117 | INDENT_NORMAL 118 | } else { 119 | INDENT_WEAK 120 | }, 121 | indent.style.1 122 | ) 123 | .expect("write failure"); 124 | } 125 | for indent in self.indents[last_important_index..].iter() { 126 | write!( 127 | self.writer, 128 | "{}{} {}", 129 | indent.style.0, INDENT_NORMAL, indent.style.1 130 | ) 131 | .expect("write failure"); 132 | } 133 | if let Some((indent, start_or_end)) = added_indent { 134 | write!( 135 | self.writer, 136 | "{}{}{}{}", 137 | indent.style.0, 138 | start_or_end.indent_text(), 139 | INDENT_TAIL, 140 | indent.style.1 141 | ) 142 | .expect("write failure"); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /sflk-lang/src/main.rs: -------------------------------------------------------------------------------- 1 | mod ast; 2 | mod bignums; 3 | mod log; 4 | mod log_indent; 5 | mod object; 6 | mod parser; 7 | mod scu; 8 | mod settings; 9 | mod sir; 10 | mod stringtree; 11 | mod tokenizer; 12 | mod utils; 13 | 14 | use parser::{Parser, ParserDebuggingLogger}; 15 | use scu::SourceCodeUnit; 16 | use settings::Settings; 17 | use settings::Source; 18 | use tokenizer::{CharReadingHead, TokBuffer}; 19 | 20 | use std::rc::Rc; 21 | 22 | fn main() { 23 | // Parse the command line arguments. 24 | let settings = Settings::from_args(); 25 | settings.print_info(); 26 | if settings.src.is_none() { 27 | return; 28 | } 29 | 30 | // Get the source code in memory. 31 | let scu = Rc::new(match settings.src { 32 | Some(Source::FilePath(ref file_path)) => SourceCodeUnit::from_filename(file_path), 33 | Some(Source::Code(ref code)) => { 34 | SourceCodeUnit::from_str(code.to_string(), "input".to_string()) 35 | }, 36 | None => panic!(), 37 | }); 38 | 39 | // Get the tokenizer ready. 40 | let tfr = TokBuffer::from(CharReadingHead::from_scu(scu)); 41 | 42 | if settings.display_tokens { 43 | // Don't execute any code, only display tokens. 44 | tfr.display_all(settings.debug_lines); 45 | return; 46 | } 47 | 48 | // Get the parser ready. 49 | let parser_logger = settings.parser_debugging_logger(); 50 | let mut parser = Parser::new(tfr, parser_logger); 51 | 52 | // Parse the source code into an AST. 53 | let ast = parser.parse_program(); 54 | if settings.debug { 55 | ast.print(); 56 | } 57 | 58 | // Transform the AST into SIR code. 59 | let sir_block = sir::program_to_sir_block(ast.unwrap_ref()); 60 | if settings.debug_sir { 61 | dbg!(&sir_block); 62 | } 63 | 64 | // Actually run the code. 65 | sir::exec_sir_block(sir_block); 66 | } 67 | -------------------------------------------------------------------------------- /sflk-lang/src/object.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bignums::{ 3 | big_frac::{BigFrac, NotAnInteger}, 4 | big_sint::BigSint, 5 | DoesNotFitInPrimitive, 6 | }, 7 | sir::Block, 8 | }; 9 | use std::{cmp::Ordering, collections::HashMap}; 10 | 11 | /// An `Object` is a value that can manipulated by SFLK code. 12 | /// Every expression evaluates to an `Object`. 13 | #[derive(Debug, Clone)] 14 | pub(crate) enum Object { 15 | /// The `()` literal is an expression that evaluates to that. 16 | Nothing, 17 | Number(BigFrac), 18 | String(String), 19 | /// A block of code literal is an expression that evaluates to that, 20 | /// without being executed (it may be executed later, but 21 | /// not by the evaluation of the literal itself). 22 | /// 23 | /// Here is an example of a block of code literal: 24 | /// `{pr "SFLK better than Python" nl v < 69}` 25 | Block(Block), 26 | List(Vec), 27 | /// A context object is a dictionary of variable names and their values. 28 | /// A context object can be obtained from a context (from the context tree 29 | /// that holds data about actual variables) via the `cx` keyword, and 30 | /// can be injected in a context (from the context tree) via the `cy` keyword. 31 | Context(HashMap), 32 | } 33 | 34 | /// Error that occured during an operation on `Object`s. 35 | /// 36 | /// The `function_name` field common across variants is supposed to be filled with the 37 | /// name of the `Object` method that raised the error (using `function_name!()` makes 38 | /// code safe for copy-pasting error construction without having to think about 39 | /// changing the function name). 40 | #[derive(Debug)] 41 | // The fields ARE used when their values are printed by `unwrap`, but for some reason 42 | // (see [https://github.com/rust-lang/rust/issues/88900]) the compiler says they are not. 43 | #[allow(unused)] 44 | pub(crate) enum ObjectOperationError { 45 | /// An unary operation did not support the given type. 46 | UnsupportedType { 47 | function_name: &'static str, 48 | type_name: &'static str, 49 | }, 50 | /// A binary operation did not support the given pair of types. 51 | UnsupportedPairOfTypes { 52 | function_name: &'static str, 53 | left_type_name: &'static str, 54 | right_type_name: &'static str, 55 | }, 56 | /// An unary operation on a list did not support to find a 57 | /// certain type in the list. 58 | UnsupportedTypeInList { 59 | function_name: &'static str, 60 | type_name: &'static str, 61 | }, 62 | /// An operation did not support a number not because of its type 63 | /// but because of its value that did not verify some expected property. 64 | UnsupportedNumber { 65 | function_name: &'static str, 66 | number_value: BigFrac, 67 | error: UnsupportedNumberError, 68 | }, 69 | NumberIndexOutOfRange { 70 | function_name: &'static str, 71 | index_value: usize, 72 | range_min_included: usize, 73 | range_max_included: usize, 74 | }, 75 | StringIndexNotFound { 76 | function_name: &'static str, 77 | index_value: String, 78 | }, 79 | } 80 | 81 | #[derive(Debug)] 82 | pub(crate) enum UnsupportedNumberError { 83 | DoesNotFitInPrimitive(DoesNotFitInPrimitive), 84 | NotAnInteger(NotAnInteger), 85 | } 86 | 87 | /// Hack to get the name of the current function, a Rust equivalent of C's `__func__`. 88 | /// Pretty useful to avoid copy-pasted code to still contain the name of another function 89 | /// in place of a string that is supposed to be the name of the function. 90 | /// 91 | /// Code stolen from https://stackoverflow.com/a/40234666 (find the full path to the 92 | /// function) and NOT upgraded to https://stackoverflow.com/a/63904992 (cut the path 93 | /// to only keep the name of the function) because the full path makes finding the 94 | /// function in the code a bit easiser. 95 | macro_rules! function_name { 96 | () => {{ 97 | // Define a sub-function. 98 | fn f() {} 99 | // Find the name of the sub-function (that is full-path-to-function + "::f"). 100 | let name = { 101 | fn type_name_of(_: T) -> &'static str { 102 | std::any::type_name::() 103 | } 104 | type_name_of(f) 105 | }; 106 | // Cut out the "::f". 107 | &name[..name.len() - 3] 108 | }}; 109 | } 110 | 111 | fn convert_big_frac_to_integer_primitive( 112 | frac: BigFrac, 113 | caller_function_name: &'static str, 114 | ) -> Result 115 | where 116 | PrimitiveType: for<'a> TryFrom<&'a BigSint, Error = DoesNotFitInPrimitive>, 117 | { 118 | let right_as_integer = match BigSint::try_from(&frac) { 119 | Ok(value) => value, 120 | Err(error @ NotAnInteger) => { 121 | return Err(ObjectOperationError::UnsupportedNumber { 122 | function_name: caller_function_name, 123 | number_value: frac, 124 | error: UnsupportedNumberError::NotAnInteger(error), 125 | }) 126 | }, 127 | }; 128 | let right_as_integer_primitive = match PrimitiveType::try_from(&right_as_integer) { 129 | Ok(value) => value, 130 | Err(error) => { 131 | return Err(ObjectOperationError::UnsupportedNumber { 132 | function_name: caller_function_name, 133 | number_value: frac, 134 | error: UnsupportedNumberError::DoesNotFitInPrimitive(error), 135 | }) 136 | }, 137 | }; 138 | Ok(right_as_integer_primitive) 139 | } 140 | 141 | impl Object { 142 | pub(crate) fn type_name(&self) -> &'static str { 143 | match self { 144 | Object::Nothing => "nothing", 145 | Object::Number(_) => "number", 146 | Object::String(_) => "string", 147 | Object::Block(_) => "block", 148 | Object::List(_) => "list", 149 | Object::Context(_) => "context", 150 | } 151 | } 152 | 153 | /// Returns the logical not of the given number intrepreted as a boolean. 154 | pub(crate) fn logical_not(&self) -> Result { 155 | match self { 156 | Object::Number(value) => Ok(Object::Number(BigFrac::from_bool(value.is_zero()))), 157 | obj => Err(ObjectOperationError::UnsupportedType { 158 | function_name: function_name!(), 159 | type_name: obj.type_name(), 160 | }), 161 | } 162 | } 163 | 164 | /// Returns the logical and of the given numbers intrepreted as booleans. 165 | pub(crate) fn logical_and(&self, rhs: &Object) -> Result { 166 | match (self, rhs) { 167 | (Object::Number(left_value), Object::Number(right_value)) => { 168 | let value = BigFrac::from_bool(!left_value.is_zero() && !right_value.is_zero()); 169 | Ok(Object::Number(value)) 170 | }, 171 | (left, right) => Err(ObjectOperationError::UnsupportedPairOfTypes { 172 | function_name: function_name!(), 173 | left_type_name: left.type_name(), 174 | right_type_name: right.type_name(), 175 | }), 176 | } 177 | } 178 | 179 | /// Returns a number boolean that is true (non-zero) iff the given list of numbers 180 | /// is ordered (increasing) (strictly or not, depending of the parameter `strictly`). 181 | /// Lists of 0 or 1 numbers are considered to be ordered. 182 | pub(crate) fn is_ordered(&self, strictly: bool) -> Result { 183 | match self { 184 | Object::List(vec) => { 185 | // Only numbers are supported by order test for now, 186 | // we make sure there is nothing else in the list. 187 | let not_a_number = vec.iter().find(|obj| !matches!(obj, Object::Number(_))); 188 | if let Some(obj) = not_a_number { 189 | return Err(ObjectOperationError::UnsupportedTypeInList { 190 | function_name: function_name!(), 191 | type_name: obj.type_name(), 192 | }); 193 | } 194 | 195 | let ordering_tester = if strictly { 196 | Ordering::is_lt 197 | } else { 198 | Ordering::is_le 199 | }; 200 | let is_ordered = vec.as_slice().windows(2).all(|window| match window { 201 | [Object::Number(left), Object::Number(right)] => { 202 | ordering_tester(left.cmp(right)) 203 | }, 204 | _ => unreachable!(), 205 | }); 206 | Ok(Object::Number(BigFrac::from_bool(is_ordered))) 207 | }, 208 | obj => Err(ObjectOperationError::UnsupportedType { 209 | function_name: function_name!(), 210 | type_name: obj.type_name(), 211 | }), 212 | } 213 | } 214 | 215 | pub(crate) fn length(&self) -> Result { 216 | match self { 217 | Object::List(vec) => Ok(Object::Number(BigFrac::from(vec.len() as u64))), 218 | Object::String(string) => { 219 | Ok(Object::Number(BigFrac::from(string.chars().count() as u64))) 220 | }, 221 | obj => Err(ObjectOperationError::UnsupportedType { 222 | function_name: function_name!(), 223 | type_name: obj.type_name(), 224 | }), 225 | } 226 | } 227 | 228 | pub(crate) fn plus(self, rhs: Object) -> Result { 229 | match (self, rhs) { 230 | (Object::Number(left_value), Object::Number(right_value)) => { 231 | Ok(Object::Number(left_value + right_value)) 232 | }, 233 | (Object::String(left_string), Object::String(right_string)) => { 234 | Ok(Object::String(left_string + &right_string)) 235 | }, 236 | (Object::Block(left_block), Object::Block(right_block)) => { 237 | Ok(Object::Block(left_block.concat(right_block))) 238 | }, 239 | (left, right) => Err(ObjectOperationError::UnsupportedPairOfTypes { 240 | function_name: function_name!(), 241 | left_type_name: left.type_name(), 242 | right_type_name: right.type_name(), 243 | }), 244 | } 245 | } 246 | 247 | pub(crate) fn minus(self, rhs: Object) -> Result { 248 | match (self, rhs) { 249 | (Object::Number(left_value), Object::Number(right_value)) => { 250 | Ok(Object::Number(left_value - right_value)) 251 | }, 252 | (Object::String(left_string), Object::String(right_string)) => { 253 | let value = BigFrac::from_bool(left_string != right_string); 254 | Ok(Object::Number(value)) 255 | }, 256 | (left, right) => Err(ObjectOperationError::UnsupportedPairOfTypes { 257 | function_name: function_name!(), 258 | left_type_name: left.type_name(), 259 | right_type_name: right.type_name(), 260 | }), 261 | } 262 | } 263 | 264 | pub(crate) fn star(self, rhs: Object) -> Result { 265 | match (self, rhs) { 266 | (Object::Number(left_value), Object::Number(right_value)) => { 267 | Ok(Object::Number(left_value * right_value)) 268 | }, 269 | (Object::String(left_string), Object::Number(right_value)) => { 270 | let right_as_usize = 271 | convert_big_frac_to_integer_primitive::(right_value, function_name!())? 272 | as usize; 273 | Ok(Object::String(left_string.repeat(right_as_usize))) 274 | }, 275 | (Object::Block(left_block), Object::Number(right_value)) => { 276 | let mut block = left_block.clone(); 277 | let right_as_u64 = 278 | convert_big_frac_to_integer_primitive::(right_value, function_name!())?; 279 | for _ in 0..right_as_u64 { 280 | block = block.concat(left_block.clone()); 281 | } 282 | Ok(Object::Block(block)) 283 | }, 284 | (left, right) => Err(ObjectOperationError::UnsupportedPairOfTypes { 285 | function_name: function_name!(), 286 | left_type_name: left.type_name(), 287 | right_type_name: right.type_name(), 288 | }), 289 | } 290 | } 291 | 292 | pub(crate) fn slash(self, rhs: Object) -> Result { 293 | match (self, rhs) { 294 | (Object::Number(left_value), Object::Number(right_value)) => { 295 | Ok(Object::Number(left_value / right_value)) 296 | }, 297 | (Object::String(left_string), Object::String(right_string)) => Ok(Object::Number( 298 | BigFrac::from(left_string.matches(right_string.as_str()).count() as u64), 299 | )), 300 | (left, right) => Err(ObjectOperationError::UnsupportedPairOfTypes { 301 | function_name: function_name!(), 302 | left_type_name: left.type_name(), 303 | right_type_name: right.type_name(), 304 | }), 305 | } 306 | } 307 | 308 | pub(crate) fn comma(self, rhs: Object) -> Result { 309 | match (self, rhs) { 310 | (Object::Nothing, right) => Ok(Object::List(vec![right])), 311 | (Object::List(mut vec), right) => { 312 | vec.push(right); 313 | Ok(Object::List(vec)) 314 | }, 315 | (left, _right) => Err(ObjectOperationError::UnsupportedType { 316 | function_name: function_name!(), 317 | type_name: left.type_name(), 318 | }), 319 | } 320 | } 321 | 322 | pub(crate) fn double_comma(self, rhs: Object) -> Object { 323 | Object::List(vec![self, rhs]) 324 | } 325 | 326 | /// Returns `self[rhs]`. 327 | pub(crate) fn index(&self, rhs: Object) -> Result { 328 | match (self, rhs) { 329 | (Object::List(vec), Object::Number(index)) => { 330 | let index_as_usize = 331 | convert_big_frac_to_integer_primitive::(index, function_name!())? as usize; 332 | match vec.get(index_as_usize) { 333 | Some(value) => Ok(value.clone()), 334 | None => Err(ObjectOperationError::NumberIndexOutOfRange { 335 | function_name: function_name!(), 336 | index_value: index_as_usize, 337 | range_min_included: 0, 338 | range_max_included: vec.len() - 1, 339 | }), 340 | } 341 | }, 342 | (Object::String(string), Object::Number(index)) => { 343 | let index_as_usize = 344 | convert_big_frac_to_integer_primitive::(index, function_name!())? as usize; 345 | match string.chars().nth(index_as_usize) { 346 | Some(value) => Ok(Object::String(value.to_string())), 347 | None => Err(ObjectOperationError::NumberIndexOutOfRange { 348 | function_name: function_name!(), 349 | index_value: index_as_usize, 350 | range_min_included: 0, 351 | range_max_included: string.chars().count() - 1, 352 | }), 353 | } 354 | }, 355 | (Object::Context(var_table), Object::String(string_index)) => { 356 | match var_table.get(&string_index) { 357 | Some(value) => Ok(value.clone()), 358 | None => Err(ObjectOperationError::StringIndexNotFound { 359 | function_name: function_name!(), 360 | index_value: string_index, 361 | }), 362 | } 363 | }, 364 | (left, right) => Err(ObjectOperationError::UnsupportedPairOfTypes { 365 | function_name: function_name!(), 366 | left_type_name: left.type_name(), 367 | right_type_name: right.type_name(), 368 | }), 369 | } 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /sflk-lang/src/scu.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Add, AddAssign}, 3 | rc::Rc, 4 | }; 5 | 6 | pub(crate) struct SourceCodeUnit { 7 | _name: String, 8 | pub(crate) content: String, 9 | _line_offsets: Vec, 10 | } 11 | 12 | impl SourceCodeUnit { 13 | pub(crate) fn from_filename(filename: &str) -> SourceCodeUnit { 14 | let src = std::fs::read_to_string(filename) 15 | .unwrap_or_else(|_| panic!("source file `{}` couldn't be read", filename)); 16 | SourceCodeUnit::from_str(src, filename.to_string()) 17 | } 18 | 19 | pub(crate) fn from_str(string: String, name: String) -> SourceCodeUnit { 20 | let line_offsets_iter = string.bytes().enumerate().filter_map(|(i, ch)| { 21 | if ch as char == '\n' { 22 | Some(i + 1) 23 | } else { 24 | None 25 | } 26 | }); 27 | let mut line_offsets: Vec = 28 | Some(0usize).into_iter().chain(line_offsets_iter).collect(); 29 | let mut content = string; 30 | if *line_offsets.last().unwrap() != content.len() { 31 | content += "\n"; 32 | line_offsets.push(content.len()); 33 | // If the content didn't end by a `\n`, then now it does. 34 | } 35 | SourceCodeUnit { _name: name, content, _line_offsets: line_offsets } 36 | } 37 | } 38 | 39 | impl Loc { 40 | pub(crate) fn total_of(scu: Rc) -> Loc { 41 | Loc { 42 | scu: Rc::clone(&scu), 43 | line_start: 1, 44 | raw_index_start: 0, 45 | raw_length: scu.content.len(), 46 | } 47 | } 48 | } 49 | 50 | #[derive(Clone)] 51 | pub(crate) struct Loc { 52 | pub(crate) scu: Rc, 53 | pub(crate) line_start: usize, 54 | pub(crate) raw_index_start: usize, 55 | pub(crate) raw_length: usize, 56 | } 57 | 58 | impl Loc { 59 | pub(crate) fn line(&self) -> usize { 60 | self.line_start 61 | } 62 | } 63 | 64 | impl AddAssign<&Loc> for Loc { 65 | fn add_assign(&mut self, right: &Loc) { 66 | std::assert_eq!(Rc::as_ptr(&self.scu), Rc::as_ptr(&right.scu)); 67 | if self.raw_index_start <= right.raw_index_start { 68 | std::assert!(self.line_start <= right.line_start); 69 | self.raw_length += (right.raw_index_start - self.raw_index_start) + right.raw_length; 70 | } else { 71 | std::assert!(self.line_start >= right.line_start); 72 | self.line_start = right.line_start; 73 | let left_part_length = self.raw_index_start - right.raw_index_start; 74 | self.raw_index_start -= left_part_length; 75 | self.raw_length += left_part_length; 76 | } 77 | } 78 | } 79 | 80 | impl AddAssign for Loc { 81 | fn add_assign(&mut self, right: Loc) { 82 | *self += &right; 83 | } 84 | } 85 | 86 | impl AddAssign<&Loc> for &mut Loc { 87 | fn add_assign(&mut self, right: &Loc) { 88 | **self += right; 89 | } 90 | } 91 | 92 | impl Add for Loc { 93 | type Output = Loc; 94 | fn add(mut self, right: Loc) -> Loc { 95 | self += right; 96 | self 97 | } 98 | } 99 | 100 | impl Add for &Loc { 101 | type Output = Loc; 102 | fn add(self, right: &Loc) -> Loc { 103 | let mut loc = self.clone(); 104 | loc += right; 105 | loc 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /sflk-lang/src/settings.rs: -------------------------------------------------------------------------------- 1 | //! Command line interface of the interpreter. 2 | //! 3 | //! The parsing of the command line arguments happen here, 4 | //! as well as the printing of help messages, version numbers, etc. 5 | 6 | use crate::{log_indent::IndentedLogger, parser::ParserDebuggingLogger}; 7 | 8 | use std::env; 9 | 10 | const HELP_MESSAGE: &str = "\ 11 | Usage:\n\ 12 | \tsflk [filename.sflk] [options]\n\ 13 | \tsflk [-c \"some SFLK source code\"] [options]\n\ 14 | \n\ 15 | Options:\n\ 16 | \t-h --help Prints this help message\n\ 17 | \t-v --version Prints the interpreter version\n\ 18 | \t-c --code Next argument is the source code to run\n\ 19 | \t-d --debug Turns on debug mode\n\ 20 | \t --tokens Prints tokens and halts\n\ 21 | \t --lines Prints line numbers in some debug logs\n\ 22 | \t --actions Prints actions in parsing debug logs\n\ 23 | \t --sir Prints the SIR code before running\n\ 24 | \n\ 25 | Examples:\n\ 26 | \tsflk tests/test6.sflk\n\ 27 | \tsflk -c \"pr 8 nl\"\n\ 28 | "; 29 | 30 | /// Copied the header from the `LICENSE` file generated by GitHub when 31 | /// adding the MIT license to the repo. 32 | /// I have no idea how binding the copyright thing is... 33 | /// If more people start contributing to this repo with a significant 34 | /// quantity of code, this should probably be extended or something? 35 | const LICENSE_NOTE: &str = "\ 36 | MIT License, Copyright (c) 2021 Anima Libera."; 37 | 38 | /// GCC prints something similar when asked for its `--version`, 39 | /// thus it is probably a good idea to do the same here. 40 | /// This is a kind of tl;dr of the last paragraph of the `LICENSE` file 41 | /// about warranties. 42 | const NO_WARRANTY_NOTE: &str = "\ 43 | Please note that there is NO warranty, \ 44 | not even for MERCHANTABILITY or \ 45 | FITNESS FOR A PARTICULAR PURPOSE."; 46 | 47 | #[derive(Debug)] 48 | pub(crate) struct Settings { 49 | /// Path to the interpreter binary (actually `argv[0]`). 50 | path: String, 51 | /// Given SFLK source code to parse, run, etc. 52 | pub(crate) src: Option, 53 | pub(crate) debug: bool, 54 | pub(crate) debug_lines: bool, 55 | debug_actions: bool, 56 | pub(crate) debug_sir: bool, 57 | wants_help: bool, 58 | wants_version: bool, 59 | pub(crate) display_tokens: bool, 60 | } 61 | 62 | #[derive(Debug)] 63 | pub(crate) enum Source { 64 | FilePath(String), 65 | Code(String), 66 | } 67 | 68 | impl Settings { 69 | /// Retrieves and parse command line arguments, returns the according settings. 70 | pub(crate) fn from_args() -> Settings { 71 | let mut args = env::args(); 72 | let mut settings = Settings { 73 | path: args.next().unwrap_or_else(|| "sflk".to_string()), 74 | src: None, 75 | debug: false, 76 | debug_lines: false, 77 | debug_actions: false, 78 | debug_sir: false, 79 | wants_help: false, 80 | wants_version: false, 81 | display_tokens: false, 82 | }; 83 | 84 | enum Mode { 85 | Normal, 86 | /// The next argument is expected to be the source code. 87 | SourceCode, 88 | } 89 | let mut mode = Mode::Normal; 90 | 91 | for arg in args { 92 | match mode { 93 | Mode::Normal => match arg.as_str() { 94 | "-h" | "--help" => { 95 | settings.wants_help = true; 96 | }, 97 | "-v" | "--version" => { 98 | settings.wants_version = true; 99 | }, 100 | "-c" | "--code" => { 101 | if settings.src.is_some() { 102 | panic!("Multiple source codes are given at the same time"); 103 | } 104 | mode = Mode::SourceCode; 105 | }, 106 | "-d" | "--debug" => { 107 | settings.debug = true; 108 | }, 109 | "--tokens" => { 110 | settings.display_tokens = true; 111 | }, 112 | "--lines" => { 113 | settings.debug_lines = true; 114 | }, 115 | "--actions" => { 116 | settings.debug_actions = true; 117 | }, 118 | "--sir" => { 119 | settings.debug_sir = true; 120 | }, 121 | arg => { 122 | if settings.src.is_none() { 123 | settings.src = Some(Source::FilePath(arg.to_string())); 124 | } else { 125 | panic!( 126 | "Unknown command line argument `{}` \ 127 | (it cannot be the source code file path because \ 128 | a source code was already provided)", 129 | arg 130 | ); 131 | } 132 | }, 133 | }, 134 | Mode::SourceCode => { 135 | if settings.src.is_none() { 136 | settings.src = Some(Source::Code(arg.to_string())); 137 | } else { 138 | panic!(); 139 | } 140 | mode = Mode::Normal; 141 | }, 142 | } 143 | } 144 | 145 | settings 146 | } 147 | 148 | /// Prints stuff like version, help message, "no source code", etc. 149 | pub(crate) fn print_info(&self) { 150 | let mut did_something = false; 151 | 152 | if self.wants_version { 153 | let version_name = "indev"; 154 | println!( 155 | "SFLK reference interpreter, version {}.{}.{} ({})", 156 | 0, 3, 0, version_name 157 | ); 158 | println!("{}", LICENSE_NOTE); 159 | println!("{}", NO_WARRANTY_NOTE); 160 | 161 | did_something = true; 162 | } 163 | 164 | if self.wants_help { 165 | println!("{}", HELP_MESSAGE); 166 | did_something = true; 167 | } 168 | 169 | if self.src.is_none() && !did_something { 170 | println!( 171 | "No source code provided, nothing to do. Try `{} --help` for usage.", 172 | self.path 173 | ); 174 | } 175 | } 176 | 177 | pub(crate) fn parser_debugging_logger(&self) -> ParserDebuggingLogger { 178 | let mut logger: Option = None; 179 | if self.debug { 180 | logger = Some(IndentedLogger::new()); 181 | } 182 | 183 | ParserDebuggingLogger { 184 | logger, 185 | log_lines: self.debug_lines, 186 | log_actions: self.debug_actions, 187 | last_line: 0, 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /sflk-lang/src/stringtree.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::Style; 2 | 3 | pub(crate) struct StringTree { 4 | string: String, 5 | style: Style, 6 | sub_trees: Vec, 7 | } 8 | 9 | impl StringTree { 10 | pub(crate) fn new_leaf(string: String, style: Style) -> StringTree { 11 | StringTree { string, style, sub_trees: Vec::new() } 12 | } 13 | 14 | pub(crate) fn new_node(string: String, style: Style, sub_trees: Vec) -> StringTree { 15 | StringTree { string, style, sub_trees } 16 | } 17 | } 18 | 19 | const INDENT_TUBE: &str = "│ "; 20 | const INDENT_ITEM: &str = "├─"; 21 | const INDENT_LAST: &str = "└─"; 22 | const INDENT_NONE: &str = " "; 23 | 24 | enum Tube { 25 | Tube, 26 | None, 27 | } 28 | 29 | impl Tube { 30 | fn str(&self) -> &'static str { 31 | match self { 32 | Tube::Tube => INDENT_TUBE, 33 | Tube::None => INDENT_NONE, 34 | } 35 | } 36 | } 37 | 38 | enum RightTube { 39 | Tube, 40 | Item, 41 | Last, 42 | } 43 | 44 | impl RightTube { 45 | fn str(&self) -> &'static str { 46 | match self { 47 | RightTube::Tube => INDENT_TUBE, 48 | RightTube::Item => INDENT_ITEM, 49 | RightTube::Last => INDENT_LAST, 50 | } 51 | } 52 | 53 | fn from_is_last(is_last: bool) -> RightTube { 54 | match is_last { 55 | false => RightTube::Item, 56 | true => RightTube::Last, 57 | } 58 | } 59 | } 60 | 61 | impl StringTree { 62 | pub(crate) fn print(&self, writer: &mut impl std::fmt::Write) { 63 | self.print_aux(writer, &mut Vec::new(), false); 64 | } 65 | 66 | fn print_aux( 67 | &self, 68 | writer: &mut impl std::fmt::Write, 69 | indent_styles: &mut Vec<(Style, Tube)>, 70 | is_last: bool, 71 | ) { 72 | // Print self.string with multiple line string support 73 | let mut lines = self.string.lines(); 74 | if let Some(line) = lines.next() { 75 | print_indents(writer, indent_styles, RightTube::from_is_last(is_last)); 76 | writeln!(writer, "{}{}{}", self.style.0, line, self.style.1).expect("error"); 77 | } 78 | for line in lines { 79 | print_indents(writer, indent_styles, RightTube::Tube); 80 | writeln!(writer, "{}{}{}", self.style.0, line, self.style.1).expect("error"); 81 | } 82 | 83 | // Manage the indentation changes and recusive printing 84 | if is_last && !indent_styles.is_empty() { 85 | indent_styles.last_mut().unwrap().1 = Tube::None; 86 | } 87 | indent_styles.push((self.style, Tube::Tube)); 88 | if let Some((last_sub_tree, sub_trees)) = self.sub_trees.split_last() { 89 | for sub_tree in sub_trees { 90 | sub_tree.print_aux(writer, indent_styles, false); 91 | } 92 | last_sub_tree.print_aux(writer, indent_styles, true); 93 | } 94 | indent_styles.pop(); 95 | } 96 | } 97 | 98 | fn print_indents( 99 | writer: &mut impl std::fmt::Write, 100 | indent_styles: &[(Style, Tube)], 101 | right_override: RightTube, 102 | ) { 103 | if let Some(((right_style, _), indents_left)) = indent_styles.split_last() { 104 | for (style, tube) in indents_left { 105 | write!(writer, "{}{}{}", style.0, tube.str(), style.1).expect("error"); 106 | } 107 | write!( 108 | writer, 109 | "{}{}{}", 110 | right_style.0, 111 | right_override.str(), 112 | right_style.1 113 | ) 114 | .expect("error"); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /sflk-lang/src/tokenizer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | scu::{Loc, SourceCodeUnit}, 3 | utils::{escape_string, styles}, 4 | }; 5 | use std::{collections::VecDeque, convert::TryFrom, fmt, rc::Rc}; 6 | 7 | pub(crate) struct CharReadingHead { 8 | scu: Rc, 9 | raw_index: usize, 10 | line: usize, 11 | } 12 | 13 | impl CharReadingHead { 14 | pub(crate) fn from_scu(scu: Rc) -> CharReadingHead { 15 | CharReadingHead { scu, raw_index: 0, line: 1 } 16 | } 17 | } 18 | 19 | impl CharReadingHead { 20 | fn peek(&self) -> Option { 21 | self.scu.content[self.raw_index..].chars().next() 22 | } 23 | 24 | fn disc(&mut self) { 25 | if let Some(ch) = self.peek() { 26 | self.raw_index += ch.len_utf8(); 27 | if ch == '\n' { 28 | self.line += 1; 29 | } 30 | } 31 | } 32 | 33 | fn loc(&self) -> Loc { 34 | Loc { 35 | scu: Rc::clone(&self.scu), 36 | line_start: self.line, 37 | raw_index_start: self.raw_index, 38 | raw_length: match self.peek() { 39 | Some(ch) => ch.len_utf8(), 40 | None => 0, 41 | }, 42 | } 43 | } 44 | 45 | fn skip_ws(&mut self) { 46 | loop { 47 | match self.peek() { 48 | Some(ch) if ch.is_ascii_whitespace() => self.disc(), 49 | _ => break, 50 | } 51 | } 52 | } 53 | } 54 | 55 | impl CharReadingHead { 56 | pub(crate) fn scu(&self) -> Rc { 57 | Rc::clone(&self.scu) 58 | } 59 | } 60 | 61 | #[derive(PartialEq, Eq, Hash)] 62 | pub(crate) enum SimpleTok { 63 | Kw(Kw), 64 | Op(Op), 65 | } 66 | 67 | impl TryFrom<&Tok> for SimpleTok { 68 | type Error = (); 69 | 70 | fn try_from(tok: &Tok) -> Result { 71 | match tok { 72 | Tok::Kw(kw) => Ok(SimpleTok::Kw(*kw)), 73 | Tok::Op(op) => Ok(SimpleTok::Op(*op)), 74 | _ => Err(()), 75 | } 76 | } 77 | } 78 | 79 | #[derive(Debug, Clone)] 80 | pub(crate) enum Tok { 81 | Kw(Kw), 82 | Op(Op), 83 | Left(Matched), 84 | Right(Matched), 85 | Name { 86 | string: String, 87 | _unstable_warning: bool, 88 | }, 89 | Integer(String), 90 | String { 91 | content: String, 92 | _no_end_quote_warning: bool, 93 | _invalid_escape_sequence_errors: Vec<(EscapeSequenceError, usize)>, 94 | // The usize is the `\` character index in the literal. 95 | }, 96 | InvalidCharacter(char), 97 | CommentBlock { 98 | _content: String, 99 | _delimitation_thickness: usize, 100 | _no_end_hash_warning: bool, 101 | }, 102 | CommentLine { 103 | _content: String, 104 | }, 105 | Eof, 106 | } 107 | 108 | #[derive(Debug, Clone)] 109 | pub(crate) enum EscapeSequenceError { 110 | InvalidFirstCharacter(char), 111 | InvalidDigitCharacter(char), 112 | UnexpectedEof, 113 | InvalidUnicodeCodePoint(u32), 114 | } 115 | 116 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 117 | pub(crate) enum Kw { 118 | Np, 119 | Pr, 120 | Nl, 121 | Do, 122 | Dh, 123 | Fh, 124 | Ev, 125 | If, 126 | Th, 127 | El, 128 | Lp, 129 | Wh, 130 | Bd, 131 | Sp, 132 | Ao, 133 | Ri, 134 | Em, 135 | Rs, 136 | Fi, 137 | In, 138 | Ix, 139 | Cx, 140 | Cy, 141 | Wi, 142 | Od, 143 | Os, 144 | Ln, 145 | Gs, 146 | Ag, 147 | } 148 | 149 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 150 | pub(crate) enum Op { 151 | Plus, 152 | Minus, 153 | Star, 154 | Slash, 155 | Comma, 156 | DoubleComma, 157 | Dot, 158 | ToRight, 159 | ToLeft, 160 | Bang, 161 | } 162 | 163 | #[derive(Debug, Clone)] 164 | pub(crate) enum Matched { 165 | Paren, 166 | Curly, 167 | Bracket, 168 | } 169 | 170 | pub(crate) struct Tokenizer {} 171 | 172 | impl Tokenizer { 173 | pub(crate) fn new() -> Tokenizer { 174 | Tokenizer {} 175 | } 176 | } 177 | 178 | impl Tokenizer { 179 | pub(crate) fn pop_tok(&mut self, crh: &mut CharReadingHead) -> (Tok, Loc) { 180 | crh.skip_ws(); 181 | let loc = crh.loc(); 182 | match crh.peek() { 183 | Some(ch) if ch.is_ascii_alphabetic() => { 184 | let (word_string, word_loc) = self.pop_word(crh); 185 | (self.word_to_tok(word_string), word_loc) 186 | }, 187 | Some(ch) if ch.is_ascii_digit() => { 188 | let (integer_string, word_loc) = self.pop_integer(crh); 189 | (Tok::Integer(integer_string), word_loc) 190 | }, 191 | Some('\"') => self.pop_string_tok(crh), 192 | Some('+') => { 193 | crh.disc(); 194 | (Tok::Op(Op::Plus), loc) 195 | }, 196 | Some('-') => { 197 | crh.disc(); 198 | (Tok::Op(Op::Minus), loc) 199 | }, 200 | Some('*') => { 201 | crh.disc(); 202 | (Tok::Op(Op::Star), loc) 203 | }, 204 | Some('/') => { 205 | crh.disc(); 206 | (Tok::Op(Op::Slash), loc) 207 | }, 208 | Some(',') => { 209 | crh.disc(); 210 | let loc2 = crh.loc(); 211 | if crh.peek() == Some(',') { 212 | crh.disc(); 213 | (Tok::Op(Op::DoubleComma), loc + loc2) 214 | } else { 215 | (Tok::Op(Op::Comma), loc) 216 | } 217 | }, 218 | Some('.') => { 219 | crh.disc(); 220 | (Tok::Op(Op::Dot), loc) 221 | }, 222 | Some('>') => { 223 | crh.disc(); 224 | (Tok::Op(Op::ToRight), loc) 225 | }, 226 | Some('(') => { 227 | crh.disc(); 228 | (Tok::Left(Matched::Paren), loc) 229 | }, 230 | Some('[') => { 231 | crh.disc(); 232 | (Tok::Left(Matched::Bracket), loc) 233 | }, 234 | Some('{') => { 235 | crh.disc(); 236 | (Tok::Left(Matched::Curly), loc) 237 | }, 238 | Some(')') => { 239 | crh.disc(); 240 | (Tok::Right(Matched::Paren), loc) 241 | }, 242 | Some(']') => { 243 | crh.disc(); 244 | (Tok::Right(Matched::Bracket), loc) 245 | }, 246 | Some('}') => { 247 | crh.disc(); 248 | (Tok::Right(Matched::Curly), loc) 249 | }, 250 | Some('<') => { 251 | crh.disc(); 252 | (Tok::Op(Op::ToLeft), loc) 253 | }, 254 | Some('!') => { 255 | crh.disc(); 256 | (Tok::Op(Op::Bang), loc) 257 | }, 258 | Some('#') => self.pop_comment_tok(crh), 259 | Some(ch) => { 260 | crh.disc(); 261 | (Tok::InvalidCharacter(ch), loc) 262 | }, 263 | None => (Tok::Eof, loc), 264 | } 265 | } 266 | 267 | fn pop_word(&mut self, crh: &mut CharReadingHead) -> (String, Loc) { 268 | let mut word_string = String::new(); 269 | let mut loc = crh.loc(); 270 | while let Some(ch) = crh.peek() { 271 | if !ch.is_ascii_alphabetic() { 272 | break; 273 | } 274 | word_string.push(ch); 275 | crh.disc(); 276 | } 277 | std::assert!(!word_string.is_empty()); 278 | loc.raw_length = word_string.bytes().len(); 279 | (word_string, loc) 280 | } 281 | 282 | fn word_to_tok(&self, word: String) -> Tok { 283 | match &word[..] { 284 | "np" => Tok::Kw(Kw::Np), 285 | "pr" => Tok::Kw(Kw::Pr), 286 | "nl" => Tok::Kw(Kw::Nl), 287 | "do" => Tok::Kw(Kw::Do), 288 | "dh" => Tok::Kw(Kw::Dh), 289 | "fh" => Tok::Kw(Kw::Fh), 290 | "ev" => Tok::Kw(Kw::Ev), 291 | "if" => Tok::Kw(Kw::If), 292 | "th" => Tok::Kw(Kw::Th), 293 | "el" => Tok::Kw(Kw::El), 294 | "lp" => Tok::Kw(Kw::Lp), 295 | "wh" => Tok::Kw(Kw::Wh), 296 | "bd" => Tok::Kw(Kw::Bd), 297 | "sp" => Tok::Kw(Kw::Sp), 298 | "ao" => Tok::Kw(Kw::Ao), 299 | "ri" => Tok::Kw(Kw::Ri), 300 | "em" => Tok::Kw(Kw::Em), 301 | "rs" => Tok::Kw(Kw::Rs), 302 | "fi" => Tok::Kw(Kw::Fi), 303 | "in" => Tok::Kw(Kw::In), 304 | "ix" => Tok::Kw(Kw::Ix), 305 | "cx" => Tok::Kw(Kw::Cx), 306 | "cy" => Tok::Kw(Kw::Cy), 307 | "wi" => Tok::Kw(Kw::Wi), 308 | "od" => Tok::Kw(Kw::Od), 309 | "os" => Tok::Kw(Kw::Os), 310 | "ln" => Tok::Kw(Kw::Ln), 311 | "gs" => Tok::Kw(Kw::Gs), 312 | "ag" => Tok::Kw(Kw::Ag), 313 | _ => { 314 | let len = word.len(); 315 | Tok::Name { string: word, _unstable_warning: len == 2 } 316 | }, 317 | } 318 | } 319 | 320 | fn pop_integer(&mut self, crh: &mut CharReadingHead) -> (String, Loc) { 321 | let mut integer_string = String::new(); 322 | let mut loc = crh.loc(); 323 | while let Some(ch) = crh.peek() { 324 | if !ch.is_ascii_digit() { 325 | break; 326 | } 327 | integer_string.push(ch); 328 | crh.disc(); 329 | } 330 | std::assert!(!integer_string.is_empty()); 331 | loc.raw_length = integer_string.bytes().len(); 332 | (integer_string, loc) 333 | } 334 | 335 | fn pop_string_tok(&mut self, crh: &mut CharReadingHead) -> (Tok, Loc) { 336 | let mut content = String::new(); 337 | let mut no_end_quote_warning = false; 338 | let mut invalid_escape_sequence_errors: Vec<(EscapeSequenceError, usize)> = Vec::new(); 339 | let mut offset = 0; 340 | let mut loc = crh.loc(); 341 | std::assert_eq!(crh.peek(), Some('\"')); 342 | crh.disc(); 343 | loop { 344 | match crh.peek() { 345 | None => { 346 | no_end_quote_warning = true; 347 | break; 348 | }, 349 | Some('\"') => { 350 | loc += crh.loc(); 351 | crh.disc(); 352 | break; 353 | }, 354 | Some('\\') => { 355 | let (escaped, len, escaped_loc) = self.pop_escaped(crh); 356 | loc += escaped_loc; 357 | match escaped { 358 | Ok(escaped_string) => { 359 | content += &escaped_string; 360 | }, 361 | Err(error) => { 362 | content += "�"; 363 | invalid_escape_sequence_errors.push((error, offset)); 364 | }, 365 | } 366 | offset += len; 367 | }, 368 | Some(ch) => { 369 | loc += crh.loc(); 370 | offset += 1; 371 | crh.disc(); 372 | content.push(ch); 373 | }, 374 | } 375 | } 376 | ( 377 | Tok::String { 378 | content, 379 | _no_end_quote_warning: no_end_quote_warning, 380 | _invalid_escape_sequence_errors: invalid_escape_sequence_errors, 381 | }, 382 | loc, 383 | ) 384 | } 385 | 386 | fn pop_escaped( 387 | &mut self, 388 | crh: &mut CharReadingHead, 389 | ) -> (Result, usize, Loc) { 390 | let loc_beg = crh.loc(); 391 | std::assert_eq!(crh.peek(), Some('\\')); 392 | crh.disc(); 393 | match crh.peek() { 394 | Some('\n') => { 395 | let loc_end = crh.loc(); 396 | crh.disc(); 397 | (Ok("".to_string()), 2, loc_beg + loc_end) 398 | }, 399 | Some('\\') => { 400 | let loc_end = crh.loc(); 401 | crh.disc(); 402 | (Ok("\\".to_string()), 2, loc_beg + loc_end) 403 | }, 404 | Some('\"') => { 405 | let loc_end = crh.loc(); 406 | crh.disc(); 407 | (Ok("\"".to_string()), 2, loc_beg + loc_end) 408 | }, 409 | Some('?') => { 410 | let loc_end = crh.loc(); 411 | crh.disc(); 412 | (Ok("�".to_string()), 2, loc_beg + loc_end) 413 | }, 414 | Some('n') => { 415 | let loc_end = crh.loc(); 416 | crh.disc(); 417 | (Ok("\n".to_string()), 2, loc_beg + loc_end) 418 | }, 419 | Some('t') => { 420 | let loc_end = crh.loc(); 421 | crh.disc(); 422 | (Ok("\t".to_string()), 2, loc_beg + loc_end) 423 | }, 424 | Some('e') => { 425 | let loc_end = crh.loc(); 426 | crh.disc(); 427 | (Ok("\x1b".to_string()), 2, loc_beg + loc_end) 428 | }, 429 | Some('a') => { 430 | let loc_end = crh.loc(); 431 | crh.disc(); 432 | (Ok("\x07".to_string()), 2, loc_beg + loc_end) 433 | }, 434 | Some('b') => { 435 | let loc_end = crh.loc(); 436 | crh.disc(); 437 | (Ok("\x08".to_string()), 2, loc_beg + loc_end) 438 | }, 439 | Some('v') => { 440 | let loc_end = crh.loc(); 441 | crh.disc(); 442 | (Ok("\x0b".to_string()), 2, loc_beg + loc_end) 443 | }, 444 | Some('f') => { 445 | let loc_end = crh.loc(); 446 | crh.disc(); 447 | (Ok("\x0c".to_string()), 2, loc_beg + loc_end) 448 | }, 449 | Some('r') => { 450 | let loc_end = crh.loc(); 451 | crh.disc(); 452 | (Ok("\r".to_string()), 2, loc_beg + loc_end) 453 | }, 454 | Some('x') | Some('d') => { 455 | let (escaped, len, loc_end) = self.pop_hex_escaped(crh); 456 | (escaped, len + 1, loc_beg + loc_end) 457 | }, 458 | Some(ch) => ( 459 | Err(EscapeSequenceError::InvalidFirstCharacter(ch)), 460 | 1, 461 | loc_beg, 462 | ), 463 | None => (Err(EscapeSequenceError::UnexpectedEof), 1, loc_beg), 464 | } 465 | } 466 | 467 | fn pop_hex_escaped( 468 | &mut self, 469 | crh: &mut CharReadingHead, 470 | ) -> (Result, usize, Loc) { 471 | let mut loc = crh.loc(); 472 | let mut len = 1; 473 | let base = { 474 | match crh.peek() { 475 | Some('x') => { 476 | crh.disc(); 477 | 16 478 | }, 479 | Some('d') => { 480 | crh.disc(); 481 | 10 482 | }, 483 | _ => unreachable!(), 484 | } 485 | }; 486 | let mut character_code = 0; 487 | if crh.peek() == Some('(') { 488 | loc += crh.loc(); 489 | len += 1; 490 | crh.disc(); 491 | loop { 492 | if let Some(ch) = crh.peek() { 493 | match ch.to_digit(base) { 494 | Some(digit) => { 495 | loc += crh.loc(); 496 | len += 1; 497 | crh.disc(); 498 | character_code = character_code * base + digit; 499 | }, 500 | None if ch == ')' => { 501 | loc += crh.loc(); 502 | len += 1; 503 | crh.disc(); 504 | break; 505 | }, 506 | None => { 507 | return ( 508 | Err(EscapeSequenceError::InvalidDigitCharacter(ch)), 509 | len, 510 | loc, 511 | ) 512 | }, 513 | } 514 | } else { 515 | return (Err(EscapeSequenceError::UnexpectedEof), len, loc); 516 | } 517 | } 518 | } else { 519 | for _ in 0..2 { 520 | if let Some(ch) = crh.peek() { 521 | match ch.to_digit(base) { 522 | Some(digit) => { 523 | loc += crh.loc(); 524 | len += 1; 525 | crh.disc(); 526 | character_code = character_code * base + digit; 527 | }, 528 | None => { 529 | return ( 530 | Err(EscapeSequenceError::InvalidDigitCharacter(ch)), 531 | len, 532 | loc, 533 | ) 534 | }, 535 | } 536 | } else { 537 | return (Err(EscapeSequenceError::UnexpectedEof), len, loc); 538 | } 539 | } 540 | } 541 | if let Some(ch) = std::char::from_u32(character_code) { 542 | (Ok(ch.to_string()), len, loc) 543 | } else { 544 | ( 545 | Err(EscapeSequenceError::InvalidUnicodeCodePoint(character_code)), 546 | len, 547 | loc, 548 | ) 549 | } 550 | } 551 | 552 | fn pop_comment_tok(&mut self, crh: &mut CharReadingHead) -> (Tok, Loc) { 553 | assert_eq!(crh.peek(), Some('#')); 554 | let mut loc = crh.loc(); 555 | let mut delimitation_thickness = 0; 556 | let mut comment_line = false; 557 | while let Some('#') = crh.peek() { 558 | delimitation_thickness += 1; 559 | loc += crh.loc(); 560 | crh.disc(); 561 | if crh.peek() == Some('!') && delimitation_thickness == 1 { 562 | comment_line = true; 563 | break; 564 | } 565 | } 566 | let delimitation_thickness = delimitation_thickness; 567 | let comment_line = comment_line; 568 | let mut content = String::new(); 569 | let mut no_end_hash_warning = false; 570 | loop { 571 | if let Some('#') = crh.peek() { 572 | let mut hashes_thickness = 0; 573 | while let Some('#') = crh.peek() { 574 | hashes_thickness += 1; 575 | loc += crh.loc(); 576 | crh.disc(); 577 | } 578 | if !comment_line && hashes_thickness == delimitation_thickness { 579 | break; 580 | } else { 581 | content.extend(std::iter::repeat('#').take(hashes_thickness)); 582 | } 583 | } else if let Some(ch) = crh.peek() { 584 | content.push(ch); 585 | loc += crh.loc(); 586 | crh.disc(); 587 | if comment_line && ch == '\n' { 588 | break; 589 | } 590 | } else { 591 | no_end_hash_warning = true; 592 | break; 593 | } 594 | } 595 | if comment_line { 596 | (Tok::CommentLine { _content: content }, loc) 597 | } else { 598 | ( 599 | Tok::CommentBlock { 600 | _content: content, 601 | _delimitation_thickness: delimitation_thickness, 602 | _no_end_hash_warning: no_end_hash_warning, 603 | }, 604 | loc, 605 | ) 606 | } 607 | } 608 | } 609 | 610 | pub(crate) struct TokBuffer { 611 | crh: CharReadingHead, 612 | tokenizer: Tokenizer, 613 | toks_ahead: VecDeque<(Tok, Loc)>, 614 | } 615 | 616 | impl TokBuffer { 617 | pub(crate) fn from(crh: CharReadingHead) -> TokBuffer { 618 | TokBuffer { 619 | crh, 620 | tokenizer: Tokenizer::new(), 621 | toks_ahead: VecDeque::new(), 622 | } 623 | } 624 | 625 | pub(crate) fn scu(&self) -> Rc { 626 | self.crh.scu() 627 | } 628 | 629 | fn tokenizer_pop_tok_no_comments(&mut self) -> (Tok, Loc) { 630 | loop { 631 | let (tok, loc) = self.tokenizer.pop_tok(&mut self.crh); 632 | if !matches!(tok, Tok::CommentBlock { .. } | Tok::CommentLine { .. }) { 633 | break (tok, loc); 634 | } 635 | } 636 | } 637 | 638 | pub(crate) fn prepare_max_index(&mut self, n: usize) { 639 | if self.toks_ahead.len() < n + 1 { 640 | self.toks_ahead.reserve(n - self.toks_ahead.len()); 641 | } 642 | while self.toks_ahead.len() < n + 1 { 643 | let (tok, loc) = self.tokenizer_pop_tok_no_comments(); 644 | self.toks_ahead.push_back((tok, loc)); 645 | } 646 | } 647 | 648 | pub(crate) fn peek(&mut self, n: usize) -> &(Tok, Loc) { 649 | self.prepare_max_index(n); 650 | &self.toks_ahead[n] 651 | } 652 | 653 | pub(crate) fn pop(&mut self) -> (Tok, Loc) { 654 | self.peek(0); 655 | let tok_loc_opt = self.toks_ahead.pop_front(); 656 | if let Some(tok_loc) = tok_loc_opt { 657 | tok_loc 658 | } else { 659 | panic!("bug: no token to pop") 660 | } 661 | } 662 | 663 | pub(crate) fn display_all(mut self, line_numbers: bool) { 664 | let mut last_line = 0; 665 | loop { 666 | let (tok, loc) = self.tokenizer.pop_tok(&mut self.crh); 667 | if line_numbers && last_line < loc.line_start { 668 | println!("Line {}:", loc.line_start); 669 | last_line = loc.line_start; 670 | } 671 | if line_numbers { 672 | print!("\t"); 673 | } 674 | match tok { 675 | Tok::InvalidCharacter(_) => println!("\x1b[31m{}\x1b[39m", tok), 676 | _ => println!("{}", tok), 677 | } 678 | if matches!(tok, Tok::Eof) { 679 | break; 680 | } 681 | } 682 | } 683 | } 684 | 685 | impl fmt::Display for Tok { 686 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 687 | match self { 688 | Tok::Op(Op::Plus) => write!(f, "operator +"), 689 | Tok::Op(Op::Minus) => write!(f, "operator -"), 690 | Tok::Op(Op::Star) => write!(f, "operator *"), 691 | Tok::Op(Op::Slash) => write!(f, "operator /"), 692 | Tok::Op(Op::ToRight) => write!(f, "operator >"), 693 | Tok::Op(Op::Comma) => write!(f, "operator ,"), 694 | Tok::Op(Op::DoubleComma) => write!(f, "operator ,,"), 695 | Tok::Op(Op::Dot) => write!(f, "operator ."), 696 | Tok::Op(Op::ToLeft) => write!(f, "operator <"), 697 | Tok::Op(Op::Bang) => write!(f, "operator !"), 698 | Tok::Left(Matched::Paren) => write!(f, "left parenthesis"), 699 | Tok::Left(Matched::Curly) => write!(f, "left curly bracket"), 700 | Tok::Left(Matched::Bracket) => write!(f, "left bracket"), 701 | Tok::Right(Matched::Paren) => write!(f, "right parenthesis"), 702 | Tok::Right(Matched::Curly) => write!(f, "right curly bracket"), 703 | Tok::Right(Matched::Bracket) => write!(f, "right bracket"), 704 | Tok::CommentBlock { .. } => write!(f, "comment block"), 705 | Tok::CommentLine { .. } => write!(f, "comment line"), 706 | Tok::Kw(kw) => write!(f, "keyword {}", kw), 707 | Tok::Integer(string) => write!(f, "integer {}", string), 708 | Tok::Name { string, .. } => write!(f, "name {}", string), 709 | Tok::String { content, .. } => { 710 | write!(f, "string \"{}\"", escape_string(content, &styles::NORMAL)) 711 | }, 712 | Tok::InvalidCharacter(c) => write!(f, "invalid character {}", c), 713 | Tok::Eof => write!(f, "end-of-file"), 714 | } 715 | } 716 | } 717 | 718 | impl fmt::Display for Kw { 719 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 720 | match self { 721 | Kw::Np => write!(f, "np"), 722 | Kw::Pr => write!(f, "pr"), 723 | Kw::Nl => write!(f, "nl"), 724 | Kw::Do => write!(f, "do"), 725 | Kw::Dh => write!(f, "dh"), 726 | Kw::Fh => write!(f, "fh"), 727 | Kw::Ev => write!(f, "ev"), 728 | Kw::If => write!(f, "if"), 729 | Kw::Th => write!(f, "th"), 730 | Kw::El => write!(f, "el"), 731 | Kw::Lp => write!(f, "lp"), 732 | Kw::Wh => write!(f, "wh"), 733 | Kw::Bd => write!(f, "bd"), 734 | Kw::Sp => write!(f, "sp"), 735 | Kw::Ao => write!(f, "ao"), 736 | Kw::Ri => write!(f, "ri"), 737 | Kw::Em => write!(f, "em"), 738 | Kw::Rs => write!(f, "rs"), 739 | Kw::Fi => write!(f, "fi"), 740 | Kw::In => write!(f, "in"), 741 | Kw::Ix => write!(f, "ix"), 742 | Kw::Cx => write!(f, "cx"), 743 | Kw::Cy => write!(f, "cy"), 744 | Kw::Wi => write!(f, "wi"), 745 | Kw::Od => write!(f, "od"), 746 | Kw::Os => write!(f, "os"), 747 | Kw::Ln => write!(f, "ln"), 748 | Kw::Gs => write!(f, "gs"), 749 | Kw::Ag => write!(f, "ag"), 750 | } 751 | } 752 | } 753 | -------------------------------------------------------------------------------- /sflk-lang/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub(crate) type Style = (&'static str, &'static str); 2 | 3 | pub(crate) mod styles { 4 | pub(crate) const NORMAL: super::Style = ("", ""); 5 | pub(crate) const NEGATIVE: super::Style = ("\x1b[7m", "\x1b[27m"); 6 | pub(crate) const CYAN: super::Style = ("\x1b[36m", "\x1b[39m"); 7 | pub(crate) const YELLOW: super::Style = ("\x1b[33m", "\x1b[39m"); 8 | pub(crate) const BLUE: super::Style = ("\x1b[34m", "\x1b[39m"); 9 | pub(crate) const UNDERLINE: super::Style = ("\x1b[4m", "\x1b[24m"); 10 | pub(crate) const BOLD_LIGHT_RED: super::Style = ("\x1b[91m\x1b[1m", "\x1b[22m\x1b[39m"); 11 | pub(crate) const BOLD: super::Style = ("\x1b[1m", "\x1b[22m"); 12 | } 13 | 14 | pub(crate) fn escape_string(string: &str, escape_style: &Style) -> String { 15 | let mut ret = String::new(); 16 | string.chars().for_each(|ch| { 17 | if let Some(escaped) = escape_character(ch) { 18 | ret.extend(format!("{}{}{}", escape_style.0, escaped, escape_style.1).chars()); 19 | } else { 20 | ret.push(ch); 21 | } 22 | }); 23 | ret 24 | } 25 | 26 | fn escape_character(ch: char) -> Option { 27 | match ch { 28 | // ASCII range escapes 29 | '\"' => Some(String::from("\\\"")), 30 | '\\' => Some("\\\\".to_string()), 31 | '\n' => Some("\\n".to_string()), 32 | '\t' => Some("\\t".to_string()), 33 | '\x1b' => Some("\\e".to_string()), 34 | '\x07' => Some("\\a".to_string()), 35 | '\x08' => Some("\\b".to_string()), 36 | '\x0b' => Some("\\v".to_string()), 37 | '\x0c' => Some("\\f".to_string()), 38 | '\r' => Some("\\r".to_string()), 39 | ch if (ch as u32) < (' ' as u32) => Some(format!("\\x{:02x}", ch as u32)), 40 | // Non-ASCII range espaces 41 | '�' => Some("\\?".to_string()), 42 | // Not to be escaped 43 | _ => None, 44 | } 45 | } 46 | 47 | pub(crate) struct StdoutWriter; 48 | 49 | impl std::fmt::Write for StdoutWriter { 50 | fn write_str(&mut self, string: &str) -> Result<(), std::fmt::Error> { 51 | print!("{}", string); 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl StdoutWriter { 57 | pub(crate) fn new() -> StdoutWriter { 58 | StdoutWriter {} 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sflk-lsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sflk-lsp" 3 | version = "0.1.0" 4 | authors = ["arthur correnson "] 5 | edition = "2018" 6 | readme = "README.md" 7 | repository = "https://github.com/sflk-lang/sflk" 8 | description = "SFLK language server" 9 | 10 | [dependencies] 11 | tower-lsp = "0.13.3" 12 | tokio = { version = "0.2", features=["full"] } -------------------------------------------------------------------------------- /sflk-lsp/src/completion.rs: -------------------------------------------------------------------------------- 1 | use tower_lsp::lsp_types::*; 2 | 3 | pub fn symb(name: &str) -> CompletionItem { 4 | CompletionItem { label: name.to_string(), ..Default::default() } 5 | } 6 | -------------------------------------------------------------------------------- /sflk-lsp/src/document.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sflk-lang/sflk/849f8b1571475e25f14017ff61d7d65bfd6fc40f/sflk-lsp/src/document.rs -------------------------------------------------------------------------------- /sflk-lsp/src/main.rs: -------------------------------------------------------------------------------- 1 | use tower_lsp::{ 2 | jsonrpc::Result, 3 | lsp_types::*, 4 | {Client, LanguageServer, LspService, Server}, 5 | }; 6 | 7 | mod completion; 8 | 9 | #[derive(Debug)] 10 | struct Backend { 11 | client: Client, 12 | } 13 | 14 | #[tower_lsp::async_trait] 15 | impl LanguageServer for Backend { 16 | async fn initialize(&self, _: InitializeParams) -> Result { 17 | Ok(InitializeResult { 18 | capabilities: ServerCapabilities { 19 | // We always sync documents 20 | text_document_sync: Some(TextDocumentSyncCapability::Kind( 21 | TextDocumentSyncKind::Full, 22 | )), 23 | // We know how to react on hover 24 | hover_provider: Some(HoverProviderCapability::Simple(true)), 25 | // We know how to provide completion 26 | completion_provider: Some(CompletionOptions { 27 | trigger_characters: Some(vec![ 28 | " ".to_string(), 29 | "=".to_string(), 30 | "\n".to_string(), 31 | ]), 32 | ..Default::default() 33 | }), 34 | ..Default::default() 35 | }, 36 | ..Default::default() 37 | }) 38 | } 39 | 40 | async fn initialized(&self, _: InitializedParams) { 41 | self.client 42 | .log_message(MessageType::Info, "sflk server initialized (uwu) !") 43 | .await; 44 | } 45 | 46 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 47 | let uri = params.text_document.uri; 48 | self.client 49 | .log_message(MessageType::Info, format!("Opening file {}", uri)) 50 | .await; 51 | } 52 | 53 | async fn did_change(&self, params: DidChangeTextDocumentParams) { 54 | let uri = params.text_document.uri; 55 | self.client 56 | .log_message(MessageType::Info, format!("Changing file {}", uri)) 57 | .await; 58 | } 59 | 60 | async fn shutdown(&self) -> Result<()> { 61 | Ok(()) 62 | } 63 | 64 | async fn completion(&self, _params: CompletionParams) -> Result> { 65 | Ok(Some(CompletionResponse::Array(vec![ 66 | completion::symb("pr"), 67 | completion::symb("nl"), 68 | completion::symb("do"), 69 | completion::symb("if"), 70 | completion::symb("redo"), 71 | ]))) 72 | } 73 | 74 | async fn hover(&self, params: HoverParams) -> Result> { 75 | let position = params.text_document_position_params; 76 | self.client 77 | .log_message( 78 | MessageType::Info, 79 | format!("hover at line {}", position.position.line), 80 | ) 81 | .await; 82 | Ok(Some(Hover { 83 | contents: HoverContents::Scalar(MarkedString::String(format!( 84 | "{}", 85 | position.position.line 86 | ))), 87 | range: None, 88 | })) 89 | } 90 | } 91 | 92 | #[tokio::main] 93 | async fn main() { 94 | let stdin = tokio::io::stdin(); 95 | let stdout = tokio::io::stdout(); 96 | 97 | let (service, messages) = LspService::new(|client| Backend { client }); 98 | Server::new(stdin, stdout) 99 | .interleave(messages) 100 | .serve(service) 101 | .await; 102 | } 103 | -------------------------------------------------------------------------------- /sflk-std/std.sflk: -------------------------------------------------------------------------------- 1 | 2 | # SFLK Standard Library, version 0.0.1 # 3 | 4 | # Prints the input followed by a newline. 5 | | Returns the input. # 6 | prnl! < { 7 | pr v nl 8 | } 9 | 10 | # Prints text to a file. 11 | | Takes an input of the form `content,, path` or `content,, path, mode` 12 | | and writes `content` to the file at `path` 13 | | using mode `mode` (or `.append` if mode is not provided). 14 | | Returns `content`. # 15 | prf! < { 16 | content! < v ix 0 17 | path! < v ix 1 18 | if os ln v. ,, 3 19 | th mode! < .append 20 | el mode! < v ix 2 21 | gs .writefile 22 | ag content 23 | ag path 24 | ag mode 25 | v < content 26 | } 27 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # SFLK tests 2 | 3 | Files in "tests/" are SFLK files that use various features of SFLK. 4 | These are not Rust tests and currently there is no way to tell if the tests 5 | are passing except by examining the output to see if it is the expected output. 6 | 7 | ## Run a test 8 | 9 | For example run `test4.sflk`: 10 | 11 | ```sh 12 | cargo run -- tests/test4.sflk 13 | ``` 14 | 15 | Adding `-d` to the arguments make the interpreter enable debug mode. 16 | -------------------------------------------------------------------------------- /tests/h.sflk: -------------------------------------------------------------------------------- 1 | 2 | b! < { 3 | pr nlhappened nl 4 | pr "owo" nl 5 | pr "uwu" nl 6 | pr nlhappened nl 7 | } 8 | 9 | # 10 | We wont touch b but we want 11 | its content to be printed 12 | only on one line, if any. 13 | # 14 | 15 | nlhappened! < 0 16 | do b wi { 17 | cy v 18 | if name - .newline el dh { 19 | nlhappened < 1 20 | pr " " 21 | } th 22 | em v rs v 23 | } 24 | if nlhappened th 25 | nl 26 | 27 | # 28 | Note: Does not yet scan for 29 | newline characters in print 30 | signals. 31 | # 32 | -------------------------------------------------------------------------------- /tests/rt.sflk: -------------------------------------------------------------------------------- 1 | 2 | ppm! < { 3 | i! < 0 4 | pr "P3" nl 5 | w! < (), .w > v h! < (), .h > v 6 | pr w pr " " pr h nl 7 | pr 255 nl 8 | y! < 0 lp wh os y,, h bd dh { 9 | x! < 0 lp wh os x,, w bd dh { 10 | color! < (), .c, x, y > v 11 | pr 0 > color pr " " 12 | pr 1 > color pr " " 13 | pr 2 > color nl 14 | x < x +1 15 | } 16 | y < y +1 17 | } 18 | } 19 | 20 | ev { 21 | w! < 256 / 2 h! < 256 / 2 22 | if 0 > v - .w el v < w th 23 | if 0 > v - .h el v < h th 24 | if 0 > v - .c el dh { 25 | x! < 1 > v y! < 2 > v 26 | r! < x * 256 / w 27 | g! < y * 256 / h 28 | b! < 63 29 | v < r,, g, b 30 | } 31 | } > ppm 32 | -------------------------------------------------------------------------------- /tests/test.sflk: -------------------------------------------------------------------------------- 1 | 2 | dh fi "sflk-std/std.sflk" 3 | 4 | ## heyyy ## 5 | ev 8 > prnl 6 | 7 | # uwu # 8 | -------------------------------------------------------------------------------- /tests/test3.sflk: -------------------------------------------------------------------------------- 1 | 2 | x! < 8 3 | lp 4 | wh x 5 | bd dh {pr x x < x-1} 6 | sp pr ", " 7 | nl 8 | 9 | # 10 | x < 2 11 | do { 12 | do { 13 | pr x'' +1 14 | } 15 | } 16 | # 17 | 18 | do({pr"u"}+{pr"w"}>{vblock 22 | pr x nl 23 | -------------------------------------------------------------------------------- /tests/test4.sflk: -------------------------------------------------------------------------------- 1 | 2 | np 3 | x! < 8 +2 /2 *3 -(2*3+1) 4 | ev x 5 | np np 6 | if x 7 | th dh {pr "h" nl} 8 | el np 9 | dh {if x th dh {pr "h" nl}} 10 | lp 11 | wh x 12 | bd dh {pr x x < x-1} 13 | sp pr ", " 14 | nl 15 | x < 2 16 | do { 17 | x! < 3 18 | if x-3 el dh {pr "h" nl} 19 | } 20 | if x-2 el dh {pr "h" nl} 21 | uwu! < "owo" 22 | pr uwu nl 23 | pr 4 > {v < v + 4} nl 24 | pr "yes" nl 25 | do {pr "no" nl} wi {v < 0} 26 | replace! < "jej" 27 | do {pr "not jej"} 28 | wi {pr replace nl} 29 | uwu < () ,"a" ,"b" ,"c" 30 | x < 0 lp wh x -3 bd dh {pr uwu ix x x < x +1} sp pr ", " nl 31 | pr "o" + "k" nl 32 | if "hh" - "hh" th pr "no" el pr "yes" nl 33 | do {pr "h"} wi {cy v pr "\e[33m" pr value pr "\e[39m" nl} 34 | do {pr "normal" nl} 35 | not! < {if v th v < 0 el v < 1} 36 | pr 3 >not pr ", " pr 0 >not nl 37 | streq! < {v < v ix 0 - (v ix 1) >{v<1-v}} 38 | pr (), "heh", "heh" >streq pr ", " pr (), "heh", "xxx" >streq nl 39 | do {pr "h" nl} wi { 40 | cy v 41 | if (), name, "print" >streq 42 | th em () > {name! < name value! < ("\e[33m" + value + "\e[39m") v < cx} rs v 43 | el em v rs v 44 | } 45 | semicolons! < { 46 | cy v 47 | if (), name, "newline" >streq 48 | th dh {pr ";" em v rs v} 49 | el em v rs v 50 | } 51 | do {pr "int x" nl} 52 | wi semicolons 53 | double! < {v < v + v} 54 | pr 4 > double pr ", " pr "h" > double nl 55 | quad! < double > double # Can't do that in Rust huh # 56 | pr 4 > quad pr ", " pr "h" > quad nl 57 | triple! < {v < v * 3} 58 | pr 4 > triple pr ", " pr "h" > triple nl 59 | neuf! < triple > double 60 | pr 4 > neuf pr ", " pr "h" > neuf nl 61 | pr "hhh" / "h" nl 62 | pr "hah" / "h" nl 63 | if 0 el pr "h" th pr "no" el pr "ey" el nl th nl 64 | do {name! < "print" value! < "hey\n" em cx} 65 | dh "pr \"owo\" nl" 66 | do "pr \"int y\" nl" wi semicolons 67 | fun! < "v < \"v < \\\"np\\\" \" + v" 68 | lp wh fun>{pr v nl}-"np" bd fun < fun > fun 69 | pr ((8)) nl 70 | dh fi "sflk-std/std.sflk" 71 | ev "jej" >prnl 72 | do { 73 | dh fi "some_file.sflk" 74 | pr 5 >fun nl 75 | } 76 | wi { 77 | cy v 78 | if name -"readfile" el dh { 79 | v < "fun < {v < v +3}" 80 | } th dh { 81 | em v rs v 82 | } 83 | } 84 | x < "\n" 85 | # x < in # 86 | pr "u typed: " pr x 87 | x < 0 lp bd pr x sp pr ", " bd x < x +1 wh x -8 nl 88 | lp wh 0 ao bd pr "h" bd nl 89 | x < {cy v if name -"readfile" th em v rs v el v < value + "|"} 90 | do {pr fi "uwu" + "owo" nl} wi x # (fi "uwu" + "owo") # 91 | do {pr fi "uwu". + "owo" nl} wi x # (fi "uwu") + "owo" # 92 | pr -1 nl 93 | pr -1+1+1 nl 94 | pr -1+1.+1 nl 95 | pr 3 > ("a",, "b", "c", "d", "e") nl 96 | inf! < 3 sup! < 6 97 | x<0 lp wh x-10 bd if od inf,,x,sup th pr x el pr"_"sp pr", "bd x prspchrs nl 103 | pr .a nl 104 | do {x! < "yes" pr .x > cx nl} 105 | pr 111111111 * 111111111 nl 106 | pr 100000000000000000000000000000000000000000000000 -1 /9 nl 107 | #! line comment # still line comment 108 | pr "after line comment" nl 109 | pr (1/10) + (1/10) + (1/10) nl 110 | if ((1/10) + (1/10) + (1/10)) - (3/10) el dh {pr "no float bs" nl} 111 | gs .testgs ag .a ag .b rs x pr x nl 112 | testfile! < "test-output/test4.out.txt" 113 | gs .writefile ag "hello file\n" ag testfile ag .append 114 | do {gs .writefile ag "gaming" ag testfile ag .overwrite } wi { 115 | cy v 116 | if name - .writefile el dh { 117 | pr value pr " " pr path pr " " pr mode nl 118 | } th em v rs v 119 | } 120 | x < "abcdefgh" ev x ix (ln x. - 1) ,, testfile > prf + "\n" ,, testfile > prf 121 | ev 8/3 > {cy v pr n pr "/" pr d pr " " pr v nl} 122 | -------------------------------------------------------------------------------- /tests/test5.sflk: -------------------------------------------------------------------------------- 1 | 2 | interceptoryellow! < { 3 | # Deploy the signal description context. 4 | | When an interceptor is run on an intercepted signal, 5 | | it is run in a sub context (of the parent that set it in place) 6 | | and the v variable is initialized to the signal. 7 | | Build-in signals should be contexts that can be deployed 8 | | with the `cy context` statement (it will define the variables 9 | | of that context into the current context). # 10 | cy v 11 | # Signals should contain a `name` variable that is a string 12 | | of which some values make the signal do specific actions. 13 | | Here we check if that name is "print" (`a - b` is 0 if 14 | | `a` is equal to `b`, so `1 - (a - b)` is 1 (true) if `a` 15 | | is equal to `b`). 16 | | The syntax `.print` is a dotted string literal that is 17 | | exactly like `"print"` but it looks a bit cooler. # 18 | if 1 -(name -.print) 19 | th 20 | # We intercepted a print signal. 21 | | Print signals should contain a `value` variable that is the object 22 | | that was given to the print statement. 23 | | Here, we print this value but in yellow. # 24 | dh {pr "\e[33m" pr value pr "\e[39m"} 25 | el 26 | # We did not intercept a print signal. 27 | | Here we re-emit the signal and transmit its result. 28 | | This is what an interceptor shoud do if it 29 | | does not want do change the behavior of the 30 | | intercepted signal. # 31 | em v rs v 32 | } 33 | 34 | # This first printed message should be normal as it is 35 | | not intercepted in any way. # 36 | pr "uwu" nl 37 | 38 | # This second printed message should be yellow as it is 39 | | intercepted by the `interceptoryellow` interceptor 40 | | that will intercept every signal emitted during the execution 41 | | of the block given to that `do` statement. # 42 | do {pr "owo" nl} wi interceptoryellow 43 | 44 | # This third printed message should be normal as it is 45 | | not intercepted. The interceptor set by the `wi` extention 46 | | of a `do` statement is only effective for that `do` statement. # 47 | do {pr "awa" nl} 48 | 49 | # This forth printed message should be yellow as it is 50 | | intercepted. Signals travels through the context tree 51 | | towards the root (after which is the interpreter) and 52 | | any interceptor on the way will intercept them. # 53 | do {do {do {pr "owo" nl}} wi interceptoryellow} 54 | -------------------------------------------------------------------------------- /tests/test6.sflk: -------------------------------------------------------------------------------- 1 | 2 | # Indentation level # 3 | lvl! < 0 4 | 5 | # Is the current line indented yet # 6 | indented! < 0 7 | 8 | # Perform the indentation # 9 | indent! < { 10 | x! < 0 lp wh x -lvl bd pr "| " bd x < x +1 11 | } 12 | 13 | interceptor! < { 14 | cy v 15 | handled! < 0 16 | # Intercepted signals # 17 | if name -.newline el dh { 18 | nl 19 | indented < 0 20 | handled < 1 21 | } 22 | if name -.print el dh { 23 | if indented el dh indent 24 | pr value 25 | indented < 1 26 | handled < 1 27 | } 28 | # Custom signals handling # 29 | if name -.indent el dh { 30 | lvl < lvl +1 31 | handled < 1 32 | } 33 | if name -.unindent el dh { 34 | lvl < lvl -1 35 | handled < 1 36 | } 37 | # Let the rest pass through # 38 | if handled el em v rs v 39 | } 40 | 41 | demo! < { 42 | # Custom signals emitters # 43 | indent! < {em ()>{name! < .indent v < cx}} 44 | unindent! < {em ()>{name! < .unindent v < cx}} 45 | 46 | # Test # 47 | pr "lvl 0" nl 48 | dh indent 49 | pr "lvl 1" nl 50 | dh indent 51 | pr "lvl 2" nl 52 | pr "lvl 2" nl 53 | dh unindent 54 | pr "lvl 1" nl 55 | dh indent 56 | dh indent 57 | pr "lvl 3" nl 58 | dh indent 59 | pr "lvl 4" nl 60 | dh unindent 61 | dh unindent 62 | dh unindent 63 | pr "lvl 1" nl 64 | dh unindent 65 | pr "lvl 0" nl 66 | } 67 | 68 | do demo wi interceptor 69 | -------------------------------------------------------------------------------- /tests/test7.sflk: -------------------------------------------------------------------------------- 1 | x! < 12 2 | lp wh x bd pr x sp pr ", " bd x < x - 1 nl 3 | x < {pr v nl} 4 | v! < "uwu" dh x 5 | 6 | do { 7 | x! < "a",, "b", "c", "d", "e" 8 | idx! < 0 lp wh idx - 5 bd pr x ix idx sp pr ", " bd idx < idx + 1 nl 9 | } wi { 10 | pr "\e[32m" 11 | em v rs v 12 | pr "\e[39m" 13 | } 14 | -------------------------------------------------------------------------------- /tests/test9.sflk: -------------------------------------------------------------------------------- 1 | 2 | # Test the ! syntax # 3 | x! < 8 4 | do { 5 | pr x nl 6 | x < 42 7 | x! < 13 8 | pr x nl 9 | x < 0 10 | pr x nl 11 | } 12 | pr x nl 13 | 14 | # Test the v declaration # 15 | pr 69-42 > {v < v + x} nl 16 | 17 | # Test the cx/cy keywords # 18 | map! < ()>{ 19 | uwu! < "u" 20 | owo! < "o" 21 | awa! < "a" 22 | v < cx 23 | } 24 | do { 25 | cy map 26 | vwv! < {v < v + "w" + v} 27 | pr uwu > vwv pr " " 28 | pr owo > vwv pr " " 29 | pr awa > vwv nl 30 | } 31 | 32 | # Test wi extention # 33 | var! < "var" 34 | do { 35 | pr "jej" nl 36 | pr var nl 37 | } wi { 38 | cy v 39 | pr "[" pr name pr "]" 40 | em v rs v 41 | } 42 | -------------------------------------------------------------------------------- /tests/test91.sflk: -------------------------------------------------------------------------------- 1 | 2 | # Test signals as contexts # 3 | uwu! < "owo" 4 | do { 5 | pr "jej" nl 6 | pr uwu nl 7 | uwu < "awa" 8 | pr uwu nl 9 | } wi { 10 | cy v 11 | newline! < 0 12 | reemit! < 1 13 | pr "[" 14 | if name - .readvar el dh { 15 | pr "read var " pr varname 16 | } th 17 | if name - .writevar el dh { 18 | pr "write " pr value pr " in var " pr varname 19 | } th 20 | if name - .print el dh { 21 | pr "print " pr value 22 | reemit < 0 23 | } th 24 | if name - .newline el dh { 25 | pr "newline" 26 | reemit < 0 27 | newline < 1 28 | } th 29 | dh { 30 | pr name 31 | } 32 | if reemit th dh { 33 | em v rs v 34 | pr " -> " pr v 35 | } el dh { 36 | pr " -> " 37 | } 38 | pr "]" 39 | if newline th nl 40 | } 41 | -------------------------------------------------------------------------------- /tests/test92.sflk: -------------------------------------------------------------------------------- 1 | 2 | # TODO # 3 | cy "test" 4 | -------------------------------------------------------------------------------- /tests/test93.sflk: -------------------------------------------------------------------------------- 1 | 2 | pr 2 / 5 nl 3 | pr 5 / 2 nl 4 | 5 | pr -2. / 5 nl 6 | pr 2 / -5. nl 7 | pr -2. / -5. nl 8 | 9 | pr 3 / 9 nl 10 | pr 100 / 25 nl 11 | 12 | pr (2/5) + 1 nl 13 | pr 123456789/987654321 + 1 nl # 123456790/109739369 # 14 | 15 | # Print a few numbers of the form 1/2^n # 16 | x! < 1 lp bd pr x sp pr ", " sp x < x / 2 wh os 1/999,, x nl 17 | -------------------------------------------------------------------------------- /tests/test94.sflk: -------------------------------------------------------------------------------- 1 | 2 | gs .writefile 3 | ag "hello file\n" 4 | ag "test-output/test94.out.txt" 5 | ag .append 6 | -------------------------------------------------------------------------------- /tree.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Use the `tree` command to print a representation of the 4 | # project file structure (omitting irrelevant directories). 5 | 6 | tree --dirsfirst -a -I "target|local|.git|.vscode" --noreport 7 | --------------------------------------------------------------------------------