=pod

=head1 NAME

oh

=head1 SYNOPSIS

=head2 Command Line Interface:

Evaluate code:

 oh e "(print :oh)"

Evaluate code and launch a REPL:

 oh e "(setq oh 3)" i

Load files:

 oh file.oh file2.oh

Load files and launch a REPL:

 oh file1 i file2 i

Load files named i and e:

 oh f i f e

Launch a REPL:

 oh

=head2 Module Interface:

    use Oh qw/interpret_string evaluate_element/;

    my $result = interpret_string '(+ 1 2 3)';
    
    my $same_result = evaluate_element ['+', 1, 2, 3];

=head1 INSTALLATION

    cpan Oh

=head1 DESCRIPTION

This is a lisp interpreter written in perl trying to get the best of both worlds:

From perl it inherits the portability, unix scripting features and the whole CPAN.

Fron lisp inherits lisp evaluation rules, s-expressions and macros.

I always thought of lisp as a black box with no access to the external world and always thought that perl could be the perfect slave for lisp, being lisp the mind and perl the body.

I think that the best way to blend both is to make an interpreter in perl or a transpiler to perl (or any other language where we want to steal features from).

The main concern is to interoperate with perl as much as possible while having proper lisp evaluation rules.

The current implementation is in a prototype state and everything is subject to change.

However you are encouraged to test this interpreter and push it to its limits in order to give constructive feedback and help with its improvement and evolution.

The language has the following operators, macros and functions:

set, setq, get, let, lambda, defun, define, fun, macro, defmacro, +, if, when, unless, perl-fun, perl-defun, print, car, cdr, funcall, eval, load, eval-string, try, anon, sub, quote, unquote, quasiquote, flatten...

It has more, but it's lacking a lot of things a proper language should have.

=head1 FUNCTIONS, MACROS AND OPERATORS

=head2 defun

defun is common lisp's defun:

 (defun oh (one &optional (two 2) three &key four (five 5))
  (list one two three four five))

=head2 define

define is scheme's define:

 (define oh 3)

This creates a new symbol named 'oh' in the current environment with the value 3

 (define (oh x . y) 
  (list x y))

 (oh 1 2 3)

This defines a new function named 'oh' using scheme style lambda arguments.

It will return the list: (1 (2 3))

=head2 let

The operator let works a bit differently than in scheme or cl:

 (let (a 1 b 2 c 3)
  (list a b c))

In cl or scheme it would be instead:

 (let ((a 1) (b 2) (c 3))
  (list a b c))

=head2 defmacro

defmacro defines a macro with common lisp style lambda arguments:

 (defmacro oh (arg1 &rest arg-list)
  `(list ,arg1 ,@arg-list))

=head2 macro

macro defines a macro with scheme style lambda arguments:

 (macro oh (arg1 . arg-list)
  `(list ,arg1 ,@arg-list))

=head2 setq

setq sets or updates a symbol

 (setq oh 3)

equivalent to:

 (define oh 3)

with the difference that if setq finds the symbol in the parent scope it will mutate that symbol instead, while define will always create a new symbol in the current environment, no matter if another symbol exists.

=head2 set

set is a function instead of an operator and has dual behavior.

It can set symbols like setq if the symbol is quoted and set hash tables or lists if the first element is one of them.

 (set 'oh 3)

This is equivalent to:

 (setq oh 3)

It will also update it if it exists in a parent scope.

 (setq some-list '(1 2 3))

This creates a new symbol named 'some-list' that retains the list: (1 2 3)

=head2 get

The function 'get' will return an element from a list or hash table.

 (get some-list 0)

This returns the first element of the list returned by the 'some-list' symbol, which in our case was the 1.

 (set some-list 0 24)

This mutates the first element of the list and sets it to the value 24. In our case the list is now: (24 2 3)

get and set when using lists can also receive negative indices, they will access the element starting from the end.

 (get some-list -1)

This returns the last element of some list, in our case was the number 3.

=head2 hash tables

They work the same with hash tables, accepting symbols as keys.

 (setq some-hash (hash one 1 two 2 three (+ 1 2)))

 (get some-hash 'one)

 (set some-hash 'one 24)

=head2 lambda

Lambda works with cl style lambda arguments:

 (lambda (x &rest y)
  (list x y))

But lambda is just a macro:

 (macro lambda (args . code)
  `(anon (lisp-args ,args) ,@code))

=head2 anon

The operator 'anon' creates a lambda with no argument bindings.

lisp-args and scheme-args are just an operator that performs the binding from a lambda argument list.

The 'anon' operator provides no bindings, but when the interpreter evaluates a lambda, it will set a symbol named 'args' with the list of arguments it receives.

 ((anon args) 1 2 3)

This returns the list of arguments: (1 2 3)

=head2 sub

The 'sub' operator creates a lambda and sets it in the current environment.

 (sub list args)

 (list 1 2 3)

sub and anon do not provide argument bindings. The arguments will be present in the symbol 'args' when a lambda is evaluated.

=head2 fun

defun, fun and define are macros using sub and scheme-args or lisp-args

 (macro fun (name args . code)
  `(sub ,name (scheme-args ,args) ,@code))

 (macro defun (name args . code)
  `(sub ,name (lisp-args ,args) ,@code))

 (macro define (name . value)
  (if (listp name)
   `(fun ,(car name) ,(cdr name) ,@value)
   `(setq ,name ,(car value))))

=head2 perl-sub

perl-sub creates perl subroutines in a given package:

 (perl-sub package-name fun-name code...)

It does not provide argument bindings, being similar to sub, but it actually creates a perl subroutine in a perl package

 (perl-sub oh hello_world
  (print "Hello, world"))

Now this function is a perl subroutine named 'hello_world' and it exists in the perl package named 'oh'

Which means that is now accessible from perl:

 oh::hello_world();

The interpreter can also call perl subroutines from a module:

 (oh::hello_world)

=head2 perl-defun

The perl-defun macro provides a way to define perl subroutines using cl style bindings:

 (macro perl-defun (module name args . code)
  `(perl-sub ,module ,name (lisp-args ,args) ,@code))

 (perl-defun oh print_something (&optional (something 'oh))
  (print something))

 (oh::print_something "Hello, world")

=head2 perl-fun

The perl-fun macro is similar, but provides scheme style bindings:

 (macro perl-fun (module name args . code)
  `(perl-sub ,module ,name (scheme-args ,args) ,@code))

 (perl-fun oh print_something something
  (print (car something)))

 (oh::print_something "Hello, world")

=head2 method

There is also the function called 'method' which will expect an object as the first argument and the name of the method as the second. It can also take the name of a module as the first argument.

 (use IO::Socket)

 (method 'IO::Socket 'new 'localhost:8080)

That would return an IO::Socket object if there was a server in localhost listening at port 8080.

perl-sub, perl-fun and perl-defun allow us to create perl subroutines into a perl package.

If the perl package does not exist it will be created, because packages in perl autovivify.

That means that we can create perl classes, since they are just packages.

=head2 parent

There is also the 'parent' operator which creates inheritance by pushing to the perl's ISA array on the package.

 (use Bot::BasicBot)

 (perl-fun bot said (bot args)
  (unless (or (eq (get args) 'NickServ)
              (eq (get args) 'ChanServ))
   'hey))

 (parent bot Bot::BasicBot)

 (method (method 'bot 'new (qw server irc.libera.chat port 6667 nick superbot)) 'run)

=head2 use

The use operator loads a perl module.

 (use Path::Tiny)

 (use IO::Socket)

=head2 bindings

The bindings function imports perl subroutines into the interpreter from a package name that receives as the first argument:

 (use Curses)

 (bindings (qw Curses initscr raw refresh endwin addstr getch))

Now those functions exist in the interpreter:

 (initscr)
 (raw)
 (addstr "oh...")
 (refresh)
 (getch)
 (endwin)

This way we do not need to do it like this:

 (Curses::initscr)
 (Curses::raw)
 (Curses::addstr "oh...")

=head2 quote

quote is an operator that returns its first and only argument

 (quote oh)

It is something so common in lisp that it deserves a shortcut.

 'oh

The quote symbol is a reader symbol like ( or " that reads one element and returns the list: (quote oh)

Which means that 'oh and (quote oh) are equivalent.

=head2 quasiquote and unquote

quasiquote is an operator similar to quote, but when it receives a list as first and only argument, it will traverse that list recursively and check for lists with unquote as the first element like: (unquote x) and evaluate them, while quoting the other lists or elements.

 (quasiquote (1 2 (unquote (+ 1 2))))

This returns a list with: (1 2 3) since the expression (+ 1 2) was evaluated as it's inside the unquote operator.

They also have their own shortcuts like quote does.

The comma ',' is unquote and the backtick '`' is quasiquote:

 `(1 2 ,(+ 1 2))

The reader returns: (quasiquote (1 2 (unquote (+ 1 2))))


=head2 quote words

qw is an operator that autoquotes words.

 (qw oh my cat is nice)

This operator does not actually return any list, the words will be 'flattened' as if they were symbols being quoted.

 (list (qw oh my cat))

is equivalent to:

 (list 'oh 'my 'cat)

It is taken from perl qw// and serves as an autoquote mechanism.

=head1 PREDICATES

=head2 eq

The eq operator checks for identity. Will return true if to objects are the same object, if two strings have the same characters and if two numbers are the same. It is just a binding for perl's eq operator

 (eq 1 1) 

returns true

 (eq '(1 2 3) '(1 2 3))

returns false because they are not the same object

 (setq oh '(1 2 3))
 (eq oh oh)

returns true since it's the same object

=head2 equal

The equal operator does not check for identity if the objects are lists or hash tables. It will iterate on all elements and check whether they are equal or not.

 (equal '(1 2 3) '(1 2 3))

Returns true

 (equal (hash oh 3) (hash oh 3))

Returns true

The equal operator does not have any safeguard for comparing circular references. It cannot compare them and it will not trigger an error. It will recurse forever until it blows the perl return stack, which will blow after your memory is exhausted.

I could add a safeguard, but at the end you are not supposed to compare circular references and the only reason to add a check and trigger an error would be to warn you when you do that inadvertently. Perl will warn you that the function compare and compare_lists or compare_hashes went into deep recursion.

=head2 =

The = operator is for comparing numbers, it is equivalent to perl's == operator, with the difference that it accepts more than two elements.

 (= 1 1 1)

Returns true

 (= 1 1 2)

Returns false

=head2 <

The < operator works similar to = by accepting several arguments

 (< 1 2 3)

Returns true

 (< 1 2 2)

Returns false

=head2 >

The > operator works the same as <

 (> 3 2 1)

Returns true

=head2 =< =>

They check if the elements are greater or equal and less or equal

 (<= 1 2 2)

Returns true

=head2 null

The null function returns true if the value is 0, an empty string, nil and an empty list.

 (null '())
 (null 0)
 (null ())
 (null nil)
 (null "")

Returns true in all those cases

=head2 defined

The defined function returns true if the element is not nil.

 (defined '())

Returns true, since an empty list is not really nil.

 (defined ())

Returns false, since the interpreter returns nil when evaluating an empty list.

 (defined "")
 (defined 0)

Returns true

=head2 not

Negates a value. If it's false (empty string, 0 or nil) it returns true, returns false otherwise

 (not nil)
 (not "")
 (not 0)
 (not)

Returns true

=head2 listp

Returns true if the argument is a list:

 (listp '(1 2 3))


=head1 LIST OPERATORS

=head2 car and cdr

car returns the first element of a list:

 (car '(1 2 3))

Returns 1

It is equivalent to:

 (get '(1 2 3) 0)

cdr returns the list without the first element:

 (cdr '(1 2 3))

Returns (2 3)

car and cdr can be combined:

 (caaar x)

Is equivalent to:

 (car (car (car x)))

cdr can be combined the same way

 (cdddr x)

Is equivalent to:

 (cdr (cdr (cdr x)))

They can both be mixed:

 (cadr x)

Is equivalent to:

 (car (cdr x))

They can be combined in any way:

 (caddar x)
 (caddr x)
 (cdadr x)

It does not have limit of nesting:

 (caaaaar '(((((oh))))))

Returns 'oh

=head2 remove

Remove will expect an element and a list and will remove the elements that are the same as that element.

 (remove nil `(1 2 ,nil 3 4 ,nil))

Returns (1 2 3 4)

It uses 'equal' to test for equality, so it can compare lists or hash tables.

 (remove '(1 2 3) '(1 (1 2 3) (1 2) (1 2 3) 3))

Returns (1 (1 2) 3)

=head2 mapcar

Expects a function and one or more lists.
Iterates the lists and gives the current elements to the function, returns a list of the values returned by the function.

 (mapcar (lambda (x) (+ 1 x)) '(1 2 3))

Returns (2 3 4)

If mapcar receives more than one list, the function should also receive more than one element, equivalent to the number of lists.

 (mapcar (lambda (x y) (+ x y)) '(1 2 3) '(4 5 6))

Returns (5 7 9)

The first time mapcar iterates will take the first element of both lists, in this case the 1 from the first list and the 4 from the second, then it will give that to the lambda which will sum them, returning 5. Then it will take the second element of both lists and do the same, until it reaches the limit of the shorter list.

The interpreter has a special syntax to return a function.
If a symbol is prefixed with a '#' symbol, the interpreter will return the function with that name or nil if does not find it.

 #list

This returns the function named 'list'.

It is equivalent to:

 (find-function 'list)

It is a shorthand to use it in functions like mapcar. 

Note that in common lisp it's #'list instead and '#' is actually a read character.

I'm yet deciding if I want to make '#' a read character. Currently this is just a special case in the interpreter and it might change in the future.

As an example it can be used with mapcar like this:

 (mapcar #list '(1 2 3) '(4 5 6) '(7 8 9))

Returns ((1 4 7) (2 5 8) (3 6 9))

=head2 remove-if

The function remove-if is similar to remove, but instead of an element it takes a function to use as predicate.

 (remove-if (lambda (x) (equal 0 x)) '(0 1 2 0 3))

Returns (1 2 3)

In this case it was equivalent to:

 (remove 0 '(0 1 2 0 3))

But it used a predicate instead.

=head2 remove-if-not

It removes element if the predicate returns false instead of true.

 (remove-if-not (lambda (x) (equal 0 x)) '(0 1 2 0 3))

Returns (0 0)

This case is equivalent to:

 (remove-if (lambda (x) (not (equal 0 x))) '(0 1 2 0 3))

=head1 CONDITIONALS

=head2 if

The if operator expects code to work as a predicate and code to evaluate if the condition is true and optionally code to evaluate if the condition is false

 (if 1
  (print "it's true")
  (print "it's false"))

This would print "it's true" on the terminal since 1 was evaluated and returned a true value.

 (if 0
  (print "it's true")
  (print "it's false"))

This would print "it's false" since 0 is a false value.

If we wanted to perform more actions than just one, we can use progn.

 (if 1
  (progn (print "it's true") (print "yeah, it's true"))
  (print "it's false"))


=head2 when

The when expects code to evaluate as a predicate and the rest of code will be evauated if the predicate returned a true value.

when has an implicit progn, 

 (when 1 (print "it's true") (print "yeah, it's true"))

when does not evaluate anything if the condition is not true.

=head2 unless

unless is the reverse of when. it will evaluate the condition if it's not true, and do nothing if it's true.

 (unless 0 (print "it's false") (print "yeah, it's false"))

This same code would be equivalent:

 (when (not 0) (print "it's false") (print "yeah, it's false"))


=head2 cond

cond takes pairs of tests and code, it will evaluate the code of the test, and if it returns true it will evaluate the code.

 (cond
  ((equal 'oh 'oh) (print "oh is oh"))
  ((< 1 2) (print "one is less than two")))

In this case, even if the two tests return true: (equal 'oh 'oh) and (< 1 2), only the first one will get executed, which will be the code for: (print "oh is oh"), cond will stop execution once it finds a match.

To evaluate code when any of the conditions match, we use t or :otherwise to set the code to evaluate when any condition returns true.

 (cond
  ((equal 'oh 'meh) (print "that will never be true"))
  (:otherwise (print "this will get executed")))

In this case the :otherwise clause will be executed because the other clause will fail.

Instead of :otherwise we can use t

 
 (cond
  ((equal 'oh 'meh) (print "that will never be true"))
  (t (print "this will get executed")))

It is equivalent to the previous one.

One thing to note is that t and :otherwise are not required to be the last clause as in common lisp. And that common lisp accepts 'otherwise' as a symbol as well as a keyword, while this interpreter only accepts :otherwise as a keyword.

=head2 case

case is similar to cond, but does not take a test as code to evaluate. Instead it only compares symbols.

 (setq x 'oh)

 (case x 
  (oh (print "The value of x is oh"))
  (t (print "the value of x is not oh")))

It will fail with strings or other objects, but accepts nil which will only be executed if the value is undefined, it will not trigger if the value is the other false values like 0 or ""

 (setq x nil)

 (case x
  (nil (print "the value of x is undefined"))
  (:otherwise (print "the value of x is not undefined")))

case can also accept more than one symbol for the same clause.

 (case x 
  ((oh meh) (print "the value of x is oh or meh")))



=head1 PREDEFINED SYMBOLS

=over 2

=item aeth

 The galactic emperor

=item nil

 undef

=item t

 1

=item version

 0.07

=back

=head1 TODO

I have to create a better abstraction for creating perl classes.

=head1 BUGS

I'm pretty sure that the common lisp style argument binding has some bugs, since I did not test it thoroughly.

The scheme like arguments might also have bugs.

It is likely that a lot of bugs are waiting for the right moment to destroy my dreams.

=head1 LICENSE

CC0 1.0 Universal (CC0 1.0) Public Domain Dedication

To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide.

This software is distributed without any warranty.

https://creativecommons.org/publicdomain/zero/1.0/

=cut
