Introduction
Lem is a minimal, only-what-you-need scripting language, built as an alternative to Bash.
Why?
While Bash has proven powerful again and again, its scripts have also proved unmaintainable time and time again. This is in part due to its unclear syntax. Lem strives to have a simplistic, readable syntax that will make maintaining scripts much more enjoyable.
Unlike Bash, Lem attempts to provide a single way to perform a single operation. Bash is notably able to do one thing in four or five different ways, which can make it hard for maintainers to remember what does what without comments littered across the script.
Lem also has intuitive methods, in hopes that you can guess it before you have to look it up. For example, splitting a string is simple, and there is only one correct way to do it: "Hello, world!".split(" ")
. In Bash, this is much more complex than it needs to be. See here for 9 ways to split a string, with only one of them being the proper method.
How?
By simplifying the syntax and removing redundant methods of performing operations, a lot can be overcome. Many maintenance issues can be overcome by making it easier to read scripts.
Getting Started
See the getting started page for installation and a first script.
Getting Started
Installation
To install Lem on your machine, there are a couple options:
If you have Rust and Cargo installed on your machine:
$ cargo install lem
If you don't, or you don't want to build Lem from scratch, there are pre-built binaries available:
$ curl -LSfs https://lem.jwpjr.tech/install | sh
TODO: add Windows command
Your First Script
All Lem scripts have a name, and end with a file extension of .lem
. This requirement will be loosened in the future (see #2).
Create a file that ends in .lem
, such as helloWorld.lem
:
$ touch helloWorld.lem
Open the file in your text editor of choice, and write the following:
println("Hello, world!");
Now, open your terminal, cd
to the working directory, and run the following:
$ lem helloWorld.lem
This is what you should see:
$ lem helloWorld
Hello, world!
Congratulations! You have written your first Lem script!
Design Choices
Many design choices were made when creating the syntax for Lem. Notable ones are listed in this chapter.
Line Terminators
In Lem, the line terminator is a semicolon (;
). However, this will eventually be removed (see #9).
Comments
Comments are started by //
.
For example:
// This is a comment!
There are no other types of comments in Lem. The following methods will not work:
/*
I won't work!
*/
# Neither will I!
Keywords
The entire list of keywords (both implemented and reserved) is as follows:
Keyword | Description |
---|---|
nameof | |
is | |
null | |
true | |
false | |
int | |
string | |
boolean | |
throw | |
let | |
if | |
else | |
for | |
in | |
while | |
return | |
fn |
Variables
Variables in Lem are likely as you might expect. They are defined with this basic syntax:
let name = value;
They can be of many types, including strings, booleans, integers, and arrays. See the respective pages in this chapter for more information.
Strings
The syntax for string variables is relatively unchanged, and looks as follows:
let helloWorld = "Hello, world!";
This variable can be used anywhere within its scope, like in this block:
let helloWorld = "Hello, world!";
println(helloWorld);
As might be expected, this would print Hello, world!
.
Strings can be concatenated with the +
operator. It cannot add two differing types outside of the println
built-in, such as an integer and a string.
For example, this program outputs Hello, world!
.
let hello = "Hello, ";
let world = "world!";
let helloWorld = hello + world;
println(helloWorld);
This program errors because string
and num
are of mismatching types (string and integer):
let str = "Look, a string!";
let num = 3;
println(str + num);
Integers can be observed more in-depth on their page.
You can also split a string into an array of strings, like so:
let str = "Item 1, Item 2, Item 3";
let array = str.split(", ");
println(array);
["Item 1", "Item 2", "Item 3"]
Integers
In Lem, all numbers are treated as floating-point. For example, the number 2:
let two = 2;
The println
built-in can print strings, integers, arrays, and booleans. It can also concatenate mismatching types, but it will not pretty-print arrays.
println("Look, a number: " + 6);
As demonstrated on the strings page, concatenating mismatching types outside of the println
built-in will cause an error (see println & print for more information).
Arrays
Arrays can be defined with the following syntax:
let array = ["Item 1", "Item 2", "Item 3"];
They can also be iterated over (see loops & recursion for more).
For example, this program utilites a for
loop.
let array = ["Item 1", "Item 2", "Item 3"];
for item in array {
println(item);
}
It outputs the following:
Item 1
Item 2
Item 3
You can also get a specific item from a list, like so:
let array = ["Item 1", "Item 2", "Item 3"];
println(array[0]);
This program will print Item 1
.
Others
Booleans
Booleans are declared with the following syntax:
let boolean = true;
let otherBoolean = false;
See comparison operators for more information.
Control Flow
There are a number of ways to control the flow of the program. They are all documented here, except for functions, which can be found here.
Conditional Statements
If Statements
let raining = true;
let temperature = 82;
let warmOut = temperature >= 70;
if raining && warmOut {
println("It's terribly humid outside...");
} else if raining && !warmOut {
println("It's cold and wet today...");
} else if !raining && warmOut {
println("It's a good day to go outside!");
} else if !raining && !warmOut {
println("It's chilly outside!");
}
See comparison operators for more information.
Comparison operators
Valid comparison operators are as follows:
Operator | Meaning |
---|---|
&& | And |
|| | Or |
== | Equal |
!= | Not equal |
> | Greater than |
< | Less than |
>= | Greater than or equal |
<= | Less than or equal |
Loops & Recursion
For Statements
For/In Statements
let array = ["Item 1", "Item 2", "Item 3"];
for item in array {
println(item)
}
See arrays for more information.
For/Iterator Statements
for i in 1..10 {
println(i);
}
While Statements
let i = 0;
while i < 10 {
println(i);
i++;
}
Functions
Lem supports fairly simple functions. It also provides a few built-in ones that you can utilise.
Defining Functions
Basic Functions
The syntax to define a basic function with no parameters is as follows:
fn helloWorld() {
println("Hello, world!");
}
Calling this function is quite straightforward:
fn helloWorld() {
println("Hello, world!");
}
helloWorld();
Parameterised Functions
To create a function with parameters, use the following syntax:
fn printMessage(message) {
println(message);
}
Calling is simple:
fn printMessage(message) {
println(message);
}
printMessage("Hello, world!");
You can also add multiple parameters:
fn printMessages(message1, message2) {
println(message1);
println(message2);
}
Built-ins
Built-ins are exactly what the name says--built-in functions. They provide core functionality and exist as special, unremovable functions.
For example, the exec
function can accept any arbitrary combination of tokens as a parameter, almost like a macro in Rust.
The exec built-in
exec
is a built-in in Lem that allows you to execute shell commands, while optionally providing a shell to run them in.
It supports piping and returns an exit code along with any potential values.
Contributing to Lem
Getting Started
Local Environment
- Ensure that Rust,
cargo
, andrustup
are installed on your system - Run
cargo install just
to install Just - Run
just dev-install
to install dependencies - Run
just serve-book
to start the documentation server - Visit
http://localhost:3000
to see the documentation book
Cloud Environment
Click this link to open Lem in Gitpod.
Committing Changes
This project strictly follows conventional commits.
Please restrict scope to any of the following: lexer
, parser
, interpreter
, or cli
. Documentation changes do not use a scope, and instead use the docs
type.
Please also run the following before committing or creating a pull request:
just test
just format
Security Policy
Reporting a Vulnerability
To report a vulnerability in Lem, do not create an issue on the issue tracker. Instead, please send an email to jwpjr567@gmail.com
with all details of the vulernability, including how to reproduce it, what version it occured on, and any other relevant pieces of information.