├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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