About

Lime is a small, strongly typed, purely functional programming language. It indends to take a minimal approach to language design---combining the power of Lisp language constructs with low-level computation models. Because of the ability for Lisp-like language code to be analyzed, optimized, and preprocessed at compile time, this results in a more usable language without any of the performance drawbacks of a higher-level language. Lime is a strongly, strictly typed language. All variables must have a type, and every function must declare an argument and return type signature. It also allows for basic algebraic typing operations--with an any type that allows for the easy manipulation of container classes. Lime is also purely functional. System I/O commands are strictly typed, and will stop the programs execution if they return an errory value (like as! in Swift). Lime compiles to a cross-platform C. Although it bypasses the C typing system (using long pointers instead of typed objects), it aims to generate readable C code which can be easily extended. It is currently in the early alpha of its development, and should not be used for any critical code. Breaking changes are expected.

Quickstart

Installing Lime

Using pip

Lime may be installed with pip, as lime-lang. Make sure to install it with the python2 version of pip
$ pip2 install lime-lang

From source

$ git clone https://github.com/lschumm/lime
Make sure that this folder is in your PATH environment variable (the lime executable must be in your PATH, and it must be located in the same folder as the Lime standard libraries).

Getting Started

Once you've installed lime, the lime command will allow you to run Lime files and an interactive REPL. Type:
$ lime
This should result in a help message:
usage: lime [run FILE | compile FILE | shell]
lime run will run a file passed to the lime command, lime compile will compile a Lime file to an executible binary, and lime shell will open an interactive Lime REPL.

Your first Lime program

Every Lime program has a main function, which is the entry point for the program. This function may have any return value; the value is not used. A "hello world" program in Lime would look like:
(function main () (sprint "hello world!"))

Introduction

Lime is an experimental stack-based functional programming language.

Syntax

All computation in Lime is in the form of expressions and atoms. An expression takes the form of a function and arguments passed to that function, and an atom is a single primitive type (a number, a string, a function, etc.) ***Note:** In the context of a computation tree, atoms are leaves.* These are atoms:
1
"abc"
iadd
and these are expressions (compositions of atoms):
(iadd 1 2)
(lambda (x:int) -> int (imul x 2))
For readability, expressions can span multiple lines; newline characters are treated as whitespace.

Applying Functions

Every expression in Lime is an application of a function or a directive to values. Every Lime expression is surrounded by parentheses; the first element is the function, the next and onwards are the arguments passed to that function. For example,
(iprint 3)
would be a Lime expression which applies the iprint (integer print) function to the number 1, therefore printing 1. Because of the simple form of Lime expression, they may be nested easily, like so:
(iadd (imul 3 2) 4)
Since Lime is a first-class language (meaning functions are just like any other value, and can be passed in and out of functions), the first argument may also be an expression:
((is_divisible 2) 4)

Defining Functions

To define a function in Lime, there are two methods:

Top-Level Function Definition

To define a top-level, named function, use the function keyword, like so:
(function happy_birthday (name:str) -> str (sjoin "Happy Birthday, " name))
L. happy_birthday "Mauricio"
=> Happy Birthday, Mauricio

Local Function Definition

To define a function and pass a reference to it inline, use the lambda keyword, like so:
L. ((lambda (name:str) -> str (sjoin "Happy Birthday, " name)))
=> "Happy Birthday, Mauricio"

Typing

Lime is a strongly, strictly typed language.

Default Types

The default atomic types in Lime are bool, int, float, and str. Lists can be made out of any type, even lists themselves, by appending a $ token. For example, a list of ints have the signature int$, and a list of int$s will have the signature int$$. Functions, since they are first class objects, must also carry types. The type information for a function that takes types a, b, and c as input and type d as output is:
lambda[a,b,c->d]
For example, the idiv command's signature would be:
lambda[int,int->int]
Functions are typed as lambda objects because when they are passed around in a program, they are nameless and identical to an inline lambda.

Custom Types

Defining a type

To define a type, use the type directive at the top level of a program.
(type person first_name:str last_name:str age:int)
This will also define a new type, person.

Initializing a type

To initialize a type, use the new keyword, which will initialize and return a new instance of the type specified.
(new person first_name "Liam" last_name "Schumm" age 17)
You can specify only a subset of the attributes when initializing, by not including the keys for those items:
(new person first_name "Liam" age 17)

Getting values of a type

To get the values of a type, use the get command. For example,
(get example_person first_name)
would return the first_name attribute of example_person.

Setting values of a type

To set the values of a type, use the set command. For example,
(set example_person (first_name 2))
would set the first_name attribute of example_person to 2, and return a reference to the object.

Type Signatures

In Lime, all functions must have a type specified for their return value and for their inputs.

Polymorphism

Lime has polymorphism, aka "overriding". This allows you to define a single function with different behavior when it is passed different types. For example,
(function increment (x:float) -> float (fadd x 1.0))
(function increment (x:int) -> int     (iadd x 1))
would define the increment function for int\ s and float\ s. This can also be used for optional arguments, like so:
(function sinput () -> str (sinput ":"))
(function sinput (prompt:str) -> str (...c implementation...))
if a prompt is not explicitly defined, it will default to ":".

Algebraic Typing

Algebraic typing allows for functions that can take multiple forms, based on an algebraic expression. For example, the car command which returns the first element of a list must have a form for int$, str$, int$$, and so on, cannot be defined through polymorphism: there would be infinitely many forms. Instead, it can be defined algebraically:
car (list:A$) -> A
where A is a placeholder, which may be substituted for any type. Therefore, car will be defined for lists of all types. More complicated expressions can be done with container types like dictionaries:
dget (dict:[A->B] key:A) -> B
In this example, the theoretical dget command (to get a value from a key in a dictionary) accepts dictionaries mapping any type to any other type, accepts a key of the key type of the dictionary, and returns a value with the type of the value type of the dictionary. Additionally, since Lime is a first-order language, all functions have explicit types signatures, declaring their input and output types and arity. Functions that operate on other functions can have super explicit type signatures. For example, the map command's signature is:
map (lambda[A->B] A$) -> B$
which will ensure that the list being mapped is of the input type of the function, and will make the output type of map a list of the output type of the function. In Lime, algebraic types can be any string of capital letters, and there may be arbitrarily many types in a function. Not all of these types need to define the return value or be referenced in other arguments.

Macros

Macros in Lime are a way of specifying certain operations to be applied to code before it is run.

Macro Syntax

Macros are made up of two components: the search and the sub. Both of these take the form of an S-expression. Macros are defined in Lime with the macro directive, like so:
(macro search_expr sub_expr)
Whenever a new macro is defined, all subsequent code will have each previous macro applied to it (including new macros!) All macros will be applied to expressions until they reach a terminal point (i.e., cannot expand any further). Lime macros by default search within expressions. For example, the following macro:
(macro (a b) c)
would replace a b in any expression with c; like so:
(operation a b d)
=> (operation c d)
However, parentheses in the search expression can be used to create function-like macros:
(macro ((first a b)) a) # if we define this macro
(iprint (first 1 2))
=> 1

search syntax

The search portion is an S-expression made up of macro tokens.

Literal tokens

Literal tokens start with an !, and match an exact expression. If you want a function-like macro that reverses two arguments, a la:
(reverse a b)
=> (b a)
the first argument used in the macro would be a literal, like so:
(macro (!reverse a b) (b a))

Variable tokens

Variable tokens look like any name in Lime. Examples of variable tokens are my_var, abc, and yadda_yadda. Variable tokens will match any atomic value or expression, and may be referenced in the sub expression to have their value used in the substitution step.

sub syntax

Substitution syntax specifies what any instances of the search expression will be replaced with. They can contain any standard Lime tokens, however all atoms that match one of the variable tokens will be substituted for the value found in the search expression.

Macro Examples

This macro transforms if statements with extra then and else statements (for clarity) to raw Lisp-style ifs:
(macro ((!if x !then y !else z)) (if x y z))
Like so:
(if (iequal x 2)
    then (sprint "x is 2!")
    else (sprint "x is not 2!"))
=> (if (iequal x 2) (sprint "x is 2!") (sprint "x is not 2!"))
This macro allows for Lisp dot cons notation, by replacing any pair like (a . b) with (cons a (cons b nil)):
(macro ((a !. b)) (cons a (cons b nil)))
For example,
(liprint (3 . 2))
=> 3
   2
These macros allow for [ a , b , c ] list notation in Lime.
(macro (x !]) (cons x nil))
(macro (a !, (!cons x y)) (cons a (cons x y)))
(macro (![ x) x)
An expansion of a list expression would look like this:
[ 1 , 2 , 3 ]
=> [ 1 , 2 , (cons 3 nil) # macro 1
=> [ 1 , (cons 2 (cons 3 nil)) # macro 2
=> [ (cons 1 (cons 2 (cons 3 nil))) # macro 2
=> (cons 1 (cons 2 (cons 3 nil))) # macro 3

Including Other Files

The include top-level directive can be used to separate and reuse code. To include a file in the same directory as another file:
(include lib.lime)
all functions in this library will be inlined at the point of the include command in the program. You can also source files in subdirectories:
(include lib/example.lime)
Or as absolute paths on the filesystem:
(include /Users/lschumm/research/notizen/html.lime)

C Foreign Function Interface

The Lime FFI allows for C functions and libraries to be used within Lime programs. All Lime functions are implemented as C functions, which manipulate the Lime stack API to pass data to and from Lime functions.

The Stack

The stack in Lime is how all data is passed to and from executed functions. When a function is called with arguments, like so:
(f a b c)
first the arguments a, b, and c will be pushed onto the stack, in reverse order:
PUSH c
PUSH b
PUSH a
then, the function is run. Lime functions get the value of their arguments by setting a base_stack_ptr at the beginning of its execution, and getting the values of arguments relative to this pointer without mutating the stack. However, when writing a function in C, it is recommended that these values may be poped to reference their values. For example, a C function to add two integers would first pop the two integer values (note that an int in lime is actually implemented as a long):
long second = pop();
long first = pop();

push(first + second);
The values pop and push manipulate must be of type long. If a pointer is to be passed, it must be casted to a long.

The LimeC format

The LimeC file format allows for Lime type signatures to be mixed with C code. Each function has a Lime type signature, documentation, and a block of C code. Here's an example LimeC function from the standard library:
ladd (list1:list[A] list2:list[A]) -> list[A]
Adds one list to another, resulting in a new list omposed of the two lists' elements.
=> list* list2 = (list*)pop();
=> list* list1 = (list*)pop();
=> list* old_list1 = list1;
=> long list2_length = list2->length;
=> for (int i = 0; i < list1->length; i++) {
=>       list1->length += list2_length;
=>       list1 = list1->next;
=> }
=> list1->next = list2;
=> push(old_list1);
This will compile to a C function with the signature stored in the name:
void ladd__list$A$_list$A$__list$A$() {
 list* list2 = (list*)pop();
 list* list1 = (list*)pop();
 list* old_list1 = list1;
 long list2_length = list2->length;
 for (int i = 0; i < list1->length; i++) {
       list1->length += list2_length;
       list1 = list1->next;
 }
 list1->next = list2;
 push(old_list1);
return;
}
To insert C code directly the pipe character (|) can be used; for example for include statements at the beginning of the file:
|#include 
|#include 
LimeC files may be created by the user and included with the include statement.

Standard Library Functions

bool

bprint abool

Prints a boolean.

bnot abool

Negates the value of the given boolean.

band abool bbool

Returns true if both of the values are true, else false.

bor abool bbool

Returns true of one of the values is true, else false.

float

fprint afloat

Prints a floating point number.

fadd afloat bfloat

Adds two floating point numbers.

fsub afloat bfloat

Subtracts two floating point numbers.

fmul afloat bfloat

Multiples two floating point numbers.

fdiv afloat bfloat

Divides the first floating point number by the second floating point number.

fequal afloat bfloat

Checks whether the two floats passed are equal, returning either 0 or 1.

fless afloat bfloat

Checks whether the first float is less than the second float.

fgreater afloat bfloat

Checks whether the first float is greater than the second float.

ffloor afloat

Returns the floor (largest integer not greater than) the input value.

fceil afloat

Returns the ceiling (smallest integer not less than) the input value.

float_to_int afloat

Converts the input floating point number to an integer by truncating all of the digits past the decimal point.

fpow afloat exponentfloat

Raises the input to the given exponent.

flog afloat

Takes the natural log of the input.

flog afloat basefloat

Takes the natural log of the input, in the given base.

float_to_str afloat

Converts a floating point number to a string.

ffloori afloat

Returns the floor (largest integer not greater than) the input value, converted to an integer.

fceili afloat

Returns the ceiling (smallest integer not less than) the input value, converted to an integer.

fsqrt afloat

Returns the square root of the input.

fs

Reads a file located at the given filename.

fs_write filenamestr contentsstr

Writes the given contents to the file.

fs_exists filenamestr

Returns whether a file with the given filename exists.

fs_list dirstr

Lists the files in the given directory.

fs_isfile pathstr

Returns whether a given path refers to a regular file.

fs_isdir pathstr

Returns whether a given path refers to a directory.

fsread filenamestr

Reads the file with the given filename.

fsappend filenamestr contentsstr

Appends the contents to the given file.

int

iadd aint bint

Returns a + b.

isub aint bint

Returns a - b.

imul aint bint

Returns the product of a and b.

idiv aint bint

Returns a divided by b, truncated.

imod aint bint

Returns the remainder of the division of a by b.

ieven aint

Returns true if the given integer is even, false if it is not.

iodd aint

Returns true if the given integer is odd, false if it is not.

ipow aint bint

Returns a raised to the power b.

iequal aint bint

Checks whether the two integers passed are equal, returning either 0 or 1.

iless aint bint

Checks whether the first integer is less than the second integer.

igreater aint bint

Checks whether the first integer is greater than the second integer.

iprint aint

Prints an integer.

int_to_str aint

Converts an integer to a string.

int_to_float aint

Converts an integer to a floating-point number.

str_to_int astr

Converts a string to an integer.

iinput

Reads an integer from the user.

iinput promptstr

Reads an integer from the user, using the given prompt.

is_divisible aint bint

Returns true if the first number is divisible by the second, false if it is not.

is_divisible aint

Returns a function which will return true if its input is divisible by the specified number, and false if it is not.

ipositive aint

Returns true if the given integer is positive, false if it is not.

inegative aint

Returns true if the given integer is negative, false if it is not.

sum listint$

Returns the sum of a list of numbers.

irange lowerint upperint stepint

Returns an integer range from the first value to the last value, in increments of the specified step size. Negative ranges are allowed.

irange lowerint upperint

Returns an integer range from the first value to the last value, in increments of 1.

irange upperint

Returns an integer range from 0 to the specified value, in increments of 1.

list

cons valueA nextlist[A]

Creates a cell with the given value, pointing to the next item in the list.

cons valueA nextnull

Creates a cell with the given value, pointing to the next item in the list.

ladd list1list[A] list2list[A]

Adds one list to another, resulting in a new list omposed of the two lists' elements.

lreverse listlist[A]

Reverses a list.

llength listlist[A]

Gets the length of the given list.

lget listlist[A] indexint

Gets an element from a list.

car listlist[A]

Returns the first item of a list.

cdr listlist[A]

Returns the second and onwards items of a list.

lappend listlist[A] itemA

Appends an item to a given list, returning the new list.

lprepend itemA listlist[A]

Prepends an item to a given list, returning the new list.

map functionlambda[A->B] listlist[A]

Maps a given function to a list.

filter functionlambda[A->bool] listlist[A]

Filters a list given a function.

foldl functionlambda[A,A->B] listlist[A] valA

Reduces with a function, using right-hand application, starting with the specified value.

foldr functionlambda[A,A->B] listlist[A]

Reduces with a function, using right-hand application.

foldl functionlambda[A,A->B] listlist[A] valA

Reduces with a function, using left-hand application, starting with the specified value.

foldl functionlambda[A,A->B] listlist[A]

Reduces with a function, using left-hand application.

liprint listint$

Prints a list of integers.

lsprint listlist[str]

Prints a list of strings.

lfprint listlist[float]

Prints a list of floats.

repeat functionlambda[A->A] inA nint

Applies the given function n times on the input.

lslice listlist[A] startint

Returns the elements in the array, starting from the specified start index.

lslice listlist[A] startint endint

Returns the elements in the array, starting from the specified start index, and ending at the specified end index.

macro

[ a , b , c , ... ]

Initializes and returns a list with the given elements. Note that spacing is important.

random

rint fromint toint

Returns a random integer in the given range (inclusive).

rfloat fromfloat tofloat

Returns a random float in the given range (inclusive).

str

sprint astr

Prints a string value.

slength strstr

Returns the length of the given string.

sjoin astr bstr

Appends the two given strings.

sslice strstr startint

Slices a string with a beginning index.

sslice strstr startint endint

Slices a string with a beginning and end index.

sinput

Reads a string from the user.

sinput promptstr

Reads a string from the user.

sequal string1str string2str

Returns whether the two given strings are equal.

sreverse stringstr

Reverses the given string.

sstartswith stringstr prefixstr

Returns whether the given string starts with the given prefix.

sendswith strstr suffixstr

Returns whether the given string ends with the given suffix.

ssplit strstr delimstr

Splits the given string by the given delimiter, returning a list of strings.

sfind strstr searchstr

Returns the index of the search string in the given string. If it does not occur, returns -1.

sreplace strstr searchstr replacestr

Replaces all instances of the search string with the substitution string in the given string.

scount strstr substringstr

Returns the number of times the substring appears in the parent string.

str_to_float astr

Parses a string representing a floating point number.

system

system commandstr

Runs a system command in a shell.

sysarg indexint

Gets the system argument with the given index.

sysarglen

Gets the number of arguments passed to the program.

exit

Exits the program.

exit messagestr

Exits the program, printing the given message.

time

time_unix

Returns UNIX time (seconds since epoch; January 1st 1970 UTC).

do_in functionA delayint

Executes the given function (with no arguments) after the specified delay (in seconds).

do_in functionA delayfloat

Executes the given function (with no arguments) after the specified delay (in seconds).