Skip to content

batrSens/LispXS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LispXS

This is a minimalistic expandable realization of the Lisp family language. High performance isn't the purpose of this project. It's reflection on how compact Lisp can be while still being fully expandable. A kind of clay from which you can sculpt anything.

Installation

build (go1.13.7):

$ git clone https://github.com/batrSens/LispXS.git
$ cd ./LispXS
$ go build

tests running:

$ cd {lispxs_directory}
$ go test ./...

run:

$ cd {lispxs_directory}
$ ./LispXS [-n -e -r]

Flags set the mode that interpreter will work:

  • -n: interpreter expects double newline at the end of program;
  • -e: interpreter expects EOF at the end of program;
  • -r: REPL mode (default).

Usage as Golang library

  • Execute(program string) (*Output, error) - returns result, output and error's output in Output struct.
  • ExecuteStdout(program string) (*ex.Expr, error) - returns result. Using fmt.Stdout, fmt.Stdin and fmt.Stderr for i/o operations.
  • ExecuteTo(program string, ioout, ioerr io.Writer, ioin io.Reader) (*ex.Expr, error) - returns result. For i/o operations used customs streams.
  • LoadLibrary(path string) (*Library, error) - loads a LispXS library to RAM for following using through Call method.
  • (lib *Library) Call(symbol string, args ...interface{}) (*ex.Expr, error) - calls functions from the library. Arguments must be of string, int, float64 or []interface{} types. Slice also must contain variables of enumerated types.
example (executable app)
package main 
import ( "fmt"
lispxs "github.com/batrSens/LispXS/interpreter" )
func main() { res, err := lispxs.Execute("(+ 'Hello, '| World!|)") if err != nil { panic(err) }
fmt.Println(res.Output.String) // Prints "Hello, World!" }
example (library)

LispXS library:

(define hello (lambda (name)
  (+ '|Hello, | name '!)))
(define ++ (lambda (a) (+ a 1)))

Golang code:

package main 
import ( "fmt"
lispxs "github.com/batrSens/LispXS/interpreter" )
func main() { lib, err := lispxs.LoadLibrary("path_to_file") // Loading library if err != nil { panic(err) }
res, err := lib.Call("hello", "World") // 'hello' call if err != nil { panic(err) }
fmt.Println(res.ToString()) // Prints "Hello, World!"
res, err = lib.Call("++", 2019) // '++' call if err != nil { panic(err) }
fmt.Println(res.ToString()) // Prints 2020 }

Usage as FFI library

Readme in progress =)

How it works

Types

  • Number (e.g. 123, 123.456, 123456e-3, 12.3456e1, -123, 6/18)
  • Symbol (e.g. sym, |sym|, |123|, |symbol with spaces|. Following entries are equivalent: {SYM}, |{SYM}| (except numbers and whitespaces))
  • Pair - non-empty list
  • Nil - empty list

In logical expressions Nil is 'false', everything else - 'true' (not nil is T symbol).

Expressions evaluating

Everything are expressions: program is expression, data is expression, result of expression calculating is expression. Program is an expression that consists of expressions and returns result of last expression. Expressions are calculated as follows:

  • if expression is symbol, it returns the expression that is assigned to it in the symbols table;
  • if this is pair [e.g. (+ (- 2 3) (+ 8 9))], then calculates all (except for the quote, define, set!, lambda, defmacro, if, or, and and macros) elements of list [(+ -1 17)] then in case result of first element of the list is function or closure - it calculates with other elements of list as arguments [16], otherwise returns error;
  • returns self otherwise.

Scopes

By default, program works with root scope that contain all functions, T symbol with self and nil symbol with Nil (empty list). New scopes can be created by closures calls. Parent scope determines at place of definition closure. This scopes exists while closure is calculates. When accessing a variable its value is searched at current scope then in parent scope etc. define func is used to define variable in current scope, set! - redefine exists variable in nearest scope that contain it.

E.g. (define func1 (lambda (a b) (+ a ((lambda (a c) (/ a c b)) b a)))) (func1 5 3) returns 5.2 because in inner scope available a, c from 'lambda', b from 'func1' (a from inner 'lambda' shadows a from 'func1') and all from root scope:

scopes

Prelude file

Before program will be executed interpreter finds 'prelude' file in current directory and evaluates its content. This file can contain functions that will be used in program. Example is located in 'readme' directory of root of this repository.

Error handling

If an error occurs in the program (e.g. zero division: (/ 9 0)), then creates object 'Fatal' that falls to the bottom of call's stack and collects info about error location. When it process finished, 'Fatal' outputs to error's channel collected info. To catch 'Fatal' object can be used operator 'catch'. Structure: (catch body_that_can_throw_an_error (tag1 res) (tag2 res) ...). It catches an error and passed through the tags in turn. If suitable tag is found (tag is an prefix of error's tag or tag is equal to 'default'), then calculates and returns its result. Otherwise, throws down the error.

Error also can be defined by user via function throw. Structure: (throw 'tag res). If suitable tag of catch operator hasn't 'res', then it returns calculated 'res' value from throw function. If it is also missing, then returns nil.

Examples located at 'Function' section of readme.

Expandability

Some examples of expandability are below (for more clarity of examples error handling is omitted and it is assumed that the input is correct).

list, apply
usageresult
(define list (lambda args args))
(list 2 3 (+ 1 3))
(2 3 4)
(define list (lambda args args))
(defmacro apply s (define f (car s)) (define args1 (car (cdr s))) (list 'eval (list 'cons f args1)))
(apply - '(4 5 6))
-7
(define list (lambda args args))
(defmacro map (f1 ,args1)
  (define helper (lambda (f args)
    (if args
      (cons 
        (list f (list quote (car args)))      
        (helper f (cdr args))))))
  (cons list (helper f1 args1)))
(map – '(-4 9 -2))
(4 -9 2)
libraries imports
usageresultfile
(define list (lambda args args))
(defmacro map (f1 ,args1)
  (define helper (lambda (f args)
    (if args
      (cons 
        (list f (list quote (car args)))      
        (helper f (cdr args))))))
  (cons 'list (helper f1 args1)))
(defmacro import (path)
    (list 'map 'eval (list 'load path)))
(import 'path_to_file)
(++ 7)
8
(define ++ (lambda (a) (+ a 1)))
(define -- (lambda (a) (- a 1)))
ban for func redefinition
usageresult
(define list (lambda args args))
((lambda ()
	(define temp set!)
	(defmacro settemp (sym val)
		(if (= sym '+) (throw '|couldn't redefine '+' func|))
		(list temp sym (eval val)))
	(set! set! settemp)))
(set! + >)
(+ 3 2)
ERROR
indexing of lists and mutability emulating
usageresult
(define list (lambda args args))
(define <= (lambda (a b) (or (< a b) (= a b)) ))
(define get (lambda (l n)
	(if (= n 0)
		(car l)
		(get (cdr l) (- n 1)))))
(defmacro setl! (l pos val)
	(define pos (eval pos))
	(define val (eval val))
	(define mut (lambda (l i v)
		(if (<= i 0) 
			(cons v (cdr l))
			(cons (car l) (mut (cdr l) (- i 1) v)))))
	(list 'set! l (list mut l pos (list 'quote val))))
	(define lst '(s trtrt 5 laa kooo r 4))
(setl! lst 3 'new)
(get lst 3)
new
definition of mutable structures definitions
usageresult
(define list (lambda args args))
(defmacro apply s (define f (car s)) (define args1 (car (cdr s))) (list 'eval (list 'cons f args1)))
(define list (lambda args args))
(define pow2 (lambda (x) (* x x)))
(define get (lambda (l n)
	(if (= n 0)
		(car l)
		(get (cdr l) (- n 1)))))
(define <= (lambda (a b) (or (< a b) (= a b)) ))
(define sqrt (lambda (x)
	(define findi (lambda (i)
		(if (<= (* i i) x)
			(findi (+ i 1))
			(- i 1))))
	(define i (findi 0))
	(define p (/ (- x (* i i)) (* 2 i)))
	(define a (+ i p))
	(- a (/ (* p p) (* 2 a)))))
(defmacro setl! (l pos val)
	(define mut (lambda (l i v)
		(if (<= i 0)
			(cons v (cdr l))
			(cons (car l) (mut (cdr l) (- i 1) v)))))
	(list 'set! l (list mut l pos (list 'quote val))))
(defmacro defstruct args
	(define structname (car args))
	(define funcname (lambda (str) (+ structname '- str)))
	(define methods (lambda (args i)
		(if (not args)
			nil
			(cons
				(list 'define (funcname (+ 'get- (car args))) (list 'lambda '(s) (list 'get 's i)))
				(cons
					(write (list 'defmacro (funcname (+ 'set- (car args))) '(s v) (list 'list ''setl! 's i 'v)))
					(methods (cdr args) (+ i 1)))))))
	(cons
		'begin
		(cons
			(list 'define (funcname 'new) (list 'lambda (cdr args) (cons 'list (cons (list 'quote structname) (cdr args)))))
			(cons
				(list 'define (funcname '?) (list 'lambda '(s) (list '= '(car s) (list 'quote structname))))
				(methods (cdr args) 1)))))
(defstruct point x y)
(define dist (lambda (p1 p2)
	(if (not (and (point-? p1) (point-? p2))) (throw '|points expected|))
	(sqrt (+ (pow2 (- (point-get-x p2) (point-get-x p1))) (pow2 (- (point-get-y p2) (point-get-y p1)))))))
(define pt1 (point-new 4 2))
(define pt2 (point-new -2 6))
(point-set-y pt1 -2)
(dist pt2 pt1)
10

Functions

quote

Returns expression without calculation. Expects one argument. Following entries are equivalent: (quote {EXPR}), '{EXPR}.

examples
usageresult
(quote (one two))
(one two)
(quote 23)
23

eval

Evaluates result of expression. Expects one argument. Following entries are equivalent: (eval (quote {EXPR})), {EXPR}.

examples
usageresult
(eval '(+ 23 32))
55
(eval 23)
23

define

Defines variable in current scope. Expected two variables: first - symbol, second - an expression whose result will be saved and returned from define.

examples
usageresult
(define a 2)
2
(define a '|some string|)
some string
(define a (+ 20 3)) 
(+ a 32)
55

set!

Redefines existed variable in nearest scope. Expected two variables: first - symbol, second - an expression whose result will be saved and returned from define.

examples
usageresult
(define a 2) 
(set! a 3)
3
(define a 5) 
((lambda (b) (set! a (+ a b)) 50) 
a
55

lambda

Returns new closure with current parent scope. When it closure will be called, a new scope is created. Expected at least two variables: first - list with symbols that means arguments or symbol that means list of arguments, second and subsequent - body of closure. Closure returns result of last expression of body.

examples
usageresult
(define a (lambda (b) (+ b b))) 
(a 3)
6
(define list (lambda args args))
(list 3 (+ 5 4) 0)
(3 9 0)
(define list (lambda args args))
(defmacro apply s (define f (car s)) (define args1 (car (cdr s))) (list 'eval (list 'cons f args1)))
(apply + '(1 2 3))
(define 100+ (lambda args
  (if args
    (cons
      (+ 100 (car args))
      (apply 100+ (cdr args)))
    nil)))
(100+ 1 4 7)
(101 104 107)
(define a 5) 
((lambda (b) (set! a (+ a b)) 50) 
a
55

defmacro

Defines macro in current scope. When it macro will be called, new code will be created and then executed. Expected at least three variables: first - symbol, second - list with symbols that means arguments or symbol that means list of arguments (if the symbol is preceded by a comma then expression calculates), third and subsequent - body of macro. Macro executes result of last expression of body.

examples
usageresult
(define list (lambda args args))
(define a 2) 
(defmacro set10! (b) (list 'set! b 10))
(set10! a)
a
10
(defmacro apply s (define f (car s)) (define args1 (car (cdr s))) (list 'eval (list 'cons f args1)))
(apply + '(3 4 5))
12
(define list (lambda args args))
(defmacro map (f1 ,args1)
  (define helper (lambda (f args)
    (if args
      (cons (list f (car args)) (helper f (cdr args)))
      nil)))
  (cons list (helper f1 args1)))
(map - '(1 2 3))
(-1 -2 -3)

if

Conditional operator. Expected two or three arguments: first - conditional, second - expression that will be calculated and whose result will be returned from if in case of result of conditional is not nil, third - else. If the third argument is missing - if returns nil in case result of conditional is nil.

examples
usageresult
(if (> 2 3) 'two 'three)
three
(if (> 2 1) 'two)
two
(if (> 2 3) 'two)
nil

or

Calculates expressions until it meats not nil value. Returns this value. If all results of expressions are nil then returns nil. Returns nil in case of zero number of arguments.

examples
usageresult
(or 2 3)
2
(define a 3)
(or nil (define a 5) (define a 10))
a
5
(or (cdr '(2)) nil)
nil
(or)
nil

and

Calculates expressions until it meats nil value. If one of results of expressions is nil then returns nil. Result of last expression otherwise. Returns T in case of zero number of arguments.

examples
usageresult
(and 2 3)
3
(define a 3)
(and nil (define a 5) (define a 10))
a
3
(and (cdr '(3 3)) 'YY)
YY
(and)
T

catch

Catches an error. For more information, see an error handling section.

examples
usageresult
(catch (/ 2 0) (/ 123123))
123123

throw

Throws an error. For more information, see an error handling section.

examples
usageresult
(catch (throw 'error) (err 123123))
123123

write

Writes string representation of expression's result to output channel. Returns it result. Expected one argument.

examples
usageresultout
(write '(2))
(2)
(2)
(write (if T 'ss 3))
ss
ss

read

Reads string representation of expressions from output channel and returns list of these expressions. Expected zero number of arguments.

examples
usageresultin
(read)
((2) 3)
(2) 3

load

Reads string representation of expressions from file and returns list of these expressions. Expected one argument - path to file.

examples
usageresultfile
(load 'path_to_file)
(45 (+ 6 7))
45
(+ 6 7)

begin

Returns result of last expression (nil in case of zero number of arguments).

examples
usageresult
(begin)
nil
(begin 4 (+ 4 5) 'qwerqwe (/ 3 (- 1 2)))
-3

cons

Returns new pair. Expected two arguments: first will be 'car' of new pair, second - 'cdr'. Second argument must be a pair or nil.

examples
usageresult
(cons 2 nil)
(2)
(cons (+ 5 6) '(12 13 14))
(11 12 13 14)

car

Returns 'car' of pair. Expected one argument that must be a pair.

examples
usageresult
(car '(2))
2
(car '((4 5) 6 7))
(4 5)

cdr

Returns 'cdr' of pair. Expected one argument that must be a pair.

examples
usageresult
(cdr '(2))
nil
(cdr '((4 5) 6 7))
(6 7)

symbol->number

Converts symbol to number. Expected one argument that must be a symbol that name equal to string representation of any number.

examples
usageresult
(symbol->number '|  23 |)
23
(symbol->number '|6/3|)
2
(symbol->number '|1234.56e-2|)
12.3456
(symbol->number '|  -23.4|)
-23.4

number->symbol

Converts number to symbol with name that equal to string representation of number. Expected one argument that must be a number.

examples
usageresult
(define |234| (lambda (a) (+ a 1)))
((eval (number->symbol 234)) 5)
6

symbol?

Returns T if argument is a symbol and nil otherwise. Expected one argument.

examples
usageresult
(symbol? '|2|)
T
(symbol? 2)
nil

number?

Returns T if argument is a number and nil otherwise. Expected one argument.

examples
usageresult
(number? 2)
T
(number? '|2|)
nil

pair?

Returns T if argument is a pair and nil otherwise. Expected one argument.

examples
usageresult
(pair? '(2 3 4 5))
T
(pair? nil)
nil

not

Returns T if argument is nil and nil otherwise. Expected one argument.

examples
usageresult
(not nil)
T
(not 1234)
nil

=

Returns T if argument are equivalent and nil otherwise. Expected at least two arguments.

examples
usageresult
(= 's2 (begin 's2) (if nil 2 's2) (+ 's '|2|))
T
(= '(2 3) (cons 2 '(3)))
T
(= 2 '|2|)
nil
(= 2 2 2 2 2 3)
nil

>

Returns T if first argument more than second and nil otherwise. Expected two numbers.

examples
usageresult
(> 3 2)
T
(> 3 3)
nil

<

Returns T if first argument less than second and nil otherwise. Expected two numbers.

examples
usageresult
(< -7 2)
T
(< 4 2)
nil

len

Returns length T of symbol's name in characters. Expected one symbol.

examples
usageresult
(len '12345)
5
(len '漢字!)
3

+

Returns sum of numbers or symbol that name is concatenation of names all symbols in arguments. Expected any quantity of numbers or at least one symbol.

examples
usageresult
(+ 2 3 4)
9
(+ 'hello, '| world!|)
hello, world!
(+)
0

-

Returns difference of numbers or symbol that name is substring of symbol's name ( [second_arg, third_arg) ). Expected any quantity of numbers or least one symbol and two numbers.

examples
usageresult
(- 2)
-2
(- 2 3 4)
-5
(- 'thisstringishuge 4 10)
string
(-)
0

*

Returns product of numbers. Expected any quantity of numbers.

examples
usageresult
(* 2 3)
6
(*)
1

/

Returns division of numbers. Expected at least one number.

examples
usageresult
(/ 6 2 4)
0.75
(/ 7)
7

About

Minimal expandable and embeddable lisp.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages