Skip to content

Essentials

Sofia edited this page Jun 6, 2023 · 16 revisions

Rewind jump start!

Rewind is a powerful shell/programming language mix. With Rewind you have the full power of a shell and a LISP!
The next sections document some basic features of the language which have more or less consolidated, so you can count on them not changing much, if at all.

First steps into the language

Rewind is essentially a LISP with less parens and a little more syntax eyecandy. Your first steps in learning a new language will probably be printing, but in Rewind that's not very interesting, it's just a print function call after all. Instead, I want to show you how to do more rather basic stuff, but that will get you really started on the language's idioms.
First off, every line that starts with a # is a comment. It will not be executed by the interpreter, You can not escape a comment without possibly running into a lexer error.

# all three statements print 3 on screen
+ 1 2
+ 1 1 1
* 3 1
# also prints 3
+ 1 2 (- 3 (/ 6 2))

Compare the above statement with the second mathematical solution to the expression, for clarity:
$1 + 2 + (3 - (6 / 2)) = 1 + 2 + (3 - 3) = 1 + 2 + 0 = 1 + 2 = 3.$

Rewind can also do comparisons:

# true
= 1 1
# false
= 1 2
# false
and (= 1 1) (= 1 2)
# true
or (= 1 1) (= 1 2)

There is also a not operator, a != (not equal) operator, and many more (refer to procedures.hpp for the whole list of builtin Rewind operators).

Conditionals

Comparisons are effectively useless if one does not know at least one way to use them, so here's one (more are planned and will be added in the future):

Conditional lambdas

Generic lambda functions will be introduced later, but for now i'd like to focus on some other lambda-like language construct which is to be preferred for a two-way (only two branches based on a single condition) conditional. The syntax is as follows:
(args...) cond(args...) ?=> if_true(args...), if_false(args...);
(note that the condition is to be put in round brackets for this to work, also the semicolon is part of the expression, as it terminates the lambda body.) args... can be any number of formal parameters.
cond(args...) can be any primitive or complex Rewind conditional expression, either as a function of args... or constant, or dependant on some other variable outside of the lambda.
if_true(args...) and if_false(args...) are both any valid Rewind expression, either as a function of args... or not (you are allowed to reference variables outside of the lambda function, since it automatically captures its enclosing scope).

some examples of conditional lambdas include (assuming a function f has been defined, with arguments g and x, and it will just call g with x as an argument):

# prints 7
f (x) (= x 0) ?=> + x 1, + x 2; 5
# also prints 7
f (x) (!= x 0) ?=> + x 2, + x 1; 5

# assuming that the variable a exists, this will print the result of 5 + (the value of a)
f (x) (!= x 0) ?=> + x $a; 5

# conditional lambdas with multiple arguments are supported, but 
# since our function only takes an argument, this will throw an error about the expected arity of f.
f (x y) (!= x y) ?=> + x y, - x y; 5 6

A side note: conflict resolution

You may have found yourself in the situation in which you have the need to return a variable, or otherwise an identifer of some sort, from a conditional lambda (or, really, any lambda, or any block of code at all). Given the Rewind parser, every identifier defaults to a function call, so you'll probably find yourself with an identifier in a list, which you in turn have to unwrap yourself, and that's ugly. Rewind supports special syntax for this scenario:

(x) (= x "yo") ?=> s+ x "yo", @x; "not a yoyo" the @ (reference operator) is there as a placeholder. it tells the Rewind parser that the particular token being considered is not to be treated as a function call, and can be thus returned safely without affecting the program logic. It is then immediately removed, and the argument is evaluated to yield (in this case) "not a yoyo" to be used outside of the function. If there's no fitting resolution (i.e. the symbol is not bound to anything), unexpected things can occur, caused by the fact that you have a dangling identifier (not a string) around. No error will be reported if the reference operator is used with an invalid identifier, so take extra care in where you decide to use it!

Communicating with the outside world

Clearly, no shell is useful if it can't first execute stuff, fetch stuff, and make stuff communicate. We'll take a look at each of these things in this order.

Executing external commands

Suppose you have the problem to list all the files in a directory. Yes, you could (presumably?) write it all in Rewind and call your shiny new recursive function, or you can just...
ls
It's this easy.
In general, you can call outside programs in much the same way you would call builtins, with little lexical differences. Clearly, you can also supply arguments to programs:
ls -al
You can also assign the result of a program call to a variable, or otherwise put it anywhere a variable could go, such in fucntion calls or builtin operators and so on. The expressions
let x (ls -al);
= (echo "ciao") "ciao";
are all valid, and the second yields true.
But what if the program needs some special environment variables? You can use a special syntax for that:
((VAR1 VAL1) (VAR2 VAL2) ... (VARN VALN) cmd args...)

Fetching and setting environment variables

get VAR is the Rewind equivalent of the bash $ operator for environment variables only!
set VAR sets the corresponding environment variable. Please note, Rewind does have a $ operator, but it's used to reference variables once they have been defined, if the scope you're in knows of such a variable.

Pipes

Executing one program is nice enough but what about two? Hell, what about three?
In fact, Rewind supports arbitrary pipes with how many programs you wish for, all with an elegant syntax:
-> (ls -al) (grep src) Will execute grep src on the output of ls -al.
You can obviously pass environment variables to the single commands in the pipes, like so:
-> ((A "B") ls -al) ((C "D") grep src)
And you may as well use pipes to assign their final output to variables, pass them as arguments to functions, etc.

A side note: redirection

Rewind has support for append-mode redirection to files with the > operator for external commands: > (ls -al) file.txt > (-> (ls -al)) file.txt The above statements are perfectly equivalent. Yes, you can technically make pipes of a single command, why do you ask?
overwrite-mode redirection is also permitted: >> (ls -al) file.txt

Naming stuff: variables

Rewind supports naming special values to give them meaning and thus referring to them later with their name instead of a "magic number" you have to guess the significance of. These are called variables (because they can change!).
We saw an example earlier, so let me refresh your memory. This is a variable declaration:
let x ls -al;
The keyword to use is let and it's followed by your chosen name and the value you want to refer to when using the name. When using $x, now, you will always refer to the output of 'that one ls -al you gave earlier'. You can assign any language object to variables, such as strings, barewords, numbers, booleans, and other variables.

Naming stuff: functions

Suppose you got a statement or block of statements you do often. Yes, you could keep copypasting it around, but that's a hassle if you need to change it (if, for instance, you find an error or inaccuracy in the code) everywhere. Also, the code quickly becomes unnecessarily long and less composable, reusable, and generally bad.
Functions solve this problem by grouping together related statements that do a certain task. Functions in Rewind are still defined with the let keyword, like so:
let id (args...) statements...;
args... is any number of positional arguments to your functions. A function may require 0 arguments; it is permitted, but discouraged. An example of a function definition is as follows:
let sqr (x) * x x;
This nice function performs the simple act of squaring a number, so sqr 5 yields 25, sqr 6 gives us back 36, and so on...
Functions are composable, meaning that the code

let sqr (x) * x x;
let +1 (x) + x 1;
+1 (sqr 5);

is interpreted correctly, and its result is 26. If you have a hard time understanding why, try to resolve the function calls from the most inner one, and assign a unique variable to each step. For instance, with the example above, you could do:
$f(x) = x + 1$
$g(x) = x^2$
$f(g(5)) = ?$
Then...
$y = g(5)$
$f(y) = f(25) = 25+1 = 26$
And done! wasn't hard, right? You can do this with any level of nesting, and with multiple function calls per level. With some practice, you'll get to do this naturally, without even the need to think about it.

A side note: Higher Order Functions (HOFs)

There's a somewhat 'weird' (at least at first thought, then it becomes intuitive, i promise) concept in computer science or mathematics, or even programming, called higher order functions. What they are, is essentially a function being able to be passed as an argument... to another function? And then you can... return a function? What on earth?
It all becomes way more understandable when you think of generalizing your code. Suppose you want to create some buttons in an interface. Instead of creating 10 different types of buttons for each possible action, you can create just one and require the function signature (name of the function + its arguments + (not in Rewind) its return type) to have an additional argument, called the callback, which is a function that gets executed each time the button is activated. An hypothetical button could be implemented in Rewind like this:

let button (color size callback)
   ...,
   (callback),
   ...;

You can then call the function with a previously defined callback. This doesn't really solve the problem, since now you have 10 different functions to define...
This problem is definitely solved with lambda functions, also called anonymous functions, which are functions with no name that you often can define anywhere, even as arguments to other functions. Rewind supports lambda functions with the following Syntax:
(params...) => statements...; args...
You can use them like this:
button red HUGE (color) => do_fancy_stuff color; i.e. you can use them to assign parameters (color, size, callback...) to a function you can call (in this case, callback is a function that just calls do_fancy_stuff color). Lambda functions are useful in several other ways, so it's a good idea to experiment with them, to get a hang of how they're used.

The function composition operator, a.k.a. the greatest thing ever (period.)

Ever wondered if there was a more elegant way to compose functions together that does not require nesting parentheses? yes, there is! Rewind supports the 'function composition' (<<) operator, which is an infix operator in between function identifiers and/or lambda function definitions, that basically pipes them to give you the final result. For example:

let f (x) + x 1;
f << (x) => * x 2; << (x) => - x 3; 5

In this case, 5 is passed as an argument to the rightmost lambda, which then maps it to 2, and then this result is passed to the lambda in the middle of the pipe, which then returns 4, which ultimately is to be fed to f that will compute 5 out of it.

An example of how to use this special pipe operator, in creating a stylish and fully operational Rewind prompt is found in the "Program examples" wiki page, on the side bar.