https://github.com/wilfred/mal_pharo
A Make A Lisp implementation in Pharo
https://github.com/wilfred/mal_pharo
lisp mal pharo-smalltalk
Last synced: 3 months ago
JSON representation
A Make A Lisp implementation in Pharo
- Host: GitHub
- URL: https://github.com/wilfred/mal_pharo
- Owner: Wilfred
- Created: 2019-10-10T23:14:35.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2019-10-20T23:28:13.000Z (over 5 years ago)
- Last Synced: 2025-01-30T17:11:31.755Z (4 months ago)
- Topics: lisp, mal, pharo-smalltalk
- Language: Smalltalk
- Size: 69.3 KB
- Stars: 9
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Make A Lisp
An implementation of [MAL](https://github.com/kanaka/mal) using [Pharo
Smalltalk](http://pharo.org/).This implementation is almost complete, and passes the self-hosting
MAL test suite.## Code Organisation
### Tests
This implementation includes over 100 unit tests in the
`MakeALisp-Tests` diretory. This is in addition to the invaluable MAL
test suite.Smalltalk has brilliant TDD tooling and supports
edit-and-continue-execution from the debugger, so it was often easier
to start features/bugfixes with a unit test.### Steps
Most MAL implementation have a shared library of functionality, then a
different step1, step2 etc file with an increasingly complex `MAL`
class.Smalltalk is image oriented, so all the code is stored
together. Maintaining 10 different images would be very cumbersome, so
I've used the same implementation for all the steps.### Dispatching Evaluation
MAL convention is to implement evaluation using an `EVAL` function
that switches on the name of special forms, and an `eval_ast` function
that switches on the runtime type (symbol, list, etc).Smalltalk strongly prefers dynamic dispatch here. See the `evalIn:`
method on `MalList` for evaluation logic.### Dispatching Special Forms and Built-Ins
Each special form (`if`, `let*`, etc) and built-in function (`+`,
`cons`, etc) is a self-contained class. To avoid needing to register
the classes elsewhere, each class implements `malName` and I use
reflection to find the correct special form or function.This is from `MalSpecialForm`:
```
matchesSymbol: aSymbol
self
subclassesDo: [ :f |
f malName = aSymbol value
ifTrue: [ ^ f ] ].
^ nil
```### Tail-call Optimisation
Smalltalk allocates stack frames on the heap. You can't get stack
overflows: if you infinitely recurse you will eventually OOM instead.Looking at the `sum2` function from `step5_tco.mal`:
```
(def! sum2
(fn* (n acc)
(if (= n 0)
acc
(sum2 (- n 1) (+ n acc)))))
```TCO means this function uses O(1) stack, whereas this implementation
uses O(N) stack. The function is still O(N) in both cases if you allow
arbitrary size numbers.Smalltalk favours letting the stack grow: the built-in implementation
of factorial is a simple recursive function.This implementation therefore doesn't implement TCO, but still passes
the TCO tests. I think it makes the code more readable and
definitely helps keep stack traces useful, but it feels like a slight
cheat.## License
Licensed under MPL 2.0 (Mozilla Public License 2.0), consistent with
the other MAL implementations.