Compare commits
10 Commits
7cdd4ee759
...
compiler
| Author | SHA1 | Date | |
|---|---|---|---|
|
8d301a6fc2
|
|||
|
81dfc07867
|
|||
|
bd6acf89e0
|
|||
|
36ef8f2a22
|
|||
|
3a7f3971ba
|
|||
|
b5b0a44400
|
|||
|
b2e3f5703b
|
|||
|
2d038279f2
|
|||
|
fae7bd8077
|
|||
|
5e91f6e8fa
|
21
.woodpecker/publish.yaml
Normal file
21
.woodpecker/publish.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
when:
|
||||||
|
event: [push, cron, pull_request, manual]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Build Nightly Artifact
|
||||||
|
image: ocaml/opam:debian-11-ocaml-5.4
|
||||||
|
commands:
|
||||||
|
- opam install . --deps-only
|
||||||
|
- opam exec -- dune build
|
||||||
|
- mkdir -p dist
|
||||||
|
- opam exec -- dune install --prefix=$(pwd)/dist
|
||||||
|
|
||||||
|
- tar czvf ollisp-nightly-amd64.tar.gz -C dist .
|
||||||
|
- name: Publish to Gitea
|
||||||
|
image: curlimages/curl
|
||||||
|
environment:
|
||||||
|
GITEA_TOKEN:
|
||||||
|
from_secret: package_token
|
||||||
|
commands:
|
||||||
|
- curl -v --user "$CI_REPO_OWNER:$GITEA_TOKEN" --upload-file ollisp-nightly-amd64.tar.gz $CI_FORGE_URL/api/packages/$CI_REPO_OWNER/generic/olisp/nightly/ollisp-nightly-amd64.tar.gz?duplicate_upgrade=true
|
||||||
|
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Emin Arslan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
45
doc/env.md
45
doc/env.md
@@ -74,21 +74,50 @@ is propagated outwards, and adder also accesses it as a free variable. The compi
|
|||||||
(when propagating free symbols) eventually reaches the global environment, and
|
(when propagating free symbols) eventually reaches the global environment, and
|
||||||
resolves these free symbols to their global definitions.
|
resolves these free symbols to their global definitions.
|
||||||
|
|
||||||
This behaviour is necessary (for some definition of "necessary") to ensure correct runtime
|
All global symbols are late-bound. Once the free symbol is propagated outwards to the global
|
||||||
behaviour. This is because all symbols are `set!`able. Thus, the adder function can be
|
definition, the compiler must notice this and insert an instruction to get the
|
||||||
defined while `+` is bound to its builtin value, then modified into a different value.
|
value of a global symbol.
|
||||||
The following is valid:
|
|
||||||
|
Thus, the following will raise an error at runtime:
|
||||||
|
|
||||||
```
|
```
|
||||||
(define (adder x)
|
(define (adder x)
|
||||||
(lambda (y) (+ x y)))
|
(lambda (y) (+ x y)))
|
||||||
(set! '+ 5)
|
(set! '+ 5)
|
||||||
; + now equals 5, but adder still works.
|
; + now equals 5.
|
||||||
|
(adder 5 5)
|
||||||
```
|
```
|
||||||
|
|
||||||
This behaviour may seem ridiculous (why on earth would anyone define `+` to be `5`?),
|
Since `5` is not a function, it cannot be called, and this will raise an error.
|
||||||
and it may be tempting to prevent using `set!` on standard library symbols, this is perfectly
|
|
||||||
valid for global symbols defined by the user.
|
## Note on boxing
|
||||||
|
|
||||||
|
Closure conversion makes some situations a bit tricky.
|
||||||
|
|
||||||
|
```
|
||||||
|
(let ((x 10))
|
||||||
|
(let ((f (lambda () x))) ;; f captures x
|
||||||
|
(set! x 20) ;; we change local x
|
||||||
|
(f))) ;; does this return 10 or 20?
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, instead of x being copied directly into the closure, a
|
||||||
|
reference to its value is copied into the closure. This is usual in
|
||||||
|
most schemes and lisps.
|
||||||
|
|
||||||
|
In fact, you can even treat these as mutable state:
|
||||||
|
|
||||||
|
```
|
||||||
|
(define (make-counter)
|
||||||
|
(let ((count 0))
|
||||||
|
(lambda ()
|
||||||
|
(set! count (+ count 1))
|
||||||
|
count)))
|
||||||
|
```
|
||||||
|
|
||||||
|
So a closure can capture not just the value of a symbol, but also a
|
||||||
|
reference to it. This reference survives the end of the `make-counter`
|
||||||
|
function.
|
||||||
|
|
||||||
## Note on currying
|
## Note on currying
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
(lang dune 3.7)
|
(lang dune 3.7)
|
||||||
(using menhir 2.1)
|
(using menhir 2.1)
|
||||||
|
(generate_opam_files true)
|
||||||
|
|
||||||
(package
|
(package
|
||||||
(name ollisp))
|
(name ollisp)
|
||||||
|
(depends menhir))
|
||||||
|
|||||||
104
lib/compiler/scope_analysis.ml
Normal file
104
lib/compiler/scope_analysis.ml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
|
||||||
|
module SymbolTable = Map.Make(String);;
|
||||||
|
|
||||||
|
let ( let* ) = Result.bind
|
||||||
|
let traverse = Util.traverse
|
||||||
|
|
||||||
|
(* literals are not modified. *)
|
||||||
|
type literal = Core_ast.literal
|
||||||
|
|
||||||
|
(* Note:
|
||||||
|
all symbol accesses are replaced with either a local or global access.
|
||||||
|
Local accesses a symbol in the local scope.
|
||||||
|
Global accesses a symbol in the global scope.
|
||||||
|
|
||||||
|
Lambda expressions are stripped of the symbol name of their single parameter.
|
||||||
|
This name is not needed at runtime, as all symbol accesses will be resolved
|
||||||
|
into an index into either the local scope linked list or the global symbol table.
|
||||||
|
|
||||||
|
Set is also split into its global and local versions, just like Var.
|
||||||
|
|
||||||
|
The rest aren't modified at all.
|
||||||
|
*)
|
||||||
|
type expression =
|
||||||
|
| Literal of literal
|
||||||
|
| Local of int
|
||||||
|
| Global of int
|
||||||
|
| Apply of expression * expression
|
||||||
|
| Lambda of expression
|
||||||
|
| If of expression * expression * expression
|
||||||
|
| SetLocal of int * expression
|
||||||
|
| SetGlobal of int * expression
|
||||||
|
| Begin of expression list
|
||||||
|
|
||||||
|
|
||||||
|
(* extract all defined global symbols, given the top-level expressions
|
||||||
|
and definitions of a program
|
||||||
|
|
||||||
|
The returned table maps symbol names to unique integers, representing
|
||||||
|
an index into a global array where the values of all global symbols will
|
||||||
|
be kept at runtime.
|
||||||
|
*)
|
||||||
|
let extract_globals (top : Core_ast.top_level list) =
|
||||||
|
let id_counter = (ref (-1)) in
|
||||||
|
let id () =
|
||||||
|
id_counter := !id_counter + 1; !id_counter in
|
||||||
|
let rec aux tbl = function
|
||||||
|
| [] -> tbl
|
||||||
|
| Core_ast.Define (sym, _) :: rest ->
|
||||||
|
aux (SymbolTable.add sym (id ()) tbl) rest
|
||||||
|
| Expr _ :: rest ->
|
||||||
|
aux tbl rest
|
||||||
|
in aux SymbolTable.empty top
|
||||||
|
|
||||||
|
(* The current lexical scope is simply a linked list of entries,
|
||||||
|
and each symbol access will be resolved as an access to an index
|
||||||
|
in this linked list. The symbol names are erased before runtime.
|
||||||
|
During this analysis we keep the lexical scope as a linked list of
|
||||||
|
symbols, and we find the index by traversing this linked list.
|
||||||
|
*)
|
||||||
|
|
||||||
|
let resolve_global tbl sym =
|
||||||
|
match SymbolTable.find_opt sym tbl with
|
||||||
|
| Some x -> Ok (Global x)
|
||||||
|
| None -> Error ("symbol " ^ sym ^ " is not defined!")
|
||||||
|
|
||||||
|
let resolve_lexical tbl env sym =
|
||||||
|
let rec aux counter = function
|
||||||
|
| [] -> resolve_global tbl sym
|
||||||
|
| x :: _ when String.equal x sym -> Ok (Local counter)
|
||||||
|
| _ :: rest -> aux (counter + 1) rest
|
||||||
|
in aux 0 env
|
||||||
|
|
||||||
|
let resolve_symbol tbl env sym =
|
||||||
|
resolve_lexical tbl env sym
|
||||||
|
|
||||||
|
let resolve_set tbl env sym expr =
|
||||||
|
let* sym = resolve_symbol tbl env sym in
|
||||||
|
match sym with
|
||||||
|
| Local i -> Ok (SetLocal (i, expr))
|
||||||
|
| Global i -> Ok (SetGlobal (i, expr))
|
||||||
|
| _ -> Error "resolve_set: symbol resolution returned something invalid."
|
||||||
|
|
||||||
|
let rec analyze tbl current = function
|
||||||
|
| Core_ast.Literal s -> Ok (Literal s)
|
||||||
|
| Var sym -> resolve_symbol tbl current sym
|
||||||
|
| Set (sym, expr) ->
|
||||||
|
let* inner = analyze tbl current expr in
|
||||||
|
resolve_set tbl current sym inner
|
||||||
|
| Lambda (s, body) ->
|
||||||
|
let* body = (analyze tbl (s :: current) body) in
|
||||||
|
Ok (Lambda body)
|
||||||
|
| Apply (f, e) ->
|
||||||
|
let* f = analyze tbl current f in
|
||||||
|
let* e = analyze tbl current e in
|
||||||
|
Ok (Apply (f, e))
|
||||||
|
| If (test, pos, neg) ->
|
||||||
|
let* test = analyze tbl current test in
|
||||||
|
let* pos = analyze tbl current pos in
|
||||||
|
let* neg = analyze tbl current neg in
|
||||||
|
Ok (If (test, pos, neg))
|
||||||
|
| Begin el ->
|
||||||
|
let* body = traverse (analyze tbl current) el in
|
||||||
|
Ok (Begin body)
|
||||||
@@ -34,13 +34,7 @@ type top_level =
|
|||||||
|
|
||||||
(* we use result here to make things nicer *)
|
(* we use result here to make things nicer *)
|
||||||
let ( let* ) = Result.bind
|
let ( let* ) = Result.bind
|
||||||
let traverse f l =
|
let traverse = Util.traverse
|
||||||
let rec aux acc = function
|
|
||||||
| x :: xs ->
|
|
||||||
let* result = f x in
|
|
||||||
aux (result :: acc) xs
|
|
||||||
| [] -> Ok (List.rev acc) in
|
|
||||||
aux [] l
|
|
||||||
let map = List.map
|
let map = List.map
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
9
lib/compiler/util.ml
Normal file
9
lib/compiler/util.ml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
let ( let* ) = Result.bind
|
||||||
|
|
||||||
|
let traverse f l =
|
||||||
|
let rec aux acc = function
|
||||||
|
| x :: xs ->
|
||||||
|
let* result = f x in
|
||||||
|
aux (result :: acc) xs
|
||||||
|
| [] -> Ok (List.rev acc) in
|
||||||
|
aux [] l
|
||||||
21
ollisp.opam
Normal file
21
ollisp.opam
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# This file is generated by dune, edit dune-project instead
|
||||||
|
opam-version: "2.0"
|
||||||
|
depends: [
|
||||||
|
"dune" {>= "3.7"}
|
||||||
|
"menhir"
|
||||||
|
"odoc" {with-doc}
|
||||||
|
]
|
||||||
|
build: [
|
||||||
|
["dune" "subst"] {dev}
|
||||||
|
[
|
||||||
|
"dune"
|
||||||
|
"build"
|
||||||
|
"-p"
|
||||||
|
name
|
||||||
|
"-j"
|
||||||
|
jobs
|
||||||
|
"@install"
|
||||||
|
"@runtest" {with-test}
|
||||||
|
"@doc" {with-doc}
|
||||||
|
]
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user