Compare commits

..

10 Commits

Author SHA1 Message Date
8d301a6fc2 scope_analysis: fix the handling of Lambda forms
All checks were successful
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/cron/debian Pipeline was successful
ci/woodpecker/cron/fedora Pipeline was successful
ci/woodpecker/cron/nix Pipeline was successful
ci/woodpecker/cron/publish Pipeline was successful
2026-02-12 18:32:36 +03:00
81dfc07867 compiler: added my first attempt at a scope analysis pass
All checks were successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/cron/debian Pipeline was successful
ci/woodpecker/cron/fedora Pipeline was successful
ci/woodpecker/cron/nix Pipeline was successful
ci/woodpecker/cron/publish Pipeline was successful
2026-02-11 23:40:53 +03:00
bd6acf89e0 util: separated the monadic traverse into a utility module 2026-02-11 23:40:17 +03:00
36ef8f2a22 Added a license
All checks were successful
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/cron/nix Pipeline was successful
ci/woodpecker/cron/fedora Pipeline was successful
ci/woodpecker/cron/debian Pipeline was successful
ci/woodpecker/cron/publish Pipeline was successful
2026-02-10 21:36:49 +03:00
3a7f3971ba ci: update publish.yaml
All checks were successful
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/cron/debian Pipeline was successful
ci/woodpecker/cron/nix Pipeline was successful
ci/woodpecker/cron/fedora Pipeline was successful
ci/woodpecker/cron/publish Pipeline was successful
2026-02-05 23:21:01 +03:00
b5b0a44400 ci: update publish workflow to use ocaml 5.4
Some checks failed
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-02-05 23:17:45 +03:00
b2e3f5703b ci: update dune-project to add menhir dependency
Some checks failed
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-02-05 23:12:56 +03:00
2d038279f2 ci: add directive in dune to generate opam file
Some checks failed
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-02-05 23:09:37 +03:00
fae7bd8077 ci: Add a woodpecker workflow to publish a nightly amd64 version
Some checks failed
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/nix Pipeline was successful
2026-02-05 23:05:34 +03:00
5e91f6e8fa correct the design document for closure conversion
All checks were successful
ci/woodpecker/push/debian Pipeline was successful
ci/woodpecker/push/nix Pipeline was successful
ci/woodpecker/push/fedora Pipeline was successful
ci/woodpecker/cron/debian Pipeline was successful
ci/woodpecker/cron/fedora Pipeline was successful
ci/woodpecker/cron/nix Pipeline was successful
2026-02-05 00:12:58 +03:00
8 changed files with 217 additions and 16 deletions

21
.woodpecker/publish.yaml Normal file
View 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
View 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.

View File

@@ -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
resolves these free symbols to their global definitions.
This behaviour is necessary (for some definition of "necessary") to ensure correct runtime
behaviour. This is because all symbols are `set!`able. Thus, the adder function can be
defined while `+` is bound to its builtin value, then modified into a different value.
The following is valid:
All global symbols are late-bound. Once the free symbol is propagated outwards to the global
definition, the compiler must notice this and insert an instruction to get the
value of a global symbol.
Thus, the following will raise an error at runtime:
```
(define (adder x)
(lambda (y) (+ x y)))
(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`?),
and it may be tempting to prevent using `set!` on standard library symbols, this is perfectly
valid for global symbols defined by the user.
Since `5` is not a function, it cannot be called, and this will raise an error.
## 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

View File

@@ -1,5 +1,7 @@
(lang dune 3.7)
(using menhir 2.1)
(generate_opam_files true)
(package
(name ollisp))
(name ollisp)
(depends menhir))

View 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)

View File

@@ -34,13 +34,7 @@ type top_level =
(* we use result here to make things nicer *)
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
let traverse = Util.traverse
let map = List.map

9
lib/compiler/util.ml Normal file
View 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
View 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}
]
]