Compare commits

...
35 Commits
Author SHA1 Message Date
jonas 762ed3c478 closure bump 2026-09-12 14:44:08 +02:00
jonas 5c5c81325d fix(build): app name 2026-04-02 19:19:26 +02:00
jonas d1211b7157 fix(texture): display inf rec 2026-04-02 19:17:23 +02:00
jonas 99bf883eeb fix(flake): default app name 2026-04-02 18:51:15 +02:00
jonas 078ad2c401 build: install scenes properly 2026-04-02 18:17:22 +02:00
jonas 3a49460fe6 feat(sphere): add texture sphere 2026-04-02 15:10:09 +02:00
jonas 684cc19302 fix(expression): PartialEq and PartialOrd impls 2026-04-02 14:48:14 +02:00
jonas 0e919c339c feat(texture): add plane textures 2026-04-02 14:41:01 +02:00
jonas 5605ad0901 fix: environment warnings 2026-04-01 01:45:35 +02:00
jonas 3e5f23a3bf feat(rt-lisp): add more math overloads 2026-04-01 01:43:50 +02:00
jonas 5aeaf72af1 feat(macro): show invalid args 2026-04-01 01:24:47 +02:00
jonas 6e6a3e8a27 feat: update demo 2026-04-01 00:56:36 +02:00
jonas aa0ba6ed7a feat(scenes): add materials.lisp 2026-04-01 00:37:25 +02:00
jonas 5ffc390d2c feat(rt_interp): enable file locations for include 2026-04-01 00:36:03 +02:00
jonas 48d4039c31 feat(core): add load and include prelude 2026-04-01 00:35:52 +02:00
jonas 3cb3e4a8fa feat(sphere): inner intersection 2026-03-31 23:01:55 +02:00
jonas fc40e0b798 feat(camera): render anim using ffmpeg 2026-03-31 20:15:30 +02:00
jonas d0840759b3 feat(core): Display for EvalError + ParserError 2026-03-31 20:14:49 +02:00
jonas d4281d3538 feat(tokenizer): allow negative numbers 2026-03-31 04:32:08 +02:00
jonas 3c73077837 feat(anim): add frame-based animations 2026-03-31 04:12:39 +02:00
jonas 36b9ad4c0d feat(lispers-macro): show function name in application error 2026-03-31 04:10:18 +02:00
jonas 7d70066213 feat(expr): from fixed sized array to Expr 2026-03-31 04:09:52 +02:00
jonas 72c0cc8445 fix: devShell deps 2026-03-31 04:09:36 +02:00
jonas 0bdeb2ceab build: use flake-parts 2026-03-31 01:06:03 +02:00
jonas 0ca3ec6973 fix: ffmpeg-next build 2026-03-31 00:48:57 +02:00
jonas d835dc48ce feat(prelude): add list/string functions 2025-01-12 21:47:06 +01:00
jonas e770e6f8a7 build(flake): add rt_interp binary 2025-01-11 17:09:45 +01:00
jonas b38e6c00a5 feat(rtlisp): add rt_inerp binary and demo script 2025-01-11 16:35:09 +01:00
jonas 1871f6cae4 feat(rtlisp): clean up rt lisp functions
- sin/cos
- scene-add proxy
2025-01-11 16:33:05 +01:00
jonas 88bbcf036f feat(core): improve printing
- Expression Conversion
- print/println
2025-01-11 16:31:41 +01:00
jonas ad0792dcd3 feat(lisp): add overloadable native functions 2025-01-07 00:55:03 +01:00
jonas 1856de7685 build(flake): adjust cargoNix to workspace 2025-01-04 21:03:49 +01:00
jonas 3e11142361 feat: add native_lisp_function macro
- refactor project layout to use child crates
  - lispers-core: parser and evaluator
  - lispers-macro: proc macros
2025-01-04 20:12:11 +01:00
jonas 9179f06132 fix(build): binary names 2024-11-28 02:05:02 +01:00
jonas 6a3348d727 feat(raytracer): add native rt functions to lisp 2024-11-28 01:57:40 +01:00
40 changed files with 3548 additions and 1141 deletions
+1
View File
@@ -1,3 +1,4 @@
.direnv/ .direnv/
target/ target/
result/ result/
*.png
Generated
+993 -324
View File
File diff suppressed because it is too large Load Diff
+30 -10
View File
@@ -1,27 +1,47 @@
[package] [package]
name = "lispers" name = "lispers"
description = "lisp interpreter in rust" description = "lisp interpreter in rust for raytracing"
publish = false publish = false
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[lib] [lib]
name = "lispers" name = "lispers"
path = "src/lib.rs" path = "src/lib.rs"
[[bin]] [[bin]]
name = "demo" name = "lisp_demo"
path = "src/bin/demo.rs" path = "src/bin/lisp_demo.rs"
[[bin]] [[bin]]
name = "repl" name = "repl"
path = "src/bin/repl.rs" path = "src/bin/repl.rs"
[[bin]]
name = "rt_lisp_demo"
path = "src/bin/rt_lisp_demo.rs"
[[bin]]
name = "rt_interp"
path = "src/bin/rt_interp.rs"
[workspace]
members = [ "lispers-core", "lispers-macro"]
[workspace.dependencies]
lispers-core = {path = "lispers-core"}
lispers-macro = {path = "lispers-macro"}
as-any = "0.3.2"
[dependencies] [dependencies]
as-any = "0.3.1" as-any = {workspace = true}
futures = "0.3.30" futures = "0.3.32"
image = "0.25.5" image = "0.25.10"
nalgebra = "0.33.2" nalgebra = "0.34.2"
nix = "0.29.0" nix = "0.31.2"
rayon = "1.10.0" rayon = "1.11.0"
lispers-core = {workspace = true}
lispers-macro = {workspace = true}
video-rs = { version = "0.11.0", features = ["ndarray"] }
ndarray = "0.17.2"
+39
View File
@@ -0,0 +1,39 @@
use std::path::Path;
fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
fn main() {
let use_local_scenes = std::env::var("LISPERS_USE_LOCAL_SCENES").unwrap_or_default() == "1";
let no_copy = std::env::var("LISPERS_DONT_COPY_SCENES").unwrap_or_default() == "1";
let out_dir = match std::env::var("LISPERS_OUT_DIR") {
Ok(val) => val,
Err(_) => std::env::var("OUT_DIR").unwrap(),
};
let mut scenes_dir = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.canonicalize()
.unwrap()
.join("scenes");
if !use_local_scenes {
let tgt_scenes_dir = Path::new(&out_dir).join("scenes");
if !no_copy {
copy_dir(&scenes_dir, &tgt_scenes_dir).expect("Failed to copy scenes directory");
}
scenes_dir = tgt_scenes_dir;
}
println!("cargo:rustc-env=SCENES_DIR={}", scenes_dir.display());
}
Generated
+86 -671
View File
@@ -8,82 +8,15 @@
"flake-compat": [ "flake-compat": [
"crate2nix" "crate2nix"
], ],
"nixpkgs": "nixpkgs", "git-hooks": "git-hooks",
"pre-commit-hooks": [ "nixpkgs": "nixpkgs"
"crate2nix"
]
}, },
"locked": { "locked": {
"lastModified": 1709700175, "lastModified": 1767714506,
"narHash": "sha256-A0/6ZjLmT9qdYzKHmevnEIC7G+GiZ4UCr8v0poRPzds=", "narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
"owner": "cachix", "owner": "cachix",
"repo": "cachix", "repo": "cachix",
"rev": "be97b37989f11b724197b5f4c7ffd78f12c8c4bf", "rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"cachix_2": {
"inputs": {
"devenv": [
"crate2nix",
"crate2nix_stable"
],
"flake-compat": [
"crate2nix",
"crate2nix_stable"
],
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": [
"crate2nix",
"crate2nix_stable"
]
},
"locked": {
"lastModified": 1716549461,
"narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=",
"owner": "cachix",
"repo": "cachix",
"rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"cachix_3": {
"inputs": {
"devenv": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable"
],
"flake-compat": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable"
],
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable"
]
},
"locked": {
"lastModified": 1716549461,
"narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=",
"owner": "cachix",
"repo": "cachix",
"rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -96,162 +29,42 @@
"crate2nix": { "crate2nix": {
"inputs": { "inputs": {
"cachix": "cachix", "cachix": "cachix",
"crate2nix_stable": "crate2nix_stable",
"devshell": "devshell_3",
"flake-compat": "flake-compat_3",
"flake-parts": "flake-parts_3",
"nix-test-runner": "nix-test-runner_3",
"nixpkgs": [
"nixpkgs"
],
"pre-commit-hooks": "pre-commit-hooks_3"
},
"locked": {
"lastModified": 1732039290,
"narHash": "sha256-LQKY7bShf2H9kJouxa9ZspfdrulnZF9o4kLTqGqCDYM=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "9ff208ce7f5a482272b1bcefbe363c772d7ff914",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable": {
"inputs": {
"cachix": "cachix_2",
"crate2nix_stable": "crate2nix_stable_2",
"devshell": "devshell_2",
"flake-compat": "flake-compat_2",
"flake-parts": "flake-parts_2",
"nix-test-runner": "nix-test-runner_2",
"nixpkgs": "nixpkgs_5",
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
"lastModified": 1719760004,
"narHash": "sha256-esWhRnt7FhiYq0CcIxw9pvH+ybOQmWBfHYMtleaMhBE=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "1dee214bb20855fa3e1e7bb98d28922ddaff8c57",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "0.14.1",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable_2": {
"inputs": {
"cachix": "cachix_3",
"crate2nix_stable": "crate2nix_stable_3",
"devshell": "devshell", "devshell": "devshell",
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"nix-test-runner": "nix-test-runner", "nix-test-runner": "nix-test-runner",
"nixpkgs": "nixpkgs_4", "nixpkgs": [
"nixpkgs"
],
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": "pre-commit-hooks"
}, },
"locked": { "locked": {
"lastModified": 1712821484, "lastModified": 1774369503,
"narHash": "sha256-rGT3CW64cJS9nlnWPFWSc1iEa3dNZecVVuPVGzcsHe8=", "narHash": "sha256-YeCF4iBhlvTqkn4mihjZgixnDcEVgfyQlNeBsbLYUgQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "crate2nix", "repo": "crate2nix",
"rev": "42883afcad3823fa5811e967fb7bff54bc3c9d6d", "rev": "b873ca53dd64e12340416f0fd5e3b33792b9c17b",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "owner": "nix-community",
"ref": "0.14.0",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable_3": {
"inputs": {
"flake-utils": "flake-utils"
},
"locked": {
"lastModified": 1702842982,
"narHash": "sha256-A9AowkHIjsy1a4LuiPiVP88FMxyCWK41flZEZOUuwQM=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "75ac2973affa6b9b4f661a7b592cba6e4f51d426",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "0.12.0",
"repo": "crate2nix", "repo": "crate2nix",
"type": "github" "type": "github"
} }
}, },
"devshell": { "devshell": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717408969,
"narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
"owner": "numtide",
"repo": "devshell",
"rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"devshell_2": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717408969,
"narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
"owner": "numtide",
"repo": "devshell",
"rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"devshell_3": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1711099426, "lastModified": 1768818222,
"narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", "narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
"owner": "numtide", "owner": "numtide",
"repo": "devshell", "repo": "devshell",
"rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", "rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -262,40 +75,12 @@
}, },
"flake-compat": { "flake-compat": {
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1733328505,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 57, "revCount": 69,
"type": "tarball", "type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_2": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_3": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@@ -306,17 +91,15 @@
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"crate2nix", "crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1719745305, "lastModified": 1768135262,
"narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -327,152 +110,47 @@
}, },
"flake-parts_2": { "flake-parts_2": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"crate2nix", "crate2nix",
"crate2nix_stable", "cachix",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"crate2nix",
"cachix",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1719745305, "lastModified": 1765404074,
"narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", "narHash": "sha256-+ZDU2d+vzWkEJiqprvV5PR26DVFN2vgddwG5SnPZcUM=",
"owner": "hercules-ci", "owner": "cachix",
"repo": "flake-parts", "repo": "git-hooks.nix",
"rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", "rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "hercules-ci", "owner": "cachix",
"repo": "flake-parts", "repo": "git-hooks.nix",
"type": "github"
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_4": {
"inputs": {
"systems": "systems_4"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_5": {
"inputs": {
"systems": "systems_5"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"inputs": {
"systems": "systems_6"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github" "type": "github"
} }
}, },
@@ -480,9 +158,8 @@
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
"crate2nix_stable", "cachix",
"crate2nix_stable", "git-hooks",
"pre-commit-hooks",
"nixpkgs" "nixpkgs"
] ]
}, },
@@ -501,29 +178,6 @@
} }
}, },
"gitignore_2": { "gitignore_2": {
"inputs": {
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_3": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
@@ -561,45 +215,13 @@
"type": "github" "type": "github"
} }
}, },
"nix-test-runner_2": {
"flake": false,
"locked": {
"lastModified": 1588761593,
"narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
"owner": "stoeffel",
"repo": "nix-test-runner",
"type": "github"
}
},
"nix-test-runner_3": {
"flake": false,
"locked": {
"lastModified": 1588761593,
"narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
"owner": "stoeffel",
"repo": "nix-test-runner",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1700612854, "lastModified": 1765186076,
"narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=", "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614", "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -609,13 +231,28 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-lib": {
"locked": {
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1715534503, "lastModified": 1774386573,
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=", "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06", "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -625,156 +262,24 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_3": {
"locked": {
"lastModified": 1715534503,
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1719506693,
"narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
"path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source",
"rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1719506693,
"narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
"path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source",
"rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_6": {
"locked": {
"lastModified": 1717179513,
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": { "pre-commit-hooks": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
"crate2nix", "crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs"
],
"nixpkgs-stable": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719259945,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_2": {
"inputs": {
"flake-compat": [
"crate2nix",
"crate2nix_stable",
"flake-compat" "flake-compat"
], ],
"gitignore": "gitignore_2", "gitignore": "gitignore_2",
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
"crate2nix_stable",
"nixpkgs"
],
"nixpkgs-stable": [
"crate2nix",
"crate2nix_stable",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1719259945, "lastModified": 1769069492,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_3": {
"inputs": {
"flake-compat": [
"crate2nix",
"flake-compat"
],
"flake-utils": "flake-utils_5",
"gitignore": "gitignore_3",
"nixpkgs": [
"crate2nix",
"nixpkgs"
],
"nixpkgs-stable": [
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712055707,
"narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "e35aed5fda3cc79f88ed7f1795021e559582093a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -786,8 +291,8 @@
"root": { "root": {
"inputs": { "inputs": {
"crate2nix": "crate2nix", "crate2nix": "crate2nix",
"flake-utils": "flake-utils_6", "flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_6", "nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
} }
}, },
@@ -798,11 +303,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1731983527, "lastModified": 1774667365,
"narHash": "sha256-JECaBgC0pQ91Hq3W4unH6K9to8s2Zl2sPNu7bLOv4ek=", "narHash": "sha256-+JamhonkPyti+oqfl1ySAyF2L02adhCEcdZOzpSukq8=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "71287228d96e9568e1e70c6bbfa3f992d145947b", "rev": "98caaa8cd1fbcc45913d1bb2b7fbabcf3e8d967a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -810,96 +315,6 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_4": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_5": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_6": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+71 -32
View File
@@ -2,39 +2,35 @@
description = "Rust-Nix"; description = "Rust-Nix";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/24.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
rust-overlay = { rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
flake-utils.url = "github:numtide/flake-utils";
crate2nix = { crate2nix = {
url = "github:nix-community/crate2nix"; url = "github:nix-community/crate2nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
nixConfig = {
extra-trusted-public-keys = "eigenvalue.cachix.org-1:ykerQDDa55PGxU25CETy9wF6uVDpadGGXYrFNJA3TUs=";
extra-substituters = "https://eigenvalue.cachix.org";
allow-import-from-derivation = true;
};
outputs = inputs @ { outputs = inputs @ {
crate2nix, flake-parts,
flake-utils,
nixpkgs,
rust-overlay, rust-overlay,
crate2nix,
... ...
}: }:
flake-utils.lib.eachDefaultSystem ( flake-parts.lib.mkFlake {inherit inputs;} (top: {
system: let systems = [
# Overlay pkgs with rust-bin "x86_64-linux"
overlays = [(import rust-overlay)]; ];
pkgs = import nixpkgs {
inherit system overlays;
};
perSystem = {
self',
pkgs,
system,
...
}: let
# Use rust-bin to generate the toolchain from rust-toolchain.toml # Use rust-bin to generate the toolchain from rust-toolchain.toml
rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
@@ -49,6 +45,22 @@
rav1e = attrs: { rav1e = attrs: {
CARGO_ENCODED_RUSTFLAGS = ""; CARGO_ENCODED_RUSTFLAGS = "";
}; };
# Fix thread 'main' (222) panicked at build.rs:250:45:
av-scenechange = attrs: {
CARGO_ENCODED_RUSTFLAGS = "";
};
# Bindgen fix
ffmpeg-sys-next = attrs: {
buildInputs = [pkgs.ffmpeg_4];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
nativeBuildInputs = [pkgs.pkg-config];
BINDGEN_EXTRA_CLANG_ARGS = [
"--sysroot=${pkgs.glibc.dev}"
];
};
ffmpeg-next = attrs: {
features = ["codec" "format" "ffmpeg4" "ffmpeg_4_0" "ffmpeg_4_1" "ffmpeg_4_2" "ffmpeg_4_3" "ffmpeg_4_4" "ff_api_vaapi" "software-scaling" "software-resampling"];
};
}; };
}; };
@@ -61,29 +73,56 @@
cargoNix = import generatedCargoNix { cargoNix = import generatedCargoNix {
inherit pkgs buildRustCrateForPkgs; inherit pkgs buildRustCrateForPkgs;
}; };
in rec { in {
apps = rec { _module.args.pkgs = import inputs.nixpkgs {
demo = { inherit system;
overlays = [rust-overlay.overlays.default];
};
packages.lispers = cargoNix.workspaceMembers.lispers.build.overrideAttrs (attrs: {
preConfigure = ''
export LISPERS_OUT_DIR="$out"
export LISPERS_DONT_COPY_SCENES=1
'';
postInstall = ''
cp -r $src/scenes $out/scenes
'';
});
packages.default = self'.packages.lispers;
apps = {
lisp_demo = {
type = "app"; type = "app";
program = "${packages.default}/bin/demo"; program = "${self'.packages.lispers}/bin/lisp_demo";
}; };
repl = { repl = {
type = "app"; type = "app";
program = "${packages.default}/bin/repl"; program = "${self'.packages.lispers}/bin/repl";
}; };
rt_demo = { rt_demo = {
type = "app"; type = "app";
program = "${packages.default}/bin/rt_demo"; program = "${self'.packages.lispers}/bin/rt_demo";
}; };
default = demo; rt_lisp_demo = {
type = "app";
program = "${self'.packages.lispers}/bin/rt_lisp_demo";
}; };
packages = rec { rt_interp = {
lispers = cargoNix.rootCrate.build; type = "app";
default = lispers; program = "${self'.packages.lispers}/bin/rt_interp";
}; };
devShell = pkgs.mkShell { default = self'.apps.rt_lisp_demo;
buildInputs = [rust-toolchain];
}; };
}
); devShells.default = pkgs.mkShell {
shellHook = ''
export LISPERS_USE_LOCAL_SCENES=1
'';
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
nativeBuildInputs = [rust-toolchain pkgs.pkg-config pkgs.ffmpeg_4];
BINDGEN_EXTRA_CLANG_ARGS = [
"--sysroot=${pkgs.glibc.dev}"
];
};
};
});
} }
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "lispers-core"
version = "0.1.0"
edition = "2021"
[dependencies]
as-any = {workspace = true}
+2
View File
@@ -0,0 +1,2 @@
pub mod lisp;
pub mod parser;
@@ -1,5 +1,5 @@
use super::{expression::Expression, prelude::mk_prelude}; use super::{expression::Expression, prelude::mk_prelude};
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
/// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from /// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from
@@ -64,7 +64,7 @@ impl<'a> Environment<'a> {
} }
/// Construct a new `Environment` with `self` as the outer `Environment`. /// Construct a new `Environment` with `self` as the outer `Environment`.
pub fn mk_inner(&self) -> Environment { pub fn mk_inner(&'a self) -> Environment<'a> {
Environment { Environment {
layer: EnvironmentLayer::new(), layer: EnvironmentLayer::new(),
outer: Some(self), outer: Some(self),
@@ -136,6 +136,30 @@ impl Default for Environment<'_> {
} }
} }
impl Display for Environment<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = &self.outer {
write!(f, "{}<{}", self.layer, x)
} else {
write!(f, "")
}
}
}
impl Display for EnvironmentLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{{}}}",
self.symbols
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[test] #[test]
fn test_environment() { fn test_environment() {
let mut env = Environment::new(); let mut env = Environment::new();
@@ -1,10 +1,12 @@
use std::fmt::Display; use std::fmt::Display;
use crate::parser::ParserError;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer; use super::environment::EnvironmentLayer;
use super::expression::Expression; use super::expression::Expression;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
/// All possible evaluation errors /// All possible evaluation errors
pub enum EvalError { pub enum EvalError {
SymbolNotBound(String), SymbolNotBound(String),
@@ -14,6 +16,13 @@ pub enum EvalError {
TypeError(String), TypeError(String),
NotASymbol(Expression), NotASymbol(Expression),
RuntimeError(String), RuntimeError(String),
ParserError(ParserError),
}
impl From<ParserError> for EvalError {
fn from(value: ParserError) -> Self {
EvalError::ParserError(value)
}
} }
impl Display for EvalError { impl Display for EvalError {
@@ -26,6 +35,7 @@ impl Display for EvalError {
EvalError::TypeError(s) => write!(f, "Type error: {}", s), EvalError::TypeError(s) => write!(f, "Type error: {}", s),
EvalError::NotASymbol(e) => write!(f, "Expression {} is not a symbol", e), EvalError::NotASymbol(e) => write!(f, "Expression {} is not a symbol", e),
EvalError::RuntimeError(s) => write!(f, "Runtime error: {}", s), EvalError::RuntimeError(s) => write!(f, "Runtime error: {}", s),
EvalError::ParserError(s) => write!(f, "Parser error: {}", s),
} }
} }
} }
@@ -87,12 +97,23 @@ fn dispatch_anonymous_function(
} }
for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) { for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) {
overlay.set(symbol.to_owned(), eval(env, arg.to_owned())?); overlay.set(symbol.to_owned(), eval(&env, arg.to_owned())?);
} }
eval(&env.overlay(overlay), body) eval(&env.overlay(overlay), body)
} }
/// Dispatch a closure. Evaluates `body` in `env` overlayed with the `layer`
fn dispatch_closure(
env: &Environment,
layer: EnvironmentLayer,
body: Expression,
) -> Result<Expression, EvalError> {
println!("Dispatching closure: {} -> {}", &layer, &body);
let env = env.overlay(layer);
eval(&env, body)
}
/// Evaluate an expression inside an environment /// Evaluate an expression inside an environment
pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
match expr { match expr {
@@ -104,6 +125,7 @@ pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError
} => dispatch_anonymous_function(env, argument_symbols, *body, *rhs), } => dispatch_anonymous_function(env, argument_symbols, *body, *rhs),
a => Err(EvalError::NotAFunction(a)), a => Err(EvalError::NotAFunction(a)),
}, },
Expression::Closure { layer, body } => dispatch_closure(env, layer, *body),
Expression::Quote(e) => Ok(*e), Expression::Quote(e) => Ok(*e),
Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)), Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)),
x => Ok(x), x => Ok(x),
@@ -7,6 +7,7 @@ use std::ops::DerefMut;
use as_any::AsAny; use as_any::AsAny;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer;
use super::eval::CellIterator; use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
@@ -121,13 +122,17 @@ impl Display for ForeignDataStore {
} }
} }
#[derive(Clone, Debug, PartialEq, PartialOrd)] #[derive(Clone, Debug)]
/// A sum type of all possible lisp expressions. /// A sum type of all possible lisp expressions.
pub enum Expression { pub enum Expression {
/// The classic lisp cons cell aka (a . b) used to construct expressions. /// The classic lisp cons cell aka (a . b) used to construct expressions.
Cell(Box<Expression>, Box<Expression>), Cell(Box<Expression>, Box<Expression>),
/// A function expression pointing to native code. /// A function expression pointing to native code.
Function(fn(&Environment, Expression) -> Result<Expression, EvalError>), Function(fn(&Environment, Expression) -> Result<Expression, EvalError>),
Closure {
layer: EnvironmentLayer,
body: Box<Expression>,
},
/// A anonymous function expression consisting of bound symbols and a body expression. /// A anonymous function expression consisting of bound symbols and a body expression.
AnonymousFunction { AnonymousFunction {
argument_symbols: Vec<String>, argument_symbols: Vec<String>,
@@ -151,6 +156,64 @@ pub enum Expression {
Nil, Nil,
} }
impl PartialEq for Expression {
fn eq(&self, other: &Self) -> bool {
use Expression::*;
match (self, other) {
(Cell(a1, b1), Cell(a2, b2)) => PartialEq::eq(a1, a2) && PartialEq::eq(b1, b2),
(
AnonymousFunction {
argument_symbols: args1,
body: body1,
},
AnonymousFunction {
argument_symbols: args2,
body: body2,
},
) => PartialEq::eq(args1, args2) && PartialEq::eq(body1, body2),
(ForeignExpression(f1), ForeignExpression(f2)) => PartialEq::eq(f1, f2),
(Quote(e1), Quote(e2)) => PartialEq::eq(e1, e2),
(Symbol(s1), Symbol(s2)) => PartialEq::eq(s1, s2),
(Integer(i1), Integer(i2)) => PartialEq::eq(i1, i2),
(Float(f1), Float(f2)) => PartialEq::eq(f1, f2),
(String(s1), String(s2)) => PartialEq::eq(s1, s2),
(Nil, Nil) => true,
(True, True) => true,
_ => false,
}
}
}
impl PartialOrd for Expression {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use Expression::*;
match (self, other) {
(Cell(a1, b1), Cell(a2, b2)) => a1.partial_cmp(a2).or_else(|| b1.partial_cmp(b2)),
(
AnonymousFunction {
argument_symbols: args1,
body: body1,
},
AnonymousFunction {
argument_symbols: args2,
body: body2,
},
) => args1
.partial_cmp(args2)
.or_else(|| body1.partial_cmp(body2)),
(ForeignExpression(f1), ForeignExpression(f2)) => f1.partial_cmp(f2),
(Quote(e1), Quote(e2)) => e1.partial_cmp(e2),
(Symbol(s1), Symbol(s2)) => s1.partial_cmp(s2),
(Float(f1), Float(f2)) => f1.partial_cmp(f2),
(Integer(i1), Integer(i2)) => i1.partial_cmp(i2),
(String(s1), String(s2)) => s1.partial_cmp(s2),
(Nil, Nil) => Some(std::cmp::Ordering::Equal),
(True, True) => Some(std::cmp::Ordering::Equal),
_ => None,
}
}
}
impl<T: ForeignData> From<ForeignDataWrapper<T>> for Expression { impl<T: ForeignData> From<ForeignDataWrapper<T>> for Expression {
fn from(value: ForeignDataWrapper<T>) -> Expression { fn from(value: ForeignDataWrapper<T>) -> Expression {
Expression::ForeignExpression(ForeignDataStore::new(value.0)) Expression::ForeignExpression(ForeignDataStore::new(value.0))
@@ -192,12 +255,52 @@ impl From<Vec<Expression>> for Expression {
} }
} }
impl<const N: usize> From<[Expression; N]> for Expression {
fn from(mut value: [Expression; N]) -> Self {
let mut current = Expression::Nil;
for e in value.iter_mut().rev() {
current = Expression::Cell(Box::new(e.to_owned()), Box::new(current));
}
current
}
}
impl From<(Expression, Expression)> for Expression { impl From<(Expression, Expression)> for Expression {
fn from(value: (Expression, Expression)) -> Self { fn from(value: (Expression, Expression)) -> Self {
Expression::Cell(Box::new(value.0), Box::new(value.1)) Expression::Cell(Box::new(value.0), Box::new(value.1))
} }
} }
impl From<i64> for Expression {
fn from(value: i64) -> Self {
Expression::Integer(value)
}
}
impl From<f64> for Expression {
fn from(value: f64) -> Self {
Expression::Float(value)
}
}
impl From<String> for Expression {
fn from(value: String) -> Self {
Expression::String(value)
}
}
impl From<bool> for Expression {
fn from(value: bool) -> Self {
if value {
Expression::True
} else {
Expression::Nil
}
}
}
impl TryFrom<Expression> for i64 { impl TryFrom<Expression> for i64 {
type Error = EvalError; type Error = EvalError;
fn try_from(value: Expression) -> Result<i64, Self::Error> { fn try_from(value: Expression) -> Result<i64, Self::Error> {
@@ -313,6 +416,9 @@ impl Display for Expression {
} }
} }
Expression::Function(_) => write!(f, "<function>"), Expression::Function(_) => write!(f, "<function>"),
Expression::Closure { layer, body } => {
write!(f, "(closure captured={}, body={})", layer, body)
}
Expression::AnonymousFunction { Expression::AnonymousFunction {
argument_symbols, argument_symbols,
body, body,
@@ -1,3 +1,6 @@
use crate::parser::ExpressionStream;
use crate::parser::ParserError;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer; use super::environment::EnvironmentLayer;
use super::eval::eval; use super::eval::eval;
@@ -5,6 +8,7 @@ use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
use super::expression::Expression; use super::expression::Expression;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf;
pub fn prelude_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [a, b] = expr.try_into()?; let [a, b] = expr.try_into()?;
@@ -144,13 +148,18 @@ pub fn prelude_let(env: &Environment, expr: Expression) -> Result<Expression, Ev
}) })
.collect::<Result<HashMap<String, Expression>, EvalError>>()?; .collect::<Result<HashMap<String, Expression>, EvalError>>()?;
eval(&env.overlay(bindings.into()), body) Ok(Expression::Closure {
layer: bindings.into(),
body: Box::new(body),
})
} }
pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [predicate, e_then, e_else] = expr.try_into()?; let [predicate, e_then, e_else]: [Expression; 3] = expr.try_into()?;
match eval(env, predicate)? { let val = eval(env, predicate.clone())?;
match val {
Expression::Nil => eval(env, e_else), Expression::Nil => eval(env, e_else),
_ => eval(env, e_then), _ => eval(env, e_then),
} }
@@ -213,10 +222,17 @@ pub fn prelude_set(env: &Environment, expr: Expression) -> Result<Expression, Ev
} }
} }
pub fn prelude_println(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [e] = expr.try_into()?;
let e = eval(env, e)?;
println!("{}", e);
Ok(e)
}
pub fn prelude_print(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_print(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [e] = expr.try_into()?; let [e] = expr.try_into()?;
let e = eval(env, e)?; let e = eval(env, e)?;
println!("Prelude: {}", e); print!("{}", e);
Ok(e) Ok(e)
} }
@@ -255,6 +271,123 @@ pub fn prelude_progn(env: &Environment, expr: Expression) -> Result<Expression,
Ok(result) Ok(result)
} }
pub fn prelude_list(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let exprs: Vec<Expression> = expr.try_into()?;
let evaled_exprs: Vec<_> = exprs
.iter()
.map(|e| eval(env, e.to_owned()))
.collect::<Result<_, _>>()?;
Ok(evaled_exprs.into())
}
pub fn prelude_append(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let exprs: Vec<Expression> = expr.try_into()?;
let evaled_exprs: Vec<_> = exprs
.iter()
.map(|e| eval(env, e.to_owned())?.try_into())
.collect::<Result<Vec<Vec<Expression>>, _>>()?;
Ok(evaled_exprs.concat().into())
}
pub fn prelude_concat(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let exprs: Vec<Expression> = expr.try_into()?;
let evaled_exprs: Vec<String> = exprs
.iter()
.map(|e| eval(env, e.to_owned())?.try_into())
.collect::<Result<_, _>>()?;
Ok(evaled_exprs.concat().into())
}
pub fn prelude_map(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [f, list]: [Expression; 2] = expr.try_into()?;
let f = eval(env, f)?;
let list: Vec<Expression> = eval(env, list)?.try_into()?;
let list: Vec<Expression> = list
.iter()
.map(|e| {
eval(
env,
Expression::Cell(
Box::new(f.clone()),
Box::new(Expression::Cell(
Box::new(e.to_owned()),
Box::new(Expression::Nil),
)),
),
)
})
.collect::<Result<_, _>>()?;
Ok(list.into())
}
pub fn prelude_to_string(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [e] = expr.try_into()?;
Ok(Expression::String(format!("{}", eval(env, e)?)))
}
pub fn prelude_load(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [expr] = expr.try_into()?;
let lisp_string: String = eval(env, expr)?.try_into()?;
let mut last_result = Expression::Nil;
for expr in ExpressionStream::from_char_stream(lisp_string.chars())
.collect::<Result<Vec<Expression>, ParserError>>()?
{
last_result = eval(env, expr)?;
}
Ok(last_result)
}
pub fn prelude_include(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [expr] = expr.try_into()?;
let lisp_file: String = eval(env, expr)?.try_into()?;
println!("From File: {}", env.get("FILE").unwrap());
// Try to resolve as relative to FILE
let resolved_lisp_file: PathBuf = PathBuf::from(
env.get("FILE")
.map(|x| x.try_into())
.unwrap_or(Ok(String::new()))?,
)
.canonicalize()
.map_err(|x| EvalError::RuntimeError(x.to_string()))?
.parent()
.ok_or(EvalError::RuntimeError(
"Could not get parent of current file.".to_string(),
))?
.join(&lisp_file);
println!("Loading {}", resolved_lisp_file.display());
let lisp_string = std::fs::read_to_string(&resolved_lisp_file)
.map_err(|e| EvalError::RuntimeError(e.to_string()))?;
// Use enviroment for resolved file or fallback to the lisp_file argument
let mut env = env.mk_inner();
env.set(
"FILE".to_string(),
resolved_lisp_file
.to_str()
.unwrap_or(&lisp_file)
.to_string()
.into(),
);
prelude_load(&env, [lisp_string.into()].into())
}
pub fn mk_prelude(layer: &mut EnvironmentLayer) { pub fn mk_prelude(layer: &mut EnvironmentLayer) {
layer.set("+".to_string(), Expression::Function(prelude_add)); layer.set("+".to_string(), Expression::Function(prelude_add));
layer.set("-".to_string(), Expression::Function(prelude_sub)); layer.set("-".to_string(), Expression::Function(prelude_sub));
@@ -270,10 +403,21 @@ pub fn mk_prelude(layer: &mut EnvironmentLayer) {
layer.set("not".to_string(), Expression::Function(prelude_not)); layer.set("not".to_string(), Expression::Function(prelude_not));
layer.set("let".to_string(), Expression::Function(prelude_let)); layer.set("let".to_string(), Expression::Function(prelude_let));
layer.set("set".to_string(), Expression::Function(prelude_set)); layer.set("set".to_string(), Expression::Function(prelude_set));
layer.set("println".to_string(), Expression::Function(prelude_println));
layer.set("print".to_string(), Expression::Function(prelude_print)); layer.set("print".to_string(), Expression::Function(prelude_print));
layer.set("cons".to_string(), Expression::Function(prelude_cons)); layer.set("cons".to_string(), Expression::Function(prelude_cons));
layer.set("car".to_string(), Expression::Function(prelude_car)); layer.set("car".to_string(), Expression::Function(prelude_car));
layer.set("cdr".to_string(), Expression::Function(prelude_cdr)); layer.set("cdr".to_string(), Expression::Function(prelude_cdr));
layer.set("eval".to_string(), Expression::Function(prelude_eval)); layer.set("eval".to_string(), Expression::Function(prelude_eval));
layer.set("progn".to_string(), Expression::Function(prelude_progn)); layer.set("progn".to_string(), Expression::Function(prelude_progn));
layer.set("list".to_string(), Expression::Function(prelude_list));
layer.set("append".to_string(), Expression::Function(prelude_append));
layer.set("concat".to_string(), Expression::Function(prelude_concat));
layer.set("map".to_string(), Expression::Function(prelude_map));
layer.set(
"to-string".to_string(),
Expression::Function(prelude_to_string),
);
layer.set("load".to_string(), Expression::Function(prelude_load));
layer.set("include".to_string(), Expression::Function(prelude_include));
} }
@@ -3,6 +3,7 @@ use super::tokenizer::tokenize;
use super::tokenizer::TokenStream; use super::tokenizer::TokenStream;
use super::tokenizer::TokenizerError; use super::tokenizer::TokenizerError;
use crate::lisp::Expression; use crate::lisp::Expression;
use std::fmt::Display;
use std::iter::Peekable; use std::iter::Peekable;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -10,6 +11,7 @@ pub enum ParserError {
UnexpectedToken(Token), UnexpectedToken(Token),
TokenizerError(TokenizerError), TokenizerError(TokenizerError),
UnexpectedEndOfInput, UnexpectedEndOfInput,
RuntimeError(String),
} }
impl From<TokenizerError> for ParserError { impl From<TokenizerError> for ParserError {
@@ -18,6 +20,17 @@ impl From<TokenizerError> for ParserError {
} }
} }
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ParserError::TokenizerError(t) => write!(f, "Tokenizer Error: {}", t),
ParserError::UnexpectedToken(t) => write!(f, "Unexpecte Token: {}", t),
ParserError::UnexpectedEndOfInput => write!(f, "Unexpected end of input."),
ParserError::RuntimeError(s) => write!(f, "{}", s),
}
}
}
fn parse_list<I>(stream: &mut Peekable<TokenStream<I>>) -> Result<Expression, ParserError> fn parse_list<I>(stream: &mut Peekable<TokenStream<I>>) -> Result<Expression, ParserError>
where where
I: Iterator<Item = char>, I: Iterator<Item = char>,
+33
View File
@@ -0,0 +1,33 @@
use std::fmt::Display;
#[derive(Debug, PartialEq, Clone)]
/// Sum type of different tokens
pub enum Token {
FloatLiteral(f64),
IntLiteral(i64),
Dot,
Nil,
ParClose,
ParOpen,
Quote,
StringLiteral(String),
Symbol(String),
True,
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::FloatLiteral(x) => write!(f, "{}", x),
Token::IntLiteral(x) => write!(f, "{}", x),
Token::Dot => write!(f, "."),
Token::Nil => write!(f, "nil"),
Token::ParClose => write!(f, ")"),
Token::ParOpen => write!(f, "("),
Token::Quote => write!(f, "'"),
Token::StringLiteral(x) => write!(f, "\"{}\"", x),
Token::Symbol(x) => write!(f, "{}", x),
Token::True => write!(f, "true"),
}
}
}
@@ -1,3 +1,5 @@
use std::fmt::Display;
use super::token::Token; use super::token::Token;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -7,6 +9,14 @@ pub enum TokenizerError {
UnmatchedSequence(String), UnmatchedSequence(String),
} }
impl Display for TokenizerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenizerError::UnmatchedSequence(s) => write!(f, "Unmatched sequence: {}", s),
}
}
}
/// A reader used to wrap the `TokenStream`. /// A reader used to wrap the `TokenStream`.
/// When reading, it starts with the staging buffer of the stream, once /// When reading, it starts with the staging buffer of the stream, once
/// it's end is reached, the input stream is copied character wise to /// it's end is reached, the input stream is copied character wise to
@@ -294,7 +304,9 @@ where
let mut buf = String::new(); let mut buf = String::new();
while let Some(c) = reader.next() { while let Some(c) = reader.next() {
if c.is_ascii_digit() { if buf.is_empty() && c == '-' {
buf.push(c);
} else if c.is_ascii_digit() {
buf.push(c); buf.push(c);
} else { } else {
reader.step_back(1); reader.step_back(1);
@@ -317,7 +329,9 @@ where
let mut has_dot = false; let mut has_dot = false;
while let Some(c) = reader.next() { while let Some(c) = reader.next() {
if c.is_ascii_digit() { if buf.is_empty() && c == '-' {
buf.push(c);
} else if c.is_ascii_digit() {
buf.push(c); buf.push(c);
} else if c == '.' && !has_dot { } else if c == '.' && !has_dot {
buf.push(c); buf.push(c);
@@ -337,11 +351,12 @@ where
#[test] #[test]
fn test_tokenize() { fn test_tokenize() {
let test_str = "(\"abcdefg( )123\" )(\n\t 'nil true \"true\")00987463 123.125 . 0+-*/go="; let test_str =
"(\"abcdefg( )123\" )(\n\t 'nil true \"true\")00987463 123.125 -20 -3.14 . 0+-*/go=";
let result: Vec<_> = tokenize(&mut test_str.chars()).collect(); let result: Vec<_> = tokenize(&mut test_str.chars()).collect();
assert_eq!(result.len(), 13); assert_eq!(result.len(), 15);
assert_eq!(result[0].clone().unwrap(), Token::ParOpen); assert_eq!(result[0].clone().unwrap(), Token::ParOpen);
assert_eq!( assert_eq!(
result[1].clone().unwrap(), result[1].clone().unwrap(),
@@ -359,9 +374,11 @@ fn test_tokenize() {
assert_eq!(result[8].clone().unwrap(), Token::ParClose); assert_eq!(result[8].clone().unwrap(), Token::ParClose);
assert_eq!(result[9].clone().unwrap(), Token::IntLiteral(987463)); assert_eq!(result[9].clone().unwrap(), Token::IntLiteral(987463));
assert_eq!(result[10].clone().unwrap(), Token::FloatLiteral(123.125)); assert_eq!(result[10].clone().unwrap(), Token::FloatLiteral(123.125));
assert_eq!(result[11].clone().unwrap(), Token::Dot); assert_eq!(result[11].clone().unwrap(), Token::IntLiteral(-20));
assert_eq!(result[12].clone().unwrap(), Token::FloatLiteral(-3.14));
assert_eq!(result[13].clone().unwrap(), Token::Dot);
assert_eq!( assert_eq!(
result[12].clone().unwrap(), result[14].clone().unwrap(),
Token::Symbol("0+-*/go=".to_string()) Token::Symbol("0+-*/go=".to_string())
); );
} }
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "lispers-macro"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.106"
quote = "1.0.45"
syn = "2.0.117"
+193
View File
@@ -0,0 +1,193 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, PatType, Token};
enum FlagOrKV {
Flag(Ident),
KV(Ident, Ident),
}
impl syn::parse::Parse for FlagOrKV {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let value: Ident = input.parse()?;
Ok(FlagOrKV::KV(ident, value))
} else {
Ok(FlagOrKV::Flag(ident))
}
}
}
struct NativeLispAttrs {
pub eval: bool,
pub fname: Option<Ident>,
}
impl syn::parse::Parse for NativeLispAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let exprs = Punctuated::<FlagOrKV, Token![,]>::parse_terminated(input)?;
let mut ret = NativeLispAttrs {
eval: false,
fname: None,
};
for e in exprs {
match e {
FlagOrKV::Flag(flag) => {
if flag.to_string() == "eval" {
ret.eval = true;
} else {
return Err(syn::Error::new_spanned(flag, "Unknown flag"));
}
}
FlagOrKV::KV(k, v) => {
if k.to_string() == "fname" {
ret.fname = Some(v);
} else {
return Err(syn::Error::new_spanned(k, "Unknown key"));
}
}
}
}
Ok(ret)
}
}
struct NativeLispProxyAttrs {
pub eval: bool,
pub fname: Ident,
pub dispatcher: Vec<Ident>,
}
impl syn::parse::Parse for NativeLispProxyAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let exprs = Punctuated::<FlagOrKV, Token![,]>::parse_terminated(input)?;
let mut ret = NativeLispProxyAttrs {
eval: false,
fname: Ident::new("proxy", proc_macro2::Span::call_site()),
dispatcher: Vec::new(),
};
for e in exprs {
match e {
FlagOrKV::Flag(flag) => {
if flag.to_string() == "eval" {
ret.eval = true;
} else {
return Err(syn::Error::new_spanned(flag, "Unknown flag"));
}
}
FlagOrKV::KV(k, v) => {
if k.to_string() == "dispatch" {
ret.dispatcher.push(v);
} else if k.to_string() == "fname" {
ret.fname = v;
} else {
return Err(syn::Error::new_spanned(k, "Unknown key"));
}
}
}
}
Ok(ret)
}
}
#[proc_macro_attribute]
pub fn native_lisp_function(attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse function
let input = parse_macro_input!(item as ItemFn);
let vis = &input.vis;
let sig = &input.sig;
let func_name = &sig.ident;
let block = &input.block;
let ret = &sig.output;
// Parse attrs
let attr = parse_macro_input!(attr as NativeLispAttrs);
// Extract argument conversion statements
let mut conversion_statements = Vec::new();
for arg in &sig.inputs {
if let FnArg::Typed(PatType { pat, ty, .. }) = arg {
if let Pat::Ident(ident) = pat.as_ref() {
let arg_name_str = ident.ident.to_string();
if attr.eval {
conversion_statements.push(quote! {
let #ident: #ty = eval(env, args_iter.next().ok_or(EvalError::ArgumentError(format!("Missing Argument {}", #arg_name_str)))?)?.try_into()?;
});
} else {
conversion_statements.push(quote! {
let #ident: #ty = args_iter.next().ok_or(EvalError::ArgumentError(format!("Missing Argument {}", #arg_name_str)))?.try_into()?;
});
}
}
}
}
let func_name = match attr.fname {
Some(fname) => fname,
None => func_name.clone(),
};
quote! {
#vis fn #func_name(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let args: Vec<Expression> = expr.try_into()?;
let mut args_iter = args.into_iter();
#(#conversion_statements)*
Ok((|| #ret #block)()?.into())
}
}
.into()
}
#[proc_macro]
pub fn native_lisp_function_proxy(item: TokenStream) -> TokenStream {
let args = parse_macro_input!(item as NativeLispProxyAttrs);
let fname = &args.fname;
let eval_statement = if args.eval {
quote! {
let exprs: Vec<Expression> = expr.try_into()?;
let exprs = exprs.into_iter().map(|expr| eval(env, expr)).collect::<Result<Vec<Expression>, EvalError>>()?;
let expr: Expression = exprs.into();
}
} else {
quote! {}
};
let try_apply_statements = args
.dispatcher
.iter()
.map(|impl_name| {
quote! {
match #impl_name(env, expr.clone()) {
Err(EvalError::ArgumentError(e)) => {/*Pass*/},
Err(EvalError::TypeError(e)) => {/*Pass*/},
x => return x,
}
}
})
.collect::<Vec<_>>();
let fname_str = fname.to_string();
quote! {
fn #fname(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
#eval_statement
#(#try_apply_statements)*
Err(EvalError::TypeError(format!("Could not call {} with arguments {} ", #fname_str, expr).to_string()))
}
}
.into()
}
+1 -1
View File
@@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "1.80.1" channel = "1.93.1"
components = [ "rustfmt", "rustc-dev", "rust-analyzer", "rust-src"] components = [ "rustfmt", "rustc-dev", "rust-analyzer", "rust-src"]
+79
View File
@@ -0,0 +1,79 @@
(set 'red
(material
(color 1 0 0)
(color 1 0 0)
(color 0.5 0 0)
50 0.25))
(set 'blue
(material
(color 0 0 1)
(color 0 0 1)
(color 0 0 0.6)
50 0.25))
(set 'green
(material
(color 0 1 0)
(color 0 1 0)
(color 0 0.6 0)
50 0.25))
(set 'white
(material
(color 1 1 1)
(color 1 1 1)
(color 0.6 0.6 0.6)
100 0.5))
(set 'black
(material
(color 0 0 0)
(color 0 0 0)
(color 0.6 0.6 0.6)
100 0.5))
(set 's1
(sphere
(point 0 1 0) 1 blue))
(set 's2
(sphere
(point 2 0.5 2) 0.5 green))
(defun spiral-sphere (i n)
(sphere
(progn
(print "Spiral Sphere at: ")
(println (point
(* 2 (cos (/ (* i 6.2) n)))
0.5
(* 2 (sin (/ (* i 6.2) n)))))
)
0.5 red))
(defun spiral (scn i n)
(if (< i n)
(scene-add
(spiral scn (+ i 1) n)
(spiral-sphere i n))
scn))
(set 'p1
(checkerboard
(point 0 0 0)
(vector 0 1 0)
black white 0.5
(vector 0.5 0 1)))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(set 'scn (scene
(color 0.1 0.1 0.1)
'(s1 s2 p1)
'(l1 l2)))
(set 'scn (spiral scn 0.0 10.0))
(println (cons "Final Scene:" scn))
(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))
(render cam scn 5 4 "demo-1.png")
+67
View File
@@ -0,0 +1,67 @@
(include "./materials.lisp")
(set 's1
(sphere
(point 0 1 0) 1 blue))
(set 's2
(sphere
(point 2 0.5 2) 0.5 green))
(set 'mirror-dome
(sphere
(point 0 -17 0)
30 dark-mirror))
(defun spiral-sphere (i n t)
(sphere
(progn
(point
(* 2 (cos (/ (* i 6.2) n)))
(+ 0.5 (* 0.3 (cos (+ (/ (* i 6.2) n) (/ t 5.0)))))
(* 2 (sin (/ (* i 6.2) n))))
)
0.2 red))
(defun spiral (scn i n t)
(if (< i n)
(scene-add
(spiral scn (+ i 1) n t)
(spiral-sphere i n t))
scn))
(set 'p1
(checkerboard
(point 0 0 0)
(vector 0 1 0)
black white 0.5
(vector 0.5 0 1)))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(set 'scn-base (scene
(color 0.1 0.1 0.1)
'(s1 s2 p1 mirror-dome)
'(l1 l2)))
(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))
(defun scene-fn (t)
(spiral scn-base 0 30 t))
(defun cam-fn (t c)
(let '((pos . (point -3 0.5 8))
(cnt . (point 0 0 0))
(to . (point -3 0.5 -8))
(up . (vector 0 1 0))
(fovy . 80)
(pct . (/ t 300.0)))
(let '((tpos . (+ pos (* (- to pos) pct)))
(tfovy . (+ fovy (* 40 pct)))
)
(camera-reposition c tpos cnt up tfovy)
)
))
(render-animation cam "demo-animation.mp4" scene-fn cam-fn 400 30 7 2)
+69
View File
@@ -0,0 +1,69 @@
(set 'red
(material
(color 1 0 0)
(color 1 0 0)
(color 0.5 0 0)
50 0.25))
(set 'blue
(material
(color 0 0 1)
(color 0 0 1)
(color 0 0 0.6)
50 0.25))
(set 'green
(material
(color 0 1 0)
(color 0 1 0)
(color 0 0.6 0)
50 0.25))
(set 'mandelbrot-red
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
))
(set 'mandelbrot-blue
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0 0 0.3)
(color 0 0 0.3)
(color 0 0 0.3)
))
(set 's1
(sphere
(point 0 1 0) 1 blue))
(set 's2
(sphere
(point 2 0.5 2) 0.5 green))
(set 'p1
(texture-plane
mandelbrot-red
(point 0 0 0)
(vector 0 1 0)
1.0
(vector 1 0 0)))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(set 'scn (scene
(color 0.1 0.1 0.1)
'(s1 s2 p1)
'(l1 l2)))
(println (cons "Final Scene:" scn))
(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))
(render cam scn 5 4 "demo-3.png")
+47
View File
@@ -0,0 +1,47 @@
(include "./materials.lisp")
(set 'frames 300)
(set 'fps 30)
(set 'ray-depth 5)
(set 'sub-pixel 2)
(set 'mz (mandel-zoom frames 1800 5000))
(defun mandel-plane (t)
(texture-plane
(mz t)
(point 0 0 0)
(vector 0 1 0)
1.0
(vector 1 0 0)
))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(defun scn (t)
(scene
(color 0.1 0.1 0.1)
'((mandel-plane t))
'(l1 l2)
))
(defun cam (t c) c)
(set 'base-cam
(camera
(point 0 3 6)
(point 0 0 0)
(vector 0 1 0)
40 1920 1080))
(render-animation
base-cam
"demo-4-animation.mp4"
scn
cam
frames
fps
ray-depth
sub-pixel)
+66
View File
@@ -0,0 +1,66 @@
(set 'red
(material
(color 1 0 0)
(color 1 0 0)
(color 0.5 0 0)
50 0.25))
(set 'blue
(material
(color 0 0 1)
(color 0 0 1)
(color 0 0 0.6)
50 0.25))
(set 'green
(material
(color 0 1 0)
(color 0 1 0)
(color 0 0.6 0)
50 0.25))
(set 'white
(material
(color 1 1 1)
(color 1 1 1)
(color 0.6 0.6 0.6)
100 0.4))
(set 'black
(material
(color 0 0 0)
(color 0 0 0)
(color 0.6 0.6 0.6)
100 0.4))
(set 'dark-mirror
(material
(color 0.01 0.05 0.15)
(color 0.01 0.05 0.15)
(color 0.01 0.05 0.15)
20 0.7))
(set 'mandelbrot-red
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
))
(defun mandel-zoom (n z1 z2)
(let '((n . n)
(z1 . z1)
(z2 . z2))
(lambda (t)
(let '((pct . (/ t (* n 1.0)))
(range . (- z2 z1)))
(mandelbrot-texture
(+ z1 (* pct range))
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
)
)))
)
+14 -4
View File
@@ -1,5 +1,5 @@
use lispers::lisp::{eval, Environment}; use lispers_core::lisp::{eval, Environment};
use lispers::parser::ExpressionStream; use lispers_core::parser::ExpressionStream;
fn main() { fn main() {
let programs = [ let programs = [
@@ -8,13 +8,20 @@ fn main() {
"(print myvar) (print 'myvar)", "(print myvar) (print 'myvar)",
"(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)", "(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)",
"(eval (car (cons 'myvar 'b)))", "(eval (car (cons 'myvar 'b)))",
"(defun test (i) (progn (println i) (* 2 i)))",
"(test 10)",
"(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))", "(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))",
"pow", "pow",
"(pow 2 10)", "(pow 2 10)",
"(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))", "(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))",
"(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))", "(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))",
"(do-n-times (lambda () (print 'hello)) 5)", "(do-n-times (lambda () (println 'hello)) 5)",
"(progn (print 'hello) (print 'world))", "(progn (print 'hello) (print 'world))",
"(load \"(defun loaded-foo (x) (+ x 1))\")",
"(loaded-foo 1)",
"(defun bind-test (x) (let '((x . x)) (lambda (y) (+ x y))))",
"(set 'add10 (bind-test 10))",
"(add10 10)",
]; ];
let environment = Environment::default(); let environment = Environment::default();
@@ -29,7 +36,10 @@ fn main() {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }
+3 -3
View File
@@ -1,7 +1,7 @@
use lispers::lisp::Expression; use lispers_core::lisp::Expression;
use lispers::parser::ParserError; use lispers_core::parser::ParserError;
use lispers::{lisp, parser}; use lispers_core::{lisp, parser};
use std::io::Write; use std::io::Write;
fn main() { fn main() {
+9 -10
View File
@@ -3,10 +3,9 @@ use lispers::raytracer::{
plane::Checkerboard, plane::Checkerboard,
scene::Scene, scene::Scene,
sphere::Sphere, sphere::Sphere,
types::{Color, Light, Material, Point3, Vector3}, types::{Color, Light, Material, Point3, RTObjectWrapper, Vector3},
}; };
extern crate nalgebra as na; extern crate nalgebra as na;
use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
fn main() { fn main() {
@@ -23,7 +22,7 @@ fn main() {
color: Color::new(1.0, 1.0, 1.0), color: Color::new(1.0, 1.0, 1.0),
}); });
scene.add_object(Arc::new(Checkerboard::new( scene.add_object(RTObjectWrapper::new(Box::new(Checkerboard::new(
Point3::new(0.0, -1.0, 0.0), Point3::new(0.0, -1.0, 0.0),
Vector3::new(0.0, 1.0, 0.0), Vector3::new(0.0, 1.0, 0.0),
Material::new( Material::new(
@@ -42,9 +41,9 @@ fn main() {
), ),
0.3, 0.3,
Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.0, 0.0, 1.0),
))); ))));
scene.add_object(Arc::new(Sphere::new( scene.add_object(RTObjectWrapper::new(Box::new(Sphere::new(
Point3::new(-2.0, 0.0, 1.0), Point3::new(-2.0, 0.0, 1.0),
1.0, 1.0,
Material::new( Material::new(
@@ -54,9 +53,9 @@ fn main() {
20.0, 20.0,
0.3, 0.3,
), ),
))); ))));
scene.add_object(Arc::new(Sphere::new( scene.add_object(RTObjectWrapper::new(Box::new(Sphere::new(
Point3::new(0.2, -0.5, -0.2), Point3::new(0.2, -0.5, -0.2),
0.5, 0.5,
Material::new( Material::new(
@@ -66,9 +65,9 @@ fn main() {
20.0, 20.0,
0.3, 0.3,
), ),
))); ))));
scene.add_object(Arc::new(Sphere::new( scene.add_object(RTObjectWrapper::new(Box::new(Sphere::new(
Point3::new(-0.5, 0.5, -2.0), Point3::new(-0.5, 0.5, -2.0),
1.5, 1.5,
Material::new( Material::new(
@@ -78,7 +77,7 @@ fn main() {
20.0, 20.0,
0.3, 0.3,
), ),
))); ))));
let camera = Camera::new( let camera = Camera::new(
Point3::new(0.0, 0.7, 5.0), Point3::new(0.0, 0.7, 5.0),
+45
View File
@@ -0,0 +1,45 @@
use std::env;
use lispers::raytracer::lisp::mk_raytrace;
use lispers_core::lisp::environment::EnvironmentLayer;
use lispers_core::lisp::prelude::mk_prelude;
use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream;
fn main() {
let program_paths: Vec<_> = env::args().skip(1).collect();
let programs: Vec<_> = program_paths
.iter()
.map(|path| std::fs::read_to_string(path).unwrap())
.collect();
let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer);
mk_raytrace(&mut layer);
let mut environment = Environment::from_layer(layer);
for (program, path) in programs.iter().zip(program_paths) {
environment.set("FILE".to_string(), path.clone().into());
for (i, r) in ExpressionStream::from_char_stream(program.chars()).enumerate() {
match r {
Err(err) => {
println!(
"ParserError in File {} Expression {}: {:?}",
path,
i + 1,
err
);
break;
}
Ok(expr) => match eval(&environment, expr) {
Ok(_) => {}
Err(e) => println!("Error evaluating Expression {}: {}", i + 1, e),
},
}
}
}
println!("Interpreter Done!");
}
+78
View File
@@ -0,0 +1,78 @@
use std::collections::HashMap;
use std::path::Path;
use lispers::raytracer::lisp::mk_raytrace;
use lispers_core::lisp::environment::EnvironmentLayer;
use lispers_core::lisp::prelude::mk_prelude;
use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream;
const SCENES_DIR: &str = env!("SCENES_DIR");
fn main() {
println!("Loading scenes from directory: {}", SCENES_DIR);
let mut scenes = HashMap::new();
for e in std::fs::read_dir(Path::new(SCENES_DIR)).expect("Failed to read scenes directory") {
let e = e.expect("Failed to read scene file");
let t = e.file_type().expect("Failed to read scene file type");
let n = e
.file_name()
.into_string()
.expect("Failed to read scene file name");
if t.is_file() && n.starts_with("demo-") && n.ends_with(".lisp") {
scenes.insert(n, e);
}
}
let args: Vec<_> = std::env::args().collect();
if args.len() != 2 {
println!("Usage: {} <scene-file.lisp>", args[0]);
println!("Available scene files:");
for name in scenes.keys() {
println!(" {}", name);
}
return;
}
let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer);
mk_raytrace(&mut layer);
let mut environment = Environment::from_layer(layer);
environment.set(
"FILE".to_string(),
scenes
.get(&args[1])
.expect("Scene file not found")
.path()
.display()
.to_string()
.into(),
);
for r in ExpressionStream::from_char_stream(
std::fs::read_to_string(scenes.get(&args[1]).expect("Scene file not found").path())
.expect("Failed to read scene file")
.chars(),
) {
match r {
Err(err) => {
println!("ParserError: {:?}", err);
return;
}
Ok(expr) => {
println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) {
Ok(e) => println!("=> {}", e),
Err(e) => {
println!("Error: {}", e);
return;
}
}
}
}
}
println!("Interpreter Done!");
}
-2
View File
@@ -1,3 +1 @@
pub mod lisp;
pub mod parser;
pub mod raytracer; pub mod raytracer;
-14
View File
@@ -1,14 +0,0 @@
#[derive(Debug, PartialEq, Clone)]
/// Sum type of different tokens
pub enum Token {
FloatLiteral(f64),
IntLiteral(i64),
Dot,
Nil,
ParClose,
ParOpen,
Quote,
StringLiteral(String),
Symbol(String),
True,
}
+73
View File
@@ -1,11 +1,18 @@
use std::{fmt::Display, path::Path};
use super::{ use super::{
scene::Scene, scene::Scene,
types::{Color, Point3, Ray, Scalar, Vector3}, types::{Color, Point3, Ray, Scalar, Vector3},
RTError,
}; };
use image::RgbImage; use image::RgbImage;
use lispers_core::lisp::eval::EvalError;
use ndarray::Array3;
use rayon::prelude::*; use rayon::prelude::*;
use video_rs::{encode::Settings, Encoder, Time};
/// A camera that can render a scene. /// A camera that can render a scene.
#[derive(Clone, PartialEq, Debug)]
pub struct Camera { pub struct Camera {
/// Position of the camera's eye. /// Position of the camera's eye.
position: Point3, position: Point3,
@@ -105,4 +112,70 @@ impl Camera {
}); });
img img
} }
pub fn reposition(
&self,
position: Point3,
center: Point3,
up: Vector3,
fovy: Scalar,
) -> Camera {
Camera::new(position, center, up, fovy, self.width, self.height)
}
pub fn render_animation<
SFn: Fn(u32) -> Result<Scene, EvalError>,
CFn: Fn(u32, &Camera) -> Result<Camera, EvalError>,
>(
&self,
path: &Path,
scene_fn: SFn,
update_cam: CFn,
frames: u32,
fps: u32,
depth: u32,
subp: u32,
) -> Result<(), RTError> {
let mut encoder = Encoder::new(
path,
Settings::preset_h264_yuv420p(self.width, self.height, false),
)?;
let frame_duration = Time::from_nth_of_a_second(fps as usize);
let mut timestamp = Time::zero();
let mut cam = self.to_owned();
for t in 0..frames {
println!(
"Rendering frame {}/{} for {}",
t + 1,
frames,
path.display()
);
cam = update_cam(t, &cam)?;
let img = cam.render(&scene_fn(t)?, depth, subp);
let frame = Array3::from_shape_fn((self.height, self.width, 3), |(y, x, c)| {
img.get_pixel(x as u32, y as u32)[c]
});
encoder.encode(&frame, timestamp)?;
timestamp = timestamp.aligned_with(frame_duration).add();
}
encoder.finish()?;
Ok(())
}
}
impl Display for Camera {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Camera {{ position: {}, lower_left: {}, x_dir: {}, y_dir: {}, width: {}, height: {} }}",
self.position, self.lower_left, self.x_dir, self.y_dir, self.width, self.height)
}
}
impl PartialOrd for Camera {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
} }
+583
View File
@@ -0,0 +1,583 @@
use std::path::PathBuf;
use crate::raytracer::{
scene::Scene,
sphere::TextureSphere,
texture::TextureWrapper,
types::{Light, Point2},
};
use lispers_macro::{native_lisp_function, native_lisp_function_proxy};
use lispers_core::lisp::{
environment::EnvironmentLayer,
eval::{eval, EvalError},
expression::ForeignDataWrapper,
Environment, Expression,
};
use super::{
camera::Camera,
plane::{Checkerboard, Plane, TexturePlane},
sphere::Sphere,
texture::MandelbrotTexture,
types::{Color, Material, Point3, RTObjectWrapper, Vector3},
RTError,
};
#[native_lisp_function(eval)]
pub fn point(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(Point3::new(x, y, z)))
}
#[native_lisp_function(eval)]
pub fn point2(x: f64, y: f64) -> Result<ForeignDataWrapper<Point2>, EvalError> {
Ok(ForeignDataWrapper::new(Point2::new(x, y)))
}
#[native_lisp_function(eval)]
pub fn vector(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(Vector3::new(x, y, z)))
}
#[native_lisp_function(eval)]
pub fn color(r: f64, g: f64, b: f64) -> Result<ForeignDataWrapper<Color>, EvalError> {
Ok(ForeignDataWrapper::new(Color::new(r, g, b)))
}
#[native_lisp_function(eval)]
pub fn light(
pos: ForeignDataWrapper<Point3>,
col: ForeignDataWrapper<Color>,
) -> Result<ForeignDataWrapper<Light>, EvalError> {
Ok(ForeignDataWrapper::new(Light::new(*pos, *col)))
}
#[native_lisp_function(eval)]
pub fn material(
amb: ForeignDataWrapper<Color>,
dif: ForeignDataWrapper<Color>,
spe: ForeignDataWrapper<Color>,
shi: f64,
mir: f64,
) -> Result<ForeignDataWrapper<Material>, EvalError> {
Ok(ForeignDataWrapper::new(Material::new(
*amb, *dif, *spe, shi, mir,
)))
}
#[native_lisp_function(eval)]
pub fn sphere(
pos: ForeignDataWrapper<Point3>,
rad: f64,
mat: ForeignDataWrapper<Material>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into())
}
#[native_lisp_function(eval)]
pub fn texture_sphere(
pos: ForeignDataWrapper<Point3>,
rad: f64,
tex: ForeignDataWrapper<TextureWrapper>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(
ForeignDataWrapper::new(RTObjectWrapper::from(TextureSphere::new(
*pos,
rad,
tex.clone(),
)))
.into(),
)
}
#[native_lisp_function(eval)]
pub fn plane(
pos: ForeignDataWrapper<Point3>,
dir: ForeignDataWrapper<Vector3>,
mat: ForeignDataWrapper<Material>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Plane::new(*pos, *dir, *mat))).into())
}
#[native_lisp_function(eval)]
pub fn checkerboard(
pos: ForeignDataWrapper<Point3>,
norm: ForeignDataWrapper<Vector3>,
mat1: ForeignDataWrapper<Material>,
mat2: ForeignDataWrapper<Material>,
sca: f64,
up: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(
ForeignDataWrapper::new(RTObjectWrapper::from(Checkerboard::new(
*pos, *norm, *mat1, *mat2, sca, *up,
)))
.into(),
)
}
#[native_lisp_function(eval)]
pub fn texture_plane(
texture: ForeignDataWrapper<TextureWrapper>,
pos: ForeignDataWrapper<Point3>,
norm: ForeignDataWrapper<Vector3>,
sca: f64,
up: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(
ForeignDataWrapper::new(RTObjectWrapper::from(TexturePlane::new(
*pos,
*norm,
texture.clone(),
sca,
*up,
)))
.into(),
)
}
#[native_lisp_function(eval)]
pub fn mandelbrot_texture(
scale: f64,
at: ForeignDataWrapper<Point2>,
max_iter: i64,
ambient_color: ForeignDataWrapper<Color>,
diffuse_color: ForeignDataWrapper<Color>,
specular_color: ForeignDataWrapper<Color>,
) -> Result<ForeignDataWrapper<TextureWrapper>, EvalError> {
Ok(ForeignDataWrapper::new(TextureWrapper::new(
MandelbrotTexture::new(
scale,
*at,
max_iter as u32,
*ambient_color,
*diffuse_color,
*specular_color,
),
)))
}
pub fn scene(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [amb, objs, lgts]: [Expression; 3] = expr.try_into()?;
let amb: ForeignDataWrapper<Color> = eval(env, amb)?.try_into()?;
let objs: Vec<Expression> = eval(env, objs)?.try_into()?;
let lgts: Vec<Expression> = eval(env, lgts)?.try_into()?;
let mut scene = Scene::new();
scene.set_ambient(*amb);
for o in objs {
let o: ForeignDataWrapper<RTObjectWrapper> = eval(env, o)?.try_into()?;
scene.add_object(o.clone());
}
for l in lgts {
let l: ForeignDataWrapper<Light> = eval(env, l)?.try_into()?;
scene.add_light(*l);
}
Ok(ForeignDataWrapper::new(scene).into())
}
#[native_lisp_function]
pub fn scene_add_object(
mut sce: ForeignDataWrapper<Scene>,
obj: ForeignDataWrapper<RTObjectWrapper>,
) -> Result<ForeignDataWrapper<Scene>, EvalError> {
sce.add_object(obj.clone());
Ok(sce)
}
#[native_lisp_function]
pub fn scene_add_light(
mut sce: ForeignDataWrapper<Scene>,
lgt: ForeignDataWrapper<Light>,
) -> Result<ForeignDataWrapper<Scene>, EvalError> {
sce.add_light(*lgt);
Ok(sce)
}
native_lisp_function_proxy!(
fname = scene_add,
eval,
dispatch = scene_add_object,
dispatch = scene_add_light
);
#[native_lisp_function(eval)]
pub fn camera(
pos: ForeignDataWrapper<Point3>,
cnt: ForeignDataWrapper<Point3>,
up: ForeignDataWrapper<Vector3>,
fovy: f64,
w: i64,
h: i64,
) -> Result<ForeignDataWrapper<Camera>, EvalError> {
Ok(ForeignDataWrapper::new(Camera::new(
*pos, *cnt, *up, fovy, w as usize, h as usize,
)))
}
#[native_lisp_function(eval)]
pub fn camera_reposition(
cam: ForeignDataWrapper<Camera>,
pos: ForeignDataWrapper<Point3>,
cnt: ForeignDataWrapper<Point3>,
up: ForeignDataWrapper<Vector3>,
fovy: f64,
) -> Result<ForeignDataWrapper<Camera>, EvalError> {
Ok(ForeignDataWrapper::new(
cam.to_owned().reposition(*pos, *cnt, *up, fovy),
))
}
#[native_lisp_function(eval)]
pub fn render(
cam: ForeignDataWrapper<Camera>,
sce: ForeignDataWrapper<Scene>,
dpt: i64,
sbp: i64,
out: String,
) -> Result<Expression, EvalError> {
println!("Rendering to {}...", out);
let img = cam.render(&sce, dpt as u32, sbp as u32);
match img.save(out) {
Ok(_) => Ok(Expression::Nil),
Err(e) => Err(EvalError::RuntimeError(e.to_string())),
}
}
pub fn render_animation(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [cam, path, scene_fn, update_cam, frames, fps, depth, subp]: [Expression; 8] =
expr.try_into()?;
let cam: ForeignDataWrapper<Camera> = eval(env, cam)?.try_into()?;
let path: String = eval(env, path)?.try_into()?;
let frames: i64 = eval(env, frames)?.try_into()?;
let fps: i64 = eval(env, fps)?.try_into()?;
let depth: i64 = eval(env, depth)?.try_into()?;
let subp: i64 = eval(env, subp)?.try_into()?;
let sfn = |t: u32| -> Result<Scene, EvalError> {
let scene_fn_call: Expression = [scene_fn.clone(), (t as i64).into()].into();
let scn: ForeignDataWrapper<Scene> = eval(env, scene_fn_call)?.try_into()?;
Ok(scn.to_owned())
};
let ucm = |t: u32, c: &Camera| -> Result<Camera, EvalError> {
let c = ForeignDataWrapper::new(c.to_owned());
let update_cam_call: Expression = [update_cam.clone(), (t as i64).into(), c.into()].into();
let new_c: ForeignDataWrapper<Camera> = eval(env, update_cam_call)?.try_into()?;
Ok(new_c.to_owned())
};
let path: PathBuf = path.into();
match cam.render_animation(
&path,
sfn,
ucm,
frames as u32,
fps as u32,
depth as u32,
subp as u32,
) {
Ok(()) => Ok(Expression::Nil),
Err(RTError::EvalError(e)) => Err(e),
Err(RTError::FFMpegError(e)) => Err(EvalError::RuntimeError(e.to_string())),
}
}
#[native_lisp_function(eval)]
pub fn sin(x: f64) -> Result<f64, EvalError> {
Ok(x.sin())
}
#[native_lisp_function(eval)]
pub fn cos(x: f64) -> Result<f64, EvalError> {
Ok(x.cos())
}
#[native_lisp_function(eval)]
pub fn add_i(x: i64, y: i64) -> Result<i64, EvalError> {
Ok(x + y)
}
#[native_lisp_function(eval)]
pub fn add_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x + y)
}
#[native_lisp_function]
pub fn vadd_vv(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a + *b))
}
#[native_lisp_function]
pub fn vadd_vp(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b + *a))
}
#[native_lisp_function]
pub fn vadd_pv(
a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a + *b))
}
native_lisp_function_proxy!(
fname = add,
eval,
dispatch = add_i,
dispatch = add_f,
dispatch = vadd_vv,
dispatch = vadd_vp,
dispatch = vadd_pv
);
#[native_lisp_function(eval)]
pub fn sub_i(x: i64, y: i64) -> Result<i64, EvalError> {
Ok(x - y)
}
#[native_lisp_function(eval)]
pub fn sub_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x - y)
}
#[native_lisp_function]
pub fn sub_vv(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b))
}
#[native_lisp_function]
pub fn sub_vp(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b - *a))
}
#[native_lisp_function]
pub fn sub_pv(
a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b))
}
#[native_lisp_function]
pub fn sub_pp(
a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b))
}
native_lisp_function_proxy!(
fname = sub,
eval,
dispatch = sub_i,
dispatch = sub_f,
dispatch = sub_vv,
dispatch = sub_vp,
dispatch = sub_pv,
dispatch = sub_pp
);
#[native_lisp_function(eval)]
pub fn mul_i(x: i64, y: i64) -> Result<i64, EvalError> {
Ok(x * y)
}
#[native_lisp_function(eval)]
pub fn mul_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x * y)
}
#[native_lisp_function]
pub fn mul_vs(
a: ForeignDataWrapper<Vector3>,
b: f64,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a * b))
}
#[native_lisp_function]
pub fn mul_sv(
a: f64,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*b * a))
}
#[native_lisp_function]
pub fn mul_ps(
a: ForeignDataWrapper<Point3>,
b: f64,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a * b))
}
#[native_lisp_function]
pub fn mul_sp(
a: f64,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b * a))
}
native_lisp_function_proxy!(
fname = mul,
eval,
dispatch = mul_i,
dispatch = mul_f,
dispatch = mul_vs,
dispatch = mul_sv,
dispatch = mul_ps,
dispatch = mul_sp
);
#[native_lisp_function(eval)]
pub fn div_i(x: i64, y: i64) -> Result<f64, EvalError> {
Ok(x as f64 / y as f64)
}
#[native_lisp_function(eval)]
pub fn div_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x / y)
}
#[native_lisp_function]
pub fn div_vs(
a: ForeignDataWrapper<Vector3>,
b: f64,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a / b))
}
#[native_lisp_function]
pub fn div_sv(
a: f64,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*b / a))
}
#[native_lisp_function]
pub fn div_ps(
a: ForeignDataWrapper<Point3>,
b: f64,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a / b))
}
#[native_lisp_function]
pub fn div_sp(
a: f64,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b / a))
}
native_lisp_function_proxy!(
fname = div,
eval,
dispatch = div_i,
dispatch = div_f,
dispatch = div_vs,
dispatch = div_sv,
dispatch = div_ps,
dispatch = div_sp
);
#[native_lisp_function(eval)]
pub fn dot(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<f64, EvalError> {
Ok(a.dot(&b))
}
#[native_lisp_function]
pub fn abs_i(a: i64) -> Result<i64, EvalError> {
Ok(a.abs())
}
#[native_lisp_function]
pub fn abs_f(a: f64) -> Result<f64, EvalError> {
Ok(a.abs())
}
#[native_lisp_function]
pub fn abs_v(a: ForeignDataWrapper<Vector3>) -> Result<f64, EvalError> {
Ok(a.dot(&a).sqrt())
}
native_lisp_function_proxy!(
fname = abs,
eval,
dispatch = abs_i,
dispatch = abs_f,
dispatch = abs_v
);
/// Adds the raytracing functions to the given environment layer.
pub fn mk_raytrace(layer: &mut EnvironmentLayer) {
layer.set("point".to_string(), Expression::Function(point));
layer.set("point2".to_string(), Expression::Function(point2));
layer.set("vector".to_string(), Expression::Function(vector));
layer.set("color".to_string(), Expression::Function(color));
layer.set("light".to_string(), Expression::Function(light));
layer.set("material".to_string(), Expression::Function(material));
layer.set("plane".to_string(), Expression::Function(plane));
layer.set(
"checkerboard".to_string(),
Expression::Function(checkerboard),
);
layer.set(
"texture-plane".to_string(),
Expression::Function(texture_plane),
);
layer.set(
"mandelbrot-texture".to_string(),
Expression::Function(mandelbrot_texture),
);
layer.set("sphere".to_string(), Expression::Function(sphere));
layer.set(
"texture-sphere".to_string(),
Expression::Function(texture_sphere),
);
layer.set("scene".to_string(), Expression::Function(scene));
layer.set("scene-add".to_string(), Expression::Function(scene_add));
layer.set("camera".to_string(), Expression::Function(camera));
layer.set(
"camera-reposition".to_string(),
Expression::Function(camera_reposition),
);
layer.set("render".to_string(), Expression::Function(render));
layer.set(
"render-animation".to_string(),
Expression::Function(render_animation),
);
layer.set("sin".to_string(), Expression::Function(sin));
layer.set("cos".to_string(), Expression::Function(cos));
layer.set("+".to_string(), Expression::Function(add));
layer.set("-".to_string(), Expression::Function(sub));
layer.set("*".to_string(), Expression::Function(mul));
layer.set("/".to_string(), Expression::Function(div));
layer.set("dot".to_string(), Expression::Function(dot));
layer.set("abs".to_string(), Expression::Function(abs));
}
+20
View File
@@ -1,6 +1,26 @@
pub mod camera; pub mod camera;
pub mod lisp;
pub mod plane; pub mod plane;
pub mod scene; pub mod scene;
pub mod sphere; pub mod sphere;
mod texture;
pub mod types; pub mod types;
mod vec; mod vec;
#[derive(Debug, Clone)]
pub enum RTError {
EvalError(lispers_core::lisp::eval::EvalError),
FFMpegError(video_rs::Error),
}
impl From<lispers_core::lisp::eval::EvalError> for RTError {
fn from(value: lispers_core::lisp::eval::EvalError) -> Self {
RTError::EvalError(value)
}
}
impl From<video_rs::Error> for RTError {
fn from(value: video_rs::Error) -> Self {
RTError::FFMpegError(value)
}
}
+166 -19
View File
@@ -1,8 +1,12 @@
use super::types::{Intersect, Material, Point3, Scalar, Vector3}; use super::{
texture::TextureWrapper,
types::{Intersect, Material, Point3, Scalar, Vector3},
};
extern crate nalgebra as na; extern crate nalgebra as na;
/// An infinite plane in 3D space. /// An infinite plane in 3D space.
#[derive(PartialEq, Clone, Debug)]
pub struct Plane { pub struct Plane {
/// The position of the plane. /// The position of the plane.
position: Point3, position: Point3,
@@ -13,6 +17,7 @@ pub struct Plane {
} }
/// A infinite checkerboard plane in 3D space. /// A infinite checkerboard plane in 3D space.
#[derive(PartialEq, Clone, Debug)]
pub struct Checkerboard { pub struct Checkerboard {
/// The base plane containing the "white" material /// The base plane containing the "white" material
base: Plane, base: Plane,
@@ -24,6 +29,21 @@ pub struct Checkerboard {
projection_matrix: na::Matrix2x3<Scalar>, projection_matrix: na::Matrix2x3<Scalar>,
} }
/// Define a plane using a 2D texture function
#[derive(Clone)]
pub struct TexturePlane {
/// The position of the plane.
position: Point3,
/// The normal of the plane.
normal: Vector3,
/// The scale of the plane (factor for x,y passed to material function)
scale: f64,
/// A projection matrix to map 3D points to the 2D plane space.
projection_matrix: na::Matrix2x3<Scalar>,
/// The texture to use.
texture: TextureWrapper,
}
impl Plane { impl Plane {
/// Create a new plane. /// Create a new plane.
/// - `position` is the position of the plane. /// - `position` is the position of the plane.
@@ -64,39 +84,76 @@ impl Checkerboard {
} }
} }
impl TexturePlane {
/// Create a new Function Plane.
/// - `position` is the position of the plane.
/// - `normal` is the normal of the plane.
/// - `texture` the texture to use
/// - `scale` is the side-length of each square.
/// - `up` is "y" direction on the plane in 3D-Space.
pub fn new(
position: Point3,
normal: Vector3,
texture: TextureWrapper,
scale: f64,
up: Vector3,
) -> TexturePlane {
let right = up.cross(&normal).normalize();
TexturePlane {
position,
normal,
scale,
projection_matrix: na::Matrix3x2::from_columns(&[right, up]).transpose(),
texture,
}
}
}
fn plane_intersect(
position: Point3,
normal: Vector3,
ray: &super::types::Ray,
) -> Option<(Point3, Vector3, super::types::Scalar)> {
let denom = normal.dot(&ray.direction);
if denom != 0.0 {
let d = normal.dot(&position.coords);
let t = (d - normal.dot(&ray.origin.coords)) / denom;
if t > 1e-5 {
let point = ray.origin + ray.direction * t;
return Some((point, normal, t));
}
}
None
}
impl Intersect for Plane { impl Intersect for Plane {
fn intersect<'a>( fn intersect(
&'a self, &self,
ray: &super::types::Ray, ray: &super::types::Ray,
) -> Option<( ) -> Option<(
Point3, Point3,
Vector3, Vector3,
super::types::Scalar, super::types::Scalar,
&'a super::types::Material, super::types::Material,
)> { )> {
let denom = self.normal.dot(&ray.direction); if let Some((point, normal, t)) = plane_intersect(self.position, self.normal, ray) {
if denom != 0.0 { Some((point, normal, t, self.material.clone()))
let d = self.normal.dot(&self.position.coords); } else {
let t = (d - self.normal.dot(&ray.origin.coords)) / denom;
if t > 1e-5 {
let point = ray.origin + ray.direction * t;
return Some((point, self.normal, t, &self.material));
}
}
None None
} }
}
} }
impl Intersect for Checkerboard { impl Intersect for Checkerboard {
fn intersect<'a>( fn intersect(
&'a self, &self,
ray: &super::types::Ray, ray: &super::types::Ray,
) -> Option<( ) -> Option<(
Point3, Point3,
Vector3, Vector3,
super::types::Scalar, super::types::Scalar,
&'a super::types::Material, super::types::Material,
)> { )> {
if let Some((point, normal, t, material)) = self.base.intersect(ray) { if let Some((point, normal, t, material)) = self.base.intersect(ray) {
let v3 = point - self.base.position; let v3 = point - self.base.position;
@@ -105,12 +162,102 @@ impl Intersect for Checkerboard {
if ((v2.x / self.scale).round() % 2.0 == 0.0) if ((v2.x / self.scale).round() % 2.0 == 0.0)
== ((v2.y / self.scale).round() % 2.0 == 0.0) == ((v2.y / self.scale).round() % 2.0 == 0.0)
{ {
Some((point, normal, t, material)) Some((point, normal, t, material.clone()))
} else { } else {
Some((point, normal, t, &self.material_alt)) Some((point, normal, t, self.material_alt.clone()))
} }
} else { } else {
None None
} }
} }
} }
impl Intersect for TexturePlane {
fn intersect(
&self,
ray: &super::types::Ray,
) -> Option<(
Point3,
Vector3,
super::types::Scalar,
super::types::Material,
)> {
if let Some((point, normal, t)) = plane_intersect(self.position, self.normal, ray) {
let v3 = point - self.position;
let v2 = self.projection_matrix * v3;
let material = self
.texture
.material_at(na::Point2::new(v2.x / self.scale, v2.y / self.scale));
Some((point, normal, t, material))
} else {
None
}
}
}
impl std::fmt::Display for Plane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(plane position: {}, normal: {}, material: {})",
self.position, self.normal, self.material
)
}
}
impl std::fmt::Display for Checkerboard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(checkerboard position: {}, normal: {}, material1: {}, material2: {}, scale: {})",
self.base.position, self.base.normal, self.base.material, self.material_alt, self.scale
)
}
}
impl std::fmt::Debug for TexturePlane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(function-plane position: {:?}, normal: {:?}, scale: {:?})",
self.position, self.normal, self.scale,
)
}
}
impl std::fmt::Display for TexturePlane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(function-plane position: {}, normal: {}, scale: {})",
self.position, self.normal, self.scale,
)
}
}
impl PartialOrd for Plane {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
impl PartialOrd for Checkerboard {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
impl PartialEq for TexturePlane {
fn eq(&self, other: &Self) -> bool {
self.normal == other.normal
&& self.position == other.position
&& self.projection_matrix == other.projection_matrix
&& self.scale == other.scale
}
}
impl PartialOrd for TexturePlane {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
+25 -4
View File
@@ -1,21 +1,24 @@
use std::fmt::Display;
use super::types::Color; use super::types::Color;
use super::types::Intersect; use super::types::Intersect;
use super::types::Light; use super::types::Light;
use super::types::Material; use super::types::Material;
use super::types::Point3; use super::types::Point3;
use super::types::RTObjectWrapper;
use super::types::Ray; use super::types::Ray;
use super::types::Vector3; use super::types::Vector3;
use super::vec::mirror; use super::vec::mirror;
use super::vec::reflect; use super::vec::reflect;
use std::sync::Arc;
extern crate nalgebra as na; extern crate nalgebra as na;
/// A scene is a collection of objects and lights, and provides a method to trace a ray through the scene. /// A scene is a collection of objects and lights, and provides a method to trace a ray through the scene.
#[derive(Debug, PartialEq, Clone)]
pub struct Scene { pub struct Scene {
/// The ambient light of the scene /// The ambient light of the scene
ambient: Color, ambient: Color,
/// The objects in the scene /// The objects in the scene
objects: Vec<Arc<dyn Intersect + Send + Sync>>, objects: Vec<RTObjectWrapper>,
/// The lights in the scene /// The lights in the scene
lights: Vec<Light>, lights: Vec<Light>,
} }
@@ -36,7 +39,7 @@ impl Scene {
} }
/// Add an object to the scene /// Add an object to the scene
pub fn add_object(&mut self, obj: Arc<dyn Intersect + Send + Sync>) { pub fn add_object(&mut self, obj: RTObjectWrapper) {
self.objects.push(obj); self.objects.push(obj);
} }
@@ -61,7 +64,7 @@ impl Scene {
{ {
Some((isect_pt, isect_norm, _, material)) => { Some((isect_pt, isect_norm, _, material)) => {
// Lighting of material at the intersection point // Lighting of material at the intersection point
let color = self.lighting(-&ray.direction, material, isect_pt, isect_norm); let color = self.lighting(-&ray.direction, &material, isect_pt, isect_norm);
// Calculate reflections, if the material has mirror properties // Calculate reflections, if the material has mirror properties
if material.mirror > 0.0 { if material.mirror > 0.0 {
@@ -128,3 +131,21 @@ impl Scene {
color color
} }
} }
impl PartialOrd for Scene {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
impl Display for Scene {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(scene ambient: {}, #objects: {}, #lights: {})",
self.ambient,
self.objects.len(),
self.lights.len()
)
}
}
+91 -11
View File
@@ -1,8 +1,12 @@
use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3}; use super::{
texture::TextureWrapper,
types::{Intersect, Material, Point2, Point3, Ray, Scalar, Vector3},
};
extern crate nalgebra as na; extern crate nalgebra as na;
/// A sphere in 3D space /// A sphere in 3D space
#[derive(PartialEq, Clone, Debug)]
pub struct Sphere { pub struct Sphere {
/// Center of the sphere /// Center of the sphere
center: Point3, center: Point3,
@@ -12,6 +16,17 @@ pub struct Sphere {
material: Material, material: Material,
} }
/// A sphere in 3D space
#[derive(PartialEq, Clone, Debug)]
pub struct TextureSphere {
/// Center of the sphere
center: Point3,
/// Radius of the sphere
radius: Scalar,
/// texture of the sphere
texture: TextureWrapper,
}
impl Sphere { impl Sphere {
/// Create a new sphere at `center` with `radius` and `material`. /// Create a new sphere at `center` with `radius` and `material`.
pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere { pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere {
@@ -26,13 +41,12 @@ impl Sphere {
/// Numerical error tolerance /// Numerical error tolerance
const EPSILON: Scalar = 1e-5; const EPSILON: Scalar = 1e-5;
impl Intersect for Sphere { fn intersect(ray: &Ray, center: &Point3, radius: Scalar) -> Option<(Point3, Vector3, Scalar)> {
fn intersect<'a>(&'a self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, &'a Material)> { let co = ray.origin - center;
let co = ray.origin - self.center;
let a = ray.direction.dot(&ray.direction); let a = ray.direction.dot(&ray.direction);
let b = 2.0 * ray.direction.dot(&co); let b = 2.0 * ray.direction.dot(&co);
let c = co.dot(&co) - (self.radius * self.radius); let c = co.dot(&co) - (radius * radius);
let d = b * b - 4.0 * a * c; let d = b * b - 4.0 * a * c;
if d >= 0.0 { if d >= 0.0 {
@@ -51,15 +65,81 @@ impl Intersect for Sphere {
if t < Scalar::MAX { if t < Scalar::MAX {
let isect_pt: Point3 = ray.origin + ray.direction * t; let isect_pt: Point3 = ray.origin + ray.direction * t;
return Some(( if c >= 0.0 {
isect_pt, return Some((isect_pt, (isect_pt - center) / radius, t));
(isect_pt - self.center) / self.radius, } else {
t, return Some((isect_pt, -(isect_pt - center) / radius, t));
&self.material, }
));
} }
} }
None
}
impl Intersect for Sphere {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
match intersect(ray, &self.center, self.radius) {
Some((isect_pt, normal, t)) => Some((isect_pt, normal, t, self.material.clone())),
None => None,
}
}
}
impl std::fmt::Display for Sphere {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(sphere center: {}, radius: {}, material: {})",
self.center, self.radius, self.material
)
}
}
impl PartialOrd for Sphere {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
impl TextureSphere {
/// Create a new sphere at `center` with `radius` and `texture`.
pub fn new(center: Point3, radius: Scalar, texture: TextureWrapper) -> TextureSphere {
TextureSphere {
center,
radius,
texture,
}
}
}
impl Intersect for TextureSphere {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
match intersect(ray, &self.center, self.radius) {
Some((isect_pt, normal, t)) => {
let n_isect_pt = (isect_pt - self.center) / self.radius;
let uv: Point2 = Point2::new(
0.5 + (n_isect_pt.z.atan2(n_isect_pt.x) / (2.0 * std::f64::consts::PI)),
0.5 - (n_isect_pt.y).asin() / std::f64::consts::PI,
);
Some((isect_pt, normal, t, self.texture.material_at(uv)))
}
None => None,
}
}
}
impl std::fmt::Display for TextureSphere {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(sphere center: {}, radius: {}, texture: {})",
self.center, self.radius, self.texture
)
}
}
impl PartialOrd for TextureSphere {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None None
} }
} }
+127
View File
@@ -0,0 +1,127 @@
use std::fmt::Debug;
use std::fmt::Display;
use std::sync::Arc;
use as_any::AsAny;
use nalgebra as na;
use super::types::Color;
use super::types::Material;
use super::types::Point2;
use super::types::Scalar;
pub trait Texture: Display + Debug + AsAny + Sync + Send {
fn material_at(&self, pt: Point2) -> Material;
}
#[derive(Clone)]
pub struct TextureWrapper(Arc<dyn Texture>);
impl TextureWrapper {
pub fn new<T: Texture>(texture: T) -> Self {
Self(Arc::new(texture))
}
}
impl Display for TextureWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Debug for TextureWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl TextureWrapper {
pub fn material_at(&self, pt: Point2) -> Material {
self.0.material_at(pt)
}
}
impl PartialEq for TextureWrapper {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl PartialOrd for TextureWrapper {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
PartialOrd::partial_cmp(&Arc::as_ptr(&self.0).addr(), &Arc::as_ptr(&other.0).addr())
}
}
pub struct MandelbrotTexture {
scale: Scalar,
at: Point2,
max_iter: u32,
ambient_color: Color,
diffuse_color: Color,
specular_color: Color,
}
impl MandelbrotTexture {
pub fn new(
scale: Scalar,
at: Point2,
max_iter: u32,
ambient_color: Color,
diffuse_color: Color,
specular_color: Color,
) -> Self {
Self {
scale,
at,
max_iter,
ambient_color,
diffuse_color,
specular_color,
}
}
}
impl Texture for MandelbrotTexture {
fn material_at(&self, pt: Point2) -> Material {
let x = (pt.x / self.scale) + self.at.x;
let y = (pt.y / self.scale) + self.at.y;
let mut z = na::Vector2::new(0.0, 0.0);
let mut n = 0;
while z.norm() < 2.0 && n < self.max_iter {
let xtemp = z.x * z.x - z.y * z.y + x;
z.y = 2.0 * z.x * z.y + y;
z.x = xtemp;
n += 1;
}
let c = n as f64 / self.max_iter as f64;
Material {
ambient_color: self.ambient_color * c,
diffuse_color: self.diffuse_color * c,
specular_color: self.specular_color * c,
shininess: (1.0 - c) * 10.0,
mirror: 1.0 - c,
}
}
}
impl Display for MandelbrotTexture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MandelbrotTexture{{at={}, max_iter={}}}",
self.at, self.max_iter
)
}
}
impl Debug for MandelbrotTexture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MandelbrotTexture{{at={:?}, max_iter={:?}}}",
self.at, self.max_iter
)
}
}
+157 -1
View File
@@ -1,3 +1,7 @@
use std::fmt::{Debug, Display};
use as_any::AsAny;
extern crate nalgebra as na; extern crate nalgebra as na;
/// The Scalar type to use for raytracing (f32 may result in acne effects) /// The Scalar type to use for raytracing (f32 may result in acne effects)
@@ -6,6 +10,8 @@ pub type Scalar = f64;
pub type Vector3 = na::Vector3<Scalar>; pub type Vector3 = na::Vector3<Scalar>;
/// The Point3 type to use for raytracing /// The Point3 type to use for raytracing
pub type Point3 = na::Point3<Scalar>; pub type Point3 = na::Point3<Scalar>;
/// The Point2 type to use for texture lookups
pub type Point2 = na::Point2<Scalar>;
/// The Color type to use for raytracing /// The Color type to use for raytracing
pub type Color = Vector3; pub type Color = Vector3;
@@ -16,10 +22,11 @@ pub trait Intersect {
/// Otherwise the intersection point, a normal vector at the intersection point, /// Otherwise the intersection point, a normal vector at the intersection point,
/// the distance from the ray origin to the intersection point and /// the distance from the ray origin to the intersection point and
/// the material of the object are returned. /// the material of the object are returned.
fn intersect<'a>(&'a self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, &'a Material)>; fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)>;
} }
/// A point light source /// A point light source
#[derive(Clone, Debug, PartialEq, Copy)]
pub struct Light { pub struct Light {
/// Position of the light source /// Position of the light source
pub position: Point3, pub position: Point3,
@@ -34,6 +41,12 @@ impl Light {
} }
} }
impl PartialOrd for Light {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
/// A ray with origin and direction /// A ray with origin and direction
pub struct Ray { pub struct Ray {
/// Ray origin /// Ray origin
@@ -50,6 +63,7 @@ impl Ray {
} }
/// A Material used for PHONG shading /// A Material used for PHONG shading
#[derive(Clone, Debug, PartialEq, Copy)]
pub struct Material { pub struct Material {
/// Ambient color, aka color without direct or indirect light /// Ambient color, aka color without direct or indirect light
pub ambient_color: Color, pub ambient_color: Color,
@@ -86,3 +100,145 @@ impl Material {
} }
} }
} }
impl PartialOrd for Material {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
//////// Display traits ////////////////////////////////////////////////////////////////////////////
impl Display for Light {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(light position: {}, color: {})",
self.position, self.color
)
}
}
impl Display for Material {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(material ambient_color: {}, diffuse_color: {}, specular_color: {}, shininess: {}, mirror: {})",
self.ambient_color, self.diffuse_color, self.specular_color, self.shininess, self.mirror
)
}
}
// RTWrapper ///////////////////////////////////////////////////////////////////////////////////////
/// A trait used for Objects, which can be stored inside of the Scene, are Intersectable and are ForeignData compatible.
pub trait RTObject: Intersect + Display + Debug + AsAny + Sync + Send + 'static {
/// Convert the object to a Box<dyn Any> allowing downcasts to Self
fn as_any_box(self: Box<Self>) -> Box<dyn std::any::Any>;
/// Explicitly compare the object with another RTObject for object safety
fn eq_impl(&self, other: &dyn RTObject) -> bool;
/// Explicitly clone the object for object safety
fn clone_impl(&self) -> Box<dyn RTObject>;
}
impl<T: Intersect + Display + Debug + PartialEq + Clone + Sync + Send + 'static> RTObject for T {
fn as_any_box(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn eq_impl(&self, other: &dyn RTObject) -> bool {
if let Some(other) = other.as_any().downcast_ref::<T>() {
self == other
} else {
false
}
}
fn clone_impl(&self) -> Box<dyn RTObject> {
Box::new(self.clone())
}
}
impl PartialEq for dyn RTObject {
fn eq(&self, other: &Self) -> bool {
self.eq_impl(other)
}
}
/// The RTObjectWrapper is a wrapper around a Box<dyn RTObject> to make it ForeignData compatible
/// (not depending on the concrete type of the object).
pub struct RTObjectWrapper(Box<dyn RTObject>);
impl RTObjectWrapper {
/// Create a new RTObjectWrapper from a Box<dyn RTObject>
pub fn new<T: RTObject>(value: Box<T>) -> RTObjectWrapper {
RTObjectWrapper(value)
}
/// Create a new RTObjectWrapper from a RTObject
pub fn from<T: RTObject>(value: T) -> RTObjectWrapper {
RTObjectWrapper::new(Box::new(value))
}
/// Get the inner box as Box<dyn Any> allowing downcasts to the concrete type
pub fn as_any_box(self) -> Box<dyn std::any::Any> {
self.0.as_any_box()
}
}
impl Clone for RTObjectWrapper {
fn clone(&self) -> Self {
RTObjectWrapper(self.0.clone_impl())
}
}
impl PartialEq for RTObjectWrapper {
fn eq(&self, other: &Self) -> bool {
*self.0 == *other.0
}
}
impl Display for RTObjectWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RTObjectWrapper({})", self.0)
}
}
impl Debug for RTObjectWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RTObjectWrapper({:?})", self.0)
}
}
impl Intersect for RTObjectWrapper {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
self.0.intersect(ray)
}
}
impl PartialOrd for RTObjectWrapper {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
#[test]
fn test_rt_wrapper_expr_conversion() {
use super::sphere::Sphere;
use lispers_core::lisp::expression::{Expression, ForeignDataWrapper};
let sphere = Sphere::new(
Point3::new(0.0, 0.0, 0.0),
1.0,
Material::new(
Color::new(0.0, 0.0, 0.0),
Color::new(0.0, 0.0, 0.0),
Color::new(0.0, 0.0, 0.0),
0.0,
0.0,
),
);
let sphere = RTObjectWrapper::new(Box::new(sphere));
let expr: Expression = ForeignDataWrapper::new(sphere.clone()).into();
let sphere2: ForeignDataWrapper<RTObjectWrapper> = expr.try_into().unwrap();
assert_eq!(sphere, *sphere2.0);
}