20 Commits
Author SHA1 Message Date
msyds 80164acb96 doc
build / build (push) Failing after 1m25s
2026-07-22 23:48:40 -06:00
msyds 1c7322614c fart 2026-07-22 23:48:02 -06:00
msyds 81a136fcf2 inspect-wasm 2026-07-22 12:22:41 -06:00
msyds be1d7566f4 idk ^w^
build / build (push) Failing after 1m10s
2026-07-20 13:09:59 -06:00
msyds fab29f6fce higher-order
build / build (push) Failing after 1m9s
2026-07-20 05:56:32 -06:00
msyds 7ab98341b9 appy
build / build (push) Successful in 1m11s
2026-07-20 05:33:23 -06:00
msyds 2f471ae4b1 idk ^w^
build / build (push) Failing after 14m11s
2026-07-20 01:56:53 -06:00
msyds 57defed077 bool tests
build / build (push) Successful in 1m5s
2026-07-19 03:29:51 -06:00
msyds 0ba49ed85c fix all haskell warnings (sigh)
build / build (push) Successful in 1m8s
2026-07-19 03:26:36 -06:00
msyds 33fb0f831c tests again
build / build (push) Successful in 1m0s
2026-07-19 03:11:59 -06:00
msyds 8120e21eae remove wat tests
build / build (push) Successful in 1m4s
2026-07-18 17:54:35 -06:00
msyds 6774c08efb lambda and if-number }:3
build / build (push) Successful in 38s
2026-07-18 17:12:55 -06:00
msyds 530a6934ba crane
build / build (push) Successful in 30s
2026-07-18 03:06:18 -06:00
msyds e2e287079c deyuck
build / build (push) Failing after 13m6s
2026-07-18 02:50:13 -06:00
msyds a97a0ad7bb fix tests }:3
build / build (push) Successful in 4m52s
2026-07-18 02:49:34 -06:00
msyds 9334373f96 "fix stuff lol" 2026-07-18 01:50:43 -06:00
msyds aa5b45ec76 rust runtime
build / build (push) Failing after 1m1s
2026-07-17 23:17:38 -06:00
msyds 85d34883a6 beautiful
build / build (push) Failing after 1m1s
2026-07-17 01:34:13 -06:00
msyds f09a63f11c top-level unquote-splice
build / build (push) Failing after 1m23s
2026-07-17 00:46:43 -06:00
msyds 2dffdf112c qq!
build / build (push) Failing after 1m15s
2026-07-16 14:16:01 -06:00
50 changed files with 3746 additions and 753 deletions
+3 -1
View File
@@ -2,4 +2,6 @@
. ((eval . ((eval
. (progn (defun apply-cabal-fmt-h () . (progn (defun apply-cabal-fmt-h ()
(haskell-mode-buffer-apply-command "cabal-fmt")) (haskell-mode-buffer-apply-command "cabal-fmt"))
(add-hook 'before-save-hook #'apply-cabal-fmt-h nil t)))))) (add-hook 'before-save-hook #'apply-cabal-fmt-h nil t)
(add-to-list 'haskell-font-lock-quasi-quote-modes
'("cps" . scheme-mode)))))))
+6
View File
@@ -15,3 +15,9 @@ XXXX XXXX XXXX XXXX XXXX XXXX XXXX XX00
zero indicates a 30-bit fixnum / zero indicates a 30-bit fixnum /
in the upper bits in the upper bits
#+end_example #+end_example
| type/value | low bits |
|------------+----------|
| small int | 0 |
| ~false~ | 01 |
| ~true~ | 11 |
+53
View File
@@ -0,0 +1,53 @@
#+title: Gyehoek Scheme
#+begin_center
(this document is written in present tense as if the project is complete, but Gyehoek is a work-in-progress.)
#+end_center
Gyehoek is an R⁷RS-compliant Scheme compiler targeting WebAssembly 3.0, relying principally on the recently standardised garbage collector and tail call proposals. the Gyehoek compiler is implemented in Haskell, and the Gyehoek runtime is a Rust program providing primitive routines and WebAssembly execution via the Wasmtime library.
primitives are implemented as native Rust functions made available to the guest by Wasmtime. in the future, it would be ideal to provide the primitives as a WASI interface to help decouple ourselves from a specific Wasm runtime, but it is not a priority.
Gyehoek allows separate compilation, ~eval~, first-class continuations, and so on.
* pipeline
a Scheme program's journey through Gyehoek is as follows:
1. read (source code → Scheme data)
2. parse (Scheme data → AST)
3. expand(?) (AST → AST)
4. contify (AST → CPS)
5. close (CPS → CPS)
6. lower (CPS → Wasm)
** read
in the read phase, Gyehoek's reader serialises textual source code into a sequence of tokens, which are then parsed into S-expressions. this phase is completely agnostic towards any interpretation of the data — it's just data, not code (yet). this distinction between reading and parsing is made so that the reader can easily be shared amongst many parsers, allowing convenient definition of human-readable representations for all sorts of compiler internals. Gyehoek's intermediate languages and WebAssembly text format are of particular interest.
the reader may be configured to extend R⁷RS's syntax with a special "antiquotation" notation, used internally in the compiler to elegantly interpolate and splice S-expression literals via Haskell's quasiquotation.
#+begin_src haskell
let meta = 123 :: Int
in [sx|(a b c #{meta} d)|] -- ⇒ (a b c 123 d)
let metas = ["c","d"] :: List Text
in [sx|(a b ##{metas} e f)|] -- ⇒ (a b "c" "d" e f)
#+end_src
Gyehoek's lexer and parser are generated by Alex and Happy, respectively.
unless otherwise noted, the term "parse" will be used in reference to the phase taking S-expressions to ASTs, while "read" refers to the combined Alex/Happy process. if the tokenisation process (Alex) must be distinguished from the "parse" process (Happy), the former is called "lexical analysis" and the latter "syntactic analysis."
** parse
- use invertible-grammar library
** expand
** contify
- procedures are distinguished from continuations, and procedure applications are distinguished from continuation jumps.
- all continuations and lambda will be named i think. the exception is continuations for primitive calls.
** close
** lower
Generated
+16
View File
@@ -66,6 +66,21 @@
"type": "github" "type": "github"
} }
}, },
"crane": {
"locked": {
"lastModified": 1784248371,
"narHash": "sha256-0l0Y4D4wbZhp1Oi6h8OpbLtIm/4FN88oCf54MK2ZgiM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "f7d151ec0bf52cf9662e2f59d7bea28588c2f070",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
@@ -587,6 +602,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"crane": "crane",
"haskellNix": "haskellNix", "haskellNix": "haskellNix",
"nixpkgs": [ "nixpkgs": [
"haskellNix", "haskellNix",
+19 -18
View File
@@ -7,6 +7,7 @@
url = "git+https://git.deertopia.net/msyds/sydpkgs"; url = "git+https://git.deertopia.net/msyds/sydpkgs";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
crane.url = "github:ipetkov/crane";
}; };
outputs = { self, nixpkgs, sydpkgs, haskellNix, ... }@inputs: outputs = { self, nixpkgs, sydpkgs, haskellNix, ... }@inputs:
@@ -16,13 +17,13 @@
"x86_64-darwin" "x86_64-linux" "x86_64-darwin" "x86_64-linux"
]; ];
overlays = [ overlays = [
haskellNix.overlay haskellNix.overlay
(final: prev: { (final: prev: {
gyehoek-wasmtime-wrapper = final.callPackage ./wasmtime.nix {}; shake-wrapper = final.callPackage ./shake-wrapper.nix {};
}) gyehoek-runtime = final.callPackage ./runtime {
(final: prev: { crane-lib = inputs.crane.mkLib final;
};
gyehoek = final.haskell-nix.project' { gyehoek = final.haskell-nix.project' {
src = ./.; src = ./.;
compiler-nix-name = "ghc912"; compiler-nix-name = "ghc912";
@@ -30,32 +31,32 @@
packages.gyehoek.components.tests.test.preCheck = packages.gyehoek.components.tests.test.preCheck =
let let
bin = [ bin = [
pkgs.gyehoek-wasmtime-wrapper pkgs.git # tasty uses git diff
pkgs.git
]; ];
in '' in ''
# Wasmtime requires a cache in $HOME. This is less export GYEHOEK_RUNTIME=${lib.getExe final.gyehoek-runtime}
# painful than reconfiguring the cache location.
export HOME=$(mktemp -d)
export PATH=${lib.makeBinPath bin}:$PATH export PATH=${lib.makeBinPath bin}:$PATH
''; '';
})]; })];
shell = { shell = {
withHoogle = true; withHoogle = true;
inputsFrom = []; inputsFrom = [
final.gyehoek-runtime
];
tools = { tools = {
cabal = {}; cabal = {};
haskell-language-server = {}; haskell-language-server = {};
}; };
buildInputs = with final; [ buildInputs = with final; [
haskellPackages.cabal-fmt haskellPackages.cabal-fmt
self.packages.${final.stdenv.hostPlatform.system}.shake shake-wrapper
final.wabt wabt
final.nodejs nodejs
final.wasm-tools wasm-tools
final.wac-cli wac-cli
final.guile guile
final.gyehoek-wasmtime-wrapper rust-analyzer
wasmtime
]; ];
}; };
}; };
@@ -87,7 +88,7 @@
hf.packages.${system} // lib.fix (packages: { hf.packages.${system} // lib.fix (packages: {
gyehoek = hf.packages.${system}."gyehoek:exe:gyehoek"; gyehoek = hf.packages.${system}."gyehoek:exe:gyehoek";
default = packages.gyehoek; default = packages.gyehoek;
shake = pkgs.callPackage ./shake-wrapper.nix {}; inherit (pkgs) gyehoek-runtime shake-wrapper;
})); }));
devShells = each-system devShells = each-system
+5
View File
@@ -0,0 +1,5 @@
;; apply `f' to `x' twice.
((λ (f x)
(f (f x)))
(λ (x) (+ x 4))
9)
-3
View File
@@ -1,5 +1,2 @@
ret > ExitSuccess ret > ExitSuccess
out > 22 out > 22
out >
err > warning: using `--invoke` with a function that returns values is experimental and may break in the future
err >
-47
View File
@@ -1,47 +0,0 @@
(module
(type $heap-object (sub (struct (field (mut i32)))))
(func
(param)
(result (ref eq))
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(i32.const 3)
(i32.const 2)
i32.shl
ref.i31
(ref.cast (ref i31))
i31.get_s
(i32.const 4)
(i32.const 2)
i32.shl
ref.i31
(ref.cast (ref i31))
i31.get_s
i32.mul
ref.i31
(local.set 0)
(i32.const 2)
(i32.const 2)
i32.shl
ref.i31
(ref.cast (ref i31))
i31.get_s
(i32.const 5)
(i32.const 2)
i32.shl
ref.i31
(ref.cast (ref i31))
i31.get_s
i32.mul
ref.i31
(local.set 1)
(local.get 0)
(ref.cast (ref i31))
i31.get_s
(local.get 1)
(ref.cast (ref i31))
i31.get_s
i32.add
ref.i31
(local.set 2)
(local.get 2))
(export "main" (func 0)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #f
+1
View File
@@ -0,0 +1 @@
#f
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 128
+1
View File
@@ -0,0 +1 @@
(((λ (f) f) (λ (x) (* x 4))) 32)
-3
View File
@@ -1,5 +1,2 @@
ret > ExitSuccess ret > ExitSuccess
out > 555 out > 555
out >
err > warning: using `--invoke` with a function that returns values is experimental and may break in the future
err >
-13
View File
@@ -1,13 +0,0 @@
(module
(type (sub (struct (field (mut i32)))))
(func
(param)
(result (ref eq))
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(i32.const 0)
ref.i31
(if
(result i32)
(then (i32.const 777) ref.i31)
(else (i32.const 555) ref.i31)))
(export "main" (func 0)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 777
+1
View File
@@ -0,0 +1 @@
(if 123 777 555)
-3
View File
@@ -1,5 +1,2 @@
ret > ExitSuccess ret > ExitSuccess
out > 777 out > 777
out >
err > warning: using `--invoke` with a function that returns values is experimental and may break in the future
err >
-13
View File
@@ -1,13 +0,0 @@
(module
(type (sub (struct (field (mut i32)))))
(func
(param)
(result (ref eq))
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(i32.const 1)
ref.i31
(if
(result i32)
(then (i32.const 777) ref.i31)
(else (i32.const 555) ref.i31)))
(export "main" (func 0)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #<procedure>
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 25
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #t
+1
View File
@@ -0,0 +1 @@
#t
+25 -10
View File
@@ -25,6 +25,11 @@ common ghcstuffs
default-extensions: default-extensions:
BlockArguments BlockArguments
DeriveGeneric DeriveGeneric
DerivingVia
DuplicateRecordFields
NoFieldSelectors
OrPatterns
OverloadedLabels
OverloadedRecordDot OverloadedRecordDot
OverloadedStrings OverloadedStrings
PartialTypeSignatures PartialTypeSignatures
@@ -34,7 +39,6 @@ common ghcstuffs
executable gyehoek executable gyehoek
import: ghcstuffs, ghcstuffs-dev import: ghcstuffs, ghcstuffs-dev
main-is: Main.hs main-is: Main.hs
build-depends: build-depends:
, base ^>=4.21.2.0 , base ^>=4.21.2.0
, gyehoek , gyehoek
@@ -51,18 +55,18 @@ library
Gyehoek.CPS.Convert Gyehoek.CPS.Convert
Gyehoek.CPS.Lower Gyehoek.CPS.Lower
Gyehoek.CPS.Syntax Gyehoek.CPS.Syntax
Gyehoek.Driver
Gyehoek.GenSym Gyehoek.GenSym
Gyehoek.Options Gyehoek.Options
Gyehoek.Scheme.Syntax Gyehoek.Scheme.Syntax
Gyehoek.Sexp Gyehoek.Sexp
Gyehoek.Wasm Gyehoek.Wasm
Gyehoek.Driver
build-depends: build-depends:
, base ^>=4.21.2.0 , base ^>=4.21.2.0
, binary , binary
, containers , containers
, cradle , typed-process
, effectful , effectful
, effectful-core , effectful-core
, effectful-plugin , effectful-plugin
@@ -74,17 +78,18 @@ library
, megaparsec , megaparsec
, mtl , mtl
, optparse-applicative , optparse-applicative
, pretty-simple
, prettyprinter , prettyprinter
, process , process
, recursion-schemes , recursion-schemes
, sexp-grammar , sexp-grammar
, string-interpolate
, template-haskell , template-haskell
, text , text
, text-short , text-short
, unordered-containers , unordered-containers
, vector , vector
, string-interpolate , bytestring
, pretty-simple
hs-source-dirs: src hs-source-dirs: src
default-language: GHC2024 default-language: GHC2024
@@ -94,10 +99,20 @@ test-suite test
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
hs-source-dirs: test hs-source-dirs: test
main-is: Main.hs main-is: Main.hs
build-depends: base other-modules:
, gyehoek Gyehoek.Test.CPS.Syntax
, filepath Gyehoek.Test.Golden
, tasty Gyehoek.Test.Sexp
, tasty-silver
build-depends:
, base
, directory , directory
, filepath
, gyehoek
, process-extras
, sexp-grammar
, tasty
, tasty-hunit
, tasty-silver
default-language: GHC2024 default-language: GHC2024
-20
View File
@@ -1,20 +0,0 @@
<html>
<head>
<script>
const imports = {
guppy: {
print: (arg) => console.log (arg)
}
}
fetch("u.wasm")
.then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes, imports))
.then((results) => {
results.instance.exports.main ();
});
</script>
</head>
<body>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
target/
+1935
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "gyehoek-runtime"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
clio = { version = "0.3.5", features = ["clap-parse"] }
memoize = "0.6.0"
wasmtime = "46.0.1"
+13
View File
@@ -0,0 +1,13 @@
{ rustPlatform
, lib
, crane-lib
}:
crane-lib.buildPackage (lib.fix (finalAttrs: {
pname = "gyehoek-runtime";
version = "0.1.0";
src = ./.;
# cargoLock = ./Cargo.lock;
doCheck = true;
meta.mainProgram = "gyehoek-runtime";
}))
+38
View File
@@ -0,0 +1,38 @@
use wasmtime::*;
use crate::internal as scm;
use crate::internal::{Scm,Immediate,HeapObject};
// pub fn small_fixnum_p (_)
// pub fn immediate_p (caller : Caller<'_, u32>, x : EqRef) -> EqRef {
// x.is_i31 ()
// }
fn write_immediate (_caller : Caller<'_, u32>, imm : Immediate) {
match imm {
Immediate::SmallFixnum (n) => print! ("{}", n),
Immediate::Bool (b) => print! ("{}", if b { "#t" } else { "#f" }),
}
}
pub fn write (caller : Caller<'_, u32>, x : Rooted<EqRef>) {
match scm::interpret (&caller, x).unwrap ().unwrap () {
Scm::Immediate (x) => write_immediate (caller, x),
Scm::HeapObject (x) => write_heap_object (caller, x),
}
}
fn write_heap_object (_caller : Caller<'_, u32>, x : HeapObject) {
match x {
HeapObject::Procedure => print! ("#<procedure>"),
}
}
pub fn truthy_p (caller : Caller<'_, u32>, x : Rooted<EqRef>,) -> u32 {
let r = scm::interpret (&caller, x).unwrap ().unwrap ();
if let Scm::Immediate (Immediate::Bool (false)) = r {
0
} else {
1
}
}
+99
View File
@@ -0,0 +1,99 @@
use wasmtime::*;
use crate::types;
pub fn immediate_p (store : impl AsContext, x : Rooted<EqRef>) -> bool {
x.is_i31 (store).unwrap ()
}
pub enum Immediate {
SmallFixnum (i32),
Bool (bool),
}
pub enum HeapObject {
Procedure
}
pub enum Scm {
Immediate (Immediate),
HeapObject (HeapObject),
}
#[allow(nonstandard_style)]
pub type scm_bits = u32;
#[allow(nonstandard_style)]
pub const scm_false : scm_bits = 0b01;
#[allow(nonstandard_style)]
pub const scm_true : scm_bits = 0b11;
pub fn interpret_immediate (x : scm_bits) -> Option<Immediate> {
if x & 1 == 0 {
Some (Immediate::SmallFixnum ((x >> 1).try_into ().unwrap ()))
} else if x == scm_true {
Some (Immediate::Bool (true))
} else if x == scm_false {
Some (Immediate::Bool (false))
} else {
None
}
}
pub fn interpret_heap_object (
store : impl AsContext,
x : Rooted<EqRef>
) -> Result<Option<HeapObject>> {
if x.matches_ty (&store, &types::closure (&store)?)? {
Ok (Some (HeapObject::Procedure))
} else {
todo! ()
}
}
pub fn interpret (
store : impl AsContext,
x : Rooted<EqRef>
) -> Result<Option<Scm>> {
if let Some (imm) = x.as_i31 (&store)? {
Ok (
interpret_immediate (imm.get_u32 ())
.map (Scm::Immediate)
)
} else {
Ok (
interpret_heap_object (&store, x)?
.map (Scm::HeapObject)
)
}
}
pub fn encode_immediate (
store : impl AsContext,
imm : Immediate
) -> scm_bits {
use Immediate::*;
match imm {
SmallFixnum (n) => (n << 1).try_into ().unwrap (),
Bool (false) => scm_false,
Bool (true) => scm_true,
}
}
pub fn encode (store : impl AsContextMut, x : Scm) -> Rooted<EqRef> {
match x {
Scm::Immediate (imm) => {
let i31 = I31::new_u32 (encode_immediate (&store, imm))
.unwrap ();
EqRef::from_i31 (store, i31)
}
Scm::HeapObject (ho) => {
todo! ()
}
}
}
pub fn encode_bool (store : impl AsContextMut, b : bool) -> Rooted<EqRef> {
let x = if b { scm_true } else { scm_false };
let i31 = I31::new_u32 (x).unwrap ();
EqRef::from_i31 (store, i31)
}
+53
View File
@@ -0,0 +1,53 @@
mod gyehoek;
mod internal;
mod types;
use std::io;
use std::io::Read;
use clio::*;
use clap::Parser;
use wasmtime::*;
/// A runtime for Gyehoek scheme.
#[derive(Parser, Debug)]
#[command(name = "gyehoek", version, about, long_about = None)]
struct Args {
/// Path to Wasm binary or textual source
#[clap(value_parser)]
wasm: Input,
}
fn read<R : Read> (mut rdr : R) -> io::Result<Vec<u8>> {
let mut buf = vec! [];
rdr.read_to_end (&mut buf)?;
Ok (buf)
}
fn get_config () -> Config {
let mut cfg = Config::new ();
cfg.wasm_reference_types (true);
cfg.wasm_function_references (true);
cfg.wasm_tail_call (true);
cfg.wasm_gc (true);
cfg
}
fn link_primitives (linker : &mut Linker<u32>) -> wasmtime::Result<()> {
linker.func_wrap ("gyehoek", "write", gyehoek::write)?;
linker.func_wrap ("gyehoek", "truthy?", gyehoek::truthy_p)?;
Ok (())
}
pub fn main () -> wasmtime::Result<()> {
let args = Args::parse ();
let wasm_config = get_config ();
let engine = Engine::new (&wasm_config)?;
let module = Module::new (&engine, read (args.wasm)?)?;
let mut linker = Linker::new (&engine);
link_primitives (&mut linker)?;
let mut store : Store<u32> = Store::new (&engine, 4);
let instance = linker.instantiate (&mut store, &module)?;
let main = instance.get_typed_func::<(),()> (&mut store, "main")?;
main.call (&mut store, ())?;
Ok (())
}
+62
View File
@@ -0,0 +1,62 @@
use wasmtime::*;
use memoize::memoize;
pub fn heap_object_struct (store : impl AsContext) -> Result<StructType> {
let ctx = store.as_context ();
let engine = ctx.engine ();
Ok (
StructType::with_finality_and_supertype (
engine,
Finality::NonFinal,
None,
vec![
hash_field ()
]
)?
)
}
pub fn heap_object (_store : impl AsContext) -> Result<HeapType> {
todo! ()
}
#[memoize]
pub fn hash_field () -> FieldType {
FieldType::new (
Mutability::Var,
StorageType::ValType (ValType::I32)
)
}
pub fn closure (store : impl AsContext) -> Result<HeapType> {
let ctx = store.as_context ();
let engine = ctx.engine ();
Ok (
HeapType::ConcreteStruct (
StructType::with_finality_and_supertype (
engine,
Finality::NonFinal,
Some (&heap_object_struct (&store)?),
vec![
hash_field (),
FieldType::new (
Mutability::Const,
StorageType::ValType (ValType::Ref (
RefType::new (
false,
HeapType::ConcreteFunc (
FuncType::new (
engine,
vec![ValType::I32],
vec![],
)
)
)
))
),
]
)?
)
)
}
+19 -7
View File
@@ -1,7 +1,7 @@
{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedLists #-}
{- HLINT ignore "Use camelCase" -}
module Gyehoek.CPS.Convert module Gyehoek.CPS.Convert
( convert ( convertProgram
, convertProgram
) where ) where
import Gyehoek.CPS.Syntax import Gyehoek.CPS.Syntax
@@ -12,6 +12,7 @@ import Effectful
import Control.Monad.Cont qualified as Cont import Control.Monad.Cont qualified as Cont
import Control.Lens import Control.Lens
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import qualified Gyehoek.Sexp
-- 뻘짓이어라 -- 뻘짓이어라
@@ -21,6 +22,14 @@ telescope
-> t a -> (t b -> r) -> r -> t a -> (t b -> r) -> r
telescope f = Cont.runCont . traverse (Cont.cont . f) telescope f = Cont.runCont . traverse (Cont.cont . f)
pattern Atomic e <-
e@( Scm.ExpLambda _ _
; Scm.ExpVar _
; Scm.ExpLit _ )
-- | Transform an expression with a meta-continuation.
convert convert
:: forall es. (GenSym :> es) :: forall es. (GenSym :> es)
=> Scm.Exp -> (Val -> Eff es Exp) -> Eff es Exp => Scm.Exp -> (Val -> Eff es Exp) -> Eff es Exp
@@ -31,21 +40,24 @@ convert (Scm.ExpLit l) k = k $ ValLit l
convert (Scm.ExpPrim p) k = convert (Scm.ExpPrim p) k =
telescope (convert @es) p \p' -> do telescope (convert @es) p \p' -> do
r <- gensym' "r" r <- gensym' "r"
ExpPrim p' [r] <$> k (ValVar r) ExpPrim p' . MkKappa [r] <$> k (ValVar r)
convert (Scm.ExpLambda xs e) k = do convert (Scm.ExpLambda xs e) k = do
f <- gensym' "λ-body" f <- gensym' "λ-body"
ktail <- gensym' "λ-tail" ktail <- gensym' "λ-tail"
m <- convert e $ \e' -> m <- convert e $ \e' -> pure $ ExpContinue ktail [e']
pure $ ExpContinue ktail [e'] ke <- k $ ValVar f
ExpLet [(f, MkLambda xs ktail m)] <$> k (ValVar f) pure [cps|
(letrec ((#{f} (λ (##{xs} #{ktail}) #{m})))
#{ke})
|]
convert (Scm.ExpApply f xs) k = convert (Scm.ExpApply f xs) k =
telescope (convert @es) (f:|xs) \(f':|xs') -> do telescope (convert @es) (f:|xs) \(f':|xs') -> do
r <- gensym' "r" r <- gensym' "r"
x <- gensym' "x" x <- gensym' "x"
m <- k (ValVar x) m <- k (ValVar x)
pure $ ExpFix [(r, MkKappa [x] m)] $ ExpApply f' (xs' ++ [ValVar r]) pure $ ExpLetRec [(r, AbsKappa' [x] m)] $ ExpApply f' xs' r
convert (Scm.ExpBegin xs) k = _ convert (Scm.ExpBegin xs) k = _
+264 -164
View File
@@ -6,40 +6,33 @@
{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE ApplicativeDo #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-} {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{-# LANGUAGE RecursiveDo #-}
{- HLINT ignore "Use camelCase" -}
module Gyehoek.CPS.Lower module Gyehoek.CPS.Lower
(lower, lowerProgram) where (lower, lowerProgram) where
import Gyehoek.CPS.Syntax import Gyehoek.CPS.Syntax
import Data.Generics.Labels import Data.Generics.Labels ()
import Gyehoek.Scheme.Syntax qualified as Scm
import Gyehoek.GenSym
import Data.List.NonEmpty (NonEmpty((:|)))
import Effectful import Effectful
import Control.Monad.Cont qualified as Cont
import Effectful.Writer.Static.Local
import Data.Text (Text) import Data.Text (Text)
import Data.Vector.Strict (Vector) import Data.Vector.Strict (Vector)
import Control.Lens import Control.Lens hiding (op)
import Data.Foldable
import Data.HashMap.Strict (HashMap)
import Numeric.Natural import Numeric.Natural
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Gyehoek.Scheme.Syntax (Lit(..))
import Text.Printf
import qualified Data.Text as T
import qualified Data.Vector.Strict as V import qualified Data.Vector.Strict as V
import Data.IntMap.Strict (IntMap)
import Data.String.Interpolate
import Gyehoek.Wasm qualified as Wasm import Gyehoek.Wasm qualified as Wasm
import Gyehoek.Wasm hiding (Expr) import Gyehoek.Wasm hiding (Expr)
import Language.Sexp.Located (pattern ParenList) import Language.Sexp.Located qualified as SL
import Debug.Pretty.Simple
import Control.Monad.Fix import Control.Monad.Fix
import qualified Gyehoek.Sexp
import Data.Text qualified as T
import Data.Foldable (fold)
import Gyehoek.Sexp (encodeOrShow, toSexp)
import Debug.Pretty.Simple
data Env = MkEnv data Env = MkEnv
{ runtime :: Runtime { vars :: Vector Name
, vars :: Vector Name
, kvars :: Vector Name , kvars :: Vector Name
} }
deriving (Show, Generic) deriving (Show, Generic)
@@ -50,201 +43,308 @@ type instance IxValue Env = Name
instance Ixed Env where instance Ixed Env where
ix i = #vars . ix (fromIntegral i) ix i = #vars . ix (fromIntegral i)
data Runtime = MkRuntime
{ argArrayType :: Idx
, argArray :: Idx
, contType :: Idx
, contStackType :: Idx
, contStackTop :: Idx
, contStack :: Idx
, result :: Idx
, halt :: Idx
}
deriving (Show, Generic)
tonat :: Integral a => a -> Natural
tonat = fromIntegral
-- | @makeSmallFixnum@ emits an expression injecting the i32 on top -- | @makeSmallFixnum@ emits an expression injecting the i32 on top
-- of the stack into the SCM unitype. -- of the stack into the SCM unitype.
makeSmallFixnum :: Wasm.Expr makeSmallFixnum :: Wasm.Expr
makeSmallFixnum = mconcat makeSmallFixnum = [expr|
[ ins "i32.const" [sxp @Int 1] (@gyehoek "construct small fixnum")
, ins "i32.shl" [] (i32.const 1)
, ins "ref.i31" [] i32.shl
] ref.i31
|]
-- | Given an expression @e@ leaving a @ref eq@ atop the stack, -- | Given an expression @e@ leaving a @ref eq@ atop the stack,
-- @pushArg rt n e@ sets the nth slot of the arg-passing array to the -- @pushArg rt n e@ sets the nth slot of the arg-passing array to the
-- result of @e@. -- result of @e@.
pushArg :: Runtime -> Int -> Wasm.Expr -> Wasm.Expr pushArg :: Natural -> Wasm.Expr -> Wasm.Expr
pushArg (MkRuntime {argArrayType,argArray}) n e = mconcat pushArg n e = [expr|
[ ins "global.get" [sxp argArray] (@gyehoek "push argument")
, ins "i32.const" [sxp n] (global.get $arg-array)
, e (i32.const #{n})
, ins "array.set" [sxp argArrayType] ##{e}
] (array.set $arg-array-type)
|]
-- | Pop the nth arg from the arg-passing array onto the stack. -- | Pop the nth arg from the arg-passing array onto the stack.
popArg :: Runtime -> Int -> Wasm.Expr popArg :: Int -> Wasm.Expr
popArg (MkRuntime {argArrayType,argArray}) n = mconcat popArg n = [expr|
[ ins "global.get" [sxp argArray] (@gyehoek "pop argument")
, ins "i32.const" [sxp n] (global.get $arg-array)
, ins "array.get" [sxp argArrayType] (i32.const #{n})
, ins "ref.as_non_null" [] (array.get $arg-array-type)
] ref.as_non_null
|]
lowerVal :: Env -> Val -> Wasm.Expr lowerVal :: GenMod :> es => Env -> Val -> Eff es Wasm.Expr
lowerVal g (ValLit l) = lowerVal g (ValLit l) =
case l of pure $ case l of
LitInt n -> LitInt n -> [expr|
ins "i32.const" [sxp n] (i32.const #{n})
<> makeSmallFixnum ##{makeSmallFixnum}
LitBool b -> |]
ins "i32.const" [sxp @Int $ if b then 1 else 0] LitBool b -> [expr|
<> ins "ref.i31" [] (i32.const #{b'})
ref.i31
|]
where b' :: Int = if b then 0b11 else 0b01
_ -> _ _ -> _
lowerVal g (ValVar x) = ins "local.get" [sxp (1+l)] lowerVal g (ValVar x) = pure $ [expr|(local.get #{l})|]
where where
l = V.elemIndex x g.vars ^?! _Just l = succ $ V.elemIndex x g.vars ^?! _Just
-- lowerVal g (ValLambda lam) = do
-- idx <- lowerLambda g lam
-- pure [expr|
-- (i32.const 0)
-- (ref.func #{idx})
-- (struct.new $closure)
-- |]
lower' :: (GenMod :> es) => Env -> Exp -> Eff es Wasm.Expr lower' :: (GenMod :> es) => Env -> Exp -> Eff es Wasm.Expr
lower' g (Halt [v]) = pure . mconcat $ lower' g (Halt [v]) = do
[ pushArg g.runtime 0 (lowerVal g v) arg <- pushArg 0 <$> lowerVal g v
, ins "return_call" [sxp @Int 1] pure [expr|
] ##{arg}
(return_call $halt (i32.const 1))
|]
lower' g (ExpPrim p rs e) = lower' g e@(ExpPrim p k) =
case p of ([expr|(@gyehoek :origin #{origin})|]<>)
PrimAdd x y -> lowerBinOp "i32.add" g x y r e <$> case p of
PrimMul x y -> lowerBinOp "i32.mul" g x y r e PrimAdd x y -> lowerBinOp "i32.add" g x y k
where PrimMul x y -> lowerBinOp "i32.mul" g x y k
r = head rs where origin = encodeOrShow @_ @Text e
lower' g (ExpIf c t f) = do lower' g (ExpIf c t f) = do
c' <- lowerVal g c
t' <- lower' g t t' <- lower' g t
f' <- lower' g f f' <- lower' g f
pure $ lowerVal g c pure [expr|
<> Wasm.if' (Wasm.result [i32]) t' f' ##{c'}
(call $gh-truthy?)
(if (then ##{t'})
(else ##{f'}))
|]
lower' g (ExpContinue k [x]) = pure . mconcat $ lower' g (ExpLetRec [(r,AbsKappa kap)] e) = do
[ pushArg rt 0 (lowerVal g x) idx <- lowerKappa g kap
, ins "i32.const" [sxp @Int 1] -- nargs let g' = g & #kvars <>~ [r]
-- get the return continuation. e' <- lower' g' e
, ins "global.get" [sxp rt.contStack] let origin = encodeOrShow @_ @Text e
, ins "global.get" [sxp rt.contStackTop] pure [expr|
, ins "array.get" [sxp rt.contStackType] (@gyehoek :origin #{origin})
, ins "ref.as_non_null" [] (@gyehoek "push cont" :idx #{idx})
-- decrement contStackTop, completing the "pop." (array.set $cont-stack-type
, ins "global.get" [sxp rt.contStackTop] (global.get $cont-stack)
, ins "i32.const" [sxp @Int (1 + l)] (global.get $cont-stack-top)
, ins "i32.sub" [] (ref.func #{idx}))
, ins "global.set" [sxp rt.contStackTop] (global.set $cont-stack-top
, ins "return_call_ref" [sxp rt.contType] (i32.add (global.get $cont-stack-top)
] (i32.const 1)))
##{e'}
|]
lower' g (ExpLetRec [(r,AbsLambda lam)] e) = do
idx <- lowerLambda g lam
let g' = g & #vars <>~ [r]
let n = succ $ length g.vars
e' <- lower' g' e
pure [expr|
(i32.const 0)
(ref.func #{idx})
(struct.new $closure)
(local.set #{n})
##{e'}
|]
lower' g e@(ExpApply f xs ktail) = do
let nargs = length xs
f' <- lowerVal g f
let l = succ $ V.elemIndex ktail g.kvars ^?! _Just
args <- fold <$>
itraverse (\i -> fmap (pushArg $ tonat i) . lowerVal g) xs
let origin = encodeOrShow @_ @Text e
pure [expr|
(@gyehoek :origin #{origin})
(@gyehoek "load args")
##{args}
(i32.const 1)
##{f'}
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type)
(@gyehoek todo
(f' ##{f'})
(ktail #{l}))
|]
lower' g e@(ExpContinue k xs) = do
let nargs = length xs
args <- fold <$>
itraverse (\i -> fmap (pushArg $ tonat i) . lowerVal g) xs
let origin = encodeOrShow @_ @Text e
pure [expr|
(@gyehoek :origin #{origin})
(@gyehoek "push args")
##{args}
(@gyehoek "nargs")
(i32.const #{nargs})
(@gyehoek "pop cont stack")
(global.get $cont-stack-top)
(i32.const #{l})
i32.sub
(global.set $cont-stack-top)
(global.get $cont-stack)
(global.get $cont-stack-top)
(array.get $cont-stack-type)
ref.as_non_null
(return_call_ref $cont-type)
|]
where where
rt = g.runtime l = succ $ V.elemIndex k g.kvars ^?! _Just
l = V.elemIndex k g.kvars ^?! _Just
lower' g (ExpLet [(r,MkLambda xs ktail m)] e) = do lower' g e = error $ case Gyehoek.Sexp.encode e of
idx <- defun [i32] [] (replicate 5 scm) \_ -> do Left _ -> show e
let g' = g & #vars <>~ V.fromList xs Right x -> T.unpack x
& #kvars <>~ [ktail]
lowerKappa :: GenMod :> es => Env -> Kappa -> Eff es Idx
lowerKappa g e@(MkKappa xs m) = do
let g' = g & #vars .~ V.fromList xs
m' <- lower' g' m m' <- lower' g' m
pure . mconcat $ let body = mconcat
[ xs & ifoldMap \n _ -> [ xs & ifoldMap \n _ ->
popArg g.runtime n <> ins "local.set" [sxp (1+n)] let n' = succ n
in popArg n <> [expr|(local.set #{n'})|]
, m' , m'
] ]
declareFuncref idx let origin = encodeOrShow @_ @Text e
let g' = g & #vars <>~ [r] idx <- Wasm.defineFunction [wat|
let n = length g.vars (func (param i32)
e' <- lower' g' e (@gyehoek :origin #{origin})
pure . mconcat $ (local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
[ ins "ref.func" [sxp idx] ##{body})
, ins "local.set" [sxp (n+1)] |]
, e' Wasm.emit [wats|(elem declare funcref (ref.func #{idx}))|]
] pure idx
lower' g e = error . show $ e lowerLambda :: GenMod :> es => Env -> Lambda -> Eff es Idx
lowerLambda g e@(MkLambda xs ktail m) = do
let g' = g & #vars .~ V.fromList xs
& #kvars <>~ [ktail]
m' <- lower' g' m
let body = mconcat
[ xs & ifoldMap \n _ ->
let n' = succ n
in popArg n <> [expr|(local.set #{n'})|]
, m'
]
let origin = encodeOrShow @_ @Text e
idx <- Wasm.defineFunction [wat|
(func (param i32)
(@gyehoek :origin #{origin})
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
##{body})
|]
Wasm.emit [wats|(elem declare funcref (ref.func #{idx}))|]
pure idx
lowerBinOp lowerBinOp
:: (GenMod :> es) :: (GenMod :> es)
=> Text -> Env -> Val -> Val -> Name -> Exp -> Eff es Wasm.Expr => Text -> Env -> Val -> Val -> Kappa -> Eff es Wasm.Expr
lowerBinOp op g x y r e = do lowerBinOp op g x y (MkKappa [r] e) = do
let op' = SL.Symbol op
let g' = g & #vars <>~ [r]
let n = succ $ length (g ^. #vars)
x' <- lowerVal g x
y' <- lowerVal g y
e' <- lower' g' e e' <- lower' g' e
pure . mconcat $ pure [expr|
[ lowerVal g x ##{x'}
, ins "ref.cast" [sxp $ ref i31] (i31.get_s (ref.cast (ref i31)))
, ins "i31.get_s" [] (i32.const 1)
, lowerVal g y i32.shr_u
, ins "ref.cast" [sxp $ ref i31] ##{y'}
, ins "i31.get_s" [] (i31.get_s (ref.cast (ref i31)))
, ins op [] (i32.const 1)
, ins "ref.i31" [] i32.shr_u
, ins "local.set" [sxp (1+n)] #{op'}
, e' ##{makeSmallFixnum}
] (local.set #{n})
where ##{e'}
g' = g & #vars <>~ [r] |]
n = length (g ^. #vars)
scm = ref eq emitRuntime :: GenMod :> es => Eff es ()
emitRuntime :: GenMod :> es => Eff es Runtime
emitRuntime = mfix \runtime -> do emitRuntime = mfix \runtime -> do
heapObjectIdx <- Wasm.deftypeNamed "$heap-object" $ Wasm.sub [] $ Wasm.struct Wasm.defineFunctions [wats|
[ Wasm.mut i32 ] (import "gyehoek" "write" (func $gh-write (param (ref eq))))
(import "gyehoek" "truthy?" (func $gh-truthy? (param (ref eq))
(result i32)))
|]
-- cont stack -- cont stack
contType <- Wasm.deftype $ Wasm.func [i32] [] Wasm.defineTypes [wats|
contStackType <- Wasm.deftype $ array $ mut $ refnull (fromIdx contType) (type $heap-object (sub (struct (field $hash (mut i32)))))
contStackTop <- Wasm.defglobal (mut i32) $ ins "i32.const" [sxp @Int 0] (type $cont-type (func (param i32)))
contStack <- Wasm.defglobal (ref (Wasm.fromIdx contStackType)) $ (type $cont-stack-type (array (mut (ref null $cont-type))))
ins "i32.const" [sxp @Int 128] (type $closure (sub $heap-object
<> ins "array.new_default" [sxp contStackType] (struct (field $hash (mut i32))
(field $code (ref $cont-type)))))
|]
Wasm.defineGlobals [wats|
(global $cont-stack-top (mut i32) (i32.const 0))
(global $cont-stack (ref $cont-stack-type)
(array.new_default $cont-stack-type (i32.const 128)))
|]
-- arg array -- arg array
argArrayType <- Wasm.deftype $ Wasm.array $ mut $ refnull eq Wasm.defineType [wat|
argArray <- Wasm.defglobal (ref (Wasm.fromIdx argArrayType)) $ (type $arg-array-type (array (mut (ref null eq))))
ins "i32.const" [sxp @Int 32] |]
<> ins "array.new_default" [sxp argArrayType] Wasm.defineGlobal [wat|
-- consIdx <- Wasm.defun _ _ _ _ (global $arg-array (ref $arg-array-type)
result <- Wasm.defglobal (mut (refnull eq)) $ ins "ref.null" [sxp eq] (array.new_default $arg-array-type (i32.const 32)))
halt <- Wasm.defun [i32] [] (replicate 5 scm) \_ -> |]
pure . mconcat $ -- other things 😼
[ popArg runtime 0 Wasm.defineGlobal [wat|
, ins "global.set" [sxp result] (global $result (mut (ref null eq))
] (ref.null eq))
pure $ MkRuntime |]
{argArray,argArrayType -- procedures
,contStack,contStackTop,contStackType,contType let arg = popArg 0
,result,halt} Wasm.defineFunction [wat|
-- pure $ error "todo" (func $halt (param i32)
##{arg}
(global.set $result))
|]
pure ()
lower :: Exp -> Eff es Text lower :: Exp -> Eff es Text
lower e = fmap Wasm.renderModule . Wasm.execGenMod $ do lower e = fmap Wasm.renderModule . Wasm.execGenMod $ do
runtime <- emitRuntime runtime <- emitRuntime
let g = MkEnv runtime mempty mempty let g = MkEnv mempty mempty
scm_entry <- Wasm.defun [i32] [] (replicate 5 scm) \_ -> e' <- lower' g e
lower' g e let origin = encodeOrShow @_ @Text e
main <- Wasm.defun [] [scm] [scm, scm, scm, scm, scm] \_ -> Wasm.defineFunction [wat|
pure . mconcat $ (func $scm-entry (param i32)
-- push return cont (@gyehoek :origin #{origin})
[-- ins "ref.func" [sxp halt] (local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
-- make call ##{e'})
ins "i32.const" [sxp @Int 0] |]
, ins "call" [sxp scm_entry] Wasm.defineFunction [wat|
, ins "global.get" [sxp runtime.result] (func (export "main")
, ins "ref.as_non_null" [] (call $scm-entry (i32.const 0))
] (call $gh-write (ref.as_non_null (global.get $result))))
Wasm.export "main" "func" main |]
lowerProgram :: Program -> Eff es Text lowerProgram :: Program -> Eff es Text
lowerProgram (MkProgram e) = lower e lowerProgram (MkProgram e) = lower e
+182 -39
View File
@@ -1,5 +1,9 @@
{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FunctionalDependencies #-}
module Gyehoek.CPS.Syntax module Gyehoek.CPS.Syntax
( Val(..) ( Val(..)
, Kappa(..) , Kappa(..)
@@ -14,80 +18,119 @@ module Gyehoek.CPS.Syntax
, pattern Halt1 , pattern Halt1
, _MkKappa , _MkKappa
, _ExpPrim , _ExpPrim
, _ExpFix , _ExpLetRec
, _ExpApply , _ExpApply
, binders
, body
, op
, args
, cont
, cps
, pattern AbsLambda'
, pattern AbsKappa'
, Abs(..)
, free
, free'
) )
where where
import Language.SexpGrammar qualified as S import Language.SexpGrammar qualified as S
import Gyehoek.Sexp qualified import Gyehoek.Sexp qualified
import Gyehoek.Scheme.Syntax (Name (..), Prim(..), primSexpIso, Lit(..), pattern Void) import Gyehoek.Scheme.Syntax (Name (..), Prim(..), primSexpIso, Lit(..), pattern Void, getName)
import Data.Text (Text)
import Data.List (List) import Data.List (List)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Language.SexpGrammar.Generic import Language.SexpGrammar.Generic
import Control.Category import Control.Category
import Control.Lens import Control.Lens hiding (op)
import Data.Text qualified as T
import Data.Generics.Labels
import Prelude hiding ((.), id) import Prelude hiding ((.), id)
import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty (NonEmpty)
import Data.InvertibleGrammar.Base qualified as IGB import Language.Haskell.TH.Quote (QuasiQuoter)
import Data.InvertibleGrammar.Base ((:-)((:-))) import Data.Data (Data)
import qualified Data.InvertibleGrammar as IG import Language.Sexp.Located (Sexp)
import qualified Data.InvertibleGrammar.Base as IG
import qualified Gyehoek.Scheme.Syntax as Gyehoek
import Data.InvertibleGrammar.Base (type (:-)((:-)))
import Data.HashSet (HashSet)
import qualified Data.HashSet as HS
import Data.Hashable (Hashable)
import Data.Monoid (Endo)
import Data.Containers.ListUtils (nubOrd)
-- Data types -- Data types
data Val data Val
= ValLabel Name = ValVar Name
| ValVar Name
| ValLit Lit | ValLit Lit
deriving (Show, Generic) deriving (Show, Generic, Data, Eq)
data Kappa = MkKappa (List Name) Exp data Kappa = MkKappa { binders :: List Name, body :: Exp }
deriving (Show, Generic) deriving (Show, Generic, Data, Eq)
data Lambda = MkLambda (List Name) Name Exp data Lambda = MkLambda { binders :: List Name, ktail :: Name, body :: Exp }
deriving (Show, Generic) deriving (Show, Generic, Data, Eq)
data Abs
= AbsKappa Kappa
| AbsLambda Lambda
deriving (Show, Generic, Data, Eq)
pattern AbsKappa' xs e = AbsKappa (MkKappa xs e)
pattern AbsLambda' xs e ktail = AbsLambda (MkLambda xs e ktail)
data Exp data Exp
= ExpPrim (Prim Val) (List Name) Exp = ExpPrim (Prim Val) Kappa
| ExpFix (NonEmpty (Name, Kappa)) Exp | ExpLetRec { binders :: NonEmpty (Name, Abs), body :: Exp }
| ExpLet (NonEmpty (Name, Lambda)) Exp
| ExpContinue Name (List Val) | ExpContinue Name (List Val)
| ExpIf Val Exp Exp | ExpIf Val Exp Exp
| ExpApply Val (List Val) | ExpApply
deriving (Show, Generic) { op :: Val
, args :: List Val
, cont :: Name
}
deriving (Show, Generic, Data, Eq)
pattern Halt :: List Val -> Exp pattern Halt :: List Val -> Exp
pattern Halt xs = ExpApply (ValVar "halt") xs pattern Halt xs = ExpContinue "halt" xs
pattern Halt1 :: Val -> Exp pattern Halt1 :: Val -> Exp
pattern Halt1 x = ExpApply (ValVar "halt") [x] pattern Halt1 x = ExpContinue "halt" [x]
data Def = DefConstant Name Exp data Def = DefConstant Name Exp
deriving (Show, Generic) deriving (Show, Generic, Data)
data Program = MkProgram data Program = MkProgram
{ body :: Exp { body :: Exp
} }
deriving (Show, Generic) deriving (Show, Generic, Data)
makePrisms ''Kappa makePrisms ''Kappa
-- makeLenses ''Kappa
makePrisms ''Exp makePrisms ''Exp
-- makeLenses ''Exp
-- makeFieldsNoPrefix ''Exp
-- makeFieldsNoPrefix ''Kappa
-- makeLensesWith abbreviatedFields ''Exp
-- makeLensesFor [("binders", "_binders"), ("body", "_body")] ''Exp
makeFieldsId ''Exp
makeFieldsId ''Kappa
makeFieldsId ''Lambda
instance HasBinders Abs (List Name) where
binders k (AbsKappa kap) = AbsKappa <$> binders k kap
binders k (AbsLambda lam) = AbsLambda <$> binders k lam
instance HasBody Abs Exp where
body k (AbsKappa kap) = AbsKappa <$> body k kap
body k (AbsLambda lam) = AbsLambda <$> body k lam
-- SexpIso instances -- SexpIso instances
instance S.SexpIso Val where instance S.SexpIso Val where
sexpIso = match sexpIso = match
$ With (. label) $ With (\var -> var . S.sexpIso)
$ With (. var) $ With (\lit -> lit . S.sexpIso)
$ With (. S.sexpIso)
$ End $ End
where
label = S.keyword >>> S.iso MkName getName
var = S.sexpIso
instance S.SexpIso Lambda where instance S.SexpIso Lambda where
sexpIso = match sexpIso = match
@@ -96,9 +139,18 @@ instance S.SexpIso Lambda where
where where
lambda = S.list $ lambda = S.list $
S.el Gyehoek.Sexp.lambdaKeyword S.el Gyehoek.Sexp.lambdaKeyword
>>> S.el (S.list (S.rest S.sexpIso)) >>> S.el binders
>>> S.el S.sexpIso
>>> S.el S.sexpIso >>> S.el S.sexpIso
binders :: forall t.
IG.Grammar S.Position (Sexp :- t) (Name :- List Name :- t)
binders = S.list $
S.rest (S.sexpIso @Name)
>>> S.onTail (S.flipped $ IG.PartialIso
(\(ktail:-args:-t) -> (args ++ [ktail]) :- t)
(\(args:-t) -> case args ^? _Snoc of
Just (args',ktail) -> Right $ ktail :- args' :- t
Nothing -> Left $ S.expected "cont param")
)
instance S.SexpIso Kappa where instance S.SexpIso Kappa where
sexpIso = match sexpIso = match
@@ -110,11 +162,16 @@ instance S.SexpIso Kappa where
>>> S.el (S.list $ S.rest S.sexpIso) >>> S.el (S.list $ S.rest S.sexpIso)
>>> S.el S.sexpIso >>> S.el S.sexpIso
instance S.SexpIso Abs where
sexpIso = match
$ With (\lambda -> lambda . S.sexpIso)
$ With (\kappa -> kappa . S.sexpIso)
$ End
instance S.SexpIso Exp where instance S.SexpIso Exp where
sexpIso = match sexpIso = match
$ With (. prim) $ With (. prim)
$ With (. fix) $ With (. letrec)
$ With (. let_)
$ With (. continue) $ With (. continue)
$ With (. if_) $ With (. if_)
$ With (. app) $ With (. app)
@@ -124,16 +181,102 @@ instance S.SexpIso Exp where
S.el (S.sym "continue") S.el (S.sym "continue")
>>> S.el S.sexpIso >>> S.el S.sexpIso
>>> S.rest S.sexpIso >>> S.rest S.sexpIso
fix = Gyehoek.Sexp.let_ "fix" S.sexpIso S.sexpIso S.sexpIso letrec = Gyehoek.Sexp.let_ "letrec" S.sexpIso S.sexpIso S.sexpIso
let_ = Gyehoek.Sexp.let_ "let" S.sexpIso S.sexpIso S.sexpIso
if_ = S.list $ S.el (S.sym "if") if_ = S.list $ S.el (S.sym "if")
>>> S.el S.sexpIso >>> S.el S.sexpIso >>> S.el S.sexpIso >>> S.el S.sexpIso >>> S.el S.sexpIso >>> S.el S.sexpIso
app = S.list $ S.el S.sexpIso >>> S.rest S.sexpIso app :: forall t.
IG.Grammar S.Position (Sexp :- t) (Name :- ([Val] :- (Val :- t)))
app = S.list $ S.el (S.sexpIso @Val)
-- >>> S.flipped Gyehoek.Sexp.nonEmptyGrammar
>>> S.rest (S.sexpIso @Val)
-- >>> _
>>> S.onTail (S.flipped $ IG.PartialIso
(\(karg :- args :- op :- t) ->
(args ++ [ValVar karg]) :- op :- t)
(\(xs :- op :- t) -> case xs ^? _Snoc of
Just (args,preview #ValVar -> Just karg) ->
Right $ karg:- args :- op :- t
_ -> Left $ S.expected "continuation arg"
))
where
_ = S.flipped $ Gyehoek.Sexp.nonEmptyGrammar @S.Position @Val
prim = S.list $ prim = S.list $
S.el (S.sym "prim") S.el (S.sym "prim")
>>> S.el (primSexpIso id (S.sexpIso @Val)) >>> S.el (primSexpIso id (S.sexpIso @Val))
>>> S.el S.sexpIso >>> S.el S.sexpIso
>>> S.el S.sexpIso
instance S.SexpIso Program where instance S.SexpIso Program where
sexpIso = with \prog -> S.sexpIso @Exp >>> prog sexpIso = with \prog -> S.sexpIso @Exp >>> prog
-- quasiquoters
class Data a => CPS a where
toCPS :: Sexp -> a
instance CPS Exp where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Val where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Kappa where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Lambda where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Abs where toCPS = Gyehoek.Sexp.fromSexp
cps :: QuasiQuoter
cps = Gyehoek.Sexp.makeSx' [| toCPS |]
deleteFrom :: (Foldable f, Hashable a) => f a -> HashSet a -> HashSet a
deleteFrom = flip $ foldr HS.delete
insertFrom :: (Foldable f, Hashable a) => f a -> HashSet a -> HashSet a
insertFrom = flip $ foldr HS.insert
toHashSetOf :: Hashable a => Getting (Endo (HashSet a)) s a -> s -> HashSet a
toHashSetOf l = foldrOf l HS.insert mempty
free :: Exp -> HashSet Name
free = go where
gokap (MkKappa xs m) = go m & deleteFrom xs
golam (MkLambda xs k m) = go m & deleteFrom xs & sans k
goabs = \case
AbsKappa kap -> gokap kap
AbsLambda lam -> golam lam
go = \case
ExpPrim p k ->
p & toHashSetOf (folded . #ValVar)
& HS.union (gokap k)
ExpLetRec bs m ->
foldMapOf (each . _2) goabs bs <> go m
& deleteFrom (bs ^.. each . _1)
ExpContinue k xs -> HS.fromList $ k : xs ^.. each . #ValVar
ExpIf c t f -> toHashSetOf #ValVar c <> go t <> go f
ExpApply f xs k -> toHashSetOf (each . #ValVar) (f:xs) <> HS.singleton k
-- | Free variables given in the order of their appearance.
free' :: Exp -> List Name
free' = nubOrd . goFree HS.empty where
goFreeKap bound (MkKappa xs m) = goFree (bound & insertFrom xs) m
goFreeLam bound (MkLambda xs k m) = goFree (bound & insertFrom (k:xs)) m
goFreeAbs bound = \case
AbsKappa kap -> goFreeKap bound kap
AbsLambda lam -> goFreeLam bound lam
goFree :: HashSet Name -> Exp -> List Name
goFree bound = \case
ExpPrim p k ->
p & toListOf (folded . #ValVar . filtered (`notElem` bound))
& (<> goFreeKap bound k)
ExpLetRec bs m ->
foldMapOf (each . _2) (goFreeAbs bound') bs <> goFree bound' m
where bound' = bound & insertFrom (bs ^.. each . _1)
ExpContinue k xs -> filter (`notElem` bound) (k : xs ^.. each . #ValVar)
ExpIf c t f ->
(c ^.. #ValVar . filtered (`notElem` bound))
<> goFree bound t <> goFree bound f
ExpApply f xs k ->
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound))
<> (k ^.. filtered (`notElem` bound))
freeLambda :: Lambda -> List Name
freeLambda (MkLambda {binders,ktail,body}) = _
+46 -25
View File
@@ -1,44 +1,33 @@
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE OrPatterns #-}
module Gyehoek.Driver module Gyehoek.Driver
(main, lower_e2e, convert_e2e, parse_e2e) (main, lower_e2e, convert_e2e, parse_e2e)
where where
import Gyehoek.Options import Gyehoek.Options
import qualified Data.Text.IO as TIO
import Data.Text (Text) import Data.Text (Text)
import Prelude hiding (readFile) import Prelude hiding (readFile)
import Options.Applicative import Options.Applicative
import Control.Lens import Control.Lens
import Data.Generics.Labels
import System.OsPath (OsPath)
import System.FilePath ((-<.>), dropExtension)
import Effectful.FileSystem import Effectful.FileSystem
import Effectful import Effectful
import Effectful.FileSystem.IO qualified as FS import Effectful.FileSystem.IO qualified as FS
import Effectful.FileSystem.IO.ByteString qualified as FB import Effectful.FileSystem.IO.ByteString qualified as FB
import Gyehoek.GenSym (runGenSym, GenSym, gensym, gensym') import Gyehoek.GenSym (runGenSym, GenSym)
import qualified Gyehoek.Sexp as Sexp import qualified Gyehoek.Sexp as Sexp
import Data.Text.Lens
import Data.List (List)
import qualified Gyehoek.Scheme.Syntax as Scm import qualified Gyehoek.Scheme.Syntax as Scm
import Effectful.Exception
import qualified Data.Text as T
import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding as T
import System.IO (Handle) import System.IO (Handle)
import Data.List.NonEmpty (NonEmpty) import System.IO qualified as IO
import qualified Cradle as C
import Gyehoek.CPS.Convert import Gyehoek.CPS.Convert
import Gyehoek.CPS.Lower import Gyehoek.CPS.Lower
import Data.Foldable
import qualified Gyehoek.Scheme.Syntax
import Gyehoek.CPS.Syntax qualified as Cps import Gyehoek.CPS.Syntax qualified as Cps
import Data.Maybe (fromMaybe)
import Control.Monad import Control.Monad
import Text.Pretty.Simple (pShow, pShowNoColor) import Text.Pretty.Simple (pShowNoColor)
import System.Process.Typed
import Data.Text.Encoding (encodeUtf8)
import System.Environment.Blank (getEnvDefault)
import GHC.Conc (atomically)
import qualified Data.Text.IO as TIO
import qualified Data.ByteString.Lazy as BS
main :: IO () main :: IO ()
@@ -48,8 +37,8 @@ main = do
hPutStr :: FileSystem :> es => Handle -> Text -> Eff es () -- hPutStr :: FileSystem :> es => Handle -> Text -> Eff es ()
hPutStr h = FB.hPutStr h . T.encodeUtf8 -- hPutStr h = FB.hPutStr h . T.encodeUtf8
hPutStrLn :: FileSystem :> es => Handle -> Text -> Eff es () hPutStrLn :: FileSystem :> es => Handle -> Text -> Eff es ()
hPutStrLn h = FB.hPutStrLn h . T.encodeUtf8 hPutStrLn h = FB.hPutStrLn h . T.encodeUtf8
@@ -57,8 +46,8 @@ hPutStrLn h = FB.hPutStrLn h . T.encodeUtf8
hGetContents :: FileSystem :> es => Handle -> Eff es Text hGetContents :: FileSystem :> es => Handle -> Eff es Text
hGetContents h = T.decodeUtf8 <$> FB.hGetContents h hGetContents h = T.decodeUtf8 <$> FB.hGetContents h
readFile :: FileSystem :> es => FilePath -> Eff es Text -- readFile :: FileSystem :> es => FilePath -> Eff es Text
readFile f = FS.withFile f FS.ReadMode hGetContents -- readFile f = FS.withFile f FS.ReadMode hGetContents
withFile withFile
:: (FileSystem :> es) :: (FileSystem :> es)
@@ -67,12 +56,41 @@ withFile "-" FS.ReadMode k = k FS.stdin
withFile "-" (FS.WriteMode; FS.AppendMode) k = k FS.stdout withFile "-" (FS.WriteMode; FS.AppendMode) k = k FS.stdout
withFile f m k = FS.withFile f m k withFile f m k = FS.withFile f m k
fileName :: FilePath -> FilePath
fileName "-" = "<interactive>"
fileName e = e
readScm :: FileSystem :> es => FilePath -> Eff es Scm.Program readScm :: FileSystem :> es => FilePath -> Eff es Scm.Program
readScm f = readScm f =
withFile f FS.ReadMode $ \h -> withFile f FS.ReadMode $ \h ->
Sexp.parseSexps @Scm.CommandOrDef f <$> hGetContents h Sexp.parseSexps @Scm.CommandOrDef (fileName f) <$> hGetContents h
>>= either error (pure . Scm.MkProgram) >>= either error (pure . Scm.MkProgram)
inspectWasm :: IOE :> es => Text -> Eff es ()
inspectWasm wat = do
pager_cmd <- liftIO $ getEnvDefault "PAGER" "less"
let wasmtools_cfg
= proc "wasm-tools" ["print", "-pf", "--print-operand-stack"
,"--color", "always", "-"]
-- & setStdin (byteStringInput . view lazy . encodeUtf8 $ wat)
-- & setStdout byteStringOutput
& setStdin createPipe
& setStdout createPipe
& setStderr inherit
let pager_cfg = proc pager_cmd []
& setStdin createPipe
& setStdout inherit
& setStderr inherit
liftIO $ withProcessWait_ wasmtools_cfg \wasmtools -> do
TIO.hPutStrLn (getStdin wasmtools) wat
IO.hFlush (getStdin wasmtools)
IO.hClose (getStdin wasmtools)
withProcessWait_ pager_cfg \pager -> do
t <- BS.hGetContents (getStdout wasmtools)
BS.hPut (getStdin pager) t
IO.hFlush (getStdin pager)
IO.hClose (getStdin pager)
driver driver
:: (GenSym :> es, FileSystem :> es, IOE :> es) :: (GenSym :> es, FileSystem :> es, IOE :> es)
=> Options -> Eff es () => Options -> Eff es ()
@@ -84,8 +102,11 @@ driver opts = do
when opts.dumpCPS do when opts.dumpCPS do
hPutStrLn FS.stdout $ Sexp.encodePretty cps ^?! _Right hPutStrLn FS.stdout $ Sexp.encodePretty cps ^?! _Right
wat <- lowerProgram cps wat <- lowerProgram cps
if not opts.inspectWasm then
withFile opts.output FS.WriteMode \h -> withFile opts.output FS.WriteMode \h ->
hPutStrLn h wat hPutStrLn h wat
else
inspectWasm wat
parse_e2e :: FilePath -> IO Scm.Program parse_e2e :: FilePath -> IO Scm.Program
parse_e2e = runEff . runFileSystem . readScm parse_e2e = runEff . runFileSystem . readScm
+3
View File
@@ -19,6 +19,7 @@ data Options = MkOptions
-- , dumpQBE :: Maybe FilePath -- , dumpQBE :: Maybe FilePath
dumpCPS :: Bool dumpCPS :: Bool
, dumpParsed :: Bool , dumpParsed :: Bool
, inspectWasm :: Bool
, output :: FilePath , output :: FilePath
, sourceFile :: FilePath , sourceFile :: FilePath
} }
@@ -49,10 +50,12 @@ parseOutput = strOption
parseDumpCPS = switch (long "dump-cps") parseDumpCPS = switch (long "dump-cps")
parseDumpParsed = switch (long "dump-parsed") parseDumpParsed = switch (long "dump-parsed")
parseInspectWasm = switch $ long "inspect-wasm" <> short 'p'
parser :: Parser Options parser :: Parser Options
parser = MkOptions parser = MkOptions
<$> parseDumpCPS <$> parseDumpCPS
<*> parseDumpParsed <*> parseDumpParsed
<*> parseInspectWasm
<*> parseOutput <*> parseOutput
<*> argument str (metavar "FILE") <*> argument str (metavar "FILE")
+16 -26
View File
@@ -20,42 +20,44 @@ module Gyehoek.Scheme.Syntax
, primSexpIso , primSexpIso
, pattern Void , pattern Void
, free , free
, qexp
, qprog
, subst , subst
, freeVariables , getName
, scm
) )
where where
import Data.Text (Text) import Data.Text (Text)
import Data.List (List) import Data.List (List)
import Language.SexpGrammar import Language.SexpGrammar
( SexpIso(..), list, el, (>>>), rest, sym, symbol ) ( SexpIso(..), list, el, rest, sym, symbol )
import Language.SexpGrammar qualified as Sexp import Language.SexpGrammar qualified as Sexp
import Language.Sexp.Located qualified as S import Language.Sexp.Located qualified as S
import Language.SexpGrammar.Generic import Language.SexpGrammar.Generic
import GHC.Generics import GHC.Generics
import Prelude hiding ((.), id) import Prelude hiding ((.), id)
import Control.Category import Control.Category
import Data.List.NonEmpty (NonEmpty ((:|))) import Data.List.NonEmpty (NonEmpty)
import Gyehoek.Sexp qualified import Gyehoek.Sexp qualified
import Gyehoek.GenSym (Gen) import Gyehoek.GenSym (Gen)
import Control.Lens import Control.Lens
import Data.String (IsString) import Data.String (IsString)
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
import Control.Lens.Unsound (prismSum)
import Data.Data (Data) import Data.Data (Data)
import Data.Functor.Foldable.TH (makeBaseFunctor) import Data.Functor.Foldable.TH (makeBaseFunctor)
import Data.Functor.Foldable hiding (fold) import Data.Functor.Foldable hiding (fold)
import Data.HashSet (HashSet) import Data.HashSet (HashSet)
import qualified Data.HashSet as HS import qualified Data.HashSet as HS
import Data.Foldable (fold) import Data.Foldable (fold)
import Language.Haskell.TH.Quote (QuasiQuoter)
newtype Name = MkName { getName :: Text } newtype Name = MkName { inner :: Text }
deriving newtype (Show, Eq, IsString, Gen, Hashable) deriving newtype (Show, Eq, Ord, IsString, Gen, Hashable)
deriving stock (Generic, Data) deriving stock (Generic, Data)
getName :: Name -> Text
getName (MkName x) = x
data Prim e data Prim e
= PrimAdd e e = PrimAdd e e
| PrimSub e e | PrimSub e e
@@ -70,7 +72,7 @@ data Prim e
| PrimWrite e | PrimWrite e
| PrimZeroP e | PrimZeroP e
| PrimNewline | PrimNewline
deriving (Show, Generic, Functor, Foldable, Traversable, Data) deriving (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
instance Each (Prim e) (Prim e') e e' instance Each (Prim e) (Prim e') e e'
@@ -80,7 +82,7 @@ data Lit
| LitBool Bool | LitBool Bool
| LitString Text | LitString Text
| LitQuote Sexp | LitQuote Sexp
deriving (Show, Generic, Data) deriving (Show, Generic, Data, Eq)
pattern Void :: Lit pattern Void :: Lit
pattern Void = LitNil pattern Void = LitNil
@@ -105,7 +107,7 @@ data Sexp
= SexpCons Sexp Sexp = SexpCons Sexp Sexp
| SexpSymbol Text | SexpSymbol Text
| SexpLit Lit | SexpLit Lit
deriving (Show, Generic, Data) deriving (Show, Generic, Data, Eq)
data CommandOrDef data CommandOrDef
= Command Exp = Command Exp
@@ -122,8 +124,6 @@ instance Each Program Program (Either Exp Def) (Either Exp Def) where
each = #commandsAndDefs . each . go each = #commandsAndDefs . each . go
where where
inj = either Command Definition inj = either Command Definition
toeither (Command e) = Left e
toeither (Definition d) = Right d
go :: Traversal' CommandOrDef (Either Exp Def) go :: Traversal' CommandOrDef (Either Exp Def)
go k (Command e) = inj <$> k (Left e) go k (Command e) = inj <$> k (Left e)
go k (Definition d) = inj <$> k (Right d) go k (Definition d) = inj <$> k (Right d)
@@ -185,7 +185,7 @@ instance SexpIso Lit where
instance SexpIso Sexp where instance SexpIso Sexp where
sexpIso = match sexpIso = match
$ With (\cons -> cons . Gyehoek.Sexp.todo) $ With (\conss -> conss . Gyehoek.Sexp.todo)
$ With (\s -> s . symbol) $ With (\s -> s . symbol)
$ With (\lit -> lit . sexpIso) $ With (\lit -> lit . sexpIso)
$ End $ End
@@ -230,8 +230,8 @@ instance SexpIso CommandOrDef where
-- utilities -- utilities
qexp = Gyehoek.Sexp.makeSx $ sexpIso @Exp scm :: QuasiQuoter
qprog = Gyehoek.Sexp.makeSxs (sexpIso @CommandOrDef) MkProgram scm = Gyehoek.Sexp.makeSx [|| Gyehoek.Sexp.fromSexp @Exp ||]
free :: Exp -> HashSet Name free :: Exp -> HashSet Name
free = cata \case free = cata \case
@@ -254,13 +254,3 @@ subst f = \e -> cata go e mempty where
go (ExpLetF _ _) _ = error "todo lol" go (ExpLetF _ _) _ = error "todo lol"
go (ExpLambdaF bs e) bound = e $ insertFrom bs bound go (ExpLambdaF bs e) bound = e $ insertFrom bs bound
go e bound = embed $ fmap ($ bound) e go e bound = embed $ fmap ($ bound) e
-- | Unlawful!
freeVariables :: Traversal Exp Exp Name Exp
freeVariables k = \e -> cataA go e mempty where
go (ExpVarF x) bound
| not (x `HS.member` bound) = k x
| otherwise = pure $ ExpVar x
go (ExpLetF _ _) _ = error "todo lol"
go (ExpLambdaF bs e) bound = e $ insertFrom bs bound
go e bound = embed <$> traverse ($ bound) e
+146 -39
View File
@@ -27,6 +27,7 @@ module Gyehoek.Sexp
, encodePretty , encodePretty
, UglySexpIso(..) , UglySexpIso(..)
, AsSexpIso(..) , AsSexpIso(..)
, SpliceSexp(..)
, parseSexpsWithPos , parseSexpsWithPos
, parseSexpWithPos , parseSexpWithPos
, parseSexp , parseSexp
@@ -34,11 +35,18 @@ module Gyehoek.Sexp
, sxs , sxs
, makeSx , makeSx
, makeSxs , makeSxs
, makeSx'
, toSexp
, fromSexp
, stripLocation
, format
, equivalent
, encodeOrShow
) )
where where
import Data.Text (Text) import Data.Text (Text)
import Language.SexpGrammar as Sexp hiding (toSexp, List, encode, decode, encodeWith, decodeWith, iso, encodePrettyWith, encodePretty) import Language.SexpGrammar as Sexp hiding (toSexp, List, encode, decode, encodeWith, decodeWith, iso, encodePrettyWith, encodePretty, fromSexp)
import Language.SexpGrammar qualified as Sexp import Language.SexpGrammar qualified as Sexp
import Language.Sexp qualified as S import Language.Sexp qualified as S
import Language.SexpGrammar.Generic import Language.SexpGrammar.Generic
@@ -51,7 +59,7 @@ import Data.List (List, groupBy)
import Data.Text.Encoding import Data.Text.Encoding
import Data.Either (either) import Data.Either (either)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Control.Lens import Control.Lens hiding (para)
import Data.Generics.Labels import Data.Generics.Labels
import System.Process import System.Process
import GHC.IO.Unsafe (unsafePerformIO) import GHC.IO.Unsafe (unsafePerformIO)
@@ -62,11 +70,23 @@ import Data.Void (absurd, Void)
import Data.Coerce (coerce) import Data.Coerce (coerce)
import qualified Data.Map import qualified Data.Map
import Language.Haskell.TH.Quote import Language.Haskell.TH.Quote
import Language.Haskell.TH (Quote, location, Loc (..), ExpQ, varE, mkName, listE, Exp, appE, conE) import Language.Haskell.TH (Quote, location, Loc (..), ExpQ, varE, mkName, listE, Exp, appE, conE, Q, Code, unTypeCode)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Control.Category import qualified Control.Category
import Data.Data (Data, Typeable, cast) import Data.Data (Data (..), Typeable, cast)
import Language.Haskell.TH.Syntax (lift, Lift) import Language.Haskell.TH.Syntax (lift, Lift, liftData)
import GHC.IsList (fromList)
import Data.Functor.Foldable (cata, para, embed)
import Data.Functor.Classes (Show1(..))
import Data.Vector (Vector)
import Numeric.Natural (Natural)
import Data.Maybe (fromMaybe)
import Control.Applicative (Alternative((<|>)))
import Debug.Pretty.Simple
import qualified Data.Vector as V
import qualified Data.Vector.Strict
import Data.Function (on)
import Data.String (IsString (fromString))
sexp :: SexpIso a => Iso' a Text sexp :: SexpIso a => Iso' a Text
@@ -74,6 +94,9 @@ sexp = iso
(either error id . encode) (either error id . encode)
(either error id . decode) (either error id . decode)
format :: Sexp -> Text
format = decodeUtf8 . view strict . SL.format
encode :: SexpIso a => a -> Either String Text encode :: SexpIso a => a -> Either String Text
encode = encodeWith sexpIso encode = encodeWith sexpIso
@@ -95,21 +118,27 @@ encodePrettyWith g =
parseSexps :: SexpIso a => FilePath -> Text -> Either String (List a) parseSexps :: SexpIso a => FilePath -> Text -> Either String (List a)
parseSexps f = marshal . SL.parseSexps f . view lazy . encodeUtf8 parseSexps f = marshal . SL.parseSexps f . view lazy . encodeUtf8
where marshal = join . traverseOf (_Right . each) (fromSexp sexpIso) where marshal = join . traverseOf (_Right . each) (Sexp.fromSexp sexpIso)
parseSexp :: SexpIso a => FilePath -> Text -> Either String a parseSexp :: SexpIso a => FilePath -> Text -> Either String a
parseSexp f = marshal . SL.parseSexp f . view lazy . encodeUtf8 parseSexp f = marshal . SL.parseSexp f . view lazy . encodeUtf8
where marshal = join . traverseOf _Right (fromSexp sexpIso) where marshal = join . traverseOf _Right (Sexp.fromSexp sexpIso)
readSexpWithPos :: Position -> Text -> Either String Sexp
readSexpWithPos pos = SL.parseSexpWithPos pos . view lazy . encodeUtf8
readSexpsWithPos :: Position -> Text -> Either String (List Sexp)
readSexpsWithPos pos = SL.parseSexpsWithPos pos . view lazy . encodeUtf8
parseSexpsWithPos :: SexpGrammar a -> Position -> Text -> Either String (List a) parseSexpsWithPos :: SexpGrammar a -> Position -> Text -> Either String (List a)
parseSexpsWithPos g pos = parseSexpsWithPos g pos =
marshal . SL.parseSexpsWithPos pos . view lazy . encodeUtf8 marshal . SL.parseSexpsWithPos pos . view lazy . encodeUtf8
where marshal = join . traverseOf (_Right . each) (fromSexp g) where marshal = join . traverseOf (_Right . each) (Sexp.fromSexp g)
parseSexpWithPos :: SexpGrammar a -> Position -> Text -> Either String a parseSexpWithPos :: SexpGrammar a -> Position -> Text -> Either String a
parseSexpWithPos g pos = parseSexpWithPos g pos =
marshal . SL.parseSexpWithPos pos . view lazy . encodeUtf8 marshal . SL.parseSexpWithPos pos . view lazy . encodeUtf8
where marshal = join . traverseOf _Right (fromSexp g) where marshal = join . traverseOf _Right (Sexp.fromSexp g)
nonEmptyGrammar :: Grammar p (NonEmpty x :- t) (List x :- x :- t) nonEmptyGrammar :: Grammar p (NonEmpty x :- t) (List x :- x :- t)
nonEmptyGrammar = IGB.Iso nonEmptyGrammar = IGB.Iso
@@ -231,21 +260,18 @@ getPos = do
Loc {loc_filename,loc_start} <- location Loc {loc_filename,loc_start} <- location
pure $ SL.Position loc_filename (fst loc_start) (snd loc_start) pure $ SL.Position loc_filename (fst loc_start) (snd loc_start)
makeSxs :: Data b => SexpGrammar a -> (List a -> b) -> QuasiQuoter fromSexp :: SexpIso a => Sexp -> a
makeSxs g f = QuasiQuoter fromSexp = either error id . Sexp.fromSexp sexpIso
{ quoteExp = \str -> do
pos <- getPos fromSexp' :: SexpGrammar a -> Sexp -> a
case parseSexpsWithPos g pos (T.pack str) of fromSexp' g = either error id . Sexp.fromSexp g
Left e -> fail e
Right xs -> dataToExpQ (const Nothing) (f xs)
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
toSexp :: SexpIso a => a -> Sexp toSexp :: SexpIso a => a -> Sexp
toSexp = either error id . Sexp.toSexp sexpIso toSexp = either error id . Sexp.toSexp sexpIso
toSexps :: (Foldable f, SexpIso a) => f a -> List Sexp
toSexps = foldMap \x -> [toSexp x]
pattern Unquote x = pattern Unquote x =
SL.Modified Hash (SL.BraceList [SL.Symbol x]) SL.Modified Hash (SL.BraceList [SL.Symbol x])
pattern UnquoteSplicing x = pattern UnquoteSplicing x =
@@ -262,12 +288,41 @@ instance Each Sexp Sexp Sexp Sexp where
each k (SL.BraceList xs) = SL.BraceList <$> traverse k xs each k (SL.BraceList xs) = SL.BraceList <$> traverse k xs
each _ e@(SL.Atom _; SL.Modified _ _) = pure e each _ e@(SL.Atom _; SL.Modified _ _) = pure e
metaSexp :: Sexp.Sexp -> Maybe ExpQ stripLocation :: Sexp -> Sexp
metaSexp (Unquote x) = stripLocation = cata \case
Just [| toSexp $(varE (mkName (T.unpack x))) |] SL.Compose (a SL.:< e) ->
metaSexp (SL.ParenList xs) SL.Fix . SL.Compose $ SL.dummyPos SL.:< e
| (_:_) <- xs ^.. each . _UnquoteSplicing
= Just [| SL.ParenList (mconcat $(listE spans)) |] -- | @('==')@ for 'Sexp's modulo source location — return true if the
-- two sexps are equal in all but 'Position' fields.
equivalent :: Sexp -> Sexp -> Bool
equivalent = (==) `on` stripLocation
instance SexpIso Natural where
sexpIso = Sexp.integer >>> Sexp.partialOsi f g
where
f n | n < 0 = Left $ Sexp.unexpected "negative"
<> Sexp.expected "natural"
| otherwise = Right $ fromIntegral n
g n = fromIntegral n
class SpliceSexp a where
spliceSexp :: a -> List Sexp
instance SexpIso a => SpliceSexp (Data.Vector.Strict.Vector a) where
spliceSexp = toSexps
instance SexpIso a => SpliceSexp (Vector a) where
spliceSexp = toSexps
instance SexpIso a => SpliceSexp (List a) where
spliceSexp = toSexps
instance SpliceSexp Sexp where
spliceSexp = toListOf each
unquoteSplicingRecursive :: List Sexp.Sexp -> ExpQ
unquoteSplicingRecursive xs = [| mconcat $(spans) |]
where where
spans = xs spans = xs
& groupBy \cases & groupBy \cases
@@ -275,9 +330,31 @@ metaSexp (SL.ParenList xs)
_ (UnquoteSplicing _) -> False _ (UnquoteSplicing _) -> False
_ _ -> True _ _ -> True
& fmap \case & fmap \case
[UnquoteSplicing x] -> varE (mkName (T.unpack x)) -- [e@(Unquote _)] ->
x -> lift x -- case unquote e of
metaSexp _ = Nothing -- Just x -> [| [$(x)] |]
-- Nothing -> error "unreachable"
[UnquoteSplicing x] ->
[| spliceSexp $(varE (mkName (T.unpack x))) |]
es -> listE $ unquoteRecursive <$> es
& listE
unquoteRecursive :: Sexp.Sexp -> ExpQ
unquoteRecursive = \case
Unquote x -> [| toSexp $(varE (mkName (T.unpack x))) |]
SL.ParenList xs -> [|SL.ParenList $(unquoteSplicingRecursive xs)|]
e -> liftData e
_ParenList :: Prism' Sexp (List Sexp)
_ParenList = prism' SL.ParenList \case
SL.ParenList xs -> Just xs
_ -> Nothing
metaSexps :: List Sexp.Sexp -> Maybe ExpQ
metaSexps = Just . unquoteSplicingRecursive
metaSexp :: Sexp.Sexp -> Maybe ExpQ
metaSexp = Just . unquoteRecursive
-- 뻘짓뻘짓뻘짓뻘짓뻘짓 -- 뻘짓뻘짓뻘짓뻘짓뻘짓
class Lift1 f where class Lift1 f where
@@ -287,10 +364,10 @@ lift1 :: (Lift1 f, Lift a, Quote m) => f a -> m Exp
lift1 = liftLift lift lift1 = liftLift lift
instance Lift1 f => Lift (SL.Fix f) where instance Lift1 f => Lift (SL.Fix f) where
lift (SL.Fix inner) = appE [|Fix|] (lift1 inner) lift (SL.Fix inner) = appE [|SL.Fix|] (lift1 inner)
instance (Lift1 f, Lift1 g) => Lift1 (SL.Compose f g) where instance (Lift1 f, Lift1 g) => Lift1 (SL.Compose f g) where
liftLift l (SL.Compose fga) = [|Compose $(liftLift (liftLift l) fga)|] liftLift l (SL.Compose fga) = [|SL.Compose $(liftLift (liftLift l) fga)|]
instance Lift a => Lift1 (SL.LocatedBy a) where instance Lift a => Lift1 (SL.LocatedBy a) where
liftLift l (a SL.:< e) = [|(SL.:<) $(lift a) $(l e)|] liftLift l (a SL.:< e) = [|(SL.:<) $(lift a) $(l e)|]
@@ -304,27 +381,57 @@ instance Lift1 SL.SexpF where
SL.ParenListF es -> [|SL.ParenListF $(liftLift l es)|] SL.ParenListF es -> [|SL.ParenListF $(liftLift l es)|]
SL.BracketListF es -> [|SL.BracketListF $(liftLift l es)|] SL.BracketListF es -> [|SL.BracketListF $(liftLift l es)|]
SL.BraceListF es -> [|SL.BraceListF $(liftLift l es)|] SL.BraceListF es -> [|SL.BraceListF $(liftLift l es)|]
SL.ModifiedF p e -> [|SL.Modified $(lift p) $(l e)|] SL.ModifiedF p e -> [|SL.ModifiedF $(lift p) $(l e)|]
-- deriving instance Lift a => Lift (SL.SexpF a) -- deriving instance Lift a => Lift (SL.SexpF a)
deriving instance Lift SL.Atom deriving instance Lift SL.Atom
deriving instance Lift SL.Position deriving instance Lift SL.Position
deriving instance Lift SL.Prefix deriving instance Lift SL.Prefix
encodeOrShow :: (SexpIso a, Show a, IsString s) => a -> s
encodeOrShow a = fromString case encode a of
Left _ -> show a
Right e -> T.unpack e
extQ :: (Typeable a, Typeable b) => (a -> r) -> (b -> r) -> a -> r extQ :: (Typeable a, Typeable b) => (a -> r) -> (b -> r) -> a -> r
extQ f g a = maybe (f a) g (cast a) extQ f g a = maybe (f a) g (cast a)
makeSx :: Data a => SexpGrammar a -> QuasiQuoter makeSxs :: Data r => Code Q (List Sexp -> r) -> QuasiQuoter
makeSx g = QuasiQuoter makeSxs f = QuasiQuoter
{ quoteExp = \str -> do { quoteExp = \str -> do
pos <- getPos pos <- getPos
case parseSexpWithPos g pos (T.pack str) of case readSexpsWithPos pos (T.pack str) of
Left e -> fail e Left e -> fail e
Right x -> dataToExpQ (const Nothing `extQ` metaSexp) x Right xs -> [| $(unTypeCode f) $e |]
where
e = dataToExpQ
(const Nothing `extQ` metaSexp `extQ` metaSexps)
xs
, quotePat = undefined , quotePat = undefined
, quoteType = undefined , quoteType = undefined
, quoteDec = undefined , quoteDec = undefined
} }
sxs = makeSxs (sexpIso @Sexp) id -- | An untyped variant of 'makeSx', useful when the user function is
sx = makeSx (sexpIso @Sexp) -- polymorphic in its return value.
makeSx' :: ExpQ -> QuasiQuoter
makeSx' f = QuasiQuoter
{ quoteExp = \str -> do
pos <- getPos
case readSexpWithPos pos (T.pack str) of
Left e -> fail e
Right x -> [| $f $e |]
where
e = dataToExpQ
(const Nothing `extQ` metaSexp `extQ` metaSexps)
x
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
makeSx :: Data r => Code Q (Sexp -> r) -> QuasiQuoter
makeSx = makeSx' . unTypeCode
sxs = makeSxs [||id||]
sx = makeSx [||id||]
+146 -30
View File
@@ -1,62 +1,75 @@
{- HLINT ignore "Use newtype instead of data" -} {- HLINT ignore "Use newtype instead of data" -}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeepSubsumption #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE RecordPuns #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE DerivingVia #-}
module Gyehoek.Wasm module Gyehoek.Wasm
( Module (
-- * syntax
Module
, Idx
, Expr
-- ** quasiquoters
, expr
, Gyehoek.Sexp.sx
, Gyehoek.Sexp.sxs
-- * GenMod effect
, GenMod
, runGenMod
, execGenMod
, defineFunction
, defineType
, defineGlobal
, emit
, renderModule
, wat
, wats
, defineFunctions
, defineTypes
, defineGlobals
) )
where where
import Language.SexpGrammar import Language.SexpGrammar
( SexpIso(..), list, el, (>>>), rest, sym, symbol, (:-) ) ( SexpIso(..), (>>>) )
import Language.SexpGrammar qualified as Sexp import Language.SexpGrammar qualified as Sexp
import Language.SexpGrammar.Generic import Language.SexpGrammar.Generic
import Data.List (List) import Data.List (List)
import GHC.Generics (Generic, Generically(..)) import GHC.Generics (Generic)
import Data.Text (Text) import Data.Text (Text)
import Data.String (IsString (fromString))
import Text.Printf
import Effectful import Effectful
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import Effectful.Dispatch.Dynamic import Effectful.Dispatch.Dynamic
import Effectful.State.Dynamic import Effectful.State.Dynamic
import Control.Lens import Control.Lens
import Data.Generics.Labels import Data.Vector.Strict (Vector)
import Data.Vector (Vector) import qualified Data.Vector.Strict as V
import Data.String.Interpolate
import qualified Data.Vector as V
import qualified Data.Text as T
import Effectful.Writer.Dynamic
import Control.Applicative (Alternative((<|>)))
import Control.Category qualified as Cat
import Data.Vector.Lens
import Data.Either (fromLeft, fromRight)
import Language.Sexp.Located import Language.Sexp.Located
import qualified Gyehoek.Sexp import qualified Gyehoek.Sexp
import GHC.IsList (IsList(..)) import GHC.IsList (IsList(..))
import Data.Coerce (coerce) import Language.Haskell.TH.Quote (QuasiQuoter)
import qualified Control.Category import Data.Data (Data)
import Data.Functor (void) import Gyehoek.Sexp (sx)
import Data.Foldable (traverse_)
newtype Module = MkModule { inner :: Vector Sexp } newtype Module = MkModule { inner :: Vector Sexp }
deriving (Show, Generic) deriving (Show, Generic)
deriving newtype (Semigroup, Monoid) deriving newtype (Semigroup, Monoid)
newtype Expr = MkExpr { inner :: Vector Sexp } newtype Expr = MkExpr { inner :: Vector Instr }
deriving (Show, Generic) deriving (Show, Generic, Data, Eq)
deriving newtype (Semigroup, Monoid) deriving newtype (Semigroup, Monoid)
instance IsList Expr where
type Item Expr = Instr
fromList = MkExpr . V.fromList
toList = V.toList . view #inner
newtype Instr = MkInstr { inner :: Sexp }
deriving (Show, Generic, Data, Eq)
newtype Idx = MkIdx { inner :: Natural } newtype Idx = MkIdx { inner :: Natural }
deriving (Generic) deriving (Generic, Data)
deriving newtype (Show) deriving newtype (Show)
@@ -68,9 +81,112 @@ data GenModState = MkGenModState
{ mod :: Module { mod :: Module
, funcs :: Natural , funcs :: Natural
, types :: Natural , types :: Natural
, globals :: Natural
} }
deriving (Show, Generic) deriving (Show, Generic)
instance Semigroup GenModState where
m1 <> m2 = MkGenModState
{ mod = m1.mod <> m2.mod
, funcs = m1.funcs + m2.funcs
, types = m1.types + m2.types
, globals = m1.globals + m2.globals
}
instance Monoid GenModState where
mempty = MkGenModState
{ mod = mempty
, funcs = 0
, types = 0
, globals = 0
}
data GenMod :: Effect where data GenMod :: Effect where
DefineFunction :: Sexp -> GenMod m Idx DefineFunction :: Sexp -> GenMod m Idx
DefineType :: Sexp -> GenMod m Idx DefineType :: Sexp -> GenMod m Idx
DefineGlobal :: Sexp -> GenMod m Idx
Emit :: Sexp -> GenMod m ()
type instance DispatchOf GenMod = Dynamic
defineFunction :: GenMod :> es => Sexp -> Eff es Idx
defineFunction = send . DefineFunction
defineFunctions :: GenMod :> es => List Sexp -> Eff es (List Idx)
defineFunctions = traverse (send . DefineFunction)
defineType :: GenMod :> es => Sexp -> Eff es Idx
defineType = send . DefineType
defineTypes :: GenMod :> es => List Sexp -> Eff es (List Idx)
defineTypes = traverse (send . DefineType)
defineGlobal :: GenMod :> es => Sexp -> Eff es Idx
defineGlobal = send . DefineGlobal
defineGlobals :: GenMod :> es => List Sexp -> Eff es (List Idx)
defineGlobals = traverse (send . DefineGlobal)
emit :: GenMod :> es => List Sexp -> Eff es ()
emit = traverse_ (send . Emit)
appendAndIncrement
:: State GenModState :> es
=> LensLike' ((,) Natural) GenModState Natural
-> Sexp
-> Eff es Idx
appendAndIncrement l s =
state \st -> st
& #mod . #inner <>~ V.singleton s
& l <<%~ succ
& _1 %~ MkIdx
runGenMod :: Eff (GenMod : es) a -> Eff es (a, Module)
runGenMod =
let run = (mapped . _2 %~ view #mod) . runStateLocal (mempty @GenModState)
in reinterpret run \cases
_ (DefineFunction s) -> appendAndIncrement #funcs s
_ (DefineType s) -> appendAndIncrement #types s
_ (DefineGlobal s) -> appendAndIncrement #globals s
_ (Emit s) -> #mod . #inner <>= V.singleton s
execGenMod :: Eff (GenMod : es) a -> Eff es Module
execGenMod = fmap snd . runGenMod
renderModule :: Module -> Text
renderModule (MkModule ss) = Gyehoek.Sexp.format [sx|
(module ##{ss})
|]
-- SexpIso instances
instance SexpIso Idx where
sexpIso = with \idx ->
Sexp.integer >>> Sexp.partialOsi f g
>>> idx
where
f n | n < 0 = Left $ Sexp.unexpected "negative"
<> Sexp.expected "natural"
| otherwise = Right $ fromIntegral n
g = fromIntegral
instance SexpIso Instr where
sexpIso = with id
instance Gyehoek.Sexp.SpliceSexp Expr where
spliceSexp = toListOf $ #inner . each . #inner
-- quasiquoters
expr :: QuasiQuoter
expr = Gyehoek.Sexp.makeSxs
[||MkExpr . V.fromList . (each . #inner %~ Gyehoek.Sexp.stripLocation)
. fmap (Gyehoek.Sexp.fromSexp @Instr) ||]
wat :: QuasiQuoter
wat = Gyehoek.Sexp.makeSx [|| id ||]
wats :: QuasiQuoter
wats = Gyehoek.Sexp.makeSxs [|| id ||]
-18
View File
@@ -1,18 +0,0 @@
const imports = {
guppy: {
print: (arg) => console.log (arg)
}
}
// Assume add.wasm file exists that contains a single function adding 2 provided arguments
const fs = require('node:fs');
// Use the readFileSync function to read the contents of the "add.wasm" file
const wasmBuffer = fs.readFileSync('u.wasm');
// Use the WebAssembly.instantiate method to instantiate the WebAssembly module
WebAssembly.instantiate(wasmBuffer, imports).then(wasmModule => {
// Exported function lives under instance.exports object
const { main } = wasmModule.instance.exports;
main ()
});
+1
View File
@@ -0,0 +1 @@
(values 1 2)
+240 -44
View File
@@ -1,69 +1,265 @@
(module (module
(type $heap-object (sub (struct (field (mut i32))))) (import
(type $open-procedure (func (param i32))) "gyehoek"
(type $closure (sub $heap-object "write"
(struct (field (mut i32)) (func $gh-write (param (ref eq))))
(field (ref $open-procedure))))) (import
(type $cont-stack-type (array (mut (ref null $open-procedure)))) "gyehoek"
(type $arg-array-type (array (mut (ref null eq)))) "truthy?"
(func $gh-truthy? (param (ref eq)) (result i32)))
(type $heap-object (sub (struct (field $hash (mut i32)))))
(type $cont-type (func (param i32)))
(type $cont-stack-type (array (mut (ref null $cont-type))))
(type
$closure
(sub
$heap-object
(struct
(field $hash (mut i32))
(field $code (ref $cont-type)))))
(global $cont-stack-top (mut i32) (i32.const 0)) (global $cont-stack-top (mut i32) (i32.const 0))
(global $cont-stack (ref $cont-stack-type) (global
(i32.const 128) $cont-stack
(array.new_default $cont-stack-type)) (ref $cont-stack-type)
(global $arg-array (ref $arg-array-type) (array.new_default $cont-stack-type (i32.const 128)))
(i32.const 32) (type $arg-array-type (array (mut (ref null eq))))
(array.new_default $arg-array-type)) (global
(global (mut (ref null eq)) (ref.null eq)) $arg-array
(elem declare funcref (ref.func 1)) (ref $arg-array-type)
(array.new_default $arg-array-type (i32.const 32)))
(global $result (mut (ref null eq)) (ref.null eq))
(func (func
$halt
(param i32) (param i32)
(result) (@gyehoek "pop argument")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) (global.get $arg-array)
(global.get 2)
(i32.const 0) (i32.const 0)
(array.get 3) (array.get $arg-array-type)
ref.as_non_null ref.as_non_null
(global.set 3)) (global.set $result))
(func (func
(param i32) (param i32)
(result) (@gyehoek :origin "(κ (x5) (continue λ-tail1 x5))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) (local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(global.get 2) (@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0) (i32.const 0)
(array.get 3) (array.get $arg-array-type)
ref.as_non_null ref.as_non_null
(local.set 1) (local.set 1)
(global.get 2) (@gyehoek :origin "(continue λ-tail1 x5)")
(@gyehoek "push args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0) (i32.const 0)
(local.get 1) (local.get 4)
(array.set 3) (array.set $arg-array-type)
(@gyehoek "nargs")
(i32.const 1) (i32.const 1)
(global.get 1) (@gyehoek "pop cont stack")
(global.get 0) (global.get $cont-stack-top)
(array.get 2)
ref.as_non_null
(global.get 0)
(i32.const 1) (i32.const 1)
i32.sub i32.sub
(global.set 0) (global.set $cont-stack-top)
(return_call_ref 1)) (global.get $cont-stack)
(global.get $cont-stack-top)
(array.get $cont-stack-type)
ref.as_non_null
(return_call_ref $cont-type))
(elem declare funcref (ref.func 3))
(func (func
(param i32) (param i32)
(result) (@gyehoek
:origin
"(κ (x3) (letrec ((r4 (κ (x5) (continue λ-tail1 x5)))) (f x3 r4)))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) (local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(ref.func 1) (@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1) (local.set 1)
(global.get 2) (@gyehoek :origin "(f x3 r4)")
(@gyehoek "push cont" :idx 3)
(array.set
$cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func 3))
(global.set
$cont-stack-top
(i32.add (global.get $cont-stack-top) (i32.const 1)))
(@gyehoek :origin "(f x3 r4)")
(@gyehoek "load args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0) (i32.const 0)
(local.get 3)
(array.set $arg-array-type)
(i32.const 1)
(local.get 1) (local.get 1)
(array.set 3) (ref.cast (ref $closure))
(return_call 1)) (struct.get $closure $code)
(return_call_ref $cont-type))
(elem declare funcref (ref.func 4))
(func (func
(param) (param i32)
(result (ref eq)) (@gyehoek
:origin
"(λ (f x λ-tail1) (letrec ((r2 (κ (x3) (letrec ((r4 (κ (x5) (continue λ-tail1 x5)))) (f x3 r4))))) (f x r2)))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 1)
(array.get $arg-array-type)
ref.as_non_null
(local.set 2)
(@gyehoek :origin "(f x r2)")
(@gyehoek "push cont" :idx 4)
(array.set
$cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func 4))
(global.set
$cont-stack-top
(i32.add (global.get $cont-stack-top) (i32.const 1)))
(@gyehoek :origin "(f x r2)")
(@gyehoek "load args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(i32.const 1)
(local.get 1)
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type))
(elem declare funcref (ref.func 5))
(func
(param i32)
(@gyehoek
:origin
"(λ (x λ-tail7) (prim (+ x 4) (κ (r8) (continue λ-tail7 r8))))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek
:origin
"(prim (+ x 4) (κ (r8) (continue λ-tail7 r8)))")
(local.get 1)
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
(i32.const 4)
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
i32.add
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(local.set 2)
(@gyehoek :origin "(continue λ-tail7 r8)")
(@gyehoek "push args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(@gyehoek "nargs")
(i32.const 1)
(@gyehoek "pop cont stack")
(global.get $cont-stack-top)
(i32.const 1)
i32.sub
(global.set $cont-stack-top)
(global.get $cont-stack)
(global.get $cont-stack-top)
(array.get $cont-stack-type)
ref.as_non_null
(return_call_ref $cont-type))
(elem declare funcref (ref.func 6))
(func
(param i32)
(@gyehoek :origin "(κ (x10) (continue halt x10))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 3)
(array.set $arg-array-type)
(return_call $halt (i32.const 1)))
(elem declare funcref (ref.func 7))
(func
$scm-entry
(param i32)
(@gyehoek
:origin
"(letrec ((λ-body0 (λ (f x λ-tail1) (letrec ((r2 (κ (x3) (letrec ((r4 (κ (x5) (continue λ-tail1 x5)))) (f x3 r4))))) (f x r2))))) (letrec ((λ-body6 (λ (x λ-tail7) (prim (+ x 4) (κ (r8) (continue λ-tail7 r8)))))) (letrec ((r9 (κ (x10) (continue halt x10)))) (λ-body0 λ-body6 9 r9))))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) (local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(i32.const 0) (i32.const 0)
(call 1) (ref.func 5)
(global.get 3) (struct.new $closure)
ref.as_non_null) (local.set 1)
(export "main" (func 3))) (i32.const 0)
(ref.func 6)
(struct.new $closure)
(local.set 2)
(@gyehoek :origin "(λ-body0 λ-body6 9 r9)")
(@gyehoek "push cont" :idx 7)
(array.set
$cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func 7))
(global.set
$cont-stack-top
(i32.add (global.get $cont-stack-top) (i32.const 1)))
(@gyehoek :origin "(λ-body0 λ-body6 9 r9)")
(@gyehoek "load args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 1)
(i32.const 9)
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(array.set $arg-array-type)
(i32.const 1)
(local.get 1)
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type))
(func
(export "main")
(call $scm-entry (i32.const 0))
(call $gh-write (ref.as_non_null (global.get $result)))))
-65
View File
@@ -1,65 +0,0 @@
(module
(type $heap-object (sub (struct (field (mut i32)))))
(type $open-procedure (func (param i32)))
(type $closure (sub $heap-object
(struct (field (mut i32))
(field (ref $open-procedure)))))
(type $cont-stack-type (array (mut (ref null $open-procedure))))
(type $arg-array-type (array (mut eqref)))
(type (func (result (ref eq))))
(global $cont-stack-top (mut i32) (i32.const 0))
(global $cont-stack (ref $cont-stack-type)
(array.new_default $cont-stack-type (i32.const 128)))
(global $arg-array (ref $arg-array-type)
(array.new_default $arg-array-type (i32.const 32)))
(global (mut eqref) (ref.null eq))
(elem declare funcref (ref.func 1))
(func $halt (param i32)
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(global.set 3
(ref.as_non_null
(array.get $arg-array-type
(global.get $arg-array)
(i32.const 0)))))
(func $f1 (param i32)
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
;; pop arg 0
(local.set
1
(ref.as_non_null
(array.get $arg-array-type
(global.get $arg-array)
(i32.const 0))))
;; push arg 0
(array.set $arg-array-type
(global.get $arg-array)
(i32.const 0)
(local.get 1))
;; pop continuation
(return_call_ref
$open-procedure
(i32.const 1)
(ref.as_non_null (array.get $cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)))
(global.set $cont-stack-top
(i32.sub
(global.get $cont-stack-top)
(i32.const 1)))))
(func $f2 (type $open-procedure) (param i32)
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(local.set 1
(struct.new $closure
(i32.const 0)
(ref.func $f1)))
(array.set $arg-array-type
(global.get $arg-array)
(i32.const 0)
(local.get 1))
(i32.const 1)
(return_call $f1))
(func $main (export "main") (result (ref eq))
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(call $f2 (i32.const 0))
(ref.as_non_null
(global.get 3))))
+52
View File
@@ -0,0 +1,52 @@
{-# LANGUAGE OverloadedLists #-}
module Gyehoek.Test.CPS.Syntax (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import Language.Sexp.Located qualified as SL
import Language.SexpGrammar ()
import Gyehoek.CPS.Syntax (cps)
import Gyehoek.CPS.Syntax qualified as Sut
import Data.Function (on)
import Gyehoek.Test.Sexp (equivto)
root :: IO TestTree
root = pure . testGroup "cps syntax" $
[ qqTree
, freeTree
]
freeTree :: TestTree
freeTree = testCase "free" do
Sut.free [cps|
(letrec ((x (lambda (r k1) (continue k1 y)))
(y (lambda (r k2) (continue k2 x))))
(continue x y k3))|] @=? ["k3"]
Sut.free' [cps|
(letrec ((x (lambda (r k1) (continue k1 y)))
(y (lambda (r k2) (continue k2 x))))
(continue x y k3))|] @=? ["k3"]
qqTree :: TestTree
qqTree = testGroup "parser"
[ testCase "lambda" do
assertEqual "" (Sut.MkLambda ["x","y"] "ktail"
(Sut.ExpContinue "ktail" [Sut.ValVar "x"]))
[cps|(λ (x y ktail) (continue ktail x))|]
assertEqual "" (Sut.MkLambda [] "ktail"
(Sut.ExpContinue "ktail" [Sut.ValVar "x"]))
[cps|(λ (ktail) (continue ktail x))|]
, testCase "kappa" do
assertEqual "" (Sut.MkKappa ["x","y"]
(Sut.ExpContinue "k123" [Sut.ValVar "x", Sut.ValVar "y"]))
[cps|(κ (x y) (continue k123 x y))|]
, testCase "application" do
assertEqual "" (Sut.ExpApply (Sut.ValVar "f")
[Sut.ValVar "x",Sut.ValVar "y"]
"k")
[cps|(f x y k)|]
assertEqual "" (Sut.ExpApply (Sut.ValVar "f")
[] "k")
[cps|(f k)|]
]
+45
View File
@@ -0,0 +1,45 @@
module Gyehoek.Test.Golden (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Silver
import Gyehoek.Driver qualified as Driver
import System.FilePath
import Data.List (List)
import Data.Functor ((<&>))
import System.Directory
import Data.Function
import System.Environment.Blank (getEnvDefault)
import qualified System.Process.Text as PT
disabled :: List String
disabled =
[
]
root :: IO TestTree
root = do
all_cases <- listDirectory "golden"
let tests = all_cases
& filter (`notElem` disabled)
& fmap ("golden"</>)
testGroup "golden" <$> sequenceA
[ executionTests tests
]
executionTests :: List FilePath -> IO TestTree
executionTests files = do
cmd <- getEnvDefault "GYEHOEK_RUNTIME"
"runtime/target/debug/gyehoek-runtime"
pure $ testGroup "execution" $ files <&> \test ->
let testname = takeFileName test
scmfile = test </> "source.scm"
resultfile = test </> "exec"
action = do
t <- Driver.lower_e2e scmfile
PT.readProcessWithExitCode cmd ["-"] t
in goldenVsAction
testname
resultfile
action
printProcResult
+56
View File
@@ -0,0 +1,56 @@
module Gyehoek.Test.Sexp
( root
, EquivSexp(..)
, assertEquiv
, equivto
)
where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import Language.Sexp.Located qualified as SL
import Language.SexpGrammar ()
import Gyehoek.Sexp (sx, equivalent)
import Data.Function (on)
root :: IO TestTree
root = pure . testGroup "sexp" $
[ sxTree
]
newtype EquivSexp = MkEquiv SL.Sexp
deriving newtype (Show)
instance Eq EquivSexp where
MkEquiv x == MkEquiv y = equivalent x y
assertEquiv
:: HasCallStack
=> String -> SL.Sexp -> SL.Sexp -> Assertion
assertEquiv prefix = assertEqual prefix `on` MkEquiv
equivto = assertEquiv ""
sxTree :: TestTree
sxTree = testGroup "sx"
[ testCase "quotation" do
equivto (SL.Symbol "abc") [sx|abc|]
equivto (SL.ParenList [SL.Symbol "a", SL.Symbol "b"]) [sx|(a b)|]
, testCase "antiquotation" do
equivto [sx|123|]
let meta = 123 :: Int
in [sx|#{meta}|]
equivto [sx|(blah (blah blah) blah)|]
let meta = [sx|blah|]
in [sx|(#{meta} (#{meta} #{meta}) #{meta})|]
, testCase "splicing" do
equivto [sx|(a b c d e f g)|]
let metas = SL.Symbol <$> ["c","d","e"]
in [sx|(a b ##{metas} f g)|]
equivto [sx|(a (b c d) e f g)|]
let
e1 = SL.Symbol "c"
e2 = SL.Symbol <$> ["e","f"]
in [sx|(a (b #{e1} d) ##{e2} g)|]
]
+9 -47
View File
@@ -1,57 +1,19 @@
module Main (main) where module Main (main) where
import Test.Tasty (TestTree, testGroup) import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Silver
import Test.Tasty.Silver.Interactive (defaultMain) import Test.Tasty.Silver.Interactive (defaultMain)
import Data.Traversable import qualified Gyehoek.Test.Golden
import Gyehoek.Driver qualified as Driver import qualified Gyehoek.Test.Sexp
import System.FilePath import qualified Gyehoek.Test.CPS.Syntax
import Data.List (List)
import Data.Functor ((<&>))
import System.Directory
import Data.Function
disabled :: List String
disabled =
[ "square"
]
main :: IO () main :: IO ()
main = defaultMain =<< goldenTests main = defaultMain =<< root
goldenTests :: IO TestTree root :: IO TestTree
goldenTests = do root = testGroup "test" <$> sequenceA
all_cases <- listDirectory "golden" [ Gyehoek.Test.Golden.root
let tests = all_cases , Gyehoek.Test.Sexp.root
& filter (`notElem` disabled) , Gyehoek.Test.CPS.Syntax.root
& fmap ("golden"</>)
pure $ testGroup "golden"
[ watTests tests
, executionTests tests
] ]
watTests :: List FilePath -> TestTree
watTests files =
testGroup "wat" $ files <&> \test ->
let source = test </> "source.scm"
golden = test </> "out.wat"
testname = takeFileName test
in goldenVsAction
testname
golden
(Driver.lower_e2e source)
id
executionTests :: List FilePath -> TestTree
executionTests files =
testGroup "execution" $ files <&> \test ->
let wat = test </> "out.wat"
testname = takeFileName test
resultfile = test </> "exec"
in goldenVsProg
testname
resultfile
"wasmtime"
["--invoke", "main", wat]
""
BIN
View File
Binary file not shown.
+113 -64
View File
@@ -1,78 +1,127 @@
(module (module
(func $print (import "guppy" "print") (param i32)) (import "gyehoek" "write" (func $gh-write (param (ref eq))))
(table 2 funcref) (import "gyehoek" "truthy?" (func $gh-truthy? (param (ref eq)) (result i32)))
(elem (i32.const 0) $halt) (type $heap-object (sub (struct (field $hash (mut i32)))))
(type $cont-type (func (param i32)))
(type $cont (func (param i32))) (type $cont-stack-type (array (mut (ref null $cont-type))))
(type $cont-stack-type (array (mut (ref null $cont)))) (type
(global $cont-stack (ref $cont-stack-type) $closure
(array.new_default $cont-stack-type (i32.const 128))) (sub
$heap-object
(struct
(field $hash (mut i32))
(field $code (ref $cont-type)))))
(global $cont-stack-top (mut i32) (i32.const 0)) (global $cont-stack-top (mut i32) (i32.const 0))
(global
$cont-stack
(ref $cont-stack-type)
(array.new_default $cont-stack-type (i32.const 128)))
(type $arg-array-type (array (mut (ref null eq)))) (type $arg-array-type (array (mut (ref null eq))))
(global $arg-array (ref $arg-array-type) (global
$arg-array
(ref $arg-array-type)
(array.new_default $arg-array-type (i32.const 32))) (array.new_default $arg-array-type (i32.const 32)))
(global $result (mut (ref null eq)) (ref.null eq))
;; (memory $memory i32 1) (func
;; (global $arg-stack-base i32 (i32.const 0)) $halt
;; (global $arg-stack-ptr i32 (global.get $arg-stack-base)) (param i32)
;; (global $cont-stack-base i32 (i32.const 32))
;; (global $cont-stack-ptr i32 (global.get $cont-stack-base))
(func $add (param $nargs i32)
(local $x (ref eq))
(local $y (ref eq))
(local $return (ref $cont))
(local.set $x (ref.as_non_null
(array.get $arg-array-type
(global.get $arg-array)
(i32.const 0))))
(local.set $y (ref.as_non_null
(array.get $arg-array-type
(global.get $arg-array)
(i32.const 1))))
(array.set $arg-array-type
(global.get $arg-array) (global.get $arg-array)
(i32.const 0) (i32.const 0)
(ref.i31 (array.get $arg-array-type)
(i32.add (i31.get_s (ref.cast (ref i31) (local.get $x))) ref.as_non_null
(i31.get_s (ref.cast (ref i31) (local.get $y)))))) (global.set $result))
(return_call_ref (func
$cont (param i32)
(@gyehoek
:origin
(lambda (x lambda-tail1)
(prim (* x x) (kappa (r2) (continue lambda-tail1 r2)))))
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(local.get 1)
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
(local.get 1)
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
i32.mul
(i32.const 1)
i32.shl
ref.i31
(local.set 2)
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(i32.const 1) (i32.const 1)
(block (result (ref $cont))
(ref.as_non_null
(array.get $cont-stack-type
(global.get $cont-stack) (global.get $cont-stack)
(global.get $cont-stack-top))) (global.get $cont-stack-top)
(global.set $cont-stack-top (array.get $cont-stack-type)
(i32.sub (global.get $cont-stack-top) ref.as_non_null
(i32.const 1)))))) (global.get $cont-stack-top)
(func $halt (param $nargs i32) (i32.const 1)
(call $print i32.sub
(i31.get_s (global.set $cont-stack-top)
(ref.cast (return_call_ref $cont-type))
(ref i31) (elem declare funcref (ref.func 3))
(ref.as_non_null (func
(array.get $arg-array-type (param i32)
(global.get $arg-array) (@gyehoek :origin (kappa (x4) (continue halt x4)))
(i32.const 0))))))) (local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(func (export "main")
;; push args
(array.set $arg-array-type
(global.get $arg-array) (global.get $arg-array)
(i32.const 0) (i32.const 0)
(ref.i31 (i32.const 4))) (array.get $arg-array-type)
(array.set $arg-array-type ref.as_non_null
(local.set 1)
(global.get $arg-array) (global.get $arg-array)
(i32.const 1) (i32.const 0)
(ref.i31 (i32.const 5))) (local.get 1)
;; push return continuation (array.set $arg-array-type)
(return_call $halt (i32.const 1)))
(func
$scm-entry
(param i32)
(@gyehoek
:origin
(letrec ((lambda-body0
(lambda (x lambda-tail1)
(prim (* x x) (kappa (r2) (continue lambda-tail1 r2))))))
(letrec ((r3 (kappa (x4) (continue halt x4))))
(lambda-body0 5 r3))))
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(i32.const 0)
(ref.func 3)
(struct.new $closure)
(local.set 1)
(@gyehoek "push return cont" :idx 4)
(array.set $cont-stack-type (array.set $cont-stack-type
(global.get $cont-stack) (global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func 4))
(global.set $cont-stack-top
(i32.add (global.get $cont-stack-top)
(i32.const 1)))
(@gyehoek "load args")
(global.get $arg-array)
(i32.const 0) (i32.const 0)
(ref.func $halt)) (i32.const 5)
;; make call }:) (i32.const 1)
(return_call $add i32.shl
;; inform $add how many arguments we called it with ref.i31
(i32.const 2)))) (array.set $arg-array-type)
(@gyehoek todo (f' (local.get 1)) (ktail 1))
(return_call_ref $cont-type
(i32.const 1)
(struct.get $closure $code
(ref.cast (ref $closure) (local.get 1)))))
(elem declare funcref (ref.func 4))
(func
(export "main")
(call $scm-entry (i32.const 0))
(call $gh-write (ref.as_non_null (global.get $result)))))