All Projects β†’ Lel-Language β†’ Lisp Esque Language

Lel-Language / Lisp Esque Language

Licence: mit
πŸ’ The Lel programming language

Programming Languages

javascript
184084 projects - #8 most used programming language
lisp
113 projects

Projects that are alternatives of or similar to Lisp Esque Language

Greynir
The greynir.is natural language processing website for Icelandic
Stars: ✭ 47 (+95.83%)
Mutual labels:  tokenizer, parser
Lfuzzer
Fuzzing Parsers with Tokens
Stars: ✭ 28 (+16.67%)
Mutual labels:  tokenizer, parser
Query Translator
Query Translator is a search query translator with AST representation
Stars: ✭ 165 (+587.5%)
Mutual labels:  tokenizer, parser
Sharpmath
A small .NET math library.
Stars: ✭ 36 (+50%)
Mutual labels:  tokenizer, parser
String Calc
PHP calculator library for mathematical terms (expressions) passed as strings
Stars: ✭ 60 (+150%)
Mutual labels:  tokenizer, parser
Works For Me
Collection of developer toolkits
Stars: ✭ 131 (+445.83%)
Mutual labels:  tokenizer, parser
Php Parser
🌿 NodeJS PHP Parser - extract AST or tokens (PHP5 and PHP7)
Stars: ✭ 400 (+1566.67%)
Mutual labels:  tokenizer, parser
Netlist Graph
Java library for parsing and manipulating graph representations of gate-level Verilog netlists
Stars: ✭ 7 (-70.83%)
Mutual labels:  parser
Ccalc
Scientific calculator in which you can define new constants and functions
Stars: ✭ 19 (-20.83%)
Mutual labels:  parser
Proposal Binary Ast
Binary AST proposal for ECMAScript
Stars: ✭ 831 (+3362.5%)
Mutual labels:  parser
Natasha
Solves basic Russian NLP tasks, API for lower level Natasha projects
Stars: ✭ 788 (+3183.33%)
Mutual labels:  tokenizer
Strictyaml
Type-safe YAML parser and validator.
Stars: ✭ 836 (+3383.33%)
Mutual labels:  parser
Chthollylang
A simple implementation of Yet another script language Chtholly
Stars: ✭ 19 (-20.83%)
Mutual labels:  parser
Markdown Wasm
Markdown parser and HTML generator implemented in WebAssembly, based on md4c
Stars: ✭ 833 (+3370.83%)
Mutual labels:  parser
Graphql Mst
Convert GraphQL to mobx-state-tree models
Stars: ✭ 22 (-8.33%)
Mutual labels:  parser
Imdbpy
IMDbPY is a Python package useful to retrieve and manage the data of the IMDb movie database about movies, people, characters and companies
Stars: ✭ 792 (+3200%)
Mutual labels:  parser
Radon
A scripting language.
Stars: ✭ 22 (-8.33%)
Mutual labels:  parser
Combine
A parser combinator library for Rust
Stars: ✭ 906 (+3675%)
Mutual labels:  parser
Arg
Simple argument parsing
Stars: ✭ 897 (+3637.5%)
Mutual labels:  parser
Pegviz
PEG trace visualizer
Stars: ✭ 18 (-25%)
Mutual labels:  parser

Lisp-esque language (Lel)

Lel is a lisp like programming language. It is not meant for practical purposes, but more as a tool to learn how to write a programming language.

This project comes with a Lel interpreter for running Lel programs, and a REPL for executing Lel expressions in real time.

Running

Installation

You can clone the repo.

git clone https://github.com/francisrstokes/Lisp-esque-language.git
cd Lisp-esque-language
npm i

Or install from npm

npm i -g lel-lang

REPL

lel

Interpreter

lel examples/helix.lel

Language features

  • Primitive types:
    • Number
    • String
    • Boolean
    • Function
    • List
  • Lexical scoping of functions and variables
    • Functions form closures
    • Anonymous functions through lambdas
    • Call and apply by reference for functions
  • Modules
    • Code can be split across files into isolated modules
    • Loaded with import keyword
  • Extendable
    • Allows extension of the language through js modules
  • Conditionals
  • List mapipulation
  • Stack safety
    • Recursive functions do not lead to stack overflow exceptions even without tail calls

The Lel Language

Variables

Variables are declared with the let keyword.

(let x 10)

They can of course be assigned as the result of a function:

(let x (+ 4 6))

A variable can be modified using the the mutate keyword, though it should be avoided where possible.

(let count 1)
(print count "\n")
(mutate count (+ count 1))
(print count "\n")

; -> 1
;    2

Functions

Functions are declared with the function keyword. The value last statement in a function body is the final return value.

(function add-one (x)
  (+ x 1))

(print (add-one 1))

; -> 2

Call

Functions are first class in Lel and can be passed around in variables by reference. A function can be executed by reference using call:

; Declare a function called say-hello
(function say-hello (name)
  (print "hello " name "!"))

; Create a variable with a reference to this function
(let referenced-hello say-hello)

; Call say-hello by reference
(call referenced-hello "Francis")

; -> "hello Francis!"

All functions are lexically scoped and form closures, meaning they have access to the scope they were declared in while also retaining their own scope.

(function create-adder (adding-value)
  (function new-adder (x)
    (+ x adding-value)))

(let add-five (create-adder 5))
(let add-ten (create-adder 10))

(print (add-five 10) "\n")
(print (add-ten 10) "\n")

; -> 15
;    20

Apply

Apply is like call except the arguments are provided as a List, or a reference to a list:

(function say-hello (name)
  (print "hello " name "!"))

(let args (list "Francis"))
(print (apply say-hello args))
(print (apply say-hello (list "Mr. Stokes")))

; -> "hello Francis!"
;    "hello Mr. Stokes!"

Lambda

A Lambda is a special kind of anonymous function that is not placed into scope when it's declared:

(let anon-func
  (lambda (x)
    (* x x)))

(call anon-func 5)

; -> 25

They can also act like javascript's Immediately-invoked function expressions:

(call
  (lambda (x)
    (let xs (* x x x))
    (print x " cubed is " xs "\n"))
  5)

; -> 5 cubed is 125

Which might be better named as a lambda call in Lel.

Conditionals

The if keyword is used for condition checking. if is a function which takes 3 arguments, a boolean expression, a true expression and a false expression. The true and false expressions can also be lists of expressions.

(if (< 1 2)
  (print "1 is less than 2")
  (print "1 is not less than 2...?"))

; -> 1 is less than 2

And since if is a function, it always returns the evaluation of it's last statement, because of this if can be used like the ? operator in many other languages. For example:

(let emotion
  (if (< 1 2)
    "love"
    "hate"
    ))

(print "I " emotion " Lel!\n")

; -> I love Lel!

Types

Lel has 5 types:

  • Number 10.5
  • String "Hello"
  • Boolean #true or #false
  • List (2 4 6 8 10)
  • Function (lambda (x) (* 3 (* x x)))
Number

The number type is a signed 64-bit double, just like javascript.

(let positive 5)
(print positive)

; -> 5

(let negative -5)
(print negative)

; -> -5

(let floating 42.2)
(print floating)

; -> 42.2
String

A string is a string of characters contained in double quotes:

(print "hello")

; -> hello

Strings can be combined with concat:

(let hello-str "hello ")
(print (concat hello-str "world"))

; -> hello world
Boolean

Boolean can be either true or false, and are represented with the identifiers true and false:

(let true-thing true)
(let false-thing false)
(print true-thing ", " false-thing)

; -> true, false

Booleans are also the return type of comparison functions, for example:

(print (> 2 1))

; -> true
List

Lists hold groups of data similar to arrays. A variable holding a list can also be used as the arguments to a function.

Lists are declared with the list keyword.

(let primes-under-ten
  (list 2 3 5 7))

In Lel there is some syntactic sugar for creating list ranges:

(let ranged-list
  (list 1 .. 10))

; -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Ranges can be ascending and descending.

Lists can be mapped over with the map keyword:

(function multiply-by-index (item index)
  (* item index))
(print (map primes-under-ten multiply-by-index))

; -> (0 3 10 21)

Note that mapping functions must have a two argument signature. map can also take an inline lambda function.

You can get the first element in the list with head:

(print (head (list 1 2 3)))

; -> 1

And you can get everything else in the list with tail:

(print (tail (list 1 2 3)))

; -> (2, 3)

You can get a list of all the elements from start to end indexes with sublist:

(let main-list (list 0 1 2 3 4 5))
(print (sublist main-list 1 4))

; -> (1, 2, 3, 4)

You can also grab the nth element of the list with nth. The list is zero indexed, just like an array.

(print (nth (list 1 2 3) 1))

; -> 2

You can get the number of elements in a list with length:

(print (length (list 1 2 3)))

; -> 3

Finally, you can combine lists with concat just like strings:

(let list-one
  (list 1 2 3 4 5))
(let list-two
  (list 6 7 8 9 10))
(print (concat list-one list-two))

; -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

From these functions more operations are quite easily composable. For instance if you wanted the last element of a list, you could write a function like:

(let planets (list "earth" "mars" "saturn" "jupiter"))

(function last-element (l)
  (let list-length
    (length l))
  (let final-index
    (- list-length 1))
  (nth l final-index))

(print (last-element planets) "\n")

; -> jupiter
Function

A function is just a data type like the others in Lel, and thus can be stored in a variable, in a list, passed as an argument into a function, be returned from a function.

Modules

Modules can be imported from a file with import.

; FILE_A.lel
(lambda ()
  (print "hello from FILE_A.lel!\n"))
; FILE_B.lel
(let from-file-a (import "./FILE_A.lel"))
(call from-file-a)

; -> hello from FILE_A.lel!

The evaluated result of an import is the final value of the module (like a function), but an imported module will also inject the variables defined in it's scope into the the importing module. Scope injection can be avoided by only exporting a lambda function or primitive value, or by wrapping the code of the module inside a lambda call.

Imported modules run in their own scope, but can recieve access via closures and dependency injection.

Standard Libraries

The standard libraries can be accessed with the use keyword. Libraries are loaded loaded into the runtime as language-level keywords. For example, the math library:

(use "math")

(print (math-sin 4.2))

-> -0.8715757724135882

The current standard library includes

  • Math
    • math-sin
    • math-cos

Tokenisation, Parsing, and Evaluation

The lel tokeniser and parser are both written in javascript from scratch. The tokeniser recognises just 8 tokens: numbers, strings, booleans, left and right parentheses, comments and whitespace, and identifiers.

The parser transforms the stream of tokens into an AST (abstract syntax tree) which describes those tokens in terms of the semantics of S-expressions. Every '(' signifies the start of a new branch in the tree and every matching ')' signifies the end of that branch.

The interpreter evaluates the AST. Evaluation is a recursive process where each branch of the tree is an "expression", and is handed to a function called evaluateExpr. Sub expressions inside the expression are then evaluated by evaluateExpr until the result returns a primitive value of the language. This could easily lead to stack overflow exceptions when nesting of expressions is too high, so all expression evaluation is handled with asynchronous promises because promise evaluation does not work with the call stack.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].