diff options
author | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-10-23 18:14:08 +0300 |
---|---|---|
committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-10-23 18:25:23 +0300 |
commit | 2b75864f9513642f306c9a217d8042bb8688d7f7 (patch) | |
tree | 2a2cf9031fe4ec397bc9924e02f3ba95b21a8fb4 | |
parent | fa5ebaa20327d0569cd5337a6b5fd22792b00ed1 (diff) | |
download | lyn-2b75864f9513642f306c9a217d8042bb8688d7f7.tar.gz lyn-2b75864f9513642f306c9a217d8042bb8688d7f7.zip |
update README
-rw-r--r-- | README.md | 181 |
1 files changed, 85 insertions, 96 deletions
@@ -1,111 +1,100 @@ # Lyn -Old swedish for lönn. Pardon my ramblings, this is an initial document to -collect my thoughts about what I might want from this project. - -## Concept - -Tcl is cool, but no quite enough like Lisp, right? This is my toy embeddable -scripting language, takes heavy cues from Tcl but goes more in the Lisp -direction by not requring `$` in front of everything and having a type system -(though more inspired by ML with implicit type inference. Though the type -inference thing might be implemented a bit later due to it possibly being a bit -too heavy for a simple scripting language, but we'll see. - -This does mean that the language is **not** suited quite as well for user -interfacing stuff as Tcl (shells, prompts, etc) but might work decently well as -an alternative to Lua (games, extensible editors, etc). I don't think people who -enjoy Lisp will like this language as I'm going for a lot more imperative -features, like in Tcl. - -In an ideal world, I think we would have three languages: -1. A compiled language for doing the heavy lifting of a program -2. A light scripting language for exposing functionality within a program -3. A (light?) shell language for user interfacing - -Tcl tries to do 2 and 3, and I personally think it achieves 3 very well. It's -all about strings, which makes it easy and fast to use as a user interface, but -this means that as a scripting language there's quite a bit of extra linenoise -with `$` and some amount of mental overhead of keeping track of what a given string -really tries to represent (is it an ad-hoc type? A class? Is it just a number?) -This makes Tcl more difficult to deal with when the scripts grow in size beyond -some (fairly small?) limit. You could of course argue that Tcl wasn't meant to -be used to write these huge programs in and it's the programmers fault, but -it will happen and I think a scripting language should still try to make that -case as painless as possible. - -Speed probably shouldn't be the be-all end-all factor when designing a scripting -language, but Tcl was for a very long time considered a *slow* language, which -didn't earn it any favors. Even Python has an on-going performance improvement -project, so I think speed should at least be considered when designing the language. - -## Syntax - -As in Tcl, everything is just a bunch of commands. +Old swedish for maple. -``` -cmd [arg1 [arg2 [arg3 [...]]]] -``` +## Scheme is cool + +...but has too many parentheses. -Sort of similarly to Tcl, curly brackets, `{}`, are used for grouping together -commands but not executing them right away. Parentheses, `()`, group together -commands and evaluate them immediately, producing a value. But here we already -run into some differences between lyn and Tcl: +## TCL is cool (yes, it really is) -+ Values can be of different types, `int`, `double`, `string`, etc. -+ Procedures can **only** take values. Command groupings can only be passed to - special `syntax` 'macros'. +...but littering `$` all over the place is annoying. -Here's an example (still up for change but let's go with this for now): +## Can we build something in-between? + +Maybe. `lyn` is effectively a pre-processor for GNU Guile, adopting TCL's +'everything is a command' attitude while keeping Scheme semantics. + +Let's start with an example, `examples/guile.lyn`: ``` -syntax for {init cond post body} { - eval init - while (eval cond) { - eval body - eval post +define-syntax-rule (for init condition post body) { + expand init + do () ((not (expand condition))) { + expand body + expand post } } - -def sum {n} { - require n int - let s 0 - for {let i 0} {< i n} {set i (+ i 1)} { - set s (+ s i) +define (sum n) { + define s 0 + for (define i 0) (< i n) (set! i (1+ i)) { + set! s (+ s i) } - return s + get s } + +display (sum 1000000) +newline +``` + +If you're familiar with Scheme, this should look eerily familiar. +Effectively, a command starts with a thing to apply, follows with some arguments +and ends with a newline or `;`. `()` applies a command to be used as an +argument, and `{}` specifies a list of commands. The top-level can also be +thought of as a block of commands. + +When encountering a `{`, a matching `}` must be found before the command can be +finished. `{}` can itself contain newlines just fine. + +A command can also be continued over multiple lines by using `\`, as in +`examples/multiline.lyn`: +``` +if (< 10 20) \ + {display "math works"; newline} \ + {display "what"; newline} ``` -They look kind of similar, but note that `for` is a `syntax` 'macro'. -Effectively, macros can only take command groupings and create a new grouping -that then later gets executed. Note that the groupings passed to `for` contain `()`, -which are executed multiple times. - -(`syntax` 'macros' can also be defined in C, and presumably a lot of the looping -constructs will be defined that way for a start) - -The `let` command defines a new variable, `set` mutates it. - -## Some implementation ideas - -I'm currently considering letting the C api define variadic functions, but -disallowing them from the scripting language (to start with, at least). -The thing is that the C api can fairly easily define a type checking function -that runs over the arguments as a list once we know how many arguments there -are, whereas that is a lot more difficult to do in the script itself. Maybe a -kind of ugly imbalance? - -`syntax` is visually just a procedure, but encountering one in the code probably -requires a bit of extra handling. I'll have to dig into the exact details, but a -syntax macro takes the command groups as they are and then can evaluate them -multiple times in the containing context. - -Eventually I might like to implement this thing on top of my `ejit` engine, -still unsure about some details but presumably this would mean that the most -common functions should be 'pattern matched' and some equivalent bytecode be -emitted, like `for/while/if` and mathematical operations etc. with slow paths as -backups, I suppose? The main issue here is how `syntax` should be handled, if -each `proc` is equivalent to one bytecode function should each `syntax` be -inlined? How complex would that be? +There are two 'builtins' beyond what GNU Guile provides (all GNU Guile functions +can be called as commands), namely `get` and `expand`. They're effectively the +identity commands, i.e. `get` returns the value that was passed into it, and +`expand` expands a macro as if it didn't exist. These are useful because, again, +*literally* everythin is a command, so if you want to return a value you can't +just write `arg` as the last element of a `(begin ...)` or whatever, because +that value will be interpreted as a command. Same thing with macros. +Note that `values` is effectively the same thing as `get`, but I think `get` has +a nicer symmetry with `let`/`set`. `macroexpand` is (maybe somewhat +surprisingly) not equivalent to `expand`, as `macroexpand` can't handle +`(define ...)`. + +Since `;` ends commands, `#` is used for comments instead. + +The current implementation just outputs all code into `/tmp/output.scm`. +Terrible, I know, but good enough to start playing around with it. +Have a look at that file to see just how closely this syntax matches Scheme, +while (IMO) being easier to read. In particular I find this style to be easier +to write, as I sometimes find that I have to count how many parentheses deep +some expression is. I'm aware that there are tools that help with this, but I +tend to want to have as little between me and my code. + +## Drawbacks + ++ Code generation is arguably not as trivial as with S-expressions. Ideally + traditional Scheme functions could be interleaved with this syntax, though I + haven't really looked into this option. + ++ Certain coding styles (like placing `{` on a new line) are more clumsy to use + ++ The current implementation parses `()` with multiple commands but produces + incorrect Scheme. I'm not sure whether to fix the outputting or disallow + multiple commands within `()` (that's what `{}` is for) + +## Isn't this all a bit silly? + +Yes. + +Schemers are presumably used to the parentheses and would prefer them over this, +while the TCL crowd likely don't like the shift away from user interfaces. I +don't expect anyone to really have any interest in this thing, but if I'd get to +choose I would probably want to use a scripting language like this over any +other current alternatives, of which there are many excellent ones. |