diff --git a/Cargo.toml b/Cargo.toml index 2984cb6..ebda98e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ members = [ "glsp-proc-macros2", "glsp-stdlib", "glsp-proc-macros", - "glsp-engine" + "glsp-engine", + "repl-cli" ] exclude = [ "benchmarks", diff --git a/repl-cli/Cargo.toml b/repl-cli/Cargo.toml new file mode 100644 index 0000000..64a1285 --- /dev/null +++ b/repl-cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "glsp-repl-cli" +version = "0.1.0" +authors = ["Alberto González Palomo "] +description = "Command line REPL for GameLisp" +readme = "README.md" +homepage = "https://gamelisp.rs/" +repository = "https://github.com/fleabitdev/glsp/" +license = "MIT OR Apache-2.0" +edition = "2018" + +[[bin]] +name = "glsp" +path = "src/main.rs" + +[dependencies] +glsp = { path = "../glsp" } +gong = "1.4.0" # https://github.com/jnqnfe/gong +linefeed = "0.6" # https://github.com/murarth/linefeed +webbrowser = "0.5.4" # https://github.com/amodm/webbrowser-rs diff --git a/repl-cli/LICENSE-APACHE b/repl-cli/LICENSE-APACHE new file mode 100644 index 0000000..a2ce4d9 --- /dev/null +++ b/repl-cli/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/repl-cli/LICENSE-MIT b/repl-cli/LICENSE-MIT new file mode 100644 index 0000000..458723b --- /dev/null +++ b/repl-cli/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/repl-cli/README.md b/repl-cli/README.md new file mode 100644 index 0000000..ead1761 --- /dev/null +++ b/repl-cli/README.md @@ -0,0 +1,55 @@ +# GameLisp command line REPL +A command line [Read-Eval-Print-Loop](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) +for [GameLisp](https://gamelisp.rs). +Runs on Linux, MacOS X, and Windows both in PowerShell and CMD.EXE +but [Mintty](https://mintty.github.io/) ([Cygwin](https://www.cygwin.com/), [MSYS2](https://www.msys2.org/)) +is [not supported](https://github.com/murarth/linefeed/issues/68) and works +only in [dumb terminal](https://books.google.com/books?id=jT2fQqJplN8C&lpg=PT3&pg=PT3) mode. + +It can be used for running GameLisp programs from shell scripts by +passing their file names as arguments. +With the `-q` option, the program quits before starting the REPL. +Without `-q`, the REPL starts right after loading all the files +and has access to the globals defined in them. There is no default init file. + +Usage: `glsp [file.glsp file2.glsp ...]` + +- `-h`, `-help` + Show this message. +- `-v`, `--version` + Show the version number and exit. +- `-q` + Quit before starting the REPL, to use as a command in shell scripts. +- `--sandboxed` + Disables filesystem access: load, include, and require. + https://gamelisp.rs/reference/the-glsp-crate.html#sandboxing +- The command input history is stored in "`./glsp-repl_history`". + +`help name` is short for `(www (help "name"))` and shows the documentation +for `name` in a web browser. +E.g.: `rust`, `lisp`, `std`, `special-forms`, `abbreviations`, `if`, `'`, `@`, `#n`, `#t`, `#f`. +Without argument, shows the reference manual. + +`(www "url")` loads `url` in a web browser, unless using `--sandboxed`. + +Shows how to: + +- create a GameLisp [runtime instance with options](https://docs.rs/glsp/*/glsp/struct.RuntimeBuilder.html) + ([`sandboxed`](https://gamelisp.rs/reference/the-glsp-crate.html#sandboxing), + ...). +- [load](https://docs.rs/glsp/*/glsp/fn.load.html) source files into it. +- [parse](https://docs.rs/glsp/*/glsp/fn.parse_all.html) and + [evaluate](https://docs.rs/glsp/*/glsp/fn.eval_multi.html) + expressions in that runtime. +- call Rust functions from GameLisp: see the `www(url: Option<&str>)` function in `src/main.rs`. + +## Dependencies + +- Command line options parsing: + [gong](https://crates.io/crates/gong) +- Command line interaction, history: + [linefeed](https://crates.io/crates/linefeed) +- Open URLs in the system’s web browser: + [webbrowser](https://crates.io/crates/webbrowser) + +[![](deps.svg)](deps.svg) diff --git a/repl-cli/deps.svg b/repl-cli/deps.svg new file mode 100644 index 0000000..601bf31 --- /dev/null +++ b/repl-cli/deps.svg @@ -0,0 +1,1033 @@ + + + + + + +dependencies + + +n0 + + +arrayref v0.3.6 + + + + + +n1 + + +arrayvec v0.5.1 + + + + + +n2 + + +autocfg v1.0.0 + + + + + +n3 + + +base64 v0.11.0 + + + + + +n4 + + +bitflags v1.2.1 + + + + + +n5 + + +blake2b_simd v0.5.10 + + + + + +n5->n0 + + + + + +n5->n1 + + + + + +n6 + + +constant_time_eq v0.1.5 + + + + + +n5->n6 + + + + + +n7 + + +cc v1.0.54 + + + + + +n8 + + +cfg-if v0.1.10 + + + + + +n9 + + +crossbeam-utils v0.7.2 + + + + + +n9->n2 + + + + + +n9->n8 + + + + + +n10 + + +lazy_static v1.4.0 + + + + + +n9->n10 + + + + + +n11 + + +dirs v1.0.5 + + + + + +n12 + + +libc v0.2.71 + + + + + +n11->n12 + + + + + +n13 + + +redox_users v0.3.4 + + + + + +n11->n13 + + + + + +n14 + + +winapi v0.3.8 + + + + + +n11->n14 + + + + + +n18 + + +getrandom v0.1.14 + + + + + +n13->n18 + + + + + +n52 + + +redox_syscall v0.1.56 + + + + + +n13->n52 + + + + + +n53 + + +rust-argon2 v0.7.0 + + + + + +n13->n53 + + + + + +n55 + + +winapi-i686-pc-windows-gnu v0.4.0 + + + + + +n14->n55 + + + + + +n56 + + +winapi-x86_64-pc-windows-gnu v0.4.0 + + + + + +n14->n56 + + + + + +n15 + + +dirs v2.0.2 + + + + + +n15->n8 + + + + + +n16 + + +dirs-sys v0.3.5 + + + + + +n15->n16 + + + + + +n16->n12 + + + + + +n16->n13 + + + + + +n16->n14 + + + + + +n17 + + +fnv v1.0.7 + + + + + +n18->n8 + + + + + +n18->n12 + + + + + +n19 + + +wasi v0.9.0+wasi-snapshot-preview1 + + + + + +n18->n19 + + + + + +n20 + + +glsp v0.1.0 + + + + + +n21 + + +glsp-engine v0.1.0 + + + + + +n20->n21 + + + + + +n22 + + +glsp-proc-macros v0.1.0 + + + + + +n20->n22 + + + + + +n23 + + +glsp-stdlib v0.1.0 + + + + + +n20->n23 + + + + + +n21->n17 + + + + + +n24 + + +owning_ref v0.4.1 + + + + + +n21->n24 + + + + + +n25 + + +smallvec v1.4.0 + + + + + +n21->n25 + + + + + +n22->n21 + + + + + +n23->n21 + + + + + +n23->n22 + + + + + +n23->n25 + + + + + +n40 + + +stable_deref_trait v1.1.1 + + + + + +n24->n40 + + + + + +n26 + + +glsp-repl v0.1.0 + + + +n26->n20 + + + + + +n27 + + +gong v1.4.0 + + + + + +n26->n27 + + + + + +n28 + + +linefeed v0.6.0 + + + + + +n26->n28 + + + + + +n29 + + +webbrowser v0.5.4 + + + + + +n26->n29 + + + + + +n28->n11 + + + + + +n28->n14 + + + + + +n30 + + +mortal v0.2.2 + + + + + +n28->n30 + + + + + +n29->n14 + + + + + +n54 + + +widestring v0.4.2 + + + + + +n29->n54 + + + + + +n30->n4 + + + + + +n30->n12 + + + + + +n30->n14 + + + + + +n32 + + +nix v0.17.0 + + + + + +n30->n32 + + + + + +n33 + + +smallstr v0.2.0 + + + + + +n30->n33 + + + + + +n34 + + +terminfo v0.7.3 + + + + + +n30->n34 + + + + + +n35 + + +unicode-normalization v0.1.12 + + + + + +n30->n35 + + + + + +n36 + + +unicode-width v0.1.7 + + + + + +n30->n36 + + + + + +n31 + + +memchr v2.3.3 + + + + + +n32->n4 + + + + + +n32->n7 + + + + + +n32->n8 + + + + + +n32->n12 + + + + + +n37 + + +void v1.0.2 + + + + + +n32->n37 + + + + + +n33->n25 + + + + + +n34->n15 + + + + + +n34->n17 + + + + + +n38 + + +nom v5.1.2 + + + + + +n34->n38 + + + + + +n41 + + +phf v0.8.0 + + + + + +n34->n41 + + + + + +n43 + + +phf_codegen v0.8.0 + + + + + +n34->n43 + + + + + +n35->n25 + + + + + +n38->n31 + + + + + +n39 + + +version_check v0.9.2 + + + + + +n38->n39 + + + + + +n42 + + +phf_shared v0.8.0 + + + + + +n41->n42 + + + + + +n46 + + +siphasher v0.3.3 + + + + + +n42->n46 + + + + + +n43->n42 + + + + + +n44 + + +phf_generator v0.8.0 + + + + + +n43->n44 + + + + + +n44->n42 + + + + + +n45 + + +rand v0.7.3 + + + + + +n44->n45 + + + + + +n45->n12 + + + + + +n45->n18 + + + + + +n48 + + +rand_chacha v0.2.2 + + + + + +n45->n48 + + + + + +n49 + + +rand_core v0.5.1 + + + + + +n45->n49 + + + + + +n50 + + +rand_hc v0.2.0 + + + + + +n45->n50 + + + + + +n51 + + +rand_pcg v0.2.1 + + + + + +n45->n51 + + + + + +n47 + + +ppv-lite86 v0.2.8 + + + + + +n48->n47 + + + + + +n48->n49 + + + + + +n49->n18 + + + + + +n50->n49 + + + + + +n51->n49 + + + + + +n53->n3 + + + + + +n53->n5 + + + + + +n53->n6 + + + + + +n53->n9 + + + + + diff --git a/repl-cli/src/main.rs b/repl-cli/src/main.rs new file mode 100644 index 0000000..d7b3a40 --- /dev/null +++ b/repl-cli/src/main.rs @@ -0,0 +1,331 @@ +// Author: Alberto González Palomo https://sentido-labs.com +// ©2019,2020 Alberto González Palomo https://sentido-labs.com +// Created: 2019-05-07 17:50 +// +// This file shows how to: +// - create a GameLisp runtime instance with options (`sandboxed`, ...). +// - load source files into it. +// - parse and evaluate single expressions in that runtime. +// - bind Rust functions to GameLisp symbols (see `www(url)` below). + +use glsp::prelude::*; + +use gong::{ ItemClass, Item }; +use linefeed::{ Interface, ReadResult, terminal::DefaultTerminal }; + +static OPTS: gong::options::OptionSet = gong::gong_option_set_fixed!( + [ + gong::gong_longopt!("help"), + gong::gong_longopt!("version"), + gong::gong_longopt!("sandboxed"), + ], + [ + gong::gong_shortopt!('h'), + gong::gong_shortopt!('v'), + gong::gong_shortopt!('q'), + ] +); + +const HISTORY_FILE: &str = concat!(env!("CARGO_PKG_NAME"), "_history"); + +fn usage() { + eprintln!(r#"Usage: glsp [file.glsp file2.glsp ...] + -h, -help + Show this message. + -v, --version + Show the version number ({version}) and exit. + -q + Quit before starting the REPL, to use as a command in shell scripts. + --sandboxed + Disables filesystem access: load, include, and require. + https://gamelisp.rs/reference/the-glsp-crate.html#sandboxing + + The command input history is stored in "./{history}"."#, + version = env!("CARGO_PKG_VERSION"), + history = HISTORY_FILE, + ); +} + +fn main() { + debug_assert!(OPTS.is_valid()); + + let args: Vec = std::env::args().skip(1).collect(); + let mut files = Vec::new(); + let mut run_repl = true; + let mut sandboxed = false; + let analysis = OPTS.process(&args[..]); + for &o in analysis.items.iter() { + match o { + ItemClass::Ok(Item::Short(_, 'h')) | + ItemClass::Ok(Item::Long(_, "help")) => { + usage(); + return; + }, + ItemClass::Ok(Item::Short(_, 'v')) | + ItemClass::Ok(Item::Long(_, "version")) => { + eprintln!("{}", env!("CARGO_PKG_VERSION")); + return; + }, + ItemClass::Ok(Item::Short(_, 'q')) => { + run_repl = false; + }, + ItemClass::Ok(Item::Long(_, "sandboxed")) => { + sandboxed = true; + }, + ItemClass::Ok(Item::NonOption(_, file_name)) => { + files.push(file_name); + }, + ItemClass::Ok(Item::Short(_, _)) => {}, + ItemClass::Ok(Item::Long (_, _)) => {}, + ItemClass::Ok(Item::LongWithData {i:_, n:_, d:_, l:_}) => {}, + ItemClass::Ok(Item::ShortWithData {i:_, c:_, d:_, l:_}) => {}, + + ItemClass::Ok(Item::EarlyTerminator(_)) => { /* -- found */ }, + + ItemClass::Warn(item) => { + eprintln!("WARNING: {:?}", item); + }, + ItemClass::Err(item) => { + eprintln!("ERROR: {:?}", item); + } + } + } + + let runtime = RuntimeBuilder::new().sandboxed(sandboxed).build(); + runtime.run(|| { + // These are the defaults, set here to show how to redirect output: + // https://gamelisp.rs/reference/the-glsp-crate.html#output-streams + glsp::set_pr_writer(Box::new(std::io::stdout())); + glsp::set_epr_writer(Box::new(std::io::stderr())); + + glsp::bind_rfn("help", rfn!(help))?; + if !sandboxed { + glsp::bind_rfn("www", rfn!(www))?; + } + + for file_name in files { prn!("{}", glsp::load(file_name)?); } + + Ok(()) + }); + + if !run_repl { return; } + + eprintln!( + r#";;; {name} {version} {homepage} +;;; “help name” is short for “(www (help "name"))” and shows the documentation +;; for “name” in a web browser. +;; E.g.: rust, lisp, std, special-forms, abbreviations, if, ', @, #n, #t, #f +;; Without argument, shows the reference manual. +;;; (www "url") loads “url” in a web browser, unless using --sandboxed."#, + name = env!("CARGO_PKG_NAME"), + version = env!("CARGO_PKG_VERSION"), + homepage = env!("CARGO_PKG_HOMEPAGE"), + ); + + match Interface::new(env!("CARGO_PKG_NAME")) { + Ok(cli) => { + repl(runtime, sandboxed, cli); + }, + Err(err) => { + eprintln!(";;; Terminal is not fully functional: {}", err); + degraded_repl(runtime, sandboxed); + } + }; +} + +fn repl(runtime: Runtime, sandboxed: bool, cli: Interface) { + let cwd = std::env::current_dir().unwrap(); + + eprintln!( + r#";;; CTRL+D to exit, command input history stored in "./{history}"."#, + history = HISTORY_FILE, + ); + + if let Err(e) = cli.load_history(HISTORY_FILE) { + if e.kind() != std::io::ErrorKind::NotFound { + eprintln!("ERROR: failed to load REPL history from {} at {}, {:?}", + HISTORY_FILE, cwd.display(), e); + } + } + + if std::env::var("TERM").map_or(false, |s| { s != "dumb" }) { + // These options need features not available in dumb terminals. + cli.lock_reader().set_blink_matching_paren(true); + } + + loop { + // Make prompt work like in Interlisp-D: + cli.set_prompt(&format!("{}← ", 1 + cli.history_len())).unwrap(); + match cli.read_line() { + Ok(line) => { + match line { + ReadResult::Input(line) => { + runtime.run(|| { + match eval_line(&line, sandboxed) { + Ok(result) => { prn!("{}", result); }, + Err(err) => { eprn!("{}", err.val()); } + } + Ok(()) + }); + cli.add_history_unique(line); + }, + ReadResult::Signal(signal) => { + eprintln!("Signal: {:?}", signal); + break; + }, + ReadResult::Eof => { break; }, + } + }, + Err(err) => { + eprintln!("Error: failed to read command line: {:?}", err); + eprintln!("Switching to degraded mode."); + degraded_repl(runtime, sandboxed); + break; + } + } + } + + if let Err(e) = cli.save_history(HISTORY_FILE) { + eprintln!("Error: failed to store command history in {} from {}, {:?}", + HISTORY_FILE, cwd.display(), e); + } +} + +use std::io::{BufRead}; +fn degraded_repl(runtime: Runtime, sandboxed: bool) { + eprint!("> "); + for line in std::io::stdin().lock().lines() { + match line { + Ok(line) => { + runtime.run(|| { + match eval_line(&line, sandboxed) { + Ok(result) => { prn!("{}", result); }, + Err(err) => { eprn!("{}", err.val()); } + } + Ok(()) + }); + }, + Err(err) => { + eprintln!("Error: failed to read command line: {:?}", err); + break; + } + } + eprint!("> "); + } +} + +fn eval_line(mut line: &str, sandboxed: bool) -> GResult { + line = line.trim_start().trim_end(); + if line == "help" || line.starts_with("help ") { + let url = help(Some(line[4..].trim_start())); + if sandboxed { + prn!("{}", url); + url.to_val() + } else { + match www(Some(&url)) { + Ok(url) => url.to_val(), + Err(err) => Err(err) + } + } + } else { + match glsp::parse_all(line, None) { + Ok(values) => { + match glsp::eval_multi(&values, Some(EnvMode::Copied)) { + Ok(result) => Ok(result), + Err(err) => Err(error!("Evaluation error: {}", err.val())) + } + }, + Err(err) => { + Err(error!("Syntax error: {}", err.val())) + } + } + } +} + +fn help(symbol: Option<&str>) -> String { + let mut symbol = (match symbol.unwrap_or("") { + "=" => "set", + "'" => "quote-abbrv", + "`" => "backquote-abbrv", + "~" => "unquote-abbrv", + ".." => "splay-abbrv", + "@" => "atsign-abbrv", + "." => "meth-name-abbrv", + "[]" => "access-abbrv", + "\"{}\"" => "template-str-abbrv", + "cond==" => "cond-num-eq", + "->" => "arrow-first", + "->>" => "arrow-last", + "+" => "add", + "-" => "sub", + "*" => "mul", + "/" => "div", + "%" => "rem", + "==" => "num-eq", + "<" => "lt", + "<=" => "lte", + ">" => "gt", + ">=" => "gte", + "name" => "name-clause", + "mixin" => "mixin-clause", + "field" => "field-clause", + "const" => "const-clause", + "meth" => "meth-clause", + "prop" => "prop-clause", + "init" => "init-clause", + "init-mixin" => "init-mixin-clause", + "fini" => "fini-clause", + "fini-mixin" => "fini-mixin-clause", + "wrap" => "wrap-clause", + "wrap-prop" => "wrap-prop-clause", + "state" => "state-clause", + "state*" => "statex-clause", + "fsm" => "fsm-clause", + "init-state" => "init-state-clause", + "fini-state" => "fini-state-clause", + symbol => symbol + }) .replace("?", "-p") + .replace("!", "-mut") + .replace("@", "atsign-") + .replace("==", "num-eq-") + .replace("->", "-to-"); + if symbol.ends_with("=") { + symbol.insert_str(0, "set-"); + symbol.pop(); + } + + match symbol.as_str() { + "" => "https://gamelisp.rs/reference/", + "rust" => "https://docs.rs/glsp/", + "lisp"|"car"|"cdr"|"cons"|"first"|"rest"|"nth"|"lambda"|"progn"|"t" => + "ref:introduction-for-lisp-programmers.html", + ";"|","|"#;"|"#|"|"|#"|"#||#" => + "ref:syntax-and-types.html#whitespace", + "#n"|"nil"|"()" => + "ref:syntax-and-types.html#nil", + "#t"|"#f" => + "ref:syntax-and-types.html#bool", + "bool"|"int"|"flo"|"char"|"sym"|"arr"|"str"| + "tab"|"iter"|"obj"|"class"|"fn"|"coro"|"rfn"|"rdata" => + "ref:syntax-and-types.html#type-summary", + "num"|"deque"|"callable"|"expander"|"iterable" => + "ref:syntax-and-types.html#abstract-types", + "std" => "https://gamelisp.rs/std/", + _ => { + symbol.insert_str(0, "https://gamelisp.rs/std/"); + symbol.as_str() + } + }.replace("ref:", "https://gamelisp.rs/reference/") +} + +fn www(url: Option<&str>) -> GResult<&str> { + let url = url.unwrap_or("https://gamelisp.rs"); + match webbrowser::open(url) { + Ok(_) => { + Ok(url) + }, + Err(err) => { + Err(error!("Failed to open URL: {}", url).with_source(err)) + } + } +}