lemur

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 12, 2024 License: BSD-3-Clause Imports: 8 Imported by: 0

README

Lemur

pipeline status coverage report

Lemur is a simple dynamically typed programming language

lemur logo

Features

  • int, bool, string primitive types
  • Arithmetic operators + - * /
  • Termination of expressions don't need ; but can be used (similar to Go)
    • A ; is automatically inserted before a new line, comment or EOF and after
      • An identifier
      • int, bool, and string
      • }, ]
      • The keywords return, continue, and break
      • The postfixes ++ and --
  • Increment and decrement expressions
    • Only via postfix a++ or assignment expressions val a += 1
  • Arrays and maps
  • Prefixes ! and -
  • if, else, else if and switch conditional control blocks
  • Comparison operators
    • Less than <
    • Greater than >
    • Less than equal to <=
    • Greater than equal to >=
    • Equal ==
    • Not equal !=
    • And &&
    • Or ||
  • while, for, and for in (over arrays and maps) loop control blocks
    • break and continue keywords within said loop control blocks
  • Single line comments // and multi line comments /* */
  • Functions with support for both declared with a name and declared as a literal assigned to a symbol
  • Functions are treated as first class supporting higher order and closure functionality
  • Assignment is immutable by default (mutable support via mut keyword)

The provided interpreter is written Go leveraging only the standard library and utilises:

  • Top down operator precedence (Pratt method) parser
  • Internal compilation to custom byte code
  • Executed within a custom built stack virtual machine with a stack frame implementation for function support

The interpreter also can execute source code:

  • Via a file
  • Via the REPL
  • As a go module for your own project

Installation

Binary
# For latest
curl -O https://gitlab.com/api/v4/projects/52586004/packages/generic/lemur/latest/lemur
# For specific version
curl -O https://gitlab.com/api/v4/projects/52586004/packages/generic/lemur/{VERSION}/lemur

chmod +x lemur

Then move to a directory that's in OS's bin $PATH e.g:

sudo mv lemur /usr/local/bin
Container
docker login registry.gitlab.com
## Pull the latest
docker pull registry.gitlab.com/maen-bn/lemur
## Run the REPL
docker run -it registry.gitlab.com/maen-bn/lemur
Go module
go get -t gitlab.com/maen-bn/lemur

Usage

The binary provides a REPL

lemur

And can execute a source file

lemur main.lmr

The go module can execute lemur code provided via a string

package main

import (
	"fmt"

	"gitlab.com/maen-bn/lemur"
)

func main() {
	input := `
	func doubler(x) {
	    return x * 2
	}
	doubler(3)
	`

	runtime := lemur.NewRuntime()
	result, err := runtime.Execute(input)
	if err != nil {
		fmt.Printf("execution error, %s\n", err)
		return
	}

	fmt.Printf("Result: %s\n", result.Inspect())
	fmt.Printf("Result type: %s\n", result.Type)
}

Or from a file

package main

import (
	"fmt"

	"gitlab.com/maen-bn/lemur"
)

func main() {
	filename := "main.lmr"
	runtime := lemur.NewRuntime()
	result, err := runtime.ExecuteFile(filename)
	if err != nil {
		fmt.Printf("execution error, %s\n", err)
		return
	}

	fmt.Printf("Result: %s\n", result.Inspect())
	fmt.Printf("Result type: %s\n", result.Type)
}

Examples

Comments
// Single line
/**
 *
 * Multi line
 * Multi line
*/
Integer Arithmetic
1 + 1 
5 - 2
2 * 2
9 / 3
Strings
"hello"
// String concatenation
"hello" + " there"
Variable declaration
// Immutable by default
val a = 1
// Mutable
val mut b = 2
Assignment
val mut a = 1

// Assignment
a = 2

// Assignment increment
a += 1

// Assigment decrement
a -= 1

// Postfix increment
a++

// Postfix decrement
a--

// Assignment to different type
a = "two"
Prefixes
// Not 5
!5

// Minus 15
-15

// Not true
!true

// Not false
!false
Comparison infixes
// Equal
true == true

// Not equal
true != false

// Greater than
10 > 3

// Greater than equal to
10 >= 3

// Less than
4 < 8

// Less than equal to
4 <= 8

// And
true && false

// Or
true || false
Conditionals
// if
if 5 == 5 {
    5
}

// if else
if 5 == 4 {
    5
} else {
    4
}

// if, else if, else
if 5 == 4 {
    5
} else if 5 == 6 {
    6
} else {
    4
}

// switch
val a = 2
val mut res = ""
switch a {
    case 1:
        res = "one"
    case 2, 3, 4, 5:
        res = "some other number"
    default:
        res = "nothing"
}
Arrays
// Array of ints
[1, 2, 3, 4, 5]

// Array of ints using expressions
[1 + 1, 2 + 2, 3 * 5, 4 / 2]

// Indexing array
val a = ["bob", "linda", "tina", "gene", "louise"]
a[1] // linda

// Indexing array with expression
a[1 + 1] // tina
Maps
// Map
{"name": "bob", "age": "46", "occupation": "restaurant owner"}

// Map with expressions
{"nam" + "e": "bob", "age": 23 * 2, "occupation": "restaurant " + "owner"}

// Indexing map
val a = {"name": "bob", "age": "46", "occupation": "restaurant owner"}
a["name"] // bob

// Indexing map with expression
a["ag" + "e"] // 46 
Loops
// while
val mut counter = 0
while counter < 3 {
    counter++
}

// for
counter = 0
for i = 0; i < 5; i++ {
    counter += i
}

// for in
val arr = [1, 2, 3, 4] // can be map too
val mut keyCounter = 0
for key in arr {
    keyCounter += key
}

// for in with value from iterable
keyCounter = 0
val mut valueCounter = 0
for key, value in arr {
    keyCounter += key
    valueCounter += value
}

// for in with just value 
valueCounter = 0
for _, value in arr {
    valueCounter += value
}
Functions
// function
func adder(x, y) {
    return x + y
}

// function without explicit return
func product(x, y) {
    x * y
}

// function as a literal
val doubler = func(x) {
    x * 2
}

// function literal as a one liner
val squarer = func(x) { x * x }

// Nested functions with a function passed as a parameter
func map(arr, f) {
    val iter = func(arr, accumulated) {
	if len(arr) == 0 {
	    return accumulated
	}

	// rest, push and first are builtins. See below for more info
	return iter(rest(arr), push(accumulated, f(first(arr))))
    }

    iter(arr, [])
}

val a = [1, 2, 3, 4]
map(a, doubler) // [2, 4, 6, 8]

// Function returned from another function
func newAdder() {
    return func(x, y) { x + y }
}

val myAdder = newAdder()
myAdder(2, 1) // 3
Built ins
len([1,2,3,4]) // 4
len("hello") // 5

first([1,2,3,4]) // 1

last([1,2,3,4]) // 4

rest([1,2,3,4]) // [2,3,4]

println("hello") // writes string hello to standard output

Documentation

Overview

Package lemur implements the capabilities to take lemur source code and execute it. Execution can be ran multiple times with different inputs holding state from the previous execution

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Result

type Result struct {
	// Type gives back what underlying lemur type the result is
	Type string
	// contains filtered or unexported fields
}

Result represents the output after the lemur source code has been ran

func (*Result) Inspect

func (r *Result) Inspect() string

Inspect is what the value output of the executed lemur source code

type Runtime

type Runtime struct {
	// contains filtered or unexported fields
}

Runtime represents and encapsulates the entire runtime of lemur source code program(s). Runtime is design to hold states of compiler constants and runtime globals along with the compiler symbol table so it can be reused in the next execution

func NewRuntime

func NewRuntime() *Runtime

NewRuntime is a simple constructor to create a fresh Runtime. NewRuntime can be used to start up a fresh set of execution or if there is no interested in maintaining state from a previous lemur source code execution

func (*Runtime) Execute

func (l *Runtime) Execute(input string) (*Result, error)

Execute runs the lemur source code input. It will return the last popped element from the stack at the end of execution or any error that may have occurred during execution (including lexer, parser, compiler and vm errors).

Execute will also retain the state from the just executed lemur source code to help facilitate use cases such as REPLs. This state retention can be avoided by just creating a fresh runtime via NewRuntime

func (*Runtime) ExecuteFile

func (l *Runtime) ExecuteFile(filename string) (*Result, error)

ExecuteFile operates exactly like Runtime.Execute as that is what it calls to execute lemur source code, but it will open a file of source code for you prior to passing the input into the runtime

Directories

Path Synopsis
cmd
internal
ast
Package ast implements the nodes that form the abstract syntax tree once the lemur code has been parsed
Package ast implements the nodes that form the abstract syntax tree once the lemur code has been parsed
compiler
Package compiler implements the translation layer of lemur from the generate AST produced by the parser to a set of instructions that consist of byte code made up of operations which are define in the op package
Package compiler implements the translation layer of lemur from the generate AST produced by the parser to a set of instructions that consist of byte code made up of operations which are define in the op package
lexer
Package lexer implements the tokenisation of the lemur source code input.
Package lexer implements the tokenisation of the lemur source code input.
op
Package op implements all the operation codes that can be created during compilation and used to drive what to execute during run time within the VM.
Package op implements all the operation codes that can be created during compilation and used to drive what to execute during run time within the VM.
parser
Package parser implements a parser for Lemur source files.
Package parser implements a parser for Lemur source files.
repl
Package repl implements your typical read, lex, parse, evaluate program for lemur source code.
Package repl implements your typical read, lex, parse, evaluate program for lemur source code.
vm
Package vm implements the backend runtime of lemur.
Package vm implements the backend runtime of lemur.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL