Skip to content

Commit

Permalink
Merge pull request #10 from tpoulsen/add-initial-prelude
Browse files Browse the repository at this point in the history
Add module system
  • Loading branch information
smpoulsen authored Aug 6, 2017
2 parents 3ea77dd + 6981252 commit 608c9e0
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1 @@
*.tp linguist-language=Scheme
*.tp linguist-language=racket
51 changes: 41 additions & 10 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Currently implemented:
+ integers
+ strings
+ atoms
+ basic arithmetic (=+=, =-=, =*=, =/=)
+ booleans (=#t=, =#f=)
+ conditionals
Expand All @@ -13,19 +14,20 @@
+ Lambda functions (=lambda=)
+ variable binding (=let=)
+ recursive functions (=letrec=)
+ module imports

Additional examples can be found in the [[https://github.com/tpoulsen/terp/tree/master/examples][examples]] directory.
* Usage
** Comments
Comments are single-line and begin with `;;`:
#+BEGIN_SRC scheme
Comments are single-line and begin with =;;=:
#+BEGIN_SRC racket
;; A comment
(+ 5 1)
#+END_SRC

** Variable binding
Variables are bound using =let=:
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
(let :x 5)

(let :identity (lambda (:x) :x))
Expand All @@ -35,7 +37,7 @@
#+END_SRC
** Conditionals
=if= expressions must include a value for both the true and false case (an =if= and an =else=).
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
(if #t 5 10)
;; 5

Expand All @@ -46,7 +48,7 @@

=cond= can be used to test multiple possible conditions without chaining if/elses:
=cond= takes conditions and their outcomes should their case be true; the last condition should be a default.
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
(let sound
(lambda (animal)
(cond
Expand All @@ -63,7 +65,7 @@
Functions are defined using =lambda=; they can be bound to a name with =let=.

The arguments must be wrapped in parens. The body of the function can be bare if it does not have to be evaluated (e.g. returns a single value). Otherwise, the body must be parenthesized as well.
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
;; An anonymous identity function.
;; It returns the value it receives.
(lambda (x) x)
Expand All @@ -77,7 +79,7 @@
#+END_SRC

Multi-argument functions:
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
(((lambda (:x)
(lambda (:y)
(+ :x :y))) 5 ) 3)
Expand All @@ -89,7 +91,7 @@
#+END_SRC

Functions are automatically [[https://en.wikipedia.org/wiki/Currying][curried]] when defined. This allows for easy partial application of multi-argument functions:
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
;; add is a function that takes two arguments.
;; Currying turns it into a series of functions
;; that each takes a single argument.
Expand All @@ -109,7 +111,7 @@
** Recursive functions
Recursive functions are defined with =letrec=.
The base case(s) and recursive case(s) must be provided or the function will not terminate.
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
(letrec :factorial
(lambda (:n)
(if (:eq :n 0)
Expand All @@ -119,12 +121,41 @@
(:factorial 5)
;; 120
#+END_SRC
** Module system
Modules can be imported in to other modules to make their functions/defined expressions available.
Modules must specify the functions that they export or they cannot be used in other modules.

Syntax is =(require ...)=, where =...= is a sequence of relative filepaths to the imported module.
#+BEGIN_SRC racket
(require "examples/factorial.tp"
"examples/identity.tp")

(factorial (:identity 10))
#+END_SRC

With [[https://github.com/tpoulsen/terp/blob/master/examples/factorial.tp]["examples/factorial.tp"]] and [[https://github.com/tpoulsen/terp/blob/master/examples/identity.tp]["examples/identity.tp"]] defined as in the examples directory.

To use functions from an imported module, the module that is imported must explicitly export functions it wants to make available externally.
The syntax is =(provide ...)= where =...= is a sequence of function names.
#+BEGIN_SRC racket
;; Module only exports factorial; identity is private.

(provide factorial)

(letrec factorial
(lambda (n)
(if (equal? n 0)
1
(* n (factorial (- n 1))))))

(let identity
(lambda (x) x))
#+END_SRC
** Evaluating a file:
There's a mix task (=mix terp.run $FILENAME=) to evaluate a file:

Filename =test.tp= (=terp= files must end in =.tp=):
#+BEGIN_SRC scheme
#+BEGIN_SRC racket
(let :identity
(lambda '(:x) :x))

Expand Down
3 changes: 2 additions & 1 deletion examples/recursive.tp → examples/factorial.tp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
;; Recursive function definition
;; Recursive definition of factorial
(provide factorial)

(letrec factorial
(lambda (n)
Expand Down
12 changes: 12 additions & 0 deletions examples/function_composition.tp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(require "prelude/function.tp")

(let add
(lambda (x y) (+ x y)))

(let add_two
(lambda (x) (add 2 x)))

(let add_five
(lambda (x) (add 5 x)))

((compose add_two add_five) 10)
1 change: 1 addition & 0 deletions examples/identity.tp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
;; Example showing variable binding
(provide :identity)

(let :identity
(lambda (:x) :x))
Expand Down
4 changes: 4 additions & 0 deletions examples/modules.tp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(require "examples/factorial.tp"
"examples/identity.tp")

(factorial (:identity 10))
133 changes: 90 additions & 43 deletions lib/terp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,71 @@ defmodule Terp do
alias Terp.Arithmetic
alias Terp.Boolean
alias Terp.Function
alias Terp.ModuleSystem

@debug false

@doc """
Evaluate a terp expression.
Evaluates a terp expression.
### Notes:
+ When defining functions, e.g. `(lambda '(:x) :x)`, the arguments have to be quoted.
Only returns the result of evaluating the code.
## Example
iex> "(+ 5 3)"
...> |> Terp.eval()
8
iex> "(* 2 4 5)"
...> |> Terp.eval()
40
iex> "(* 2 4 (+ 4 1))"
...> |> Terp.eval()
40
iex> "(if #t (* 5 5) (+ 4 1))"
...> |> Terp.eval()
25
iex> "(if #f (* 5 5) 5)"
...> |> Terp.eval()
5
"""
def eval(str) do
trees = str
{result, _environment} = evaluate_source(str)
result
end

@doc """
Loads a terp module's code and returns both the result of evaluation and
the resulting environment.
"""
def evaluate_source(str, env \\ fn (z) -> {:error, {:unbound, z}} end) do
str
|> Parser.parse()
|> Enum.flat_map(&Parser.to_tree/1)
|> run_eval(env)

end
def run_eval(trees, env) do
trees
|> filter_comments()
|> eval_trees()
|> filter_nodes(:__comment)
|> eval_trees(env)
end

# Given a list of trees and an environment, evaluates the trees in
# the context of the environment.
defp eval_trees(_, env \\ fn (z) -> {:error, {:unbound, z}} end)
defp eval_trees([tree | []], env), do: eval_expr(tree, env)
defp eval_trees([tree | trees], env) do
defp eval_trees([tree | []], env) do
res = eval_expr(tree, env)
if is_function(res) do
eval_trees(trees, res)
else
eval_trees(trees, env)
case res do
x when is_function(x) -> {nil, res}
x -> {x, env}
end
end
defp eval_trees([tree | trees], env) do
case eval_expr(tree, env) do
x when is_function(x) ->
eval_trees(trees, x)
{:error, e} ->
e
_ ->
eval_trees(trees, env)
end
end

# Filters comments out of the AST.
defp filter_comments(trees) do
Enum.reject(trees, fn %RoseTree{node: node} -> node == :__comment end)
# Filters nodes out of the AST.
defp filter_nodes(trees, node_name) do
Enum.reject(trees, fn %RoseTree{node: node} -> node == node_name end)
end

@doc """
Expand Down Expand Up @@ -100,25 +108,15 @@ defmodule Terp do
str = List.first(children)
str.node
:__lambda ->
Function.lambda(children, env)
:__lambda
:__require ->
:__require
:__provide ->
:__provide
:__quote ->
children
:__letrec ->
Function.letrec(tree, env)
Enum.map(children, &(&1.node))
:__cond ->
Boolean.cond(children, env)
:__let ->
[name | [bound | []]] = children
eval_expr(name,
fn y ->
fn name ->
if y == name do
eval_expr(bound, env)
else
env.(name)
end
end
end)
:__apply ->
[operator | operands] = children
operator = eval_expr(operator, env)
Expand All @@ -129,6 +127,26 @@ defmodule Terp do
case operator do
:__if ->
Boolean.conditional(operands, env)
:__letrec ->
Function.letrec(operands, env)
:__let ->
[name | [bound | []]] = operands
eval_expr(name,
fn y ->
fn name ->
if y == name do
eval_expr(bound, env)
else
env.(name)
end
end
end)
:__lambda ->
Function.lambda(operands, env)
:__require ->
ModuleSystem.require_modules(Enum.map(operands, &eval_expr(&1, env)), env)
:__provide ->
:noop
:+ ->
Arithmetic.add(Enum.map(operands, &eval_expr(&1, env)))
:* ->
Expand All @@ -139,6 +157,30 @@ defmodule Terp do
Arithmetic.divide(Enum.map(operands, &eval_expr(&1, env)))
:eq ->
(fn [x | [y | []]] -> x == y end).(Enum.map(operands, &eval_expr(&1, env)))
:__car ->
with operand <- List.first(operands),
evaluated_list <- eval_expr(operand, env),
true <- is_list(evaluated_list) do
List.first(evaluated_list)
else
nil -> {:error, {:terp, :empty_list}}
end
:__cdr ->
with operand <- List.first(operands),
evaluated_list <- eval_expr(operand, env),
true <- is_list(evaluated_list) do
case evaluated_list do
[] -> {:error, {:terp, :empty_list}}
[_h | t] -> t
end
else
nil -> {:error, {:terp, :empty_list}}
end
:__empty ->
operands
|> Enum.map(&eval_expr(&1, env))
|> List.first()
|> Enum.empty?()
true ->
true
false ->
Expand All @@ -159,6 +201,11 @@ defmodule Terp do
:/ -> :/
:__if -> :__if
:__cond -> :__cond
:__car -> :__car
:__cdr -> :__cdr
:__empty -> :__empty
:__let -> :__let
:__letrec -> :__letrec
x -> env.(x)
end
end
Expand Down
15 changes: 7 additions & 8 deletions lib/terp/function.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,19 @@ defmodule Terp.Function do
120
"""
def y(f) do
#fix = fn (x) ->
#f.(fn (g) -> x.(x).(g) end)
#end
#fix.(fix)
f.(fn x ->
y(f).(x)
end)
end

def letrec(%RoseTree{node: :__letrec, children: children}, env) do
[name | [bound | []]] = children
def letrec([name | [bound | []]], env) do
# Make a new function wrapping bound, replacing the recursive call with a bound variable, :z
recursive_fn = :__lambda
|> RoseTree.new([RoseTree.new(:__quote, [:z]), RoseTree.update_node(bound, name.node, :z)])
recursive_fn = :__apply
|> RoseTree.new([
RoseTree.new(:__lambda),
RoseTree.new(:__quote, [:z]),
RoseTree.update_node(bound, name.node, :z)
])
|> Terp.eval_expr(env)
|> y()

Expand Down
Loading

0 comments on commit 608c9e0

Please sign in to comment.