Compare commits
3 commits
2c50b04108
...
af1dd02257
Author | SHA1 | Date | |
---|---|---|---|
af1dd02257 | |||
781e8ba3a3 | |||
622893ebf0 |
27 changed files with 516 additions and 50 deletions
40
.forgejo/workflows/webrepl.yaml
Normal file
40
.forgejo/workflows/webrepl.yaml
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: webrepl
|
||||
on: [push]
|
||||
#on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
jobs:
|
||||
test:
|
||||
runs-on: docker
|
||||
|
||||
container:
|
||||
image: rust:1.83-alpine
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apk add --no-cache git nodejs util-linux
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: cargo install wasm-pack
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build talc-web
|
||||
run: wasm-pack build --release --no-typescript --no-pack --target web
|
||||
working-directory: talc-web
|
||||
|
||||
- name: Save artifacts
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: webrepl
|
||||
path: |
|
||||
*.html
|
||||
*.js
|
||||
*.css
|
||||
pkg/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Done
|
||||
run: echo done!
|
126
Cargo.lock
generated
126
Cargo.lock
generated
|
@ -29,6 +29,12 @@ version = "2.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
@ -41,12 +47,6 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
|
@ -106,7 +106,7 @@ version = "3.4.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
|
||||
dependencies = [
|
||||
"nix 0.29.0",
|
||||
"nix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -150,8 +150,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -169,6 +171,16 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
|
@ -208,18 +220,6 @@ dependencies = [
|
|||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
|
@ -228,7 +228,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
|||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -306,6 +306,12 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
|
@ -417,9 +423,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "14.0.0"
|
||||
version = "15.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
|
||||
checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
|
@ -429,12 +435,12 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix 0.28.0",
|
||||
"nix",
|
||||
"radix_trie",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -445,9 +451,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.94"
|
||||
version = "2.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3"
|
||||
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -495,6 +501,16 @@ dependencies = [
|
|||
"talc-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "talc-web"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"talc-lang",
|
||||
"talc-std",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
|
@ -509,9 +525,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
|
@ -525,6 +541,60 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["talc-lang", "talc-bin", "talc-std", "talc-macros"]
|
||||
members = ["talc-lang", "talc-bin", "talc-std", "talc-macros", "talc-web"]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
- [Installation](./install/installation.md)
|
||||
- [Tour of the REPL](./install/repl.md)
|
||||
- [The Web REPL](./install/webrepl.md)
|
||||
|
||||
# The language
|
||||
|
||||
|
|
18
docs/src/install/webrepl.md
Normal file
18
docs/src/install/webrepl.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# The Web REPL
|
||||
|
||||
As an alterative to the command-line REPL, you can try out Talc in the browser
|
||||
at (https://talc.trimill.xyz/repl)[https://talc.trimill.xyz/repl]. Although
|
||||
the full Talc language and standard library (aside from file I/O and process
|
||||
control) are available, editing features are more limited than the command-line
|
||||
interface - especially of note is the lack of syntax highlighting and tab
|
||||
completion.
|
||||
|
||||
## Usage
|
||||
|
||||
Just like the command-line REPL, enter an expression and press Enter to
|
||||
evaluate it. Use the up and down arrows to access input history.
|
||||
|
||||
For multiline editing, use Shift+Enter to insert a newline. Hold the Control
|
||||
key while pressing the up or down arrows to move the cursor between lines.
|
||||
|
||||
To clear the screen, press Ctrl+L.
|
|
@ -14,7 +14,7 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
talc-lang = { path = "../talc-lang" }
|
||||
talc-std = { path = "../talc-std" }
|
||||
rustyline = "14.0"
|
||||
rustyline = "15.0"
|
||||
clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
|
||||
ctrlc = "3.4"
|
||||
lazy_static = "1.5"
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, cell::RefCell, rc::Rc};
|
|||
|
||||
use rustyline::{
|
||||
completion::Completer,
|
||||
highlight::Highlighter,
|
||||
highlight::{CmdKind, Highlighter},
|
||||
hint::Hinter,
|
||||
validate::{ValidationContext, ValidationResult, Validator},
|
||||
Helper, Result,
|
||||
|
@ -105,8 +105,11 @@ impl Highlighter for TalcHelper {
|
|||
Cow::Owned(format!("\x1b[37m{hint}\x1b[0m"))
|
||||
}
|
||||
|
||||
fn highlight_char(&self, line: &str, _: usize, forced: bool) -> bool {
|
||||
forced || !line.is_empty()
|
||||
fn highlight_char(&self, line: &str, _: usize, forced: CmdKind) -> bool {
|
||||
match forced {
|
||||
CmdKind::ForcedRefresh => true,
|
||||
_ => !line.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,6 @@ rust-version = "1.81.0"
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
num = { version = "0.4", features = [] }
|
||||
num = "0.4"
|
||||
lazy_static = "1.5"
|
||||
unicode-ident = "1.0"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::chunk::{Arg24, Catch, Chunk, Instruction as I};
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
symbol::{Symbol, SYM_MSG, SYM_TRACE, SYM_TYPE},
|
||||
value::Value,
|
||||
};
|
||||
use std::{fmt::Display, rc::Rc};
|
||||
use std::{error::Error, fmt::Display, rc::Rc};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Exception>;
|
||||
|
||||
|
@ -114,6 +114,8 @@ impl Display for Exception {
|
|||
}
|
||||
}
|
||||
|
||||
impl Error for Exception {}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! exception {
|
||||
($exc_ty:expr, $($t:tt)*) => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use core::{f64, panic};
|
||||
use std::{borrow::Cow, cmp::Ordering, fmt, hash::Hash, mem::ManuallyDrop, ops, rc::Rc};
|
||||
|
||||
use num::{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use core::panic;
|
||||
|
||||
use crate::{
|
||||
parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind, ListItem, TableItem},
|
||||
value::Value,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::fmt;
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
ops::{BinaryOp, UnaryOp},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use core::fmt;
|
||||
use std::num::ParseFloatError;
|
||||
use std::{fmt, num::ParseFloatError};
|
||||
|
||||
use num::{bigint::ParseBigIntError, Num};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::fmt;
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use num::{bigint::Sign, BigInt};
|
||||
|
|
|
@ -16,6 +16,7 @@ regex = { version = "1.11", optional = true }
|
|||
rand = { version = "0.8", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["rand", "regex"]
|
||||
default = ["rand", "regex", "file"]
|
||||
rand = ["dep:rand", "dep:num-bigint"]
|
||||
regex = ["dep:regex"]
|
||||
file = []
|
||||
|
|
|
@ -678,6 +678,7 @@ fn spawn_inner(fname: &str, proc: &mut Command, opts: Value) -> Result<Value> {
|
|||
Ok(c) => c,
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
};
|
||||
#[expect(clippy::mutable_key_type)]
|
||||
let mut table = HashMap::new();
|
||||
if let Some(stdin) = child.stdin.take() {
|
||||
let bf = match BufFile::from_raw_fd(stdin.into_raw_fd(), true) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{
|
||||
io::{BufRead, Write},
|
||||
os::unix::ffi::OsStrExt,
|
||||
sync::Mutex,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
@ -127,7 +126,7 @@ pub fn env(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
let val = std::env::var_os(key.to_os_str());
|
||||
match val {
|
||||
Some(val) => Ok(LString::from(val.as_bytes()).into()),
|
||||
Some(val) => Ok(LString::from(val.as_encoded_bytes()).into()),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -619,6 +619,7 @@ pub fn list(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn table(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
#[expect(clippy::mutable_key_type)]
|
||||
let mut result = HashMap::new();
|
||||
while let Some(value) = vmcalliter!(vm; iter.clone())? {
|
||||
let Value::List(l) = value else {
|
||||
|
@ -851,6 +852,7 @@ pub fn count(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
#[expect(clippy::mutable_key_type)]
|
||||
let mut map = HashMap::new();
|
||||
|
||||
while let Some(v) = vmcalliter!(vm; iter.clone())? {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::mutable_key_type)]
|
||||
|
||||
use talc_lang::{
|
||||
symbol::{symbol, Symbol},
|
||||
vm::Vm,
|
||||
|
@ -7,7 +5,6 @@ use talc_lang::{
|
|||
|
||||
pub mod collection;
|
||||
pub mod exception;
|
||||
pub mod file;
|
||||
pub mod format;
|
||||
pub mod ints;
|
||||
pub mod io;
|
||||
|
@ -16,6 +13,8 @@ pub mod math;
|
|||
pub mod string;
|
||||
pub mod value;
|
||||
|
||||
#[cfg(feature = "file")]
|
||||
pub mod file;
|
||||
#[cfg(feature = "rand")]
|
||||
pub mod random;
|
||||
#[cfg(feature = "regex")]
|
||||
|
@ -24,7 +23,6 @@ pub mod regex;
|
|||
pub fn load_all(vm: &mut Vm) {
|
||||
collection::load(vm);
|
||||
exception::load(vm);
|
||||
file::load(vm);
|
||||
format::load(vm);
|
||||
io::load(vm);
|
||||
iter::load(vm);
|
||||
|
@ -33,6 +31,8 @@ pub fn load_all(vm: &mut Vm) {
|
|||
string::load(vm);
|
||||
value::load(vm);
|
||||
|
||||
#[cfg(feature = "file")]
|
||||
file::load(vm);
|
||||
#[cfg(feature = "rand")]
|
||||
random::load(vm);
|
||||
#[cfg(feature = "regex")]
|
||||
|
|
|
@ -130,11 +130,14 @@ pub fn copy_inner(value: Value) -> Result<Value> {
|
|||
Ok(v?.into())
|
||||
}
|
||||
Value::Table(t) => {
|
||||
#[expect(clippy::mutable_key_type)]
|
||||
let t = Rc::unwrap_or_clone(t).take();
|
||||
|
||||
let v: Result<HashMap<HashValue, Value>> = t
|
||||
.into_iter()
|
||||
.map(|(k, v)| copy_inner(v).map(|v| (k, v)))
|
||||
.collect();
|
||||
|
||||
Ok(v?.into())
|
||||
}
|
||||
Value::Native(ref n) => match n.copy_value()? {
|
||||
|
|
16
talc-web/Cargo.toml
Normal file
16
talc-web/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "talc-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
talc-lang = { path = "../talc-lang" }
|
||||
talc-std = { path = "../talc-std", default-features = false, features = ["rand", "regex"] }
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
wasm-bindgen = "0.2"
|
16
talc-web/README.md
Normal file
16
talc-web/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Talc Web
|
||||
|
||||
The `talc-web` crate provides a WASM interface to execute Talc expressions
|
||||
in a REPL. Together with the associated HTML, CSS, and JavaScript files, this
|
||||
creates an alternative to the command-line REPL.
|
||||
|
||||
## Building
|
||||
|
||||
In the `talc-web` directory, run:
|
||||
|
||||
```bash
|
||||
wasm-pack build --release --no-typescript --no-pack --target web
|
||||
```
|
||||
|
||||
See (the main README.md)[../README.md] for more information about building
|
||||
Talc.
|
29
talc-web/index.html
Normal file
29
talc-web/index.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Talc Web REPL</title>
|
||||
|
||||
<script type="module" src="main.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal">
|
||||
<div id="history">
|
||||
<div>Talc Web REPL</div>
|
||||
<div>For help, see
|
||||
<a href="https://talc.trimill.xyz/" target="_blank">the docs</a>
|
||||
or
|
||||
<a href="https://g.trimill.xyz/trimill/talc" target="_blank">the repository</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input_line">
|
||||
<div id="prompt" class="prompt">>> </div>
|
||||
<div id="input" style="display: inline-block" contenteditable="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
161
talc-web/main.js
Normal file
161
talc-web/main.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
"use strict";
|
||||
|
||||
import init, * as talc from "./pkg/talc_web.js";
|
||||
await init();
|
||||
|
||||
/*
|
||||
* DOM
|
||||
*/
|
||||
|
||||
const $ = (q) => document.querySelector(q);
|
||||
|
||||
function newEl(tag, attrs, children) {
|
||||
const el = document.createElement(tag);
|
||||
for (const attr in attrs) {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
for (const child of children) {
|
||||
if (typeof(child) === "string") {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
el.appendChild(child);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
/*
|
||||
* History
|
||||
*/
|
||||
|
||||
let historyEntries = [""];
|
||||
let historyIndex = 0;
|
||||
let currentLine = "";
|
||||
|
||||
function prevHistory() {
|
||||
const inputEl = $("#input");
|
||||
if (historyIndex === 0) {
|
||||
currentLine = inputEl.innerText;
|
||||
}
|
||||
historyIndex = Math.min(historyIndex+1, historyEntries.length);
|
||||
const text = historyEntries[historyEntries.length - historyIndex];
|
||||
inputEl.innerText = text;
|
||||
|
||||
document.getSelection().setPosition(inputEl, inputEl.childNodes.length);
|
||||
}
|
||||
|
||||
function nextHistory() {
|
||||
const inputEl = $("#input");
|
||||
historyIndex = Math.max(historyIndex-1, 0);
|
||||
let text;
|
||||
if (historyIndex === 0) {
|
||||
text = currentLine;
|
||||
} else {
|
||||
text = historyEntries[historyEntries.length - historyIndex];
|
||||
}
|
||||
inputEl.innerText = text;
|
||||
|
||||
document.getSelection().setPosition(inputEl, inputEl.childNodes.length);
|
||||
}
|
||||
|
||||
/*
|
||||
* REPL
|
||||
*/
|
||||
|
||||
function nextLine() {
|
||||
const newPrompt = newEl("div", {"class": "prompt"}, []);
|
||||
newPrompt.innerHTML = $("#prompt").innerHTML;
|
||||
|
||||
const newInput = newEl("div", {}, []);
|
||||
newInput.innerHTML = $("#input").innerHTML;
|
||||
|
||||
$("#history").appendChild(newEl(
|
||||
"div",
|
||||
{"class": "input_line"},
|
||||
[newPrompt, newInput]
|
||||
));
|
||||
|
||||
$("#input").innerHTML = "";
|
||||
|
||||
currentLine = "";
|
||||
historyIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
function execLine() {
|
||||
const inputLine = $("#input").innerText;
|
||||
nextLine();
|
||||
historyEntries.push(inputLine);
|
||||
|
||||
try {
|
||||
const fixedInput = inputLine.replaceAll("\u00a0", " ");
|
||||
const out = talc.eval_line(fixedInput);
|
||||
if (out !== undefined) {
|
||||
$("#history").appendChild(newEl("div", {}, [out]));
|
||||
}
|
||||
} catch (e) {
|
||||
$("#history").appendChild(newEl("div", {}, [
|
||||
newEl("span", {"class": "error"}, ["Error: "]),
|
||||
newEl("pre", {"class": "err_msg"}, [e.toString()])
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Input
|
||||
*/
|
||||
|
||||
function handleCtrlKey(event) {
|
||||
switch (event.code) {
|
||||
case "KeyL":
|
||||
event.preventDefault();
|
||||
$("#history").innerHTML = "";
|
||||
break;
|
||||
case "KeyC":
|
||||
event.preventDefault();
|
||||
nextLine();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressed(event) {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
execLine()
|
||||
} else if (event.ctrlKey) {
|
||||
handleCtrlKey(event);
|
||||
} else if (event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
prevHistory();
|
||||
} else if (event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
nextHistory();
|
||||
}
|
||||
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Listeners
|
||||
*/
|
||||
|
||||
$("#input").addEventListener("keydown", keyPressed);
|
||||
$("#input").focus();
|
||||
|
||||
$("#terminal").addEventListener("click", (event) => {
|
||||
if (event.target.id === "terminal" || event.target.id === "input") {
|
||||
$("#input").focus();
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Query parameter
|
||||
*/
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const initialLine = urlParams.get("eval");
|
||||
|
||||
if (initialLine !== null) {
|
||||
$("#input").innerText = initialLine;
|
||||
execLine();
|
||||
}
|
51
talc-web/src/lib.rs
Normal file
51
talc-web/src/lib.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use talc_lang::{
|
||||
compiler, parser,
|
||||
symbol::{symbol, Symbol},
|
||||
value::Value,
|
||||
vm::Vm,
|
||||
};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
type BoxError = Box<dyn std::error::Error>;
|
||||
|
||||
struct Talc {
|
||||
vm: Vm,
|
||||
globals: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl Talc {
|
||||
pub fn eval_line(&mut self, line: &str) -> Result<Option<String>, BoxError> {
|
||||
let expr = parser::parse(line)?;
|
||||
let func = compiler::compile_repl(&expr, &mut self.globals)?;
|
||||
let func = Rc::new(func);
|
||||
let res = self.vm.run_function(func.clone(), vec![func.into()])?;
|
||||
let res_str = (res != Value::Nil).then(|| format!("{res:#}"));
|
||||
self.vm.set_global(symbol!("_"), res);
|
||||
Ok(res_str)
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TALC: RefCell<Talc> = {
|
||||
let mut vm = Vm::new(128, Vec::new());
|
||||
talc_std::load_all(&mut vm);
|
||||
|
||||
let globals = vec![symbol!("_")];
|
||||
vm.set_global(symbol!("_"), Value::Nil);
|
||||
|
||||
RefCell::new(Talc { vm, globals })
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
TALC.with(|_| ());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn eval_line(line: &str) -> Result<Option<String>, String> {
|
||||
TALC.with_borrow_mut(|t| t.eval_line(line))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
57
talc-web/style.css
Normal file
57
talc-web/style.css
Normal file
|
@ -0,0 +1,57 @@
|
|||
body {
|
||||
font-family: monospace;
|
||||
font-size: 14pt;
|
||||
|
||||
background-color: #14171d;
|
||||
color: #c7c6c3;
|
||||
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.input_line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#prompt {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
#input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div, pre {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#terminal {
|
||||
min-height: calc(100vh - 60px - 8px);
|
||||
padding: 10px;
|
||||
border: 4px solid #4d4754;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #82bfb3;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #82bfb3;
|
||||
}
|
||||
|
||||
pre.err_msg {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #cc5c5c;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #789ebf;
|
||||
}
|
Loading…
Add table
Reference in a new issue