Introduction to Shim
A simple scripting language for embedding into applications.
Whether you’re curious about Shim, want to add it to an application you’re developing, or you are a user of someone else’s application, this is where you should start for a detailed guide through the Shim language.
Philosophy
Shim is designed to be a simple language. It’s meant to empower other tools and applications. It’s extensible, lightweight, and easy to learn.
Shim fills a similar niche to Lua, but it’s designed to have more familiar syntax and semantics.
For Agents
See Shim For Agents for details on setting up coding agents to work with Shim code.
In general, Shim has an intuitive syntax/semantics for most coding models. If you want a coding agent to have a more explicit understanding of the language, you can point them to this single file which summarizes the language in 12k-18k tokens. You’ll also need to point your agent to whatever documentation is available for the Shim host application (such as a game engine, or this website’s coding playground).
Shim is not intended for coding agents. It’s designed to give a tight development loop for updating/testing code. If you want a game framework that should work great with coding agents, check out Love2D. Nevertheless, Shim code is easily generated by LLMs. It will definitely be useful for creating bindings between host applications and Shim, and for implementing replacements for libraries from other languages.
Basic Printing
Let’s start with the basic syntax for printing. You can execute the code in any codeblock you see on the page. Modify and rerun to see the results!
print("Hello, Shim!")Press the play button to execute
The print function accepts a list of arguments and will print them space-separated.
let name = "Reader"
print("Hello,", name)Press the play button to execute
See string interpolation for more details on formatting values.
Errors
Before we continue, it’s useful to see how Shim formats errors.
let a = 12 * does_not_exist
Press the play button to execute
As much as possible, Shim tries to give useful information for pinpointing exactly where an error occurs. At the moment stack traces are not available.
Functions
Functions are declared in Shim using the keyword fn
fn add(a, b) {
return a+b
}
print(add(2, 4))Press the play button to execute
In Shim, the value returned by a {} block is the last expression in the block.
That means you can write an equivalent version of add like this!
fn add(a, b) {
a+b
}Press the play button to execute
Typically we only write return for early returns and use this feature to return
the value on the last line. If you prefer to always use return, please feel
free. But you’ll see in these docs that we don’t typically take this approach.
See this page on functions for more details about writing functions, optional arguments, keyword arguments, default values, and closures.
Blocks
Functions are not the only place where the last line of a block is the value of
the block. Here we can see that we can compute the value hyp based on two
intermediate values x and y. This is a useful approach to limit the
scope of variables. In this case, it’s clear that x and y are only used
to compute hyp and aren’t otherwise needed. Without this approach a reader
doesn’t know if the x/y values will be used later.
let hyp = {
let x = 3
let y = 4
(x*x + y*y).sqrt()
}
print(hyp)Press the play button to execute
If-Else Blocks
Shim supports if-else statements.
let num = 14
if num < 10 {
print("little")
} else if num < 20 {
print("medium")
} else {
print("large")
}Press the play button to execute
Like functions and blocks, if/else can be used as an expression.
let num = 14
let size = if num < 10 {
"little"
} else if num < 20 {
"medium"
} else {
"large"
}
print("The size is \(size)")Press the play button to execute
Structs
Structs in Shim are declared with the struct keyword. Here’s a quick example.
struct Node {
x,
y,
value="default",
fn dist(self, other) {
let dx = self.x - other.x
let dy = self.y - other.y
(dx*dx + dy*dy).sqrt()
}
}
let a = Node(2, 3)
let b = Node(5, 7, "test")
print(a)
print(b)
print("The distance between \(a.value) and \(b.value) is \(a.dist(b))")Press the play button to execute
Lists
Lists can be created by using the list literal syntax []. Like Python, lists
are ordered, mutable collections.
let lst = [3, 2, 1] print(lst)
Press the play button to execute
Dicts
Dicts can be created by using the dict literal syntax {:}. Like Python, dicts
are mutable collections that map one value to another. For example, in a game
you could map an integer item id to data about that item.
let d = {:}
d[42] = "World Splitter"
d[7] = "Lucky Dice"
d[4] = "Reaper's Blade"
print(d)Press the play button to execute
You might also notice that when you run the above code example, the keys and values are always shown in insertion order.
Sets
Sets are collections that store hashable values.
Unlike lists, a set keeps track of what values are present in the collection.
A value is either in the set or absent from the set. You cannot have multiple
instances of the same value. You can efficiently add/remove values from the
set and efficiently check if a value is contained in the set. You can also iterate
over all values in the set and the values will be returned in insertion order.
let s = {,}
print(s)Press the play button to execute
Tuples
Tuples are a fixed number of items, ordered, and immutable.let t = ("foo", 42)
print(t[0])
print(t[1])Press the play button to execute
Tuples are hashable if all the items it contains are hashable. This is particularly useful if you have a sparse grid that you want to store in a dict.
This shows the immutability of tuples and should always fail.let t = ("foo", 42)
t[0] = "bar" // This should fail since tuples are immutablePress the play button to execute
Tuples can be destructured in assignment statements and as for loop variables.
Tuples can be destructured during assignment:let t = ("foo", 42)
let (word, value) = tPress the play button to execute
Tuples can also be destructured in for loops:let lst = [("foo", 42), ("bar", 7)]
for word, value in lst {
print("\(word): \(value)")
}
// `.enumerate()` yields tuples that are destructured
print(".enumerate():")
lst = ["foo", "bar", "baz"]
for idx, item in lst.enumerate() {
print(idx, item)
}Press the play button to execute