Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c107dfe
Makefile
billhails May 22, 2026
7944758
got rid of .generated, .typedefs and the indent target
billhails May 22, 2026
22ae8d2
shift and reduce
billhails May 22, 2026
0bada5d
basic type checker wired up
billhails May 22, 2026
3985b5f
infer_expr more in line with tc_analyze
billhails May 22, 2026
00a7061
cleaner test output
billhails May 22, 2026
b0de13d
prototyping improvements
billhails May 23, 2026
6e4993b
typeof, amb, and cut
billhails May 23, 2026
183bed3
fn/rewrite typececks cond
billhails May 23, 2026
f4f98fa
print and tag
billhails May 23, 2026
a55179f
thunks
billhails May 23, 2026
ee7bc6e
match_cases
billhails May 23, 2026
09a6f45
back
billhails May 23, 2026
1aa3f07
Merge branch 'main' into delimited-continuations
billhails May 23, 2026
1a7dae2
Merge branch 'main' into delimited-continuations
billhails May 26, 2026
84d0d30
Merge branch 'main' into delimited-continuations
billhails May 26, 2026
e57a493
Merge branch 'main' into delimited-continuations
billhails May 27, 2026
43caf62
Merge branch 'main' into delimited-continuations
billhails May 27, 2026
b6d8a8b
typedefs parse and print in the prototype
billhails May 31, 2026
9113065
focussed rewrite tests
billhails May 31, 2026
a3b8344
construct typedefs
billhails May 31, 2026
0e0f8b6
deconstruct
billhails May 31, 2026
842f0ff
tests confirm user types type check
billhails May 31, 2026
b705363
fn/rewrite/docs
billhails May 31, 2026
2351827
test_harness runs to completion
billhails May 31, 2026
97ac460
planning for inver_expr.fn done
billhails Jun 1, 2026
4cc2a6f
checklist in progrss
billhails Jun 1, 2026
6112a3a
checklist in progress
billhails Jun 1, 2026
ad50ecf
checklist in progress
billhails Jun 1, 2026
7246bd0
checklist in progress
billhails Jun 1, 2026
c6e1434
checklist in progress
billhails Jun 1, 2026
a134e8d
checklist in progress
billhails Jun 1, 2026
25d445f
checklist in progress
billhails Jun 1, 2026
80b6640
checklist in progress
billhails Jun 1, 2026
46605ef
checklist in progress
billhails Jun 1, 2026
e667cd0
checklist in progress
billhails Jun 1, 2026
3413495
checklist in progress
billhails Jun 1, 2026
eb70c5b
checklist in progress
billhails Jun 1, 2026
300f12e
checklist in progress
billhails Jun 1, 2026
e0c31ad
checklist in progress
billhails Jun 2, 2026
4afa58a
checklist in progress
billhails Jun 2, 2026
6e538e8
checklist in progress
billhails Jun 2, 2026
af288db
checklist in progress
billhails Jun 2, 2026
ac0138c
checklist in progress
billhails Jun 2, 2026
a5324cc
refactoring infer_expr
billhails Jun 2, 2026
975804e
refactoring infer_expr
billhails Jun 2, 2026
4f7c573
refactoring infer_expr
billhails Jun 2, 2026
13ea9eb
refactoring infer_expr
billhails Jun 2, 2026
00c0740
refactoring
billhails Jun 2, 2026
487e9ba
refactoring WiP
billhails Jun 5, 2026
3ff0b77
refactoring infer_expr done
billhails Jun 6, 2026
1501d5b
messing about
billhails Jun 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ coverage.info
coverage_filtered.info
test_output.log
.github/skills
.codegraph

# Refactoring testing baseline (optional: remove this line to track in git)
test_baseline/
89 changes: 43 additions & 46 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
list-cores test indent indent-src indent-generated docs \
install-sqlite3 install-raylib coverage extracov view-coverage \
coverage-target test-a test-fail test-sh test-c test-unit test-b test-big-c help \
test-gfx test-gfx-stress establish-baseline test-refactoring update-baseline clean-baseline \
test-rewrite test-gfx test-gfx-stress establish-baseline test-refactoring update-baseline clean-baseline \
scratch bench-regex-cache rebuild-default rebuild-raylib

# pass on the command line, i.e. `make test MODE=prod`
Expand Down Expand Up @@ -97,6 +97,23 @@ EXTRA_TARGETS= \
$(GENDIR)/cps_kont_impl.h \
$(GENDIR)/cps_kont_impl.c

GENERATED_BOOTSTRAP= \
$(EXTRA_H_TARGETS) \
$(EXTRA_OBJTYPES_H_TARGETS) \
$(EXTRA_DEBUG_H_TARGETS) \
$(GENDIR)/UnicodeData.inc \
$(GENDIR)/UnicodeCasing.inc \
$(GENDIR)/UnicodeDigits.inc \
$(GENDIR)/UnicodeNumbers.inc \
$(GENDIR)/anf_kont.h \
$(GENDIR)/anf_kont_objtypes.h \
$(GENDIR)/anf_kont_debug.h \
$(GENDIR)/anf_kont_impl.inc \
$(GENDIR)/cps_kont.h \
$(GENDIR)/cps_kont_objtypes.h \
$(GENDIR)/cps_kont_debug.h \
$(GENDIR)/cps_kont_impl.h

MAIN=$(SRCDIR)/main.c
PREAMBLE=$(GENDIR)/preamble.c
CFILES=$(filter-out $(MAIN), $(wildcard $(SRCDIR)/*.c))
Expand Down Expand Up @@ -153,15 +170,15 @@ FN_BFILES=$(patsubst $(FNDIR)/%,$(TMPDIR)/%,$(patsubst %.fn,%.fnc,$(FN_FILES)))
FN_OFILES=$(patsubst %.c,%.o,$(FN_CFILES))
FN_BINARIES=$(patsubst %.o,%,$(FN_OFILES))

TARGET_ARGS=--include=fn --flat-closure
TARGET_ARGS=--include=fn --assertions-accumulate

$(TEST_FN_CFILES): $(TMPDIR)/%.c: $(TEST_FN_DIR)/%.fn $(TARGET) | $(TMPDIR)
$(TARGET) $(TARGET_ARGS) --target-c=$@~ $< && mv $@~ $@
$(TARGET) $(TARGET_ARGS) --flat-closure --target-c=$@~ $< && mv $@~ $@
indent $@
rm -f $@~

$(FN_CFILES): $(TMPDIR)/%.c: $(FNDIR)/%.fn $(TARGET) | $(TMPDIR)
$(TARGET) $(TARGET_ARGS) --target-c=$@~ $< && mv $@~ $@
$(TARGET) $(TARGET_ARGS) --flat-closure --target-c=$@~ $< && mv $@~ $@
indent $@
rm -f $@~

Expand All @@ -172,10 +189,10 @@ $(FN_BFILES): $(TMPDIR)/%.fnc: $(FNDIR)/%.fn $(TARGET) | $(TMPDIR)
$(TARGET) --binary-out=$@~ $< && mv $@~ $@

$(TEST_FN_SFILES): $(TMPDIR)/%.scm: $(TEST_FN_DIR)/%.fn $(TARGET) | $(TMPDIR)
$(TARGET) $(TARGET_ARGS) --target-c --dump-inline-f $< > $@~ && mv $@~ $@
$(TARGET) $(TARGET_ARGS) --flat-closure --target-c --dump-inline-f $< > $@~ && mv $@~ $@

$(FN_SFILES): $(TMPDIR)/%.scm: $(FNDIR)/%.fn $(TARGET) | $(TMPDIR)
$(TARGET) $(TARGET_ARGS) --target-c --dump-inline-f $< > $@~ && mv $@~ $@
$(TARGET) $(TARGET_ARGS) --flat-closure --target-c --dump-inline-f $< > $@~ && mv $@~ $@

$(FN_OFILES) $(TEST_FN_OFILES): %.o: %.c
$(LAXCC) $(INCLUDE_PATHS) -c $< -o $@
Expand Down Expand Up @@ -240,7 +257,7 @@ Double \
Control
EXTRA_INDENT_ARGS=$(patsubst %,-T %,$(EXTRA_TYPES))

include $(ALL_DEP)
-include $(ALL_DEP)

$(PREAMBLE): $(PREAMBLE_SRC) | $(GENDIR)
tools/make-preamble.sh
Expand Down Expand Up @@ -291,61 +308,49 @@ $(GENDIR)/cps_kont_impl.c: tools/cps_continuations.yaml $(GENDEPS) $(SRCDIR)/pri
$(EXTRA_DOCS): $(DOCDIR)/%.md: $(SRCDIR)/%.yaml $(GENDEPS) $(SRCDIR)/primitives.yaml | $(DOCDIR)
$(MAKE_AST) $< md > $@~ && mv $@~ $@

.generated: $(EXTRA_TARGETS)
touch $@

tags: $(SRCDIR)/* $(EXTRA_TARGETS)
ctags $(SRCDIR)/* $(EXTRA_TARGETS)

xref: $(SRCDIR)/* $(EXTRA_TARGETS)
ctags -x $(SRCDIR)/* $(EXTRA_TARGETS) > $@

$(MAIN_OBJ) $(OBJ): $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(INCLUDE_PATHS) -c $< -o $@

$(EXTRA_OBJ) $(PREAMBLE_OBJ): $(OBJDIR)/%.o: $(GENDIR)/%.c | $(OBJDIR)
$(CC) $(INCLUDE_PATHS) -c $< -o $@

$(TEST_OBJ): $(OBJDIR)/%.o: $(TSTDIR)/src/%.c | $(OBJDIR)
$(LAXCC) $(INCLUDE_PATHS) -c $< -o $@

$(MAIN_DEP) $(DEP): $(DEPDIR)/%.d: $(SRCDIR)/%.c .generated | $(DEPDIR)
$(CC) $(INCLUDE_PATHS) -MM -MT $(patsubst $(DEPDIR)/%,$(OBJDIR)/%,$(patsubst %.d,%.o,$@)) -o $@ $<
$(MAIN_OBJ) $(OBJ): $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) $(DEPDIR) $(GENERATED_BOOTSTRAP)
$(CC) $(INCLUDE_PATHS) -MMD -MP -MF $(DEPDIR)/$*.d -MT $@ -c $< -o $@

$(EXTRA_DEP) $(PREAMBLE_DEP): $(DEPDIR)/%.d: $(GENDIR)/%.c .generated | $(DEPDIR)
$(CC) $(INCLUDE_PATHS) -MM -MT $(patsubst $(DEPDIR)/%,$(OBJDIR)/%,$(patsubst %.d,%.o,$@)) -o $@ $<
$(EXTRA_OBJ) $(PREAMBLE_OBJ): $(OBJDIR)/%.o: $(GENDIR)/%.c | $(OBJDIR) $(DEPDIR) $(GENERATED_BOOTSTRAP)
$(CC) $(INCLUDE_PATHS) -MMD -MP -MF $(DEPDIR)/$*.d -MT $@ -c $< -o $@

$(TEST_DEP): $(DEPDIR)/%.d: $(TSTDIR)/src/%.c .generated | $(DEPDIR)
$(CC) $(INCLUDE_PATHS) -MM -MT $(patsubst $(DEPDIR)/%,$(OBJDIR)/%,$(patsubst %.d,%.o,$@)) -o $@ $<
$(TEST_OBJ): $(OBJDIR)/%.o: $(TSTDIR)/src/%.c | $(OBJDIR) $(DEPDIR) $(GENERATED_BOOTSTRAP)
$(LAXCC) $(INCLUDE_PATHS) -MMD -MP -MF $(DEPDIR)/$*.d -MT $@ -c $< -o $@

test: test-unit test-a test-b test-sh test-fail
test: test-unit test-a test-b test-sh test-fail test-rewrite
@echo "All tests passed."

test-unit: all $(TEST_TARGETS)
for t in $(TEST_TARGETS) ; do echo '***' $$t '***' ; $$t || exit 1 ; done
test-unit: $(TEST_TARGETS)
set -x; for t in $(TEST_TARGETS) ; do $$t || exit 1 ; done
@echo "All unit tests passed."

test-a: all
for t in $(TSTDIR)/fn/test_*.fn ; do echo '***' $$t '***' ; ./$(TARGET) --include=fn --assertions-accumulate $$t || exit 1 ; done
set -x; for t in $(TSTDIR)/fn/test_*.fn ; do $(TARGET) $(TARGET_ARGS) $$t || exit 1 ; done
@echo "All a tests passed."

test-sh: all
for t in $(TSTDIR)/sh/*.sh ; do [ -e $$t ] || continue ; echo '***' $$t '***' ; bash $$t || exit 1 ; done
set -x ; for t in $(TSTDIR)/sh/*.sh ; do bash $$t || exit 1 ; done
@echo "All sh tests passed."

test-fail: all
for t in $(TSTDIR)/fn/fail_*.fn ; do echo '***' $$t '***' ; ! ./$(TARGET) --include=fn --assertions-accumulate $$t >/dev/null 2>&1 || exit 1 ; done
set -x ; for t in $(TSTDIR)/fn/fail_*.fn ; do ! ./$(TARGET) $(TARGET_ARGS) $$t >/dev/null 2>&1 || exit 1 ; done
@echo "All negative tests passed."

test-c: all $(TEST_FN_BINARIES)
@for t in $(TEST_FN_BINARIES) ; do echo $$t ; $$t || exit 1 ; done
set -x ; for t in $(TEST_FN_BINARIES) ; do $$t || exit 1 ; done
@echo All generated C tests pass

test-big-c: all $(TMPDIR)/test_harness
$(TMPDIR)/test_harness

test-b: all
@for t in $(TEST_FN_FILES) ; do set -x; $(TARGET) $(TARGET_ARGS) --target-b $$t || exit 1 ; done
set -x; for t in $(TSTDIR)/fn/test_*.fn ; do $(TARGET) $(TARGET_ARGS) --flat-closure --target-b $$t || exit 1 ; done
@echo All B-code tests pass

test-gfx: all
Expand All @@ -357,6 +362,10 @@ test-gfx-stress: all
@for t in $(TSTDIR)/fn/test_gfx_shader_reload_smoke.fn $(TSTDIR)/fn/test_gfx_resource_churn_smoke.fn ; do echo '***' $$t '***' ; ./$(TARGET) --include=fn --assertions-accumulate --stress-gc $$t || exit 1 ; done
@echo "All gfx stress smoke tests passed."

test-rewrite: all
set -x; for t in fn/rewrite/tests/test_*.fn ; do $(TARGET) $(TARGET_ARGS) --flat-closure --target-b $$t || exit 1 ; done
@echo All rewrite tests pass

$(TEST_TARGETS): $(TSTDIR)/%: $(OBJDIR)/%.o $(ALL_OBJ)
$(CC) -o $@ $< $(ALL_OBJ) $(LIBS)

Expand Down Expand Up @@ -398,7 +407,7 @@ realclean: clean
rm -rf tags xref $(UNIDIR)

clean: deps
rm -rf $(BINDIR) $(OBJDIR) callgrind.out.* $(GENDIR) $(TEST_TARGETS) .typedefs $(SRCDIR)/*~ .generated gmon.out *.fnc core.* coverage_html coverage_report.txt gcov_output *.gcda *.gcno coverage.info coverage_filtered.info test_output.log $(TEST_FN_CFILES) $(TEST_FN_OFILES) $(TEST_FN_BINARIES) $(TEST_FN_SFILES) $(TMPDIR)
rm -rf $(BINDIR) $(OBJDIR) callgrind.out.* $(GENDIR) $(TEST_TARGETS) $(SRCDIR)/*~ gmon.out *.fnc core.* coverage_html coverage_report.txt gcov_output *.gcda *.gcno coverage.info coverage_filtered.info test_output.log $(TEST_FN_CFILES) $(TEST_FN_OFILES) $(TEST_FN_BINARIES) $(TEST_FN_SFILES) $(TMPDIR)
$(MAKE) -C scratch clean

rebuild-default:
Expand Down Expand Up @@ -441,18 +450,6 @@ profile-parse: all
leak-check: all
valgrind --leak-check=full ./$(TARGET) $(FNDIR)/$(PROF_SRC).fn

indent: indent-src indent-generated

indent-src: .typedefs .indent.pro
indent `cat .typedefs | sort -u | xargs` $(EXTRA_INDENT_ARGS) $(SRCDIR)/*.[ch]
rm -f $(SRCDIR)/*~

indent-generated: .typedefs .indent.pro
indent `cat .typedefs | sort -u | xargs` $(EXTRA_INDENT_ARGS) $(GENDIR)/*.[ch]
rm -f $(GENDIR)/*~

.typedefs: .generated

list-cores:
@ls -rt1 /var/lib/apport/coredump/* | tail -1

Expand Down
32 changes: 12 additions & 20 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,33 @@

More of a wish-list than a hard and fast plan.

* Fn/Rewrite.
* Add an interpreter for the final IR.
* Add a parser and AST.
* Type Checking.
* etc.
* More folding opportunities.
* fold boolean expressions `true and false => false`.
* tricky because `and`, `or` etc. are not primitive, they are lazy operators defined in terms of `if` in the preamble.
* fold comparisons `a == a => true`, `a >= a => true` etc. DONE
* fold constant conditions `(if true a b) => a`. DONE
* This solves the boolean expression folding problem, after β/η-reduction:
* `true and false => (if true false false) => false`
* fold duplicate condition branches `(if x a a) => a`.
* Fold duplicate condition branches `(if x a a) => a`.
* Continuations.
* Reinstate `cut` (prunes current back continuation). DONE
* Implement delimited continuations.
* Regular Expressions.
* Enlist the new regex engine to support the core Pratt scanner.
* delimit/capture instead of reset/shift?
* Types.
* Consider type classes as a general solution to `EQ <type>`, `map` etc.
* Records should create accessor functions for each tag.
* if there is only one type variant.
* extend the `typedef` keyword.
* `typedef container(#t);` is shorthand for `typedef container(#t) { container(#t) };`
* `typedef container(char);` is shorthand for `typedef container { container(char) };`
* no additional AST should be required, or if it is it gets immediately desugared so doesn't leak downstream.
* Namespaces.
* We want `import <ns> function <x>` and `import <ns> functions`. DONE mostly.
* And `import <ns> typedef <x>` and `import <ns> typedefs`.
* `import <ns> typedef <x>` and `import <ns> typedefs`.
* Parser.
* re-elist the now-available `macro` keyword for proper syntactic extensibility. DONE
* if/then/else => `(fn { (true) {then} (false) {else} }(if))` (we already do this but hard-coded in the parser).
* `do` notation for monads.
* if/then/else => `(fn { (true) {then} (false) {else} }(if))` (we already do this but hard-coded in the parser).
* `do` notation for monads.
* Memory Management.
* Replace mark and sweep GC with a generational stop and copy.
* Pipeline.
* Re-order Type Checking before TPMC.
* Target LLVM.
* Generate.
* Add a generator for equivalent F♮ `typedef`s to facilitate `fn/rewrite/`.
* Will need F♮ support for hashes, symbols arrays etc.
* Move all signatures into `signature_helper.py`, not just the shared ones.
* More numbers.
* Vectors.
Expand All @@ -51,7 +44,6 @@ More of a wish-list than a hard and fast plan.
* does `a / 6` match `2 / 3` and bind `a` to `4`.
* match would fail if the actual argument is not integer or rational.
* `b` would be bound to `1` if the argument was an integer.
* can't allow arbitrary ops and combinations because i.e. `a * b` can't uniquely determine `a` and `b`, and `a ** 2` would require `a` to be bound to the square root of the actual argument.
* allow unpacking of the real and imaginary parts of a complex number in patterns.
* another special case: `a + b`, `b` would be bound to `0i` if the actual argument is not complex.
* `mathutils.fn` for `factorial`, `gcd`, `lcm`, `fib`, `pi` etc.
Expand Down
140 changes: 140 additions & 0 deletions docs/delimited_continuations_notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Delimited Continuations

Standared `shift/reset` semantics:

* `reset (1 + shift k { 10 + k(2) }) = 13`
* `reset (1 + shift k { 2 }) = 2`
* `reset (1 + shift k { k(2) }) = 3`
* `reset (1 + shift k { k(2) + k(3) }) = 7`
* `reset (1 + shift k { 100 + k(k(2)) }) = 104`
* `reset (1 + shift k { 100 + k(2) + 1000 }) = 1103`
* `reset (1 + (reset (2 + shift k { k(3) }))) = 6`
* `reset (1) = 1`
* `reset (1 + shift k { 2 }) = 2`
* `1 + reset (shift k { 2 }) = 3`

## Difference between `shift/reset` and `control/prompt`

* `reset ((1 + shift k { 2 + k(3) }) + shift h { h(4) + h(5) }) = 19`
* `prompt ((1 + control k { 2 + k(3) }) + control h { h(4) + h(5) }) = 21`

### The `shift/reset` example

* `reset ((1 + shift k { 2 + k(3) }) + shift h { h(4) + h(5) }) = 19`

To understand that `shift/reset` example first, let

```fn
T = reset (
(1 + shift k { 2 + k(3) })
+ shift h { h(4) + h(5) }
)
```

The first `shift` captures the context:

```fn
E1 = (1 + [ ]) + shift h { h(4) + h(5) }
```

So under `shift/reset`:

```txt
k(v) = reset( (1 + v) + shift h { h(4) + h(5) } )
// ^^^^^
```

That extra `reset` is the crucial part. So the term becomes:

```fn
T = reset( 2 + k(3) )
= reset( 2 + reset( (1 + 3) + shift h { h(4) + h(5) } ) )
```

Now look at the inner `reset`:

```fn
reset( (1 + 3) + shift h { h(4) + h(5) } )
= reset( 4 + shift h { h(4) + h(5) } )
```

`h` captures only

```fn
E2 = 4 + [ ]
```

so

```fn
h(v) = reset(4 + v)
```

Hence:

```fn
h(4) = 8
h(5) = 9
```

so the inner reset is:

```fn
reset( h(4) + h(5) )
= reset( 8 + 9 )
= 17
```

and therefore the whole term is:

```fn
reset( 2 + 17 ) = 19
```

## The Difference

```fn
shift/reset:
reset (
(1 + shift k { 2 + k(3) })
+ shift h { h(4) + h(5) }
)
```

```fn
control/prompt:
prompt (
(1 + control k { 2 + k(3) })
+ control h { h(4) + h(5) }
)
```

### shift/reset

```fn
reset ((1 + shift k { 2 + k(3) }) + shift h { h(4) + h(5) }) = 19
E1 = (1 + [ ]) + shift h { h(4) + h(5) }
k(v) = reset((1 + v ) + shift h { h(4) + h(5) })
reset(2 + reset((1 + 3 ) + shift h { h(4) + h(5) }))
E2 = 4 + [ ]
h(v) = reset(4 + v)
h(4) = 8
h(5) = 9
inner reset: 17
final result: 19
```

### control/prompt

```fn
prompt ((1 + control k { 2 + k(3) }) + control h { h(4) + h(5) }) = 21
E1 = (1 + [ ]) + control h { h(4) + h(5) }
k(v) = (1 + v ) + control h { h(4) + h(5) }
prompt(2 + ((1 + 3 ) + control h { h(4) + h(5) }))
prompt(6 + control h { h(4) + h(5) })
E2 = 6 + [ ]
h(v) = 6 + v
h(4) = 10
h(5) = 11
final result: 21
```
Loading
Loading