README, callp

This commit is contained in:
Daniella / Tove 2023-02-20 14:33:30 +01:00
parent b9eae2bbc4
commit b5df29f7a5
Signed by: TudbuT
GPG key ID: 7D63D5634B7C417F
4 changed files with 207 additions and 2 deletions

175
README.md Normal file
View file

@ -0,0 +1,175 @@
# "Stack Programming Language" =SPL
SPL is a simple, concise, concatenative scripting language.
Example:
```js
func main { mega |
"Running with args: " print
argv:iter
{ str | " " concat } swap:map
&print swap:foreach
"" println
}
```
## "5 minutes" SPL:in
- `def` introduces a variable.
```js
def a
```
- Writing a constant pushes it to the stack. This works with strings and numbers.
```js
"Hello, World!"
```
- Use `=<name>` to assign the topmost value to a variable. In this case, that is
"Hello, World!"
```js
=a
```
- This can be written as a single line - line breaks are always optional, and
equal to a space.
```js
def a "Hello, World!" =a
```
- Variables consist of two functions: `<name>` and `=<name>`. Use `<name>` to
obtain the value again.
```js
a
```
- The `print` function is used to print a value. It takes one value from the stack
and prints it without a newline. To print with a newline, use `println`. The
semicolon at the end means 'if this function returns anything, throw it away'.
This can be used on strings to make them comments, but is not available for
numeric constants.
```js
println;
```
```txt
Hello, World!
```
- The `func` keyword introduces a function. The `{ mega |` is the return type
declaration, which in SPL is done within the block. In this case, our function
returns one of the `mega` type, which is a 128-bit integer.
```js
func main { mega |
```
- Now, we can write code like before:
```js
def list
```
- SPL has a varying-length array type, the list. To create any construct (object),
we use `:new`.
```js
List:new =list
```
- To add to the end of a list, we `push` to it. All construct methods are
written with a colon, like before in the `new` example.
```js
"Hello," list:push
```
Note the lowercase `list`, because we are pushing to the construct in the
variable.
- Now, let's also push "World!".
```js
"World" list:push
```
Beautiful. I'd like to print it now, but how?
- We can't print a list directly (with what we know so far), but we can iterate
through it!
```js
{ | with item ;
item print;
" " print;
} list:foreach;
"" println;
```
**There is a lot to unpack here!**
- `{ |` creates a closure with no return type (in C-style languages, that'd be
a void function).
- `with item ;` declares arguments. This is optional, and not needed if the
function does not take arguments. Running `"a" "b" "c"` and calling
something with a b c ; will leave each letter in the corresponding variable.
- We already know what print does - it prints the item and a space in this
case.
- The semicolons mean we don't care about the result of printing. In this
case, printing does not return anything, but I added the semicolons just for
clarity or in case it did.
- `}` ends the closure, and puts it on the top of our stack.
- `list:foreach` calls the `foreach` method on our `list`, which is declared
with callable this ; - that means we need to provide one argument along with
the implied `this` argument (it can have any name - the interpreter does not
care about names in any way - `this` is just convention). The `callable`
here is *not* a type!
- `foreach` also does not return anything, but I added the semicolon for
clarity.
- We then print a newline.
```txt
Hello, World!
```
- SPL has Ranges, constructed using `<lower> <upper> Range:new`. You can iterate
over them.
```js
0 5 Range:new:iter
```
- Now, let's multiply all of these values by 5.
```js
{ mega | 5 * } swap:map
```
Wait, what?
Why is there suddenly an inconsistency in method calls, the iterator isn't
being called, it's something else now!
It sure does look like it, doesn't it? `swap` swaps the topmost two values on
the stack. `a b -> b a`. That means we are actually calling to our iterator.
The closure and the iterator are swapped before the call is made. `swap:map`
is a more concise way of writing `swap _:map`. The underscore is used as a
placeholder for the topmost value of the stack when making method calls,
because `swap :map` would try to call map on an empty expression.
The map function on the iterator (which is available through `:iter` on most
collection constructs) is used to apply a function to all items in the
iterator. The closure here actually takes an argument, but the with
declaration is omitted. The longer version would be:
```js
{ mega | with item ;
item 5 *
}
```
But this is quite clunky, so when arguments are directly passed on to the next
function, they are often simply kept on the stack. The `*` is simply a
function taking two numbers and multilying them. The same goes for `+`, `-`,
`%`, and `/`. `a b -` is equivalent to `a - b` in other languages. `lt`,
`gt`, and `eq` are used to compare values.
Returning is simply done by leaving something on the stack when the function
exits, and the return declaration *can* technically be left off, but the
semicolon won't be able to determine the amount of constructs to discard that
way, so this should never be done unless you're absolutely sure. In this case,
we are absolutely sure that it will never be called with a
semicolon, because the mapping iterator has no use for the closure other than
the returned object (which is the case for most closures in practice.),
therefore we could even omit the return type declaration and get `{ | 5 *}`.
Neat!
- We can use `foreach` on iterators just like arrays. `_str` is used to convert
a number to a string.
```js
{ | _str println } swap:foreach
```
```txt
0
5
10
15
20
```
Ranges are inclusive of the lower bound and exclusive in the upper bound.
They are often used similarly to the (pseudocode) equivalent in other
languages:
```java
for(int i = 0; i < 5; i++) { println((String) i * 5); }
```
More of this tutorial to follow.

View file

@ -1022,7 +1022,6 @@ impl Words {
name: s + &x,
}));
}
stack.pop();
stack.push(f.spl());
} else {
stack.call(&f)?;

View file

@ -236,6 +236,19 @@ pub fn star(stack: &mut Stack) -> OError {
Ok(())
}
pub fn percent(stack: &mut Stack) -> OError {
let b = stack.pop().lock_ro().native.clone();
let a = stack.pop().lock_ro().native.clone();
stack.push(
match (a, b) {
(Value::Mega(a), Value::Mega(b)) => Value::Mega(a % b),
_ => todo!(),
}
.spl(),
);
Ok(())
}
pub fn to_int(stack: &mut Stack) -> OError {
let o = stack.pop().lock_ro().native.clone();
stack.push(
@ -410,6 +423,17 @@ pub fn call(stack: &mut Stack) -> OError {
stack.call(&a)
}
pub fn callp(stack: &mut Stack) -> OError {
let Value::Func(a) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("callp".to_owned()))
};
stack.call(&a)?;
for _ in 0..a.ret_count {
stack.pop();
};
Ok(())
}
pub fn trace(stack: &mut Stack) -> OError {
let trace = stack.trace();
stack.push(Value::Array(trace.into_iter().map(|x| Value::Str(x).spl()).collect()).spl());
@ -578,7 +602,7 @@ pub fn readln(stack: &mut Stack) -> OError {
pub fn register(r: &mut Stack, o: Arc<Frame>) {
type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 42] = [
let fns: [(&str, Fn, u32); 44] = [
("pop", pop, 0),
("dup", dup, 2),
("clone", clone, 1),
@ -601,6 +625,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("-", minus, 1),
("/", slash, 1),
("*", star, 1),
("%", percent, 1),
("_int", to_int, 1),
("_long", to_long, 1),
("_mega", to_mega, 1),
@ -609,6 +634,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("_array", to_array, 1),
("_str", to_str, 1),
("call", call, 0),
("callp", callp, 0),
("trace", trace, 1),
("mr-trace", mr_trace, 1),
("exit", exit, 0),

View file

@ -93,6 +93,11 @@ func main { int | with args ;
"}" println
"" println
"Running with args: " print
argv:iter
{ str | " " concat } swap:map
&print swap:foreach
"" println
100
}