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:

KeywordDescription
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:

OperatorMeaning
&&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

  1. Ensure that Rust, cargo, and rustup are installed on your system
  2. Run cargo install just to install Just
  3. Run just dev-install to install dependencies
  4. Run just serve-book to start the documentation server
  5. 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.