├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── Rhai.toml ├── crates ├── rhai-cli │ ├── Cargo.toml │ └── src │ │ ├── args.rs │ │ ├── bin │ │ └── rhai.rs │ │ ├── execute │ │ ├── config.rs │ │ ├── fmt.rs │ │ ├── lsp.rs │ │ └── mod.rs │ │ └── lib.rs ├── rhai-common │ ├── Cargo.toml │ └── src │ │ ├── config.rs │ │ ├── environment.rs │ │ ├── environment │ │ └── native.rs │ │ ├── lib.rs │ │ ├── log.rs │ │ └── util.rs ├── rhai-fmt │ ├── Cargo.toml │ ├── benches │ │ └── fmt.rs │ ├── src │ │ ├── algorithm.rs │ │ ├── comments.rs │ │ ├── cst.rs │ │ ├── expr.rs │ │ ├── item.rs │ │ ├── lib.rs │ │ ├── options.rs │ │ ├── path.rs │ │ ├── ring.rs │ │ ├── source.rs │ │ └── util.rs │ └── tests │ │ ├── fmt.rs │ │ └── snapshots │ │ ├── fmt__format@array.snap │ │ ├── fmt__format@assignment.snap │ │ ├── fmt__format@char.snap │ │ ├── fmt__format@comments.snap │ │ ├── fmt__format@fibonacci.snap │ │ ├── fmt__format@for1.snap │ │ ├── fmt__format@for2.snap │ │ ├── fmt__format@function_decl1.snap │ │ ├── fmt__format@function_decl2.snap │ │ ├── fmt__format@function_decl3.snap │ │ ├── fmt__format@function_decl4.snap │ │ ├── fmt__format@if1.snap │ │ ├── fmt__format@if2.snap │ │ ├── fmt__format@loop.snap │ │ ├── fmt__format@mat_mul.snap │ │ ├── fmt__format@module.snap │ │ ├── fmt__format@oop.snap │ │ ├── fmt__format@op1.snap │ │ ├── fmt__format@op2.snap │ │ ├── fmt__format@op3.snap │ │ ├── fmt__format@optional_ops.snap │ │ ├── fmt__format@primes.snap │ │ ├── fmt__format@simple.snap │ │ ├── fmt__format@speed_test.snap │ │ ├── fmt__format@string.snap │ │ ├── fmt__format@string_escape.snap │ │ ├── fmt__format@strings_map.snap │ │ ├── fmt__format@switch.snap │ │ ├── fmt__format@template.snap │ │ ├── fmt__format@throw_try_catch.snap │ │ ├── fmt__format@unary_ops.snap │ │ └── fmt__format@while.snap ├── rhai-hir │ ├── Cargo.toml │ ├── src │ │ ├── error.rs │ │ ├── eval.rs │ │ ├── fmt.rs │ │ ├── hir.rs │ │ ├── hir │ │ │ ├── add │ │ │ │ ├── def.rs │ │ │ │ ├── mod.rs │ │ │ │ └── script.rs │ │ │ ├── errors.rs │ │ │ ├── query │ │ │ │ ├── mod.rs │ │ │ │ ├── modules.rs │ │ │ │ ├── scope_iter.rs │ │ │ │ └── types.rs │ │ │ ├── remove.rs │ │ │ └── resolve │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ ├── lib.rs │ │ ├── module.rs │ │ ├── scope.rs │ │ ├── source.rs │ │ ├── symbol.rs │ │ ├── ty.rs │ │ └── util.rs │ └── tests │ │ ├── definitions.rs │ │ ├── imports.rs │ │ ├── smoke.rs │ │ └── visible_symbols.rs ├── rhai-lsp │ ├── Cargo.toml │ └── src │ │ ├── config.rs │ │ ├── diagnostics.rs │ │ ├── handlers.rs │ │ ├── handlers │ │ ├── completion.rs │ │ ├── configuration.rs │ │ ├── convert_offsets.rs │ │ ├── debug.rs │ │ ├── document_symbols.rs │ │ ├── documents.rs │ │ ├── folding_ranges.rs │ │ ├── formatting.rs │ │ ├── goto.rs │ │ ├── hover.rs │ │ ├── initialize.rs │ │ ├── references.rs │ │ ├── rename.rs │ │ ├── semantic_tokens.rs │ │ ├── syntax_tree.rs │ │ ├── watch.rs │ │ └── workspaces.rs │ │ ├── lib.rs │ │ ├── lsp_ext.rs │ │ ├── utils.rs │ │ └── world.rs ├── rhai-rowan │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ └── parse.rs │ ├── build.rs │ ├── src │ │ ├── ast │ │ │ ├── ext.rs │ │ │ ├── mod.rs │ │ │ └── rhai.ungram │ │ ├── lib.rs │ │ ├── parser │ │ │ ├── context.rs │ │ │ ├── mod.rs │ │ │ └── parsers │ │ │ │ ├── def.rs │ │ │ │ ├── mod.rs │ │ │ │ └── ty.rs │ │ ├── query │ │ │ ├── mod.rs │ │ │ ├── tests.rs │ │ │ └── util.rs │ │ ├── syntax.rs │ │ └── util.rs │ └── tests │ │ ├── smoke.rs │ │ └── snapshots │ │ ├── smoke__parse_custom_operator.snap │ │ ├── smoke__parse_valid@array.snap │ │ ├── smoke__parse_valid@assignment.snap │ │ ├── smoke__parse_valid@char.snap │ │ ├── smoke__parse_valid@comments.snap │ │ ├── smoke__parse_valid@fibonacci.snap │ │ ├── smoke__parse_valid@for1.snap │ │ ├── smoke__parse_valid@for2.snap │ │ ├── smoke__parse_valid@function_decl1.snap │ │ ├── smoke__parse_valid@function_decl2.snap │ │ ├── smoke__parse_valid@function_decl3.snap │ │ ├── smoke__parse_valid@function_decl4.snap │ │ ├── smoke__parse_valid@if1.snap │ │ ├── smoke__parse_valid@if2.snap │ │ ├── smoke__parse_valid@loop.snap │ │ ├── smoke__parse_valid@mat_mul.snap │ │ ├── smoke__parse_valid@module.snap │ │ ├── smoke__parse_valid@oop.snap │ │ ├── smoke__parse_valid@op1.snap │ │ ├── smoke__parse_valid@op2.snap │ │ ├── smoke__parse_valid@op3.snap │ │ ├── smoke__parse_valid@optional_ops.snap │ │ ├── smoke__parse_valid@primes.snap │ │ ├── smoke__parse_valid@simple.snap │ │ ├── smoke__parse_valid@speed_test.snap │ │ ├── smoke__parse_valid@string.snap │ │ ├── smoke__parse_valid@string_escape.snap │ │ ├── smoke__parse_valid@strings_map.snap │ │ ├── smoke__parse_valid@switch.snap │ │ ├── smoke__parse_valid@template.snap │ │ ├── smoke__parse_valid@throw_try_catch.snap │ │ ├── smoke__parse_valid@unary_ops.snap │ │ └── smoke__parse_valid@while.snap ├── rhai-sourcegen │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── syntax │ │ ├── decl.rs │ │ └── mod.rs │ │ └── util.rs └── rhai-wasm │ ├── .gitignore │ ├── Cargo.toml │ └── src │ ├── environment.rs │ ├── lib.rs │ └── lsp.rs ├── editors └── vscode │ ├── .gitignore │ ├── .vscodeignore │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── assets │ └── rhai-icon-transparent-black.svg │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── client.ts │ ├── commands │ │ └── index.ts │ ├── extension.ts │ ├── server.ts │ ├── syntax-tree.ts │ └── util.ts │ ├── syntax │ ├── rhai.configuration.json │ ├── rhai.markdown.json │ ├── rhai.tmLanguage.json │ └── rhai.tmLanguage.yaml │ ├── tsconfig.json │ └── yarn.lock ├── examples ├── bar.d.rhai ├── bar.rhai ├── external.d.rhai ├── foo.rhai ├── global.d.rhai ├── main.rhai ├── nested.d.rhai ├── static.d.rhai └── static2.d.rhai ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz-parser.rs ├── images └── bench.png ├── js ├── core │ ├── .gitignore │ ├── .npmignore │ ├── .yarn │ │ └── install-state.gz │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── config.ts │ │ ├── environment.ts │ │ ├── index.ts │ │ └── lsp.ts │ ├── tsconfig.json │ └── yarn.lock └── lsp │ ├── .gitignore │ ├── .npmignore │ ├── .yarn │ └── install-state.gz │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── index.ts │ └── rust.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── rustfmt.toml ├── taplo.toml └── testdata ├── benchmarks ├── array.rhai ├── assignment.rhai ├── comments.rhai ├── fibonacci.rhai ├── for1.rhai ├── for2.rhai ├── function_decl1.rhai ├── function_decl2.rhai ├── function_decl3.rhai ├── function_decl4.rhai ├── if1.rhai ├── if2.rhai ├── loop.rhai ├── mat_mul.rhai ├── module.rhai ├── oop.rhai ├── op1.rhai ├── op2.rhai ├── op3.rhai ├── primes.rhai ├── speed_test.rhai ├── string.rhai ├── strings_map.rhai ├── switch.rhai └── while.rhai └── valid ├── array.rhai ├── assignment.rhai ├── char.rhai ├── comments.rhai ├── doc-comments.rhai ├── fibonacci.rhai ├── for1.rhai ├── for2.rhai ├── for3.rhai ├── function_decl1.rhai ├── function_decl2.rhai ├── function_decl3.rhai ├── function_decl4.rhai ├── if1.rhai ├── if2.rhai ├── loop.rhai ├── mat_mul.rhai ├── module.rhai ├── oop.rhai ├── op1.rhai ├── op2.rhai ├── op3.rhai ├── operators.rhai ├── optional_ops.rhai ├── primes.rhai ├── simple.rhai ├── speed_test.rhai ├── string.rhai ├── string_escape.rhai ├── strings_map.rhai ├── switch.rhai ├── template.rhai ├── throw_try_catch.rhai ├── unary_ops.rhai └── while.rhai /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.local* 4 | yarn-error.log 5 | .vscode 6 | .cargo/config.toml 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*", "fuzz"] 3 | exclude = ["crates/rhai-wasm"] 4 | 5 | [profile.release] 6 | lto = true 7 | codegen-units = 1 8 | opt-level = 3 9 | 10 | [profile.bench] 11 | lto = true 12 | codegen-units = 1 13 | opt-level = 3 14 | -------------------------------------------------------------------------------- /Rhai.toml: -------------------------------------------------------------------------------- 1 | [source] 2 | include = ["examples/**/*.rhai"] 3 | -------------------------------------------------------------------------------- /crates/rhai-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | rhai-common = { version = "0.1.0", path = "../rhai-common" } 9 | rhai-lsp = { version = "0.1.0", path = "../rhai-lsp" } 10 | 11 | clap = { version = "4.0.0", features = ["derive", "cargo"] } 12 | rhai = "1.8.0" 13 | anyhow = "1.0.59" 14 | async-ctrlc = { version = "1.2.0", features = ["stream"] } 15 | tracing = "0.1.36" 16 | rhai-fmt = { version = "0.1.0", path = "../rhai-fmt", features = ["schema"] } 17 | schemars = "0.8.10" 18 | toml = "0.5.9" 19 | figment = "0.10.6" 20 | serde_json = "1.0.85" 21 | codespan-reporting = "0.11.1" 22 | rhai-rowan = { version = "0.1.0", path = "../rhai-rowan" } 23 | itertools = "0.10.3" 24 | glob = "0.3.0" 25 | rhai-hir = { version = "0.1.0", path = "../rhai-hir" } 26 | url = "2.2.2" 27 | 28 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 29 | atty = "0.2.14" 30 | tokio = { version = "1.19.2", features = [ 31 | "sync", 32 | "fs", 33 | "time", 34 | "io-std", 35 | "rt-multi-thread", 36 | "parking_lot", 37 | ] } 38 | lsp-async-stub = { version = "0.6.0", features = ["tokio-tcp", "tokio-stdio"] } 39 | 40 | [target.'cfg(target_arch = "wasm32")'.dependencies] 41 | tokio = { version = "1.19.2", features = ["sync", "parking_lot", "io-util"] } 42 | -------------------------------------------------------------------------------- /crates/rhai-cli/src/args.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{crate_version, Parser, Subcommand, ValueEnum}; 4 | 5 | #[derive(Clone, Parser)] 6 | #[clap(name = "rhai")] 7 | #[clap(bin_name = "rhai")] 8 | #[clap(version = crate_version!())] 9 | pub struct RhaiArgs { 10 | #[clap(long, global = true, default_value = "auto")] 11 | pub colors: Colors, 12 | /// Enable a verbose logging format. 13 | #[clap(long, global = true)] 14 | pub verbose: bool, 15 | /// Enable logging spans. 16 | #[clap(long, global = true)] 17 | pub log_spans: bool, 18 | /// Path to `Rhai.toml` configuration file. 19 | #[clap(long, global = true)] 20 | pub config: Option, 21 | #[clap(subcommand)] 22 | pub cmd: RootCommand, 23 | } 24 | 25 | #[derive(Clone, Subcommand)] 26 | pub enum RootCommand { 27 | /// Language server operations. 28 | Lsp { 29 | #[clap(subcommand)] 30 | cmd: LspCommand, 31 | }, 32 | /// Configuration file operations 33 | #[clap(visible_aliases = &["cfg"])] 34 | Config { 35 | #[clap(subcommand)] 36 | cmd: ConfigCommand, 37 | }, 38 | /// Format Rhai source code. 39 | #[clap(visible_aliases = &["format"])] 40 | Fmt(FmtCommand), 41 | } 42 | 43 | #[derive(Clone, Subcommand)] 44 | pub enum LspCommand { 45 | /// Run the language server and listen on a TCP address. 46 | Tcp { 47 | /// The address to listen on. 48 | #[clap(long, default_value = "0.0.0.0:9181")] 49 | address: String, 50 | }, 51 | /// Run the language server over the standard input and output. 52 | Stdio, 53 | } 54 | 55 | #[derive(Clone, Subcommand)] 56 | pub enum ConfigCommand { 57 | /// Print the configuration JSON schema. 58 | Schema, 59 | /// Create a new configuration file with default values. 60 | Init { 61 | /// Output file path. 62 | #[clap(short = 'o', long, default_value = "Rhai.toml")] 63 | output: String, 64 | }, 65 | } 66 | 67 | #[derive(Clone, Parser)] 68 | pub struct FmtCommand { 69 | /// Proceed with formatting even if the files contain 70 | /// syntax errors. 71 | #[clap(short, long)] 72 | pub force: bool, 73 | 74 | /// Dry-run and report any files that are not correctly formatted. 75 | #[clap(long)] 76 | pub check: bool, 77 | 78 | /// Optional pattern to search for files. 79 | /// 80 | /// If not provided, it will be determined by 81 | /// the configuration. 82 | pub files: Option, 83 | } 84 | 85 | #[derive(Clone, Copy, ValueEnum)] 86 | pub enum Colors { 87 | /// Determine whether to colorize output automatically. 88 | Auto, 89 | /// Always colorize output. 90 | Always, 91 | /// Never colorize output. 92 | Never, 93 | } 94 | -------------------------------------------------------------------------------- /crates/rhai-cli/src/bin/rhai.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use rhai_cli::{ 3 | args::{Colors, RhaiArgs}, 4 | Rhai, 5 | }; 6 | use rhai_common::{environment::native::NativeEnvironment, log::setup_stderr_logging}; 7 | use std::process::exit; 8 | use tracing::Instrument; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let cli = RhaiArgs::parse(); 13 | setup_stderr_logging( 14 | NativeEnvironment, 15 | cli.log_spans, 16 | cli.verbose, 17 | match cli.colors { 18 | Colors::Auto => None, 19 | Colors::Always => Some(true), 20 | Colors::Never => Some(false), 21 | }, 22 | ); 23 | 24 | match Rhai::new(NativeEnvironment) 25 | .execute(cli) 26 | .instrument(tracing::info_span!("rhai")) 27 | .await 28 | { 29 | Ok(_) => { 30 | exit(0); 31 | } 32 | Err(error) => { 33 | tracing::error!(error = %format!("{error:#}"), "operation failed"); 34 | exit(1); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/rhai-cli/src/execute/config.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Context; 4 | use rhai_common::{config::Config, environment::Environment}; 5 | use tokio::io::AsyncWriteExt; 6 | 7 | use crate::{args::ConfigCommand, Rhai}; 8 | 9 | impl Rhai { 10 | pub async fn execute_config(&self, cmd: ConfigCommand) -> Result<(), anyhow::Error> { 11 | let mut stdout = self.env.stdout(); 12 | 13 | match cmd { 14 | ConfigCommand::Schema => { 15 | stdout 16 | .write_all(&serde_json::to_vec(&schemars::schema_for!(Config))?) 17 | .await?; 18 | } 19 | ConfigCommand::Init { output } => { 20 | let mut p = PathBuf::from(output); 21 | 22 | if !self.env.is_absolute(&p) { 23 | p = self.env.cwd().context("invalid working directory")?.join(p); 24 | } 25 | 26 | if self.env.read_file(&p).await.is_ok() { 27 | tracing::info!("already initialized"); 28 | return Ok(()); 29 | } 30 | 31 | self.env 32 | .write_file( 33 | &p, 34 | toml::to_string_pretty(&Config::default()) 35 | .unwrap() 36 | .as_bytes(), 37 | ) 38 | .await?; 39 | } 40 | } 41 | 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/rhai-cli/src/execute/fmt.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::{anyhow, Context}; 4 | use codespan_reporting::files::SimpleFile; 5 | use rhai_common::{environment::Environment, util::Normalize}; 6 | use rhai_fmt::format_syntax; 7 | 8 | use crate::{args::FmtCommand, Rhai}; 9 | 10 | impl Rhai { 11 | pub async fn execute_fmt(&mut self, cmd: FmtCommand) -> Result<(), anyhow::Error> { 12 | let cwd = self 13 | .env 14 | .cwd() 15 | .context("invalid working directory")? 16 | .normalize(); 17 | 18 | let hir = self.load_hir(&cwd).await?; 19 | 20 | if let Some(mut files) = cmd.files { 21 | if self.env.is_dir(Path::new(&files)) { 22 | files = PathBuf::from(files) 23 | .join("**/*.rhai") 24 | .normalize() 25 | .to_string_lossy() 26 | .into(); 27 | } 28 | 29 | self.config.source.include = Some(vec![files]); 30 | self.config.source.exclude = None; 31 | } 32 | 33 | self.config.prepare( 34 | &self.env, 35 | &self.env.cwd().context("invalid working directory")?, 36 | )?; 37 | 38 | let files = self.collect_files(&cwd, &self.config, true).await?; 39 | 40 | let mut result = Ok(()); 41 | 42 | let mut format_opts = rhai_fmt::Options::default(); 43 | format_opts.update(self.config.fmt.options.clone()); 44 | 45 | for path in files { 46 | let f = self.env.read_file(&path).await?; 47 | let source = String::from_utf8_lossy(&f).into_owned(); 48 | 49 | let parser = rhai_rowan::Parser::new(&source).with_operators(hir.parser_operators()); 50 | 51 | let p = if rhai_rowan::util::is_rhai_def(&source) { 52 | parser.parse_def() 53 | } else { 54 | parser.parse_script() 55 | }; 56 | 57 | if !p.errors.is_empty() { 58 | self.print_parse_errors( 59 | &SimpleFile::new(&*path.to_string_lossy(), source.as_str()), 60 | &p.errors, 61 | ) 62 | .await?; 63 | 64 | if !cmd.force { 65 | if cmd.check { 66 | result = Err(anyhow!("some files had syntax errors")); 67 | } else { 68 | result = Err(anyhow!( 69 | "some files were not formatted due to syntax errors" 70 | )); 71 | } 72 | 73 | continue; 74 | } 75 | } 76 | 77 | let formatted = format_syntax(p.into_syntax(), format_opts.clone()); 78 | 79 | if source != formatted { 80 | if cmd.check { 81 | tracing::error!(?path, "the file is not properly formatted"); 82 | result = Err(anyhow!("some files were not properly formatted")); 83 | } else { 84 | self.env.write_file(&path, formatted.as_bytes()).await?; 85 | } 86 | } 87 | } 88 | 89 | result 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/rhai-cli/src/execute/lsp.rs: -------------------------------------------------------------------------------- 1 | use rhai_common::environment::{native::NativeEnvironment, Environment}; 2 | 3 | use crate::{args::LspCommand, Rhai}; 4 | 5 | impl Rhai { 6 | pub async fn execute_lsp(&self, cmd: LspCommand) -> Result<(), anyhow::Error> { 7 | let server = rhai_lsp::create_server(); 8 | let world = rhai_lsp::create_world(NativeEnvironment); 9 | 10 | match cmd { 11 | LspCommand::Tcp { address } => { 12 | server 13 | .listen_tcp(world, &address, async_ctrlc::CtrlC::new().unwrap()) 14 | .await 15 | } 16 | LspCommand::Stdio {} => { 17 | server 18 | .listen_stdio(world, async_ctrlc::CtrlC::new().unwrap()) 19 | .await 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/rhai-cli/src/execute/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | args::{Colors, RhaiArgs, RootCommand}, 3 | Rhai, 4 | }; 5 | use rhai_common::environment::Environment; 6 | 7 | mod config; 8 | mod fmt; 9 | mod lsp; 10 | 11 | impl Rhai { 12 | pub async fn execute(&mut self, args: RhaiArgs) -> Result<(), anyhow::Error> { 13 | if let RootCommand::Fmt(_) = &args.cmd { 14 | self.load_config(&args).await? 15 | } 16 | 17 | self.colors = match args.colors { 18 | Colors::Auto => self.env.atty_stderr(), 19 | Colors::Always => true, 20 | Colors::Never => false, 21 | }; 22 | 23 | match args.cmd { 24 | RootCommand::Lsp { cmd } => self.execute_lsp(cmd).await, 25 | RootCommand::Config { cmd } => self.execute_config(cmd).await, 26 | RootCommand::Fmt(cmd) => self.execute_fmt(cmd).await, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/rhai-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.59" 10 | async-trait = "0.1.57" 11 | futures = "0.3.21" 12 | globset = "0.4.9" 13 | percent-encoding = "2.1.0" 14 | rhai-fmt = { version = "0.1.0", path = "../rhai-fmt", features = ["schema"] } 15 | schemars = "0.8.10" 16 | serde = { version = "1.0.142", features = ["derive"] } 17 | serde_json = "1.0.83" 18 | tracing = "0.1.36" 19 | tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } 20 | url = "2.2.2" 21 | 22 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 23 | tokio = { version = "1.19.2", features = [ 24 | "rt", 25 | "rt-multi-thread", 26 | "sync", 27 | "parking_lot", 28 | "macros", 29 | "fs", 30 | "time", 31 | ] } 32 | glob = "0.3.0" 33 | atty = "0.2.14" 34 | 35 | [target.'cfg(target_arch = "wasm32")'.dependencies] 36 | tokio = { version = "1.19.2", features = ["io-util"]} 37 | -------------------------------------------------------------------------------- /crates/rhai-common/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | environment::Environment, 3 | util::{GlobRule, Normalize}, 4 | }; 5 | use schemars::JsonSchema; 6 | use serde::{Deserialize, Serialize}; 7 | use std::path::Path; 8 | 9 | #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] 10 | pub struct Config { 11 | #[serde(default)] 12 | pub source: SourceConfig, 13 | #[serde(default)] 14 | pub fmt: FmtConfig, 15 | } 16 | 17 | impl Config { 18 | pub fn prepare(&mut self, e: &impl Environment, base: &Path) -> anyhow::Result<()> { 19 | self.source.prepare(e, base) 20 | } 21 | } 22 | 23 | #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] 24 | pub struct SourceConfig { 25 | /// A list of UNIX-style glob patterns 26 | /// for Rhai files that should be included. 27 | pub include: Option>, 28 | /// A list of UNIX-style glob patterns. 29 | /// For Rhai files that should be excluded. 30 | pub exclude: Option>, 31 | 32 | #[serde(skip)] 33 | pub file_rule: Option, 34 | } 35 | 36 | impl SourceConfig { 37 | pub fn prepare(&mut self, e: &impl Environment, base: &Path) -> anyhow::Result<()> { 38 | self.include = self 39 | .include 40 | .take() 41 | .or_else(|| Some(vec![String::from("**/*.rhai")])); 42 | self.make_absolute(e, base); 43 | 44 | self.file_rule = Some(GlobRule::new( 45 | self.include.as_deref().unwrap(), 46 | self.exclude.as_deref().unwrap_or(&[] as &[String]), 47 | )?); 48 | 49 | Ok(()) 50 | } 51 | 52 | #[must_use] 53 | pub fn is_included(&self, path: &Path) -> bool { 54 | match &self.file_rule { 55 | Some(r) => r.is_match(path), 56 | None => { 57 | tracing::debug!("no file matches were set up"); 58 | false 59 | } 60 | } 61 | } 62 | 63 | fn make_absolute(&mut self, e: &impl Environment, base: &Path) { 64 | if let Some(included) = &mut self.include { 65 | for pat in included { 66 | if !e.is_absolute(Path::new(pat)) { 67 | *pat = base 68 | .join(pat.as_str()) 69 | .normalize() 70 | .to_string_lossy() 71 | .into_owned(); 72 | } 73 | } 74 | } 75 | 76 | if let Some(excluded) = &mut self.exclude { 77 | for pat in excluded { 78 | if !e.is_absolute(Path::new(pat)) { 79 | *pat = base 80 | .join(pat.as_str()) 81 | .normalize() 82 | .to_string_lossy() 83 | .into_owned(); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] 91 | pub struct FmtConfig { 92 | #[serde(default)] 93 | pub options: rhai_fmt::options::OptionsIncomplete, 94 | } 95 | -------------------------------------------------------------------------------- /crates/rhai-common/src/environment.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use futures::Future; 3 | use std::{ 4 | path::{Path, PathBuf}, 5 | time::Duration, 6 | }; 7 | use tokio::io::{AsyncRead, AsyncWrite}; 8 | use url::Url; 9 | 10 | #[cfg(not(target_family = "wasm"))] 11 | pub mod native; 12 | 13 | #[async_trait(?Send)] 14 | pub trait Environment: Clone + Send + Sync + 'static { 15 | type Stdin: AsyncRead + Unpin; 16 | type Stdout: AsyncWrite + Unpin; 17 | type Stderr: AsyncWrite + Unpin; 18 | 19 | fn atty_stderr(&self) -> bool; 20 | fn stdin(&self) -> Self::Stdin; 21 | fn stdout(&self) -> Self::Stdout; 22 | fn stderr(&self) -> Self::Stderr; 23 | 24 | fn spawn(&self, fut: F) 25 | where 26 | F: Future + Send + 'static, 27 | F::Output: Send; 28 | 29 | fn spawn_local(&self, fut: F) 30 | where 31 | F: Future + 'static; 32 | 33 | fn env_var(&self, name: &str) -> Option; 34 | 35 | async fn read_file(&self, path: &Path) -> Result, anyhow::Error>; 36 | 37 | async fn write_file(&self, path: &Path, bytes: &[u8]) -> Result<(), anyhow::Error>; 38 | 39 | fn url_to_file_path(&self, url: &Url) -> Option; 40 | 41 | /// Absolute current working dir. 42 | fn cwd(&self) -> Option; 43 | 44 | fn glob_files(&self, glob: &str) -> Result, anyhow::Error>; 45 | 46 | fn is_absolute(&self, path: &Path) -> bool; 47 | 48 | fn discover_rhai_config(&self, root: &Path) -> Option; 49 | 50 | fn is_dir(&self, root: &Path) -> bool; 51 | 52 | async fn sleep(&self, duration: Duration); 53 | } 54 | -------------------------------------------------------------------------------- /crates/rhai-common/src/environment/native.rs: -------------------------------------------------------------------------------- 1 | use super::Environment; 2 | use async_trait::async_trait; 3 | use std::{ 4 | path::{Path, PathBuf}, 5 | time::Duration, 6 | }; 7 | use url::Url; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct NativeEnvironment; 11 | 12 | #[async_trait(?Send)] 13 | impl Environment for NativeEnvironment { 14 | type Stdin = tokio::io::Stdin; 15 | type Stdout = tokio::io::Stdout; 16 | type Stderr = tokio::io::Stderr; 17 | 18 | fn atty_stderr(&self) -> bool { 19 | atty::is(atty::Stream::Stderr) 20 | } 21 | 22 | fn stdin(&self) -> Self::Stdin { 23 | tokio::io::stdin() 24 | } 25 | 26 | fn stdout(&self) -> Self::Stdout { 27 | tokio::io::stdout() 28 | } 29 | 30 | fn stderr(&self) -> Self::Stderr { 31 | tokio::io::stderr() 32 | } 33 | 34 | fn spawn(&self, fut: F) 35 | where 36 | F: futures::Future + Send + 'static, 37 | F::Output: Send, 38 | { 39 | tokio::spawn(fut); 40 | } 41 | 42 | fn spawn_local(&self, fut: F) 43 | where 44 | F: futures::Future + 'static, 45 | { 46 | tokio::task::spawn_local(fut); 47 | } 48 | 49 | fn env_var(&self, name: &str) -> Option { 50 | std::env::var(name).ok() 51 | } 52 | 53 | async fn read_file(&self, path: &Path) -> Result, anyhow::Error> { 54 | Ok(tokio::fs::read(path).await?) 55 | } 56 | 57 | async fn write_file(&self, path: &std::path::Path, bytes: &[u8]) -> Result<(), anyhow::Error> { 58 | Ok(tokio::fs::write(path, bytes).await?) 59 | } 60 | 61 | fn url_to_file_path(&self, url: &Url) -> Option { 62 | url.to_file_path().ok() 63 | } 64 | 65 | fn cwd(&self) -> Option { 66 | std::env::current_dir().ok() 67 | } 68 | 69 | fn glob_files(&self, pattern: &str) -> Result, anyhow::Error> { 70 | let paths = glob::glob_with( 71 | pattern, 72 | glob::MatchOptions { 73 | case_sensitive: true, 74 | ..Default::default() 75 | }, 76 | )?; 77 | Ok(paths.filter_map(Result::ok).collect()) 78 | } 79 | 80 | fn is_absolute(&self, base: &std::path::Path) -> bool { 81 | base.is_absolute() 82 | } 83 | 84 | fn discover_rhai_config(&self, root: &Path) -> Option { 85 | let path = root.join("Rhai.toml"); 86 | 87 | if let Ok(meta) = std::fs::metadata(root.join("Rhai.toml")) { 88 | if meta.is_file() { 89 | return Some(path); 90 | } 91 | } 92 | 93 | None 94 | } 95 | 96 | async fn sleep(&self, duration: Duration) { 97 | tokio::time::sleep(duration).await; 98 | } 99 | 100 | fn is_dir(&self, root: &Path) -> bool { 101 | root.is_dir() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /crates/rhai-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod environment; 3 | pub mod log; 4 | pub mod util; 5 | -------------------------------------------------------------------------------- /crates/rhai-common/src/util.rs: -------------------------------------------------------------------------------- 1 | use globset::{Glob, GlobSetBuilder}; 2 | use std::{ 3 | mem, 4 | path::{Path, PathBuf}, borrow::Cow, 5 | }; 6 | use url::Url; 7 | use percent_encoding::percent_decode_str; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct GlobRule { 11 | include: globset::GlobSet, 12 | exclude: globset::GlobSet, 13 | } 14 | 15 | impl GlobRule { 16 | pub fn new( 17 | include: impl IntoIterator>, 18 | exclude: impl IntoIterator>, 19 | ) -> Result { 20 | let mut inc = GlobSetBuilder::new(); 21 | for glob in include { 22 | inc.add(Glob::new(glob.as_ref())?); 23 | } 24 | 25 | let mut exc = GlobSetBuilder::new(); 26 | for glob in exclude { 27 | exc.add(Glob::new(glob.as_ref())?); 28 | } 29 | 30 | Ok(Self { 31 | include: inc.build()?, 32 | exclude: exc.build()?, 33 | }) 34 | } 35 | 36 | pub fn is_match(&self, text: impl AsRef) -> bool { 37 | if !self.include.is_match(text.as_ref()) { 38 | return false; 39 | } 40 | 41 | !self.exclude.is_match(text.as_ref()) 42 | } 43 | } 44 | 45 | pub trait Normalize { 46 | /// Normalizing in the context of this library means the following: 47 | /// 48 | /// - replace `\` with `/` on windows 49 | /// - decode all percent-encoded characters 50 | #[must_use] 51 | fn normalize(self) -> Self; 52 | } 53 | 54 | impl Normalize for PathBuf { 55 | fn normalize(self) -> Self { 56 | match self.to_str() { 57 | Some(s) => (*normalize_str(s)).into(), 58 | None => self, 59 | } 60 | } 61 | } 62 | 63 | impl Normalize for Vec { 64 | fn normalize(mut self) -> Self { 65 | for p in &mut self { 66 | *p = mem::take(p).normalize(); 67 | } 68 | self 69 | } 70 | } 71 | 72 | impl Normalize for Url { 73 | fn normalize(self) -> Self { 74 | if self.scheme() != "file" { 75 | return self; 76 | } 77 | 78 | if let Ok(u) = normalize_str(self.as_str()).parse() { 79 | return u; 80 | } 81 | 82 | self 83 | } 84 | } 85 | 86 | pub(crate) fn normalize_str(s: &str) -> Cow { 87 | let percent_decoded = match percent_decode_str(s).decode_utf8().ok() { 88 | Some(s) => s, 89 | None => return s.into(), 90 | }; 91 | 92 | if cfg!(windows) { 93 | percent_decoded.replace('\\', "/").into() 94 | } else { 95 | percent_decoded 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/rhai-fmt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-fmt" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | rhai-rowan = { version = "0.1.0", path = "../rhai-rowan" } 9 | rowan = "0.15.8" 10 | schemars = { version = "0.8.10", optional = true } 11 | serde = { version = "1.0.144", features = ["derive"] } 12 | tracing = "0.1.36" 13 | 14 | [dev-dependencies] 15 | criterion = "0.4.0" 16 | insta = "1.19.1" 17 | test-case = "2.2.1" 18 | tracing-subscriber = "0.3.15" 19 | 20 | [features] 21 | schema = ["schemars"] 22 | 23 | [[bench]] 24 | name = "fmt" 25 | harness = false 26 | -------------------------------------------------------------------------------- /crates/rhai-fmt/benches/fmt.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::OsStr, fs, path::Path}; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; 4 | use rhai_fmt::{format_source, format_syntax}; 5 | 6 | fn mega_script() -> String { 7 | let root_path = Path::new(env!("CARGO_MANIFEST_DIR")) 8 | .join("..") 9 | .join("..") 10 | .join("testdata") 11 | .join("valid"); 12 | let scripts = fs::read_dir(&root_path).unwrap(); 13 | 14 | let mut script = String::new(); 15 | 16 | for entry in scripts { 17 | let entry = entry.unwrap(); 18 | 19 | if entry.path().extension() == Some(OsStr::new("rhai")) { 20 | script += &fs::read_to_string(root_path.join(entry.path())).unwrap(); 21 | script += ";\n"; 22 | } 23 | } 24 | 25 | script 26 | } 27 | 28 | pub fn criterion_benchmark(c: &mut Criterion) { 29 | let source = mega_script(); 30 | 31 | let parsed = rhai_rowan::parser::Parser::new(&source) 32 | .parse_script() 33 | .into_syntax(); 34 | 35 | let mut group = c.benchmark_group("fmt-throughput"); 36 | group.throughput(Throughput::Bytes(source.len() as u64)); 37 | group.bench_function("fmt all", |b| { 38 | b.iter(|| format_source(black_box(&source), Default::default())) 39 | }); 40 | group.bench_function("fmt parsed", |b| { 41 | b.iter(|| format_syntax(black_box(parsed.clone()), Default::default())) 42 | }); 43 | group.finish(); 44 | } 45 | 46 | criterion_group!(benches, criterion_benchmark); 47 | criterion_main!(benches); 48 | -------------------------------------------------------------------------------- /crates/rhai-fmt/src/item.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use rhai_rowan::ast::{AstNode, Item, Stmt}; 4 | 5 | use crate::{algorithm::Formatter, util::ScopedStatic}; 6 | 7 | impl Formatter { 8 | pub(crate) fn fmt_stmt(&mut self, stmt: Stmt) -> io::Result<()> { 9 | if let Some(item) = stmt.item() { 10 | self.fmt_item(item)?; 11 | } 12 | Ok(()) 13 | } 14 | 15 | pub(crate) fn fmt_item(&mut self, item: Item) -> io::Result<()> { 16 | self.cbox(0); 17 | 18 | for doc in item.docs() { 19 | self.fmt_doc(doc)?; 20 | } 21 | 22 | if let Some(expr) = item.expr() { 23 | self.fmt_expr(expr)?; 24 | } 25 | 26 | self.end(); 27 | 28 | Ok(()) 29 | } 30 | 31 | pub(crate) fn fmt_doc(&mut self, doc: rhai_rowan::ast::Doc) -> Result<(), io::Error> { 32 | let syntax = doc.syntax(); 33 | if let Some(t) = doc.token() { 34 | self.word(t.static_text().trim_end())?; 35 | self.comment_same_line_after(&syntax)?; 36 | self.standalone_comments_after(&syntax, false)?; 37 | self.hardbreak(); 38 | }; 39 | Ok(()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/rhai-fmt/src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![warn(clippy::pedantic)] 2 | #![allow( 3 | clippy::cast_possible_wrap, 4 | clippy::cast_sign_loss, 5 | clippy::derive_partial_eq_without_eq, 6 | clippy::doc_markdown, 7 | clippy::enum_glob_use, 8 | clippy::items_after_statements, 9 | clippy::match_like_matches_macro, 10 | clippy::match_same_arms, 11 | clippy::module_name_repetitions, 12 | clippy::must_use_candidate, 13 | clippy::needless_pass_by_value, 14 | clippy::similar_names, 15 | clippy::too_many_lines, 16 | clippy::unused_self, 17 | clippy::vec_init_then_push, 18 | clippy::wildcard_imports, 19 | clippy::cast_possible_truncation 20 | )] 21 | 22 | mod algorithm; 23 | mod comments; 24 | mod cst; 25 | mod expr; 26 | mod item; 27 | mod path; 28 | mod ring; 29 | mod source; 30 | mod util; 31 | 32 | pub mod options; 33 | 34 | pub use options::Options; 35 | use rhai_rowan::{parser::Parser, syntax::SyntaxElement}; 36 | 37 | pub use algorithm::Formatter; 38 | 39 | #[must_use] 40 | #[allow(clippy::missing_panics_doc)] 41 | pub fn format_syntax(cst: impl Into, options: Options) -> String { 42 | let mut s = Vec::new(); 43 | Formatter::new_with_options(&mut s, options) 44 | .format(cst) 45 | .unwrap(); 46 | // SAFETY: we only print valid UTF-8. 47 | unsafe { String::from_utf8_unchecked(s) } 48 | } 49 | 50 | #[must_use] 51 | #[allow(clippy::missing_panics_doc)] 52 | pub fn format_source(src: &str, options: Options) -> String { 53 | let mut s = Vec::new(); 54 | 55 | let cst = if rhai_rowan::util::is_rhai_def(src) { 56 | Parser::new(src).parse_def().into_syntax() 57 | } else { 58 | Parser::new(src).parse_script().into_syntax() 59 | }; 60 | 61 | Formatter::new_with_options(&mut s, options) 62 | .format(cst) 63 | .unwrap(); 64 | // SAFETY: we only print valid UTF-8. 65 | unsafe { String::from_utf8_unchecked(s) } 66 | } 67 | -------------------------------------------------------------------------------- /crates/rhai-fmt/src/path.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use rhai_rowan::ast::Path; 4 | 5 | use crate::{algorithm::Formatter, util::ScopedStatic}; 6 | 7 | impl Formatter { 8 | pub(crate) fn fmt_path(&mut self, path: Path) -> io::Result<()> { 9 | let mut first = true; 10 | for segment in path.segments() { 11 | if !first { 12 | self.word("::")?; 13 | } 14 | first = false; 15 | 16 | self.word(segment.static_text())?; 17 | } 18 | Ok(()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/rhai-fmt/src/ring.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::ops::{Index, IndexMut}; 3 | 4 | pub struct RingBuffer { 5 | data: VecDeque, 6 | // Abstract index of data[0] in infinitely sized queue 7 | offset: usize, 8 | } 9 | 10 | impl RingBuffer { 11 | pub fn new() -> Self { 12 | RingBuffer { 13 | data: VecDeque::new(), 14 | offset: 0, 15 | } 16 | } 17 | 18 | pub fn is_empty(&self) -> bool { 19 | self.data.is_empty() 20 | } 21 | 22 | pub fn len(&self) -> usize { 23 | self.data.len() 24 | } 25 | 26 | pub fn push(&mut self, value: T) -> usize { 27 | let index = self.offset + self.data.len(); 28 | self.data.push_back(value); 29 | index 30 | } 31 | 32 | pub fn clear(&mut self) { 33 | self.data.clear(); 34 | } 35 | 36 | pub fn index_of_first(&self) -> usize { 37 | self.offset 38 | } 39 | 40 | pub fn first(&self) -> &T { 41 | &self.data[0] 42 | } 43 | 44 | pub fn first_mut(&mut self) -> &mut T { 45 | &mut self.data[0] 46 | } 47 | 48 | pub fn pop_first(&mut self) -> T { 49 | self.offset += 1; 50 | self.data.pop_front().unwrap() 51 | } 52 | 53 | pub fn last(&self) -> &T { 54 | self.data.back().unwrap() 55 | } 56 | 57 | pub fn last_mut(&mut self) -> &mut T { 58 | self.data.back_mut().unwrap() 59 | } 60 | 61 | pub fn second_last(&self) -> &T { 62 | &self.data[self.data.len() - 2] 63 | } 64 | 65 | pub fn pop_last(&mut self) { 66 | self.data.pop_back().unwrap(); 67 | } 68 | } 69 | 70 | impl Index for RingBuffer { 71 | type Output = T; 72 | fn index(&self, index: usize) -> &Self::Output { 73 | &self.data[index.checked_sub(self.offset).unwrap()] 74 | } 75 | } 76 | 77 | impl IndexMut for RingBuffer { 78 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 79 | &mut self.data[index.checked_sub(self.offset).unwrap()] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/rhai-fmt/src/source.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use rhai_rowan::{ 4 | ast::{AstNode, Item, Rhai}, 5 | syntax::SyntaxKind::*, 6 | T, 7 | }; 8 | 9 | use crate::{algorithm::Formatter, util::ScopedStatic}; 10 | 11 | impl Formatter { 12 | pub(crate) fn fmt_rhai(&mut self, rhai: Rhai) -> io::Result<()> { 13 | self.cbox(0); 14 | 15 | if let Some(t) = rhai.shebang_token() { 16 | self.word(t.static_text())?; 17 | // No hardbreak required here, as we should 18 | // already have whitespace in the file. 19 | } 20 | 21 | self.standalone_leading_comments_in(&rhai.syntax())?; 22 | 23 | let count = rhai.statements().count(); 24 | 25 | for (idx, stmt) in rhai.statements().enumerate() { 26 | let item = match stmt.item() { 27 | Some(item) => item, 28 | _ => continue, 29 | }; 30 | let stmt_syntax = stmt.syntax(); 31 | let item_syntax = item.syntax(); 32 | 33 | let last = count == idx + 1; 34 | let needs_sep = needs_stmt_separator(&item); 35 | 36 | self.ibox(0); 37 | 38 | self.fmt_item(item)?; 39 | 40 | let had_sep = stmt 41 | .syntax() 42 | .children_with_tokens() 43 | .any(|c| c.kind() == T![";"]); 44 | 45 | if last { 46 | if had_sep && needs_sep { 47 | self.word(";")?; 48 | } 49 | } else if needs_sep { 50 | self.word(";")?; 51 | } 52 | 53 | self.comment_same_line_after(&item_syntax)?; 54 | self.comment_same_line_after(&stmt_syntax)?; 55 | 56 | self.end(); 57 | 58 | let standalone_comments = if had_sep { 59 | self.standalone_comments_after(&stmt_syntax, !last)? 60 | } else { 61 | self.standalone_comments_after(&item_syntax, !last)? 62 | }; 63 | 64 | if !standalone_comments.hardbreak_end { 65 | self.hardbreak(); 66 | } 67 | } 68 | 69 | self.end(); 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | pub(crate) fn needs_stmt_separator(item: &Item) -> bool { 76 | match item 77 | .expr() 78 | .and_then(|e| e.syntax().first_child()) 79 | .map(|e| e.kind()) 80 | { 81 | Some( 82 | EXPR_BLOCK | EXPR_IF | EXPR_LOOP | EXPR_FOR | EXPR_WHILE | EXPR_SWITCH | EXPR_FN 83 | | EXPR_TRY, 84 | ) => false, 85 | _ => true, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/fmt.rs: -------------------------------------------------------------------------------- 1 | use test_case::test_case; 2 | 3 | #[test_case("simple", include_str!("../../../testdata/valid/simple.rhai"))] 4 | #[test_case("array", include_str!("../../../testdata/valid/array.rhai"))] 5 | #[test_case("assignment", include_str!("../../../testdata/valid/assignment.rhai"))] 6 | #[test_case("comments", include_str!("../../../testdata/valid/comments.rhai"))] 7 | #[test_case("fibonacci", include_str!("../../../testdata/valid/fibonacci.rhai"))] 8 | #[test_case("for1", include_str!("../../../testdata/valid/for1.rhai"))] 9 | #[test_case("for2", include_str!("../../../testdata/valid/for2.rhai"))] 10 | #[test_case("function_decl1", include_str!("../../../testdata/valid/function_decl1.rhai"))] 11 | #[test_case("function_decl2", include_str!("../../../testdata/valid/function_decl2.rhai"))] 12 | #[test_case("function_decl3", include_str!("../../../testdata/valid/function_decl3.rhai"))] 13 | #[test_case("function_decl4", include_str!("../../../testdata/valid/function_decl4.rhai"))] 14 | #[test_case("if1", include_str!("../../../testdata/valid/if1.rhai"))] 15 | #[test_case("if2", include_str!("../../../testdata/valid/if2.rhai"))] 16 | #[test_case("loop", include_str!("../../../testdata/valid/loop.rhai"))] 17 | #[test_case("mat_mul", include_str!("../../../testdata/valid/mat_mul.rhai"))] 18 | #[test_case("module", include_str!("../../../testdata/valid/module.rhai"))] 19 | #[test_case("oop", include_str!("../../../testdata/valid/oop.rhai"))] 20 | #[test_case("op1", include_str!("../../../testdata/valid/op1.rhai"))] 21 | #[test_case("op2", include_str!("../../../testdata/valid/op2.rhai"))] 22 | #[test_case("op3", include_str!("../../../testdata/valid/op3.rhai"))] 23 | #[test_case("primes", include_str!("../../../testdata/valid/primes.rhai"))] 24 | #[test_case("speed_test", include_str!("../../../testdata/valid/speed_test.rhai"))] 25 | #[test_case("string", include_str!("../../../testdata/valid/string.rhai"))] 26 | // FIXME: the snapshot for this randomly fails. (#102) 27 | // #[test_case("strings_map", include_str!("../../../testdata/valid/strings_map.rhai"))] 28 | #[test_case("switch", include_str!("../../../testdata/valid/switch.rhai"))] 29 | #[test_case("while", include_str!("../../../testdata/valid/while.rhai"))] 30 | #[test_case("char", include_str!("../../../testdata/valid/char.rhai"))] 31 | #[test_case("throw_try_catch", include_str!("../../../testdata/valid/throw_try_catch.rhai"))] 32 | #[test_case("optional_ops", include_str!("../../../testdata/valid/optional_ops.rhai"))] 33 | #[test_case("string_escape", include_str!("../../../testdata/valid/string_escape.rhai"))] 34 | #[test_case("template", include_str!("../../../testdata/valid/template.rhai"))] 35 | #[test_case("unary_ops", include_str!("../../../testdata/valid/unary_ops.rhai"))] 36 | fn format(name: &str, src: &str) { 37 | let formatted = rhai_fmt::format_source(src, Default::default()); 38 | insta::with_settings!( 39 | { snapshot_suffix => name }, 40 | { 41 | insta::assert_snapshot!(formatted); 42 | } 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@array.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | let x = [1, 2, 3]; 6 | 7 | print("x[1] should be 2:"); 8 | print(x[1]); 9 | 10 | x[1] = 5; 11 | 12 | print(`x[1] should be 5: ${x[1]}`); 13 | 14 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@assignment.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script contains a single assignment statement. 6 | 7 | let x = 78; 8 | 9 | print(`x should be 78: ${x}`); 10 | 11 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@char.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | let val = 'a'; 6 | let val = '\n'; 7 | 8 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@comments.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // I am a single line comment! 6 | 7 | let /* I am a spy in a variable declaration! */ x = 5; 8 | 9 | /* I am a simple 10 | multi-line 11 | comment */ 12 | 13 | /* look /* at /* that, /* multi-line */ comments */ can be */ nested */ 14 | 15 | /* surrounded by */ 16 | let this_is_not_a_comment = true // comments 17 | 18 | 19 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@fibonacci.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script calculates the n-th Fibonacci number using a really dumb algorithm 6 | // to test the speed of the scripting engine. 7 | 8 | const TARGET = 28; 9 | const REPEAT = 5; 10 | const ANSWER = 317_811; 11 | 12 | fn fib(n) { 13 | if n < 2 { 14 | n 15 | } else { 16 | fib(n - 1) + fib(n - 2) 17 | } 18 | } 19 | 20 | print(`Running Fibonacci(${TARGET}) x ${REPEAT} times...`); 21 | print("Ready... Go!"); 22 | 23 | let result; 24 | let now = timestamp(); 25 | 26 | for n in 0..REPEAT { 27 | result = fib(TARGET); 28 | } 29 | 30 | print(`Finished. Run time = ${now.elapsed} seconds.`); 31 | 32 | print(`Fibonacci number #${TARGET} = ${result}`); 33 | 34 | if result != ANSWER { 35 | print(`The answer is WRONG! Should be ${ANSWER}!`); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@for1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs for-loops. 6 | 7 | let arr = [1, true, 123.456, "hello", 3, 42]; 8 | 9 | // Loop over array with counter 10 | for (a, i) in arr { 11 | for (b, j) in ['x', 42, (), 123, 99, 0.5] { 12 | if b > 100 { 13 | continue; 14 | } 15 | print(`(${i}, ${j}) = (${a}, ${b})`); 16 | } 17 | 18 | if a == 3 { 19 | break; 20 | } 21 | } 22 | //print(a); // <- if you uncomment this line, the script will fail to compile 23 | // because 'a' is not defined here 24 | 25 | for i in range(5, 0, -1) {// runs from 5 down to 1 26 | print(i); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@for2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs for-loops 6 | 7 | const MAX = 1_000_000; 8 | 9 | print(`Iterating an array with ${MAX} items...`); 10 | 11 | print("Ready... Go!"); 12 | 13 | let now = timestamp(); 14 | 15 | let list = []; 16 | 17 | // Loop over range 18 | for i in 0..MAX { 19 | list.push(i); 20 | } 21 | 22 | print(`Time = ${now.elapsed} seconds...`); 23 | 24 | let sum = 0; 25 | 26 | // Loop over array 27 | for i in list { 28 | sum += i; 29 | } 30 | 31 | print(`Sum = ${sum}`); 32 | print(`Finished. Total run time = ${now.elapsed} seconds.`); 33 | 34 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@function_decl1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script defines a function and calls it. 6 | 7 | fn call_me() { 8 | return 3; 9 | } 10 | 11 | let result = call_me(); 12 | 13 | print(`call_me() should be 3: ${result}`); 14 | 15 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@function_decl2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script defines a function with two parameters and local variables. 6 | 7 | let a = 3; 8 | 9 | fn add(a, b) { 10 | a = 42; // notice that 'a' is passed by value 11 | a 12 | + b; // notice that the last value is returned even if terminated by a semicolon 13 | } 14 | 15 | let result = add(a, 4); 16 | 17 | print(`add(a, 4) should be 46: ${result}`); 18 | 19 | print(`a should still be 3: ${a}`); // prints 3: 'a' is never changed 20 | 21 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@function_decl3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script defines a function with many parameters. 6 | // 7 | 8 | const KEY = 38; 9 | 10 | fn f(a, b, c, d, e, f) { 11 | let x = global::KEY; // <- access global module 12 | a - b * c - d * e - f + x 13 | } 14 | 15 | let result = f(100, 5, 2, 9, 6, 32); 16 | 17 | print(`result should be 42: ${result}`); 18 | 19 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@function_decl4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script defines a function that acts as a method. 6 | 7 | // Use 'this' to refer to the object of a method call 8 | fn action(x, y) { 9 | this = this.abs() + x * y; // 'this' can be modified 10 | } 11 | 12 | let obj = -40; 13 | 14 | obj.action(1, 2); // call 'action' as method 15 | 16 | print(`obj should now be 42: ${obj}`); 17 | 18 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@if1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs if statements. 6 | 7 | let a = 42; 8 | let b = 123; 9 | let x = 999; 10 | 11 | if a > b { 12 | print("Oops! a > b"); 13 | } else if a < b { 14 | print("a < b, x should be 0"); 15 | 16 | let x = 0; // <- this 'x' shadows the global 'x' 17 | print(x); // should print 0 18 | } else { 19 | print("Oops! a == b"); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@if2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs an if expression. 6 | 7 | let a = 42; 8 | let b = 123; 9 | 10 | let x = if a <= b {// <- if-expression 11 | b - a 12 | } else { 13 | a - b 14 | } * 10; 15 | 16 | print(`x should be 810: ${x}`); 17 | 18 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@loop.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs an infinite loop, ending it with a break statement. 6 | 7 | let x = 10; 8 | 9 | // simulate do..while using loop 10 | loop { 11 | print(x); 12 | 13 | x -= 1; 14 | 15 | if x <= 0 { 16 | break; 17 | } 18 | } 19 | export x as foo; 20 | 21 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@mat_mul.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script simulates multi-dimensional matrix calculations. 6 | 7 | const SIZE = 50; 8 | 9 | fn new_mat(x, y) { 10 | let row = []; 11 | row.pad(y, 0.0); 12 | 13 | let matrix = []; 14 | matrix.pad(x, row); 15 | 16 | matrix 17 | } 18 | 19 | fn mat_gen() { 20 | const n = global::SIZE; 21 | const tmp = 1.0 / n / n; 22 | let m = new_mat(n, n); 23 | 24 | for i in 0..n { 25 | for j in 0..n { 26 | m[i][j] = tmp * (i - j) * (i + j); 27 | } 28 | } 29 | 30 | m 31 | } 32 | 33 | fn mat_mul(a, b) { 34 | let b2 = new_mat(a[0].len, b[0].len); 35 | 36 | for i in 0..a[0].len { 37 | for j in 0..b[0].len { 38 | b2[j][i] = b[i][j]; 39 | } 40 | } 41 | 42 | let c = new_mat(a.len, b[0].len); 43 | 44 | for i in 0..c.len { 45 | for j in 0..c[i].len { 46 | c[i][j] = 0.0; 47 | 48 | for z in 0..a[i].len { 49 | c[i][j] += a[i][z] * b2[j][z]; 50 | } 51 | } 52 | } 53 | 54 | c 55 | } 56 | 57 | const now = timestamp(); 58 | 59 | const a = mat_gen(); 60 | const b = mat_gen(); 61 | const c = mat_mul(a, b); 62 | 63 | /* 64 | for i in 0..SIZE) { 65 | print(c[i]); 66 | } 67 | */ 68 | 69 | print(`Finished. Run time = ${now.elapsed} seconds.`); 70 | 71 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@module.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script imports an external script as a module. 6 | 7 | import "loop" as x; 8 | 9 | print(`Module test! foo = ${x::foo}`); 10 | 11 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@oop.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script simulates object-oriented programming (OOP) techniques using closures. 6 | 7 | // External variable that will be captured. 8 | let last_value = (); 9 | 10 | // Define object 11 | let obj1 = #{ 12 | _data: 42, 13 | // data field 14 | get_data: || this._data, 15 | // property getter 16 | action: || print(`Data=${this._data}`), 17 | // method 18 | update: |x| {// property setter 19 | this._data = x; 20 | last_value = this._data; // capture 'last_value' 21 | this.action(); 22 | }, 23 | }; 24 | 25 | if obj1.get_data() > 0 {// property access 26 | obj1.update(123); // call method 27 | } else { 28 | print("we have a problem here"); 29 | } // Define another object based on the first object 30 | 31 | let obj2 = #{ 32 | _data: 0, 33 | // data field - new value 34 | update: |x| {// property setter - another function 35 | this._data = x * 2; 36 | last_value = this._data; // capture 'last_value' 37 | this.action(); 38 | }, 39 | }; 40 | obj2.fill_with(obj1); // add all other fields from obj1 41 | 42 | if obj2.get_data() > 0 {// property access 43 | print("we have another problem here"); 44 | } else { 45 | obj2.update(42); // call method 46 | } 47 | print(`Should be 84: ${last_value}`); 48 | 49 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@op1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs a single expression. 6 | 7 | print("The result should be 46:"); 8 | 9 | print(34 + 12); 10 | 11 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@op2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs a complex expression. 6 | 7 | print("The result should be 182:"); 8 | 9 | let x = 12 + 34 * 5; 10 | 11 | print(x); 12 | 13 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@op3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs a complex expression. 6 | 7 | print("The result should be 230:"); 8 | 9 | let x = (12 + 34) * 5; 10 | 11 | print(x); 12 | 13 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@optional_ops.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | fn test_a() { 6 | let foo = #{}; 7 | let val = foo?.bar["baz"] ?? 2; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@primes.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script uses the Sieve of Eratosthenes to calculate prime numbers. 6 | 7 | let now = timestamp(); 8 | 9 | const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000 10 | 11 | let prime_mask = []; 12 | prime_mask.pad(MAX_NUMBER_TO_CHECK + 1, true); 13 | 14 | prime_mask[0] = false; 15 | prime_mask[1] = false; 16 | 17 | let total_primes_found = 0; 18 | 19 | for p in 2..=MAX_NUMBER_TO_CHECK { 20 | if !prime_mask[p] { 21 | continue; 22 | } 23 | //print(p); 24 | 25 | total_primes_found += 1; 26 | 27 | for i in range(2 * p, MAX_NUMBER_TO_CHECK + 1, p) { 28 | prime_mask[i] = false; 29 | } 30 | } 31 | 32 | print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`); 33 | print(`Run time = ${now.elapsed} seconds.`); 34 | 35 | if total_primes_found != 78_498 { 36 | print("The answer is WRONG! Should be 78,498!"); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@simple.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | #!/bin/echo hello 6 | 7 | // It's a 8 | let a = "0"; 9 | 10 | /// It's b 11 | let b = a; 12 | 13 | const c = b; /* */ 14 | 15 | const ident = 2; 16 | 17 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@speed_test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs 1 million iterations to test the speed of the scripting engine. 6 | 7 | let now = timestamp(); 8 | let x = 1_000_000; 9 | 10 | print("Ready... Go!"); 11 | 12 | while x > 0 { 13 | x -= 1; 14 | } 15 | 16 | print(`Finished. Run time = ${now.elapsed} seconds.`); 17 | 18 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@string.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script tests string operations. 6 | 7 | print("hello"); 8 | print("this\nis \\ nice"); // escape sequences 9 | print("0x40 hex is \x40"); // hex escape sequence 10 | print("Unicode fun: \u2764"); // Unicode escape sequence 11 | print("more fun: \U0001F603"); // Unicode escape sequence 12 | print("foo" + " " + "bar"); // string building using strings 13 | print("foo" < "bar"); // string comparison 14 | print("foo" >= "bar"); // string comparison 15 | print("the answer is " + 42); // string building using non-string types 16 | 17 | let s = "\u2764 hello, world! \U0001F603"; // string variable 18 | print(`length=${s.len}`); // should be 17 19 | 20 | s[s.len - 3] = '?'; // change the string 21 | print(`Question: ${s}`); // should print 'Question: hello, world?' 22 | 23 | // Line continuation: 24 | let s = "This is a long \ 25 | string constructed using \ 26 | line continuation"; 27 | 28 | // String interpolation 29 | print(`One string: ${s}`); 30 | 31 | // Multi-line literal string: 32 | let s = ` 33 | \U0001F603 This is a multi-line 34 | "string" with \t\x20\r\n 35 | made using multi-line literal 36 | string syntax. 37 | `; 38 | 39 | print(s); 40 | 41 | // Interpolation 42 | let s = `This is interpolation ${ 43 | let x = `within ${ 44 | let y = "yet another level \ 45 | of interpolation!"; 46 | y} interpolation`; x} within literal string.`; 47 | 48 | print(s); 49 | 50 | print(">>> END <<<"); 51 | 52 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@string_escape.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | let a = """"; 6 | let a = " "" "; 7 | 8 | let b = ````; 9 | let b = ` `` `; 10 | 11 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@switch.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs a switch statement in a for-loop. 6 | 7 | let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4]; 8 | 9 | for item in arr { 10 | switch item { 11 | // Match single integer 12 | 42 => print("The Answer!"), 13 | // Match single floating-point number 14 | 123.456 => print(`Floating point... ${item}`), 15 | // Match single string 16 | "hello" => print(`${item} world!`), 17 | // Match another integer 18 | 999 => print(`Got 999: ${item}`), 19 | // Match range with condition 20 | 0..100 if item % 2 == 0 => print(`A small even number: ${item}`), 21 | // Match another range 22 | 0..100 => print(`A small odd number: ${item}`), 23 | // Default case 24 | _ => print(`Something else: <${item}> is ${type_of(item)}`), 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@template.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | let hi = 2; 6 | `${2}💩 💩 asd abc${3 + 2 + 1 + `${""}`}`; 7 | 8 | let a = `with \interpolation ${hi} `; 9 | let a = `${hi}`; 10 | 11 | let a = `multiple ${hi}${hi} ${hi} \interpolations ${hi + 2} and more complex ${ 12 | { 13 | let a = 2; 14 | let b = `nested ${hi} interpolation ${3 ** 3 + 4}`; 15 | let c = `with escaped `` but you cannot escape the \`` `; 16 | a + a 17 | }} expressions`; 18 | 19 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@throw_try_catch.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | try { 6 | throw "hello"; 7 | } catch {} 8 | 9 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@unary_ops.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | let a = !a + 2; 6 | 7 | let b = 3 + !+++4 + -2 * 3; 8 | 9 | let b = !!!!foo.bar; 10 | 11 | let hm = !a.b; 12 | 13 | -------------------------------------------------------------------------------- /crates/rhai-fmt/tests/snapshots/fmt__format@while.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rhai-fmt/tests/fmt.rs 3 | expression: formatted 4 | --- 5 | // This script runs a while loop. 6 | 7 | let x = 10; 8 | 9 | while x > 0 { 10 | print(x); 11 | x -= 1; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /crates/rhai-hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-hir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rhai-rowan = { path = "../rhai-rowan" } 11 | 12 | ahash = { version = "0.8.0", features = ["serde"] } 13 | indexmap = { version = "1.7.0", features = ["serde"] } 14 | itertools = "0.10.1" 15 | slotmap = { version = "1.0.6", features = ["serde"] } 16 | static_assertions = "1.1.0" 17 | thiserror = "1.0.30" 18 | tracing = "0.1.29" 19 | strsim = "0.10.0" 20 | url = "2.2.2" 21 | pulldown-cmark = "0.9.2" 22 | strum = { version = "0.24.1", features = ["derive"] } 23 | anyhow = "1.0.62" 24 | 25 | [dev-dependencies] 26 | insta = "1.8.0" 27 | criterion = { version = "0.4", features = ["html_reports"] } 28 | pprof = { version = "0.10.1", features = ["flamegraph", "criterion"] } 29 | test-case = "2.1.0" 30 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::Symbol; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Clone, Error)] 5 | #[error("{kind}")] 6 | pub struct Error { 7 | pub kind: ErrorKind, 8 | } 9 | 10 | #[derive(Debug, Clone, Error)] 11 | pub enum ErrorKind { 12 | #[error("duplicate function parameter")] 13 | DuplicateFnParameter { 14 | duplicate_symbol: Symbol, 15 | existing_symbol: Symbol, 16 | }, 17 | #[error( 18 | "cannot resolve reference{}", 19 | match &similar_name { 20 | Some(n) => { 21 | format!(", did you mean `{}`?", n) 22 | } 23 | None => { 24 | String::from("") 25 | } 26 | } 27 | )] 28 | UnresolvedReference { 29 | reference_symbol: Symbol, 30 | similar_name: Option, 31 | }, 32 | #[error("unresolved import")] 33 | UnresolvedImport { import: Symbol }, 34 | #[error("nested functions are not allowed")] 35 | NestedFunction { function: Symbol }, 36 | } 37 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/eval.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub enum Value { 3 | Int(i64), 4 | Float(f64), 5 | Bool(bool), 6 | String(String), 7 | Char(char), 8 | Unknown, 9 | } 10 | 11 | impl core::fmt::Display for Value { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | Value::Int(v) => v.fmt(f), 15 | Value::Float(v) => v.fmt(f), 16 | Value::Bool(v) => v.fmt(f), 17 | Value::String(v) => write!(f, r#""{v}""#), 18 | Value::Char(v) => write!(f, "'{v}'"), 19 | Value::Unknown => "UNKNOWN VALUE".fmt(f), 20 | } 21 | } 22 | } 23 | 24 | impl Value { 25 | /// Returns `true` if the value is [`Int`]. 26 | /// 27 | /// [`Int`]: Value::Int 28 | #[must_use] 29 | pub fn is_int(&self) -> bool { 30 | matches!(self, Self::Int(..)) 31 | } 32 | 33 | #[must_use] 34 | pub fn as_int(&self) -> Option<&i64> { 35 | if let Self::Int(v) = self { 36 | Some(v) 37 | } else { 38 | None 39 | } 40 | } 41 | 42 | /// Returns `true` if the value is [`Float`]. 43 | /// 44 | /// [`Float`]: Value::Float 45 | #[must_use] 46 | pub fn is_float(&self) -> bool { 47 | matches!(self, Self::Float(..)) 48 | } 49 | 50 | #[must_use] 51 | pub fn as_float(&self) -> Option<&f64> { 52 | if let Self::Float(v) = self { 53 | Some(v) 54 | } else { 55 | None 56 | } 57 | } 58 | 59 | /// Returns `true` if the value is [`Bool`]. 60 | /// 61 | /// [`Bool`]: Value::Bool 62 | #[must_use] 63 | pub fn is_bool(&self) -> bool { 64 | matches!(self, Self::Bool(..)) 65 | } 66 | 67 | #[must_use] 68 | pub fn as_bool(&self) -> Option<&bool> { 69 | if let Self::Bool(v) = self { 70 | Some(v) 71 | } else { 72 | None 73 | } 74 | } 75 | 76 | /// Returns `true` if the value is [`String`]. 77 | /// 78 | /// [`String`]: Value::String 79 | #[must_use] 80 | pub fn is_string(&self) -> bool { 81 | matches!(self, Self::String(..)) 82 | } 83 | 84 | #[must_use] 85 | pub fn as_string(&self) -> Option<&String> { 86 | if let Self::String(v) = self { 87 | Some(v) 88 | } else { 89 | None 90 | } 91 | } 92 | 93 | /// Returns `true` if the value is [`Char`]. 94 | /// 95 | /// [`Char`]: Value::Char 96 | #[must_use] 97 | pub fn is_char(&self) -> bool { 98 | matches!(self, Self::Char(..)) 99 | } 100 | 101 | #[must_use] 102 | pub fn as_char(&self) -> Option<&char> { 103 | if let Self::Char(v) = self { 104 | Some(v) 105 | } else { 106 | None 107 | } 108 | } 109 | 110 | /// Returns `true` if the value is [`Unknown`]. 111 | /// 112 | /// [`Unknown`]: Value::Unknown 113 | #[must_use] 114 | pub fn is_unknown(&self) -> bool { 115 | matches!(self, Self::Unknown) 116 | } 117 | } 118 | 119 | impl Default for Value { 120 | fn default() -> Self { 121 | Self::Unknown 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/hir/errors.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{Error, ErrorKind}, 3 | source::Source, 4 | symbol::SymbolKind, 5 | HashMap, Hir, Symbol, 6 | }; 7 | 8 | impl Hir { 9 | #[must_use] 10 | pub fn errors(&self) -> Vec { 11 | let mut errors = Vec::new(); 12 | 13 | for (symbol, _) in self.symbols() { 14 | self.collect_errors_from_symbol(symbol, &mut errors); 15 | } 16 | 17 | errors 18 | } 19 | 20 | #[must_use] 21 | pub fn errors_for_source(&self, source: Source) -> Vec { 22 | let mut errors = Vec::new(); 23 | 24 | for (symbol, _) in self 25 | .symbols() 26 | .filter(|(_, symbol_data)| symbol_data.source.source == Some(source)) 27 | { 28 | self.collect_errors_from_symbol(symbol, &mut errors); 29 | } 30 | 31 | errors 32 | } 33 | 34 | fn collect_errors_from_symbol(&self, symbol: Symbol, errors: &mut Vec) { 35 | if let Some(symbol_data) = self.symbol(symbol) { 36 | match &symbol_data.kind { 37 | SymbolKind::Ref(r) => { 38 | if !r.field_access && r.target.is_none() && r.name != "this" { 39 | errors.push(Error { 40 | kind: ErrorKind::UnresolvedReference { 41 | reference_symbol: symbol, 42 | similar_name: self.find_similar_name(symbol, &r.name), 43 | }, 44 | }); 45 | } 46 | } 47 | SymbolKind::Fn(f) => { 48 | if f.is_def { 49 | return; 50 | } 51 | 52 | let top_level = self 53 | .modules 54 | .iter() 55 | .any(|(_, m)| m.scope == symbol_data.parent_scope); 56 | 57 | if !top_level { 58 | errors.push(Error { 59 | kind: ErrorKind::NestedFunction { function: symbol }, 60 | }); 61 | } 62 | 63 | let mut param_names: HashMap<&str, Symbol> = HashMap::new(); 64 | 65 | for ¶m in &self[f.scope].symbols { 66 | let param_data = &self[param]; 67 | 68 | if !param_data.is_param() { 69 | break; 70 | } 71 | 72 | if let Some(name) = param_data.name(self) { 73 | if name != "_" { 74 | if let Some(existing_param) = param_names.insert(name, param) { 75 | errors.push(Error { 76 | kind: ErrorKind::DuplicateFnParameter { 77 | duplicate_symbol: param, 78 | existing_symbol: existing_param, 79 | }, 80 | }); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | SymbolKind::Import(import) => { 87 | if import.target.is_none() { 88 | errors.push(Error { 89 | kind: ErrorKind::UnresolvedImport { import: symbol }, 90 | }); 91 | } 92 | } 93 | _ => {} 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/hir/query/types.rs: -------------------------------------------------------------------------------- 1 | use crate::{hir::BuiltinTypes, Hir}; 2 | 3 | impl Hir { 4 | #[must_use] 5 | #[inline] 6 | pub const fn builtin_types(&self) -> BuiltinTypes { 7 | self.builtin_types 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow( 3 | clippy::unused_async, 4 | clippy::single_match, 5 | clippy::wildcard_imports, 6 | clippy::too_many_lines, 7 | clippy::enum_glob_use, 8 | clippy::module_name_repetitions, 9 | clippy::single_match_else, 10 | clippy::default_trait_access, 11 | clippy::too_many_arguments 12 | )] 13 | 14 | pub mod error; 15 | pub mod eval; 16 | pub mod hir; 17 | pub mod module; 18 | pub mod scope; 19 | pub mod source; 20 | pub mod symbol; 21 | pub mod ty; 22 | pub(crate) mod util; 23 | pub mod fmt; 24 | 25 | pub(crate) type IndexMap = indexmap::IndexMap; 26 | pub(crate) type IndexSet = indexmap::IndexSet; 27 | pub(crate) type HashSet = ahash::AHashSet; 28 | pub(crate) type HashMap = ahash::AHashMap; 29 | 30 | pub use hir::Hir; 31 | pub use module::Module; 32 | pub use scope::Scope; 33 | pub use symbol::Symbol; 34 | pub use ty::TypeKind; 35 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/scope.rs: -------------------------------------------------------------------------------- 1 | use crate::{source::SourceInfo, HashSet, IndexSet, Symbol}; 2 | 3 | slotmap::new_key_type! { pub struct Scope; } 4 | 5 | #[derive(Debug, Default, Clone)] 6 | #[non_exhaustive] 7 | pub struct ScopeData { 8 | pub source: SourceInfo, 9 | pub parent: Option, 10 | pub symbols: IndexSet, 11 | pub hoisted_symbols: HashSet, 12 | } 13 | 14 | impl ScopeData { 15 | /// Iterate over all direct symbols in the scope. 16 | pub fn iter_symbols(&self) -> impl Iterator + '_ { 17 | self.hoisted_symbols 18 | .iter() 19 | .copied() 20 | .chain(self.symbols.iter().copied()) 21 | } 22 | 23 | /// Total count of direct symbols in the scope. 24 | #[must_use] 25 | pub fn symbol_count(&self) -> usize { 26 | self.symbols.len() + self.hoisted_symbols.len() 27 | } 28 | 29 | #[must_use] 30 | pub fn is_empty(&self) -> bool { 31 | self.symbol_count() == 0 32 | } 33 | } 34 | 35 | #[derive(Debug, Clone, Copy)] 36 | pub enum ScopeParent { 37 | Scope(Scope), 38 | Symbol(Symbol), 39 | } 40 | 41 | impl ScopeParent { 42 | #[must_use] 43 | pub fn as_scope(&self) -> Option<&Scope> { 44 | if let Self::Scope(v) = self { 45 | Some(v) 46 | } else { 47 | None 48 | } 49 | } 50 | 51 | #[must_use] 52 | pub fn as_symbol(&self) -> Option<&Symbol> { 53 | if let Self::Symbol(v) = self { 54 | Some(v) 55 | } else { 56 | None 57 | } 58 | } 59 | } 60 | 61 | impl From for ScopeParent { 62 | fn from(s: Scope) -> Self { 63 | Self::Scope(s) 64 | } 65 | } 66 | 67 | impl From for ScopeParent { 68 | fn from(s: Symbol) -> Self { 69 | Self::Symbol(s) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/source.rs: -------------------------------------------------------------------------------- 1 | use rhai_rowan::TextRange; 2 | use url::Url; 3 | 4 | use crate::Module; 5 | 6 | slotmap::new_key_type! { pub struct Source; } 7 | 8 | #[derive(Debug, Clone)] 9 | #[non_exhaustive] 10 | pub struct SourceData { 11 | pub url: Url, 12 | pub kind: SourceKind, 13 | pub module: Module, 14 | } 15 | 16 | #[derive(Debug, Copy, Clone)] 17 | pub enum SourceKind { 18 | Script, 19 | Def, 20 | } 21 | 22 | impl SourceKind { 23 | /// Returns `true` if the source kind is [`Script`]. 24 | /// 25 | /// [`Script`]: SourceKind::Script 26 | #[must_use] 27 | pub fn is_script(&self) -> bool { 28 | matches!(self, Self::Script) 29 | } 30 | 31 | /// Returns `true` if the source kind is [`Def`]. 32 | /// 33 | /// [`Def`]: SourceKind::Def 34 | #[must_use] 35 | pub fn is_def(&self) -> bool { 36 | matches!(self, Self::Def) 37 | } 38 | } 39 | 40 | #[derive(Debug, Default, Clone, Copy)] 41 | pub struct SourceInfo { 42 | pub source: Option, 43 | pub text_range: Option, 44 | pub selection_text_range: Option, 45 | } 46 | 47 | impl SourceInfo { 48 | #[must_use] 49 | pub fn is(&self, source: Source) -> bool { 50 | self.source.map_or(false, |s| s == source) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/rhai-hir/src/util.rs: -------------------------------------------------------------------------------- 1 | use url::Url; 2 | 3 | /// Return the rhai script url from an url. 4 | /// 5 | /// This is needed for rhai definitions that typically 6 | /// end with `.d.rhai`. 7 | /// 8 | /// If the URL is already a script URL, [`None`] is returned. 9 | #[allow(clippy::case_sensitive_file_extension_comparisons)] 10 | pub fn script_url(url: &Url) -> Option { 11 | let script_name = match url.path_segments().and_then(Iterator::last) { 12 | Some(name) => name, 13 | None => { 14 | tracing::debug!(%url, "could not figure out script url"); 15 | return None; 16 | } 17 | }; 18 | 19 | if let Some(script_name_base) = script_name.strip_suffix(".d.rhai") { 20 | Some(url.join(&format!("{script_name_base}.rhai")).unwrap()) 21 | } else if url.as_str().ends_with(".rhai") { 22 | None 23 | } else { 24 | tracing::debug!(%url, "could not figure out script url"); 25 | None 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/rhai-hir/tests/definitions.rs: -------------------------------------------------------------------------------- 1 | use rhai_hir::Hir; 2 | use rhai_rowan::parser::Parser; 3 | 4 | #[test] 5 | fn test_global_definition() { 6 | let root_src = r#" 7 | global::print(); 8 | "#; 9 | 10 | let global_src = r#" 11 | module global; 12 | 13 | fn print(); 14 | "#; 15 | 16 | let mut hir = Hir::new(); 17 | 18 | hir.add_source( 19 | &"test:///root.rhai".parse().unwrap(), 20 | &Parser::new(root_src).parse_script().into_syntax(), 21 | ); 22 | hir.add_source( 23 | &"test:///global.d.rhai".parse().unwrap(), 24 | &Parser::new(global_src).parse_def().into_syntax(), 25 | ); 26 | 27 | hir.resolve_all(); 28 | 29 | assert!(hir.errors().is_empty()); 30 | } 31 | 32 | #[test] 33 | fn test_static_module() { 34 | let root_src = r#" 35 | print("hello"); 36 | "#; 37 | 38 | let global_src = r#" 39 | module static; 40 | 41 | fn print(); 42 | "#; 43 | 44 | let mut hir = Hir::new(); 45 | 46 | hir.add_source( 47 | &"test:///root.rhai".parse().unwrap(), 48 | &Parser::new(root_src).parse_script().into_syntax(), 49 | ); 50 | hir.add_source( 51 | &"test:///static.d.rhai".parse().unwrap(), 52 | &Parser::new(global_src).parse_def().into_syntax(), 53 | ); 54 | 55 | hir.resolve_all(); 56 | 57 | assert!(hir.errors().is_empty()); 58 | } 59 | 60 | #[test] 61 | fn test_define_file() { 62 | let root_src = r#" 63 | print("hello"); 64 | "#; 65 | 66 | let global_src = r#" 67 | module; 68 | 69 | fn print(); 70 | "#; 71 | 72 | let mut hir = Hir::new(); 73 | 74 | hir.add_source( 75 | &"test:///root.rhai".parse().unwrap(), 76 | &Parser::new(root_src).parse_script().into_syntax(), 77 | ); 78 | hir.add_source( 79 | &"test:///root.d.rhai".parse().unwrap(), 80 | &Parser::new(global_src).parse_def().into_syntax(), 81 | ); 82 | 83 | hir.resolve_all(); 84 | 85 | assert!(hir.errors().is_empty()); 86 | } 87 | 88 | #[test] 89 | fn test_define_file_explicitly() { 90 | let root_src = r#" 91 | print("hello"); 92 | "#; 93 | 94 | let global_src = r#" 95 | module "./root.rhai"; 96 | 97 | fn print(); 98 | "#; 99 | 100 | let mut hir = Hir::new(); 101 | 102 | hir.add_source( 103 | &"test:///root.rhai".parse().unwrap(), 104 | &Parser::new(root_src).parse_script().into_syntax(), 105 | ); 106 | hir.add_source( 107 | &"test:///root.d.rhai".parse().unwrap(), 108 | &Parser::new(global_src).parse_def().into_syntax(), 109 | ); 110 | 111 | hir.resolve_all(); 112 | 113 | assert!(hir.errors().is_empty()); 114 | } 115 | -------------------------------------------------------------------------------- /crates/rhai-hir/tests/imports.rs: -------------------------------------------------------------------------------- 1 | use rhai_hir::Hir; 2 | use rhai_rowan::parser::Parser; 3 | 4 | #[test] 5 | fn test_relative_import() { 6 | let root_src = r#" 7 | import "./module.rhai" as m; 8 | 9 | m::x; 10 | "#; 11 | 12 | let module_src = r#" 13 | export const x = 1; 14 | "#; 15 | 16 | let mut hir = Hir::new(); 17 | 18 | hir.add_source( 19 | &"test:///root.rhai".parse().unwrap(), 20 | &Parser::new(root_src).parse_script().into_syntax(), 21 | ); 22 | hir.add_source( 23 | &"test:///module.rhai".parse().unwrap(), 24 | &Parser::new(module_src).parse_script().into_syntax(), 25 | ); 26 | 27 | hir.resolve_all(); 28 | 29 | assert!(hir.errors().is_empty()); 30 | } 31 | 32 | #[test] 33 | fn test_import_sub_modules() { 34 | let root_src = r#" 35 | import "./foo.rhai" as foo; 36 | 37 | foo::bar::baz; 38 | "#; 39 | 40 | let foo_src = r#" 41 | import "./bar.rhai" as bar; 42 | "#; 43 | 44 | let bar_src = r#" 45 | export const baz = 1; 46 | "#; 47 | 48 | let mut hir = Hir::new(); 49 | 50 | hir.add_source( 51 | &"test:///root.rhai".parse().unwrap(), 52 | &Parser::new(root_src).parse_script().into_syntax(), 53 | ); 54 | hir.add_source( 55 | &"test:///foo.rhai".parse().unwrap(), 56 | &Parser::new(foo_src).parse_script().into_syntax(), 57 | ); 58 | hir.add_source( 59 | &"test:///bar.rhai".parse().unwrap(), 60 | &Parser::new(bar_src).parse_script().into_syntax(), 61 | ); 62 | 63 | hir.resolve_all(); 64 | 65 | assert!(hir.errors().is_empty()); 66 | } 67 | 68 | #[test] 69 | fn test_missing_modules() { 70 | let root_src = r#" 71 | import "./foo.rhai" as foo; 72 | 73 | "#; 74 | 75 | let mut hir = Hir::new(); 76 | 77 | hir.add_source( 78 | &"test:///root.rhai".parse().unwrap(), 79 | &Parser::new(root_src).parse_script().into_syntax(), 80 | ); 81 | 82 | hir.resolve_all(); 83 | 84 | assert_eq!(hir.missing_modules().len(), 1); 85 | } 86 | -------------------------------------------------------------------------------- /crates/rhai-hir/tests/visible_symbols.rs: -------------------------------------------------------------------------------- 1 | use rhai_hir::Hir; 2 | use rhai_rowan::{parser::Parser, util::src_cursor_offset}; 3 | 4 | #[test] 5 | fn test_visible_symbols_from_offset() { 6 | let (offset, src) = src_cursor_offset( 7 | r#"fn foo() {} 8 | $$ 9 | let bar = 3; 10 | 11 | 12 | "#, 13 | ); 14 | 15 | let mut hir = Hir::new(); 16 | 17 | let url = "test:///global.rhai".parse().unwrap(); 18 | 19 | hir.add_source(&url, &Parser::new(&src).parse_def().into_syntax()); 20 | 21 | hir.resolve_all(); 22 | 23 | assert!(hir 24 | .visible_symbols_from_offset(hir.source_by_url(&url).unwrap(), offset, false) 25 | .next() 26 | .is_some()) 27 | } 28 | 29 | #[test] 30 | fn test_visible_import() { 31 | let (offset, src) = src_cursor_offset( 32 | r#"import "module" as m; 33 | 34 | $$ 35 | "#, 36 | ); 37 | 38 | let mut hir = Hir::new(); 39 | 40 | let url = "test:///global.rhai".parse().unwrap(); 41 | 42 | hir.add_source(&url, &Parser::new(&src).parse_def().into_syntax()); 43 | 44 | hir.resolve_all(); 45 | 46 | assert!(hir 47 | .visible_symbols_from_offset(hir.source_by_url(&url).unwrap(), offset, false) 48 | .find_map(|symbol| hir[symbol].kind.as_import().and_then(|d| d.alias)) 49 | .is_some()) 50 | } 51 | -------------------------------------------------------------------------------- /crates/rhai-lsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-lsp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rhai-hir = { version = "0.1.0", path = "../rhai-hir" } 11 | rhai-rowan = { version = "0.1.0", path = "../rhai-rowan" } 12 | 13 | ahash = "0.8.0" 14 | anyhow = "1.0.58" 15 | arc-swap = "1.5.0" 16 | async-trait = "0.1.56" 17 | az = "1.2.0" 18 | clap = { version = "4.0.0", features = ["derive"] } 19 | figment = "0.10.6" 20 | futures = "0.3.21" 21 | glob = "0.3.0" 22 | globset = "0.4.9" 23 | indexmap = "1.9.1" 24 | itertools = "0.10.3" 25 | lsp-async-stub = "0.6.0" 26 | lsp-types = "0.93.0" 27 | once_cell = "1.12.0" 28 | serde = { version = "1.0.137", features = ["derive"] } 29 | serde_json = "1.0.81" 30 | toml = "0.5.9" 31 | tracing = "0.1.35" 32 | tracing-subscriber = { version = "0.3.11", features = ["env-filter"] } 33 | url = "2.2.2" 34 | rhai-common = { version = "0.1.0", path = "../rhai-common" } 35 | tokio = { version = "1.20.1", features = ["sync"] } 36 | rhai-fmt = { version = "0.1.0", path = "../rhai-fmt" } 37 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/config.rs: -------------------------------------------------------------------------------- 1 | use figment::{providers::Serialized, Figment}; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_json::Value; 4 | use std::path::PathBuf; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct InitConfig { 9 | pub cache_path: Option, 10 | #[serde(default = "default_configuration_section")] 11 | pub configuration_section: String, 12 | } 13 | 14 | impl Default for InitConfig { 15 | fn default() -> Self { 16 | Self { 17 | cache_path: Default::default(), 18 | configuration_section: default_configuration_section(), 19 | } 20 | } 21 | } 22 | 23 | fn default_configuration_section() -> String { 24 | String::from("rhai") 25 | } 26 | 27 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 28 | #[serde(rename_all = "camelCase")] 29 | pub struct LspConfig { 30 | pub syntax: SyntaxConfig, 31 | pub debug: DebugConfig, 32 | } 33 | 34 | impl LspConfig { 35 | pub fn update_from_json(&mut self, json: &Value) -> Result<(), anyhow::Error> { 36 | *self = Figment::new() 37 | .merge(Serialized::defaults(&self)) 38 | .merge(Serialized::defaults(json)) 39 | .extract()?; 40 | Ok(()) 41 | } 42 | } 43 | 44 | #[derive(Debug, Clone, Serialize, Deserialize)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct SyntaxConfig { 47 | pub semantic_tokens: bool, 48 | } 49 | 50 | impl Default for SyntaxConfig { 51 | fn default() -> Self { 52 | Self { 53 | semantic_tokens: true, 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 59 | #[serde(rename_all = "camelCase")] 60 | pub struct DebugConfig { 61 | pub hir: DebugHirConfig, 62 | } 63 | 64 | #[derive(Debug, Clone, Serialize, Deserialize)] 65 | #[serde(rename_all = "camelCase")] 66 | pub struct DebugHirConfig { 67 | pub full: bool, 68 | } 69 | 70 | impl Default for DebugHirConfig { 71 | fn default() -> Self { 72 | Self { full: true } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers.rs: -------------------------------------------------------------------------------- 1 | mod initialize; 2 | pub(crate) use initialize::*; 3 | 4 | mod configuration; 5 | pub(crate) use configuration::*; 6 | 7 | mod documents; 8 | pub(crate) use documents::*; 9 | 10 | mod workspaces; 11 | pub(crate) use workspaces::*; 12 | 13 | mod folding_ranges; 14 | pub(crate) use folding_ranges::*; 15 | 16 | mod goto; 17 | pub(crate) use goto::*; 18 | 19 | mod references; 20 | pub(crate) use references::*; 21 | 22 | mod document_symbols; 23 | pub(crate) use document_symbols::*; 24 | 25 | mod syntax_tree; 26 | pub(crate) use syntax_tree::*; 27 | 28 | mod convert_offsets; 29 | pub(crate) use convert_offsets::*; 30 | 31 | mod hover; 32 | pub(crate) use hover::*; 33 | 34 | mod rename; 35 | pub(crate) use rename::*; 36 | 37 | mod watch; 38 | pub(crate) use watch::*; 39 | 40 | mod completion; 41 | pub(crate) use completion::*; 42 | 43 | mod semantic_tokens; 44 | pub(crate) use semantic_tokens::*; 45 | 46 | mod debug; 47 | pub(crate) use debug::*; 48 | 49 | mod formatting; 50 | pub(crate) use formatting::*; 51 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/configuration.rs: -------------------------------------------------------------------------------- 1 | use rhai_common::environment::Environment; 2 | use crate::{world::World}; 3 | use anyhow::Context as AnyhowContext; 4 | use lsp_async_stub::{Context, Params, RequestWriter}; 5 | use lsp_types::{ 6 | request::WorkspaceConfiguration, ConfigurationItem, ConfigurationParams, 7 | DidChangeConfigurationParams, 8 | }; 9 | use std::iter::once; 10 | 11 | #[tracing::instrument(skip_all)] 12 | pub async fn configuration_change( 13 | context: Context>, 14 | params: Params, 15 | ) { 16 | let p = match params.optional() { 17 | None => return, 18 | Some(p) => p, 19 | }; 20 | 21 | let mut workspaces = context.workspaces.write().await; 22 | 23 | for (_, ws) in workspaces.iter_mut() { 24 | if let Err(error) = ws.config.update_from_json(&p.settings) { 25 | tracing::error!(?error, "invalid configuration"); 26 | } 27 | } 28 | } 29 | 30 | #[tracing::instrument(skip_all)] 31 | pub async fn update_configuration(context: Context>) { 32 | let init_config = context.init_config.load(); 33 | 34 | let mut workspaces = context.workspaces.write().await; 35 | 36 | let config_items: Vec<_> = workspaces 37 | .iter() 38 | .filter_map(|(url, ws)| { 39 | if ws.is_detached() { 40 | None 41 | } else { 42 | Some(ConfigurationItem { 43 | scope_uri: Some(url.clone()), 44 | section: Some(init_config.configuration_section.clone()), 45 | }) 46 | } 47 | }) 48 | .collect(); 49 | 50 | let res = context 51 | .clone() 52 | .write_request::(Some(ConfigurationParams { 53 | items: once(ConfigurationItem { 54 | scope_uri: None, 55 | section: Some(init_config.configuration_section.clone()), 56 | }) 57 | .chain(config_items.iter().cloned()) 58 | .collect::>(), 59 | })) 60 | .await 61 | .context("failed to fetch configuration") 62 | .and_then(|res| res.into_result().context("invalid configuration response")); 63 | 64 | match res { 65 | Ok(configs) => { 66 | for (i, config) in configs.into_iter().enumerate() { 67 | if i == 0 && config.is_object() { 68 | for (_, ws) in workspaces.iter_mut() { 69 | if let Err(error) = ws.config.update_from_json(&config) { 70 | tracing::error!(?error, "invalid configuration"); 71 | } 72 | } 73 | } else if config.is_object() { 74 | let ws_url = config_items.get(i - 1).unwrap().scope_uri.as_ref().unwrap(); 75 | let ws = workspaces.get_mut(ws_url).unwrap(); 76 | if let Err(error) = ws.config.update_from_json(&config) { 77 | tracing::error!(?error, "invalid configuration"); 78 | } 79 | } 80 | } 81 | } 82 | Err(error) => { 83 | tracing::error!(?error, "failed to fetch configuration"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/convert_offsets.rs: -------------------------------------------------------------------------------- 1 | use rhai_common::environment::Environment; 2 | use lsp_async_stub::{rpc, util::LspExt, Context, Params}; 3 | 4 | use crate::{ 5 | lsp_ext::request::{ConvertOffsetsParams, ConvertOffsetsResult}, 6 | world::World, 7 | }; 8 | 9 | #[tracing::instrument(skip_all)] 10 | pub(crate) async fn convert_offsets( 11 | context: Context>, 12 | params: Params, 13 | ) -> Result, rpc::Error> { 14 | let p = params.required()?; 15 | let workspaces = context.workspaces.read().await; 16 | let ws = workspaces.by_document(&p.uri); 17 | 18 | let doc = ws.document(&p.uri)?; 19 | 20 | Ok(Some(ConvertOffsetsResult { 21 | positions: p.positions.map(|offsets| { 22 | offsets 23 | .into_iter() 24 | .map(|offset| doc.mapper.position(offset).unwrap_or_default().into_lsp()) 25 | .collect() 26 | }), 27 | ranges: p.ranges.map(|ranges| { 28 | ranges 29 | .into_iter() 30 | .map(|range| doc.mapper.range(range).unwrap_or_default().into_lsp()) 31 | .collect() 32 | }), 33 | })) 34 | } 35 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | lsp_ext::request::{HirDumpParams, HirDumpResult}, 3 | world::World, 4 | }; 5 | use lsp_async_stub::{rpc, Context, Params}; 6 | use rhai_common::environment::Environment; 7 | use rhai_hir::fmt::HirFmt; 8 | 9 | #[tracing::instrument(skip_all)] 10 | pub(crate) async fn hir_dump( 11 | context: Context>, 12 | params: Params, 13 | ) -> Result, rpc::Error> { 14 | let p = params.required()?; 15 | let workspaces = context.workspaces.read().await; 16 | let ws = if let Some(uri) = p.workspace_uri { 17 | match workspaces.get(&uri) { 18 | Some(w) => w, 19 | None => return Ok(None), 20 | } 21 | } else { 22 | workspaces.get_detached() 23 | }; 24 | 25 | Ok(Some(HirDumpResult { 26 | hir: if ws.config.debug.hir.full { 27 | format!("{:#?}", ws.hir) 28 | } else { 29 | HirFmt::new(&ws.hir).with_source().to_string() 30 | }, 31 | })) 32 | } 33 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/documents.rs: -------------------------------------------------------------------------------- 1 | use lsp_async_stub::{Context, Params}; 2 | use lsp_types::{ 3 | DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, 4 | DidSaveTextDocumentParams, Url, 5 | }; 6 | use rhai_common::environment::Environment; 7 | use crate::{ 8 | diagnostics::{publish_all_diagnostics, publish_diagnostics}, 9 | world::World, 10 | }; 11 | 12 | #[tracing::instrument(skip_all)] 13 | pub(crate) async fn document_open( 14 | context: Context>, 15 | params: Params, 16 | ) { 17 | let p = match params.optional() { 18 | None => return, 19 | Some(p) => p, 20 | }; 21 | 22 | update_document( 23 | context.clone(), 24 | p.text_document.uri.clone(), 25 | &p.text_document.text, 26 | ) 27 | .await; 28 | publish_diagnostics(context.clone(), p.text_document.uri).await; 29 | 30 | context 31 | .clone() 32 | .all_diagnostics_debouncer 33 | .spawn(publish_all_diagnostics(context)); 34 | } 35 | 36 | #[tracing::instrument(skip_all)] 37 | pub(crate) async fn document_change( 38 | context: Context>, 39 | params: Params, 40 | ) { 41 | let mut p = match params.optional() { 42 | None => return, 43 | Some(p) => p, 44 | }; 45 | 46 | // We expect one full change 47 | let change = match p.content_changes.pop() { 48 | None => return, 49 | Some(c) => c, 50 | }; 51 | 52 | update_document(context.clone(), p.text_document.uri.clone(), &change.text).await; 53 | publish_diagnostics(context.clone(), p.text_document.uri).await; 54 | 55 | context 56 | .clone() 57 | .all_diagnostics_debouncer 58 | .spawn(publish_all_diagnostics(context)); 59 | } 60 | 61 | #[tracing::instrument(skip_all)] 62 | pub(crate) async fn document_save( 63 | _context: Context>, 64 | _params: Params, 65 | ) { 66 | // stub to silence warnings 67 | } 68 | 69 | #[tracing::instrument(skip_all)] 70 | pub(crate) async fn document_close( 71 | _context: Context>, 72 | _params: Params, 73 | ) { 74 | // no-op, we track it until it is deleted. 75 | } 76 | 77 | #[tracing::instrument(skip_all)] 78 | pub(crate) async fn update_document(ctx: Context>, uri: Url, text: &str) { 79 | let mut ws = ctx.workspaces.write().await; 80 | let ws = ws.by_document_mut(&uri); 81 | ws.add_document(uri, text); 82 | ws.hir.resolve_all(); 83 | } 84 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/formatting.rs: -------------------------------------------------------------------------------- 1 | use crate::world::World; 2 | use lsp_async_stub::{rpc, util::LspExt, Context, Params}; 3 | use lsp_types::{DocumentFormattingParams, TextEdit}; 4 | use rhai_common::environment::Environment; 5 | 6 | #[tracing::instrument(skip_all)] 7 | pub(crate) async fn format( 8 | context: Context>, 9 | params: Params, 10 | ) -> Result>, rpc::Error> { 11 | let p = params.required()?; 12 | 13 | let workspaces = context.workspaces.read().await; 14 | let ws = workspaces.by_document(&p.text_document.uri); 15 | let doc = match ws.document(&p.text_document.uri) { 16 | Ok(d) => d, 17 | Err(error) => { 18 | tracing::debug!(%error, "failed to get document from workspace"); 19 | return Ok(None); 20 | } 21 | }; 22 | 23 | let format_opts = rhai_fmt::Options { 24 | indent_string: if p.options.insert_spaces { 25 | " ".repeat(p.options.tab_size as usize) 26 | } else { 27 | "\t".into() 28 | }, 29 | ..Default::default() 30 | }; 31 | 32 | Ok(Some(vec![TextEdit { 33 | range: doc.mapper.all_range().into_lsp(), 34 | new_text: rhai_fmt::format_syntax(doc.parse.clone_syntax(), format_opts), 35 | }])) 36 | } 37 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/hover.rs: -------------------------------------------------------------------------------- 1 | use crate::{utils::documentation_for, world::World}; 2 | use lsp_async_stub::{rpc, util::LspExt, Context, Params}; 3 | use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind, Range}; 4 | use rhai_common::{environment::Environment, util::Normalize}; 5 | use rhai_hir::{symbol::ReferenceTarget, Hir, Symbol}; 6 | use rhai_rowan::{query::Query, syntax::SyntaxNode, TextSize}; 7 | 8 | pub(crate) async fn hover( 9 | context: Context>, 10 | params: Params, 11 | ) -> Result, rpc::Error> { 12 | let p = params.required()?; 13 | 14 | let uri = p.text_document_position_params.text_document.uri; 15 | let pos = p.text_document_position_params.position; 16 | 17 | let workspaces = context.workspaces.read().await; 18 | let ws = workspaces.by_document(&uri); 19 | 20 | let doc = ws.document(&uri)?; 21 | 22 | let offset = match doc 23 | .mapper 24 | .offset(lsp_async_stub::util::Position::from_lsp(pos)) 25 | { 26 | Some(p) => p + TextSize::from(1), 27 | None => return Ok(None), 28 | }; 29 | 30 | let source = match ws.hir.source_of(&uri.clone().normalize()) { 31 | Some(s) => s, 32 | None => return Ok(None), 33 | }; 34 | 35 | let syntax = doc.parse.clone_syntax(); 36 | 37 | let query = Query::at(&syntax, offset); 38 | 39 | if let Some(ident) = query.binary_op_ident() { 40 | if let Some(op) = ws.hir.operator_by_name(ident.text()) { 41 | return Ok(Some(Hover { 42 | contents: HoverContents::Markup(MarkupContent { 43 | kind: MarkupKind::Markdown, 44 | value: op.docs.clone(), 45 | }), 46 | range: doc.mapper.range(ident.text_range()).map(LspExt::into_lsp), 47 | })); 48 | } 49 | } 50 | 51 | let target_symbol = ws 52 | .hir 53 | .symbol_selection_at(source, offset, true) 54 | .map(|s| (s, &ws.hir[s])); 55 | 56 | if let Some((symbol, data)) = target_symbol { 57 | let highlight_range = data 58 | .selection_or_text_range() 59 | .and_then(|range| doc.mapper.range(range).map(LspExt::into_lsp)); 60 | 61 | return Ok(hover_for_symbol( 62 | &ws.hir, 63 | &doc.parse.clone_syntax(), 64 | highlight_range, 65 | symbol, 66 | )); 67 | } 68 | 69 | Ok(None) 70 | } 71 | 72 | fn hover_for_symbol( 73 | hir: &Hir, 74 | root: &SyntaxNode, 75 | highlight_range: Option, 76 | symbol: Symbol, 77 | ) -> Option { 78 | match &hir[symbol].kind { 79 | rhai_hir::symbol::SymbolKind::Fn(_) | rhai_hir::symbol::SymbolKind::Decl(_) => { 80 | Some(Hover { 81 | contents: HoverContents::Markup(MarkupContent { 82 | kind: MarkupKind::Markdown, 83 | value: documentation_for(hir, symbol, true), 84 | }), 85 | range: highlight_range, 86 | }) 87 | } 88 | rhai_hir::symbol::SymbolKind::Ref(r) => match &r.target { 89 | Some(ReferenceTarget::Symbol(target)) => { 90 | hover_for_symbol(hir, root, highlight_range, *target) 91 | } 92 | _ => None, 93 | }, 94 | _ => None, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/syntax_tree.rs: -------------------------------------------------------------------------------- 1 | use lsp_async_stub::{rpc, Context, Params}; 2 | use rhai_common::environment::Environment; 3 | use crate::{ 4 | lsp_ext::request::{SyntaxTreeParams, SyntaxTreeResult}, 5 | world::World, 6 | }; 7 | 8 | pub(crate) async fn syntax_tree( 9 | context: Context>, 10 | params: Params, 11 | ) -> Result, rpc::Error> { 12 | let p = params.required()?; 13 | let workspaces = context.workspaces.read().await; 14 | let ws = workspaces.by_document(&p.uri); 15 | 16 | let doc = ws.document(&p.uri)?; 17 | 18 | let syntax = doc.parse.clone().into_syntax(); 19 | Ok(Some(SyntaxTreeResult { 20 | text: format!("{:#?}", &syntax), 21 | tree: serde_json::to_value(&syntax).unwrap_or_default(), 22 | })) 23 | } 24 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/watch.rs: -------------------------------------------------------------------------------- 1 | use lsp_async_stub::{Context, Params}; 2 | use lsp_types::{DidChangeWatchedFilesParams, FileChangeType}; 3 | use rhai_common::environment::Environment; 4 | use crate::{ 5 | diagnostics::{clear_diagnostics, publish_all_diagnostics}, 6 | world::World, 7 | }; 8 | 9 | use super::update_document; 10 | 11 | pub(crate) async fn watched_file_change( 12 | context: Context>, 13 | params: Params, 14 | ) { 15 | let params = match params.optional() { 16 | Some(p) => p, 17 | None => return, 18 | }; 19 | 20 | for change in params.changes { 21 | let uri = change.uri; 22 | 23 | let mut workspaces = context.workspaces.write().await; 24 | 25 | match change.typ { 26 | FileChangeType::CREATED | FileChangeType::CHANGED => { 27 | let path = match context.env.url_to_file_path(&uri) { 28 | Some(p) => p, 29 | None => { 30 | tracing::warn!(url = %uri, "could not create file path from url"); 31 | continue; 32 | } 33 | }; 34 | 35 | let file_content = match context.env.read_file(&path).await { 36 | Ok(c) => c, 37 | Err(err) => { 38 | tracing::error!(error = %err, "failed to read file"); 39 | continue; 40 | } 41 | }; 42 | 43 | let source = match String::from_utf8(file_content) { 44 | Ok(s) => s, 45 | Err(error) => { 46 | tracing::error!(url = %uri, %error, "source is not valid UTF-8"); 47 | continue; 48 | } 49 | }; 50 | 51 | drop(workspaces); 52 | update_document(context.clone(), uri, &source).await; 53 | } 54 | FileChangeType::DELETED => { 55 | let ws = workspaces.by_document_mut(&uri); 56 | ws.remove_document(&uri) ; 57 | clear_diagnostics(context.clone(), uri).await; 58 | } 59 | _ => { 60 | tracing::warn!(change_type = ?change.typ, "unknown file event"); 61 | } 62 | } 63 | } 64 | 65 | context 66 | .clone() 67 | .all_diagnostics_debouncer 68 | .spawn(publish_all_diagnostics(context)); 69 | } 70 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/handlers/workspaces.rs: -------------------------------------------------------------------------------- 1 | use super::update_configuration; 2 | use crate::{ 3 | world::{Workspace, World}, 4 | }; 5 | use lsp_async_stub::{Context, Params}; 6 | use lsp_types::DidChangeWorkspaceFoldersParams; 7 | use rhai_common::environment::Environment; 8 | 9 | pub async fn workspace_change( 10 | context: Context>, 11 | params: Params, 12 | ) { 13 | let p = match params.optional() { 14 | None => return, 15 | Some(p) => p, 16 | }; 17 | 18 | let mut workspaces = context.workspaces.write().await; 19 | 20 | for removed in p.event.removed { 21 | workspaces.remove(&removed.uri); 22 | } 23 | 24 | for added in p.event.added { 25 | let mut ws = Workspace::new(context.env.clone(), added.uri.clone()); 26 | 27 | if let Err(error) = ws.load_rhai_config().await { 28 | tracing::error!(%error, "invalid configuration"); 29 | } 30 | ws.load_all_files().await; 31 | 32 | workspaces.entry(added.uri).or_insert(ws); 33 | } 34 | 35 | drop(workspaces); 36 | update_configuration(context).await; 37 | } 38 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow( 3 | clippy::unused_async, 4 | clippy::single_match, 5 | clippy::wildcard_imports, 6 | clippy::too_many_lines, 7 | clippy::enum_glob_use, 8 | clippy::module_name_repetitions, 9 | clippy::single_match_else, 10 | clippy::default_trait_access, 11 | clippy::missing_errors_doc 12 | )] 13 | 14 | use std::sync::Arc; 15 | use rhai_common::environment::Environment; 16 | use lsp_async_stub::Server; 17 | use lsp_types::{notification, request}; 18 | pub use world::{World, WorldState}; 19 | 20 | pub(crate) mod config; 21 | pub(crate) mod diagnostics; 22 | pub(crate) mod lsp_ext; 23 | pub(crate) mod utils; 24 | pub(crate) mod world; 25 | 26 | mod handlers; 27 | 28 | pub(crate) type IndexMap = indexmap::IndexMap; 29 | 30 | #[must_use] 31 | pub fn create_server() -> Server> { 32 | Server::new() 33 | .on_request::(handlers::initialize) 34 | .on_request::(handlers::folding_ranges) 35 | .on_request::(handlers::goto_declaration) 36 | .on_request::(handlers::goto_definition) 37 | .on_request::(handlers::references) 38 | .on_request::(handlers::document_symbols) 39 | .on_request::(handlers::hover) 40 | .on_request::(handlers::semantic_tokens) 41 | .on_request::(handlers::completion) 42 | .on_request::(handlers::prepare_rename) 43 | .on_request::(handlers::rename) 44 | .on_request::(handlers::format) 45 | .on_notification::(handlers::initialized) 46 | .on_notification::(handlers::document_open) 47 | .on_notification::(handlers::document_change) 48 | .on_notification::(handlers::document_save) 49 | .on_notification::(handlers::document_close) 50 | .on_notification::(handlers::configuration_change) 51 | .on_notification::(handlers::workspace_change) 52 | .on_notification::(handlers::watched_file_change) 53 | .on_request::(handlers::hir_dump) 54 | .on_request::(handlers::syntax_tree) 55 | .on_request::(handlers::convert_offsets) 56 | .build() 57 | } 58 | 59 | #[must_use] 60 | pub fn create_world(env: E) -> World { 61 | Arc::new(WorldState::new(env)) 62 | } 63 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/lsp_ext.rs: -------------------------------------------------------------------------------- 1 | pub mod request { 2 | use lsp_types::{request::Request, Url}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | pub enum HirDump {} 6 | 7 | #[derive(Serialize, Deserialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct HirDumpParams { 10 | pub workspace_uri: Option, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct HirDumpResult { 16 | /// The unstable textual representation 17 | /// of the HIR. 18 | pub hir: String, 19 | } 20 | 21 | impl Request for HirDump { 22 | type Params = HirDumpParams; 23 | 24 | type Result = Option; 25 | 26 | const METHOD: &'static str = "rhai/hirDump"; 27 | } 28 | 29 | pub enum SyntaxTree {} 30 | 31 | #[derive(Serialize, Deserialize)] 32 | #[serde(rename_all = "camelCase")] 33 | pub struct SyntaxTreeParams { 34 | pub uri: Url, 35 | } 36 | 37 | #[derive(Serialize, Deserialize)] 38 | pub struct SyntaxTreeResult { 39 | /// Text representation of the syntax tree. 40 | pub text: String, 41 | /// JSON representation of the syntax tree. 42 | pub tree: serde_json::Value, 43 | } 44 | 45 | impl Request for SyntaxTree { 46 | type Params = SyntaxTreeParams; 47 | 48 | type Result = Option; 49 | 50 | const METHOD: &'static str = "rhai/syntaxTree"; 51 | } 52 | 53 | pub enum ConvertOffsets {} 54 | 55 | #[derive(Serialize, Deserialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct ConvertOffsetsParams { 58 | pub uri: Url, 59 | pub ranges: Option>, 60 | pub positions: Option>, 61 | } 62 | 63 | #[derive(Serialize, Deserialize)] 64 | pub struct ConvertOffsetsResult { 65 | /// LSP-compatible UTF-16 ranges. 66 | pub ranges: Option>, 67 | /// LSP-compatible UTF-16 positions. 68 | pub positions: Option>, 69 | } 70 | 71 | impl Request for ConvertOffsets { 72 | type Params = ConvertOffsetsParams; 73 | 74 | type Result = Option; 75 | 76 | const METHOD: &'static str = "rhai/convertOffsets"; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/rhai-lsp/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use arc_swap::ArcSwapOption; 4 | use futures::{ 5 | future::{AbortHandle, Abortable}, 6 | Future, 7 | }; 8 | use rhai_hir::{symbol::SymbolKind, Hir, Symbol}; 9 | 10 | use rhai_common::environment::Environment; 11 | 12 | /// Format signatures and definitions of symbols. 13 | pub fn signature_of(hir: &Hir, symbol: Symbol) -> String { 14 | let sym_data = &hir[symbol]; 15 | 16 | match &sym_data.kind { 17 | SymbolKind::Decl(decl) => { 18 | format!( 19 | "{}{}: {}", 20 | if decl.is_param { 21 | "" 22 | } else if decl.is_const { 23 | "const " 24 | } else { 25 | "let " 26 | }, 27 | decl.name, 28 | sym_data.ty.fmt(hir) 29 | ) 30 | } 31 | _ => { 32 | format!("{}", sym_data.ty.fmt(hir)) 33 | } 34 | } 35 | } 36 | 37 | pub fn documentation_for(hir: &Hir, symbol: Symbol, signature: bool) -> String { 38 | if let Some(m) = hir.target_module(symbol) { 39 | return hir[m].docs.clone(); 40 | } 41 | 42 | let sig = if signature { 43 | signature_of(hir, symbol).wrap_rhai_markdown() 44 | } else { 45 | String::new() 46 | }; 47 | 48 | let sym_data = &hir[symbol]; 49 | 50 | if let Some(docs) = sym_data.docs() { 51 | return format!( 52 | "{sig}{docs}", 53 | sig = sig, 54 | docs = if docs.is_empty() { 55 | String::new() 56 | } else { 57 | format!("\n{}", docs) 58 | } 59 | ); 60 | } 61 | 62 | String::new() 63 | } 64 | 65 | pub trait RhaiStringExt { 66 | fn wrap_rhai_markdown(&self) -> String; 67 | } 68 | 69 | impl> RhaiStringExt for T { 70 | fn wrap_rhai_markdown(&self) -> String { 71 | format!("```rhai\n{}\n```", self.as_ref().trim_end()) 72 | } 73 | } 74 | 75 | pub struct Debouncer { 76 | duration: Duration, 77 | handle: ArcSwapOption, 78 | env: E, 79 | } 80 | 81 | impl Debouncer { 82 | pub fn new(duration: Duration, env: E) -> Self { 83 | Self { 84 | duration, 85 | handle: Default::default(), 86 | env, 87 | } 88 | } 89 | 90 | pub fn spawn(&self, fut: impl Future + 'static) { 91 | let prev_handle = self.handle.swap(None); 92 | 93 | if let Some(handle) = prev_handle { 94 | handle.abort(); 95 | } 96 | 97 | let (abort_handle, abort_reg) = AbortHandle::new_pair(); 98 | 99 | let duration = self.duration; 100 | let env = self.env.clone(); 101 | 102 | let fut = Abortable::new( 103 | async move { 104 | env.sleep(duration).await; 105 | fut.await; 106 | }, 107 | abort_reg, 108 | ); 109 | 110 | self.handle.store(Some(Arc::new(abort_handle))); 111 | self.env.spawn_local(fut); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /crates/rhai-rowan/.gitignore: -------------------------------------------------------------------------------- 1 | src/ast/generated.rs 2 | -------------------------------------------------------------------------------- /crates/rhai-rowan/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-rowan" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | logos = "0.12.0" 11 | rowan = { version = "0.15.5", features = ["serde1"] } 12 | thiserror = "1.0.29" 13 | tracing = { version = "0.1.28" } 14 | serde = { version = "1", features = ["derive"] } 15 | strum = { version = "0.24.1", features = ["derive"] } 16 | 17 | [dev-dependencies] 18 | insta = "1.8.0" 19 | criterion = { version = "0.4.0", features = ["html_reports"] } 20 | pprof = { version = "0.10.1", features = ["flamegraph", "criterion"] } 21 | test-case = "2.1.0" 22 | tracing-subscriber = "0.3.11" 23 | rhai = { version = "1.8.0", features = ["debugging"] } 24 | 25 | [build-dependencies] 26 | cargo-emit = "0.2.1" 27 | rhai-sourcegen = { path = "../rhai-sourcegen" } 28 | 29 | [[bench]] 30 | name = "parse" 31 | harness = false 32 | -------------------------------------------------------------------------------- /crates/rhai-rowan/README.md: -------------------------------------------------------------------------------- 1 | # Rhai Rowan 2 | 3 | A Rhai parser and CST/AST definitions based on [Rowan](https://github.com/rust-analyzer/rowan). 4 | 5 | -------------------------------------------------------------------------------- /crates/rhai-rowan/benches/parse.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; 2 | use pprof::criterion::{Output, PProfProfiler}; 3 | use rhai_rowan::parser::{Parse, Parser}; 4 | 5 | fn parse(src: &str) -> Parse { 6 | Parser::new(src).parse_script() 7 | } 8 | 9 | fn bench(c: &mut Criterion) { 10 | const SIMPLE_SRC: &str = include_str!("../../../testdata/valid/simple.rhai"); 11 | const OOP_SRC: &str = include_str!("../../../testdata/valid/oop.rhai"); 12 | 13 | let mut g = c.benchmark_group("simple"); 14 | g.throughput(Throughput::Bytes(SIMPLE_SRC.as_bytes().len() as u64)) 15 | .bench_function("parse simple", |b| b.iter(|| parse(black_box(SIMPLE_SRC)))); 16 | g.finish(); 17 | 18 | let mut g = c.benchmark_group("oop"); 19 | g.throughput(Throughput::Bytes(OOP_SRC.as_bytes().len() as u64)) 20 | .bench_function("parse oop", |b| b.iter(|| parse(black_box(OOP_SRC)))); 21 | g.finish(); 22 | } 23 | 24 | criterion_group!( 25 | name = benches; 26 | config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 27 | targets = bench 28 | ); 29 | criterion_main!(benches); 30 | -------------------------------------------------------------------------------- /crates/rhai-rowan/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | fn main() { 4 | cargo_emit::rerun_if_changed!("src/ast/rhai.ungram"); 5 | 6 | let ungram = std::fs::read_to_string("src/ast/rhai.ungram").unwrap(); 7 | 8 | let ungram = if cfg!(windows) { 9 | ungram.replace("\r\n", "\n") 10 | } else { 11 | ungram 12 | }; 13 | 14 | let generated = rhai_sourcegen::syntax::generate_syntax(&ungram).unwrap(); 15 | 16 | let syntax_file = std::fs::read_to_string("src/syntax.rs").unwrap(); 17 | 18 | let nodes_region_idx = syntax_file.find("// region: Nodes").unwrap(); 19 | 20 | let nodes_region = &syntax_file[nodes_region_idx..]; 21 | 22 | let nodes_region_end_idx = nodes_region.find("// endregion").unwrap(); 23 | 24 | let new_syntax_file = 25 | String::from(&syntax_file[..nodes_region_idx + "// region: Nodes".len() + 1]) 26 | + " // This region is generated from ungrammar, do not touch it!\n" 27 | + &generated 28 | .node_kinds 29 | .into_iter() 30 | .map(|s| String::from(" ") + &s + ",\n") 31 | .collect::() 32 | + " " 33 | + &syntax_file[nodes_region_idx + nodes_region_end_idx..]; 34 | 35 | let mut f = std::fs::File::create("src/syntax.rs").unwrap(); 36 | 37 | f.write_all(new_syntax_file.as_bytes()).unwrap(); 38 | 39 | let mut f = std::fs::File::create("src/ast/generated.rs").unwrap(); 40 | f.write_all(b"//! This file was generated from ungrammar, do not touch it!\n") 41 | .unwrap(); 42 | f.write_all(generated.token_macro.as_bytes()).unwrap(); 43 | f.write_all(b"\n").unwrap(); 44 | f.write_all(rhai_sourcegen::util::format_rust(&generated.ast).as_bytes()) 45 | .unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /crates/rhai-rowan/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains all generated AST types and helpers from ungrammar code. 2 | 3 | #[allow(clippy::pedantic, clippy::iter_skip_next)] 4 | mod generated; 5 | pub use generated::*; 6 | 7 | mod ext; 8 | pub use ext::*; 9 | -------------------------------------------------------------------------------- /crates/rhai-rowan/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow( 3 | clippy::unused_async, 4 | clippy::single_match, 5 | clippy::wildcard_imports, 6 | clippy::too_many_lines, 7 | clippy::enum_glob_use, 8 | clippy::cast_possible_truncation, 9 | clippy::cast_lossless, 10 | clippy::module_name_repetitions, 11 | clippy::single_match_else, 12 | clippy::option_option 13 | )] 14 | 15 | pub mod ast; 16 | pub mod parser; 17 | pub mod query; 18 | pub mod syntax; 19 | pub mod util; 20 | 21 | pub use rowan::{TextRange, TextSize}; 22 | 23 | pub use parser::{parsers, Parser}; 24 | -------------------------------------------------------------------------------- /crates/rhai-rowan/src/query/util.rs: -------------------------------------------------------------------------------- 1 | use crate::syntax::{ 2 | SyntaxElement, 3 | SyntaxKind::{self, *}, 4 | SyntaxNode, SyntaxToken, 5 | }; 6 | 7 | pub(super) trait SyntaxExt { 8 | fn syntax_kind(&self) -> SyntaxKind; 9 | fn next_non_ws_sibling(&self) -> Option; 10 | fn prev_non_ws_sibling(&self) -> Option; 11 | } 12 | 13 | impl SyntaxExt for SyntaxElement { 14 | fn syntax_kind(&self) -> SyntaxKind { 15 | self.kind() 16 | } 17 | 18 | fn next_non_ws_sibling(&self) -> Option { 19 | match self { 20 | rowan::NodeOrToken::Node(v) => v.next_non_ws_sibling(), 21 | rowan::NodeOrToken::Token(v) => v.next_non_ws_sibling(), 22 | } 23 | } 24 | 25 | fn prev_non_ws_sibling(&self) -> Option { 26 | match self { 27 | rowan::NodeOrToken::Node(v) => v.prev_non_ws_sibling(), 28 | rowan::NodeOrToken::Token(v) => v.prev_non_ws_sibling(), 29 | } 30 | } 31 | } 32 | 33 | impl SyntaxExt for SyntaxToken { 34 | fn syntax_kind(&self) -> SyntaxKind { 35 | self.kind() 36 | } 37 | 38 | fn next_non_ws_sibling(&self) -> Option { 39 | self.siblings_with_tokens(rowan::Direction::Next) 40 | .find(not_ws_or_comment) 41 | } 42 | 43 | fn prev_non_ws_sibling(&self) -> Option { 44 | self.siblings_with_tokens(rowan::Direction::Prev) 45 | .find(not_ws_or_comment) 46 | } 47 | } 48 | 49 | impl SyntaxExt for SyntaxNode { 50 | fn syntax_kind(&self) -> SyntaxKind { 51 | self.kind() 52 | } 53 | 54 | fn next_non_ws_sibling(&self) -> Option { 55 | self.siblings_with_tokens(rowan::Direction::Next) 56 | .find(not_ws_or_comment) 57 | } 58 | 59 | fn prev_non_ws_sibling(&self) -> Option { 60 | self.siblings_with_tokens(rowan::Direction::Prev) 61 | .find(not_ws_or_comment) 62 | } 63 | } 64 | 65 | #[allow(dead_code)] 66 | pub(super) fn not_ws_or_comment(token: &T) -> bool { 67 | !matches!( 68 | token.syntax_kind(), 69 | WHITESPACE | COMMENT_BLOCK | COMMENT_LINE 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_custom_operator.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 70 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..49 7 | STMT@0..15 8 | ITEM@0..14 9 | EXPR@0..14 10 | EXPR_LET@0..14 11 | KW_LET@0..3 "let" 12 | WHITESPACE@3..4 " " 13 | IDENT@4..5 "a" 14 | WHITESPACE@5..6 " " 15 | OP_ASSIGN@6..7 "=" 16 | EXPR@7..14 17 | EXPR_BINARY@7..14 18 | EXPR@7..10 19 | WHITESPACE@7..8 " " 20 | EXPR_LIT@8..9 21 | LIT@8..9 22 | LIT_INT@8..9 "3" 23 | WHITESPACE@9..10 " " 24 | KW_IN@10..12 "in" 25 | EXPR@12..14 26 | WHITESPACE@12..13 " " 27 | EXPR_LIT@13..14 28 | LIT@13..14 29 | LIT_INT@13..14 "4" 30 | PUNCT_SEMI@14..15 ";" 31 | WHITESPACE@15..17 "\n\n" 32 | STMT@17..48 33 | ITEM@17..47 34 | EXPR@17..47 35 | EXPR_LET@17..47 36 | KW_LET@17..20 "let" 37 | WHITESPACE@20..21 " " 38 | IDENT@21..36 "custom_operator" 39 | WHITESPACE@36..37 " " 40 | OP_ASSIGN@37..38 "=" 41 | EXPR@38..47 42 | EXPR_BINARY@38..47 43 | EXPR@38..41 44 | WHITESPACE@38..39 " " 45 | EXPR_LIT@39..40 46 | LIT@39..40 47 | LIT_INT@39..40 "3" 48 | WHITESPACE@40..41 " " 49 | IDENT@41..45 "over" 50 | EXPR@45..47 51 | WHITESPACE@45..46 " " 52 | EXPR_LIT@46..47 53 | LIT@46..47 54 | LIT_INT@46..47 "4" 55 | PUNCT_SEMI@47..48 ";" 56 | WHITESPACE@48..49 "\n" 57 | 58 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@assignment.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 65 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..100 7 | COMMENT_LINE@0..54 "// This script contai ..." 8 | WHITESPACE@54..56 "\n\n" 9 | STMT@56..67 10 | ITEM@56..66 11 | EXPR@56..66 12 | EXPR_LET@56..66 13 | KW_LET@56..59 "let" 14 | WHITESPACE@59..60 " " 15 | IDENT@60..61 "x" 16 | WHITESPACE@61..62 " " 17 | OP_ASSIGN@62..63 "=" 18 | EXPR@63..66 19 | WHITESPACE@63..64 " " 20 | EXPR_LIT@64..66 21 | LIT@64..66 22 | LIT_INT@64..66 "78" 23 | PUNCT_SEMI@66..67 ";" 24 | WHITESPACE@67..69 "\n\n" 25 | STMT@69..99 26 | ITEM@69..98 27 | EXPR@69..98 28 | EXPR_CALL@69..98 29 | EXPR@69..74 30 | EXPR_IDENT@69..74 31 | IDENT@69..74 "print" 32 | ARG_LIST@74..98 33 | PUNCT_PAREN_START@74..75 "(" 34 | EXPR@75..97 35 | EXPR_LIT@75..97 36 | LIT@75..97 37 | LIT_STR_TEMPLATE@75..97 38 | LIT_STR@75..92 "`x should be 78: " 39 | INTERPOLATION_START@92..94 "${" 40 | LIT_STR_TEMPLATE_INTERPOLATION@94..95 41 | STMT@94..95 42 | ITEM@94..95 43 | EXPR@94..95 44 | EXPR_IDENT@94..95 45 | IDENT@94..95 "x" 46 | PUNCT_BRACE_END@95..96 "}" 47 | LIT_STR@96..97 "`" 48 | PUNCT_PAREN_END@97..98 ")" 49 | PUNCT_SEMI@98..99 ";" 50 | WHITESPACE@99..100 "\n" 51 | 52 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@char.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 38 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..31 7 | STMT@0..14 8 | ITEM@0..13 9 | EXPR@0..13 10 | EXPR_LET@0..13 11 | KW_LET@0..3 "let" 12 | WHITESPACE@3..4 " " 13 | IDENT@4..7 "val" 14 | WHITESPACE@7..8 " " 15 | OP_ASSIGN@8..9 "=" 16 | EXPR@9..13 17 | WHITESPACE@9..10 " " 18 | EXPR_LIT@10..13 19 | LIT@10..13 20 | LIT_CHAR@10..13 "'a'" 21 | PUNCT_SEMI@13..14 ";" 22 | WHITESPACE@14..15 "\n" 23 | STMT@15..30 24 | ITEM@15..29 25 | EXPR@15..29 26 | EXPR_LET@15..29 27 | KW_LET@15..18 "let" 28 | WHITESPACE@18..19 " " 29 | IDENT@19..22 "val" 30 | WHITESPACE@22..23 " " 31 | OP_ASSIGN@23..24 "=" 32 | EXPR@24..29 33 | WHITESPACE@24..25 " " 34 | EXPR_LIT@25..29 35 | LIT@25..29 36 | LIT_CHAR@25..29 "'\\n'" 37 | PUNCT_SEMI@29..30 ";" 38 | WHITESPACE@30..31 "\n" 39 | 40 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@comments.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 37 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..272 7 | COMMENT_LINE@0..30 "// I am a single line ..." 8 | WHITESPACE@30..32 "\n\n" 9 | STMT@32..86 10 | ITEM@32..85 11 | EXPR@32..85 12 | EXPR_LET@32..85 13 | KW_LET@32..35 "let" 14 | WHITESPACE@35..36 " " 15 | COMMENT_BLOCK@36..79 "/* I am a spy in a va ..." 16 | WHITESPACE@79..80 " " 17 | IDENT@80..81 "x" 18 | WHITESPACE@81..82 " " 19 | OP_ASSIGN@82..83 "=" 20 | EXPR@83..85 21 | WHITESPACE@83..84 " " 22 | EXPR_LIT@84..85 23 | LIT@84..85 24 | LIT_INT@84..85 "5" 25 | PUNCT_SEMI@85..86 ";" 26 | WHITESPACE@86..88 "\n\n" 27 | COMMENT_BLOCK@88..132 "/* I am a simple\n m ..." 28 | WHITESPACE@132..134 "\n\n" 29 | COMMENT_BLOCK@134..205 "/* look /* at /* that ..." 30 | WHITESPACE@205..207 "\n\n" 31 | COMMENT_BLOCK@207..226 "/* surrounded by */" 32 | WHITESPACE@226..227 " " 33 | STMT@227..272 34 | ITEM@227..272 35 | EXPR@227..272 36 | EXPR_LET@227..272 37 | KW_LET@227..230 "let" 38 | WHITESPACE@230..231 " " 39 | IDENT@231..252 "this_is_not_a_comment" 40 | WHITESPACE@252..253 " " 41 | OP_ASSIGN@253..254 "=" 42 | EXPR@254..272 43 | WHITESPACE@254..255 " " 44 | EXPR_LIT@255..259 45 | LIT@255..259 46 | LIT_BOOL@255..259 "true" 47 | WHITESPACE@259..260 " " 48 | COMMENT_LINE@260..271 "// comments" 49 | WHITESPACE@271..272 "\n" 50 | 51 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@function_decl1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 65 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..149 7 | COMMENT_LINE@0..47 "// This script define ..." 8 | WHITESPACE@47..49 "\n\n" 9 | STMT@49..81 10 | ITEM@49..79 11 | EXPR@49..79 12 | EXPR_FN@49..79 13 | KW_FN@49..51 "fn" 14 | WHITESPACE@51..52 " " 15 | IDENT@52..59 "call_me" 16 | PARAM_LIST@59..61 17 | PUNCT_PAREN_START@59..60 "(" 18 | PUNCT_PAREN_END@60..61 ")" 19 | EXPR_BLOCK@61..79 20 | WHITESPACE@61..62 " " 21 | PUNCT_BRACE_START@62..63 "{" 22 | WHITESPACE@63..68 "\n " 23 | STMT@68..77 24 | ITEM@68..76 25 | EXPR@68..76 26 | EXPR_RETURN@68..76 27 | KW_RETURN@68..74 "return" 28 | WHITESPACE@74..75 " " 29 | EXPR@75..76 30 | EXPR_LIT@75..76 31 | LIT@75..76 32 | LIT_INT@75..76 "3" 33 | PUNCT_SEMI@76..77 ";" 34 | WHITESPACE@77..78 "\n" 35 | PUNCT_BRACE_END@78..79 "}" 36 | WHITESPACE@79..81 "\n\n" 37 | STMT@81..104 38 | ITEM@81..103 39 | EXPR@81..103 40 | EXPR_LET@81..103 41 | KW_LET@81..84 "let" 42 | WHITESPACE@84..85 " " 43 | IDENT@85..91 "result" 44 | WHITESPACE@91..92 " " 45 | OP_ASSIGN@92..93 "=" 46 | EXPR@93..103 47 | EXPR_CALL@93..103 48 | EXPR@93..101 49 | WHITESPACE@93..94 " " 50 | EXPR_IDENT@94..101 51 | IDENT@94..101 "call_me" 52 | ARG_LIST@101..103 53 | PUNCT_PAREN_START@101..102 "(" 54 | PUNCT_PAREN_END@102..103 ")" 55 | PUNCT_SEMI@103..104 ";" 56 | WHITESPACE@104..106 "\n\n" 57 | STMT@106..148 58 | ITEM@106..147 59 | EXPR@106..147 60 | EXPR_CALL@106..147 61 | EXPR@106..111 62 | EXPR_IDENT@106..111 63 | IDENT@106..111 "print" 64 | ARG_LIST@111..147 65 | PUNCT_PAREN_START@111..112 "(" 66 | EXPR@112..146 67 | EXPR_LIT@112..146 68 | LIT@112..146 69 | LIT_STR_TEMPLATE@112..146 70 | LIT_STR@112..136 "`call_me() should be 3: " 71 | INTERPOLATION_START@136..138 "${" 72 | LIT_STR_TEMPLATE_INTERPOLATION@138..144 73 | STMT@138..144 74 | ITEM@138..144 75 | EXPR@138..144 76 | EXPR_IDENT@138..144 77 | IDENT@138..144 "result" 78 | PUNCT_BRACE_END@144..145 "}" 79 | LIT_STR@145..146 "`" 80 | PUNCT_PAREN_END@146..147 ")" 81 | PUNCT_SEMI@147..148 ";" 82 | WHITESPACE@148..149 "\n" 83 | 84 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@module.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 65 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..116 7 | COMMENT_LINE@0..54 "// This script import ..." 8 | WHITESPACE@54..56 "\n\n" 9 | STMT@56..75 10 | ITEM@56..74 11 | EXPR@56..74 12 | EXPR_IMPORT@56..74 13 | KW_IMPORT@56..62 "import" 14 | EXPR@62..70 15 | WHITESPACE@62..63 " " 16 | EXPR_LIT@63..69 17 | LIT@63..69 18 | LIT_STR@63..69 "\"loop\"" 19 | WHITESPACE@69..70 " " 20 | KW_AS@70..72 "as" 21 | WHITESPACE@72..73 " " 22 | IDENT@73..74 "x" 23 | PUNCT_SEMI@74..75 ";" 24 | WHITESPACE@75..77 "\n\n" 25 | STMT@77..115 26 | ITEM@77..114 27 | EXPR@77..114 28 | EXPR_CALL@77..114 29 | EXPR@77..82 30 | EXPR_IDENT@77..82 31 | IDENT@77..82 "print" 32 | ARG_LIST@82..114 33 | PUNCT_PAREN_START@82..83 "(" 34 | EXPR@83..113 35 | EXPR_LIT@83..113 36 | LIT@83..113 37 | LIT_STR_TEMPLATE@83..113 38 | LIT_STR@83..103 "`Module test! foo = " 39 | INTERPOLATION_START@103..105 "${" 40 | LIT_STR_TEMPLATE_INTERPOLATION@105..111 41 | STMT@105..111 42 | ITEM@105..111 43 | EXPR@105..111 44 | EXPR_PATH@105..111 45 | PATH@105..111 46 | IDENT@105..106 "x" 47 | PUNCT_COLON2@106..108 "::" 48 | IDENT@108..111 "foo" 49 | PUNCT_BRACE_END@111..112 "}" 50 | LIT_STR@112..113 "`" 51 | PUNCT_PAREN_END@113..114 ")" 52 | PUNCT_SEMI@114..115 ";" 53 | WHITESPACE@115..116 "\n" 54 | 55 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@op1.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 37 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..94 7 | COMMENT_LINE@0..40 "// This script runs a ..." 8 | WHITESPACE@40..42 "\n\n" 9 | STMT@42..76 10 | ITEM@42..75 11 | EXPR@42..75 12 | EXPR_CALL@42..75 13 | EXPR@42..47 14 | EXPR_IDENT@42..47 15 | IDENT@42..47 "print" 16 | ARG_LIST@47..75 17 | PUNCT_PAREN_START@47..48 "(" 18 | EXPR@48..74 19 | EXPR_LIT@48..74 20 | LIT@48..74 21 | LIT_STR@48..74 "\"The result should be ..." 22 | PUNCT_PAREN_END@74..75 ")" 23 | PUNCT_SEMI@75..76 ";" 24 | WHITESPACE@76..78 "\n\n" 25 | STMT@78..93 26 | ITEM@78..92 27 | EXPR@78..92 28 | EXPR_CALL@78..92 29 | EXPR@78..83 30 | EXPR_IDENT@78..83 31 | IDENT@78..83 "print" 32 | ARG_LIST@83..92 33 | PUNCT_PAREN_START@83..84 "(" 34 | EXPR@84..91 35 | EXPR_BINARY@84..91 36 | EXPR@84..87 37 | EXPR_LIT@84..86 38 | LIT@84..86 39 | LIT_INT@84..86 "34" 40 | WHITESPACE@86..87 " " 41 | OP_ADD@87..88 "+" 42 | EXPR@88..91 43 | WHITESPACE@88..89 " " 44 | EXPR_LIT@89..91 45 | LIT@89..91 46 | LIT_INT@89..91 "12" 47 | PUNCT_PAREN_END@91..92 ")" 48 | PUNCT_SEMI@92..93 ";" 49 | WHITESPACE@93..94 "\n" 50 | 51 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@op2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 37 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..112 7 | COMMENT_LINE@0..41 "// This script runs a ..." 8 | WHITESPACE@41..43 "\n\n" 9 | STMT@43..78 10 | ITEM@43..77 11 | EXPR@43..77 12 | EXPR_CALL@43..77 13 | EXPR@43..48 14 | EXPR_IDENT@43..48 15 | IDENT@43..48 "print" 16 | ARG_LIST@48..77 17 | PUNCT_PAREN_START@48..49 "(" 18 | EXPR@49..76 19 | EXPR_LIT@49..76 20 | LIT@49..76 21 | LIT_STR@49..76 "\"The result should be ..." 22 | PUNCT_PAREN_END@76..77 ")" 23 | PUNCT_SEMI@77..78 ";" 24 | WHITESPACE@78..80 "\n\n" 25 | STMT@80..100 26 | ITEM@80..99 27 | EXPR@80..99 28 | EXPR_LET@80..99 29 | KW_LET@80..83 "let" 30 | WHITESPACE@83..84 " " 31 | IDENT@84..85 "x" 32 | WHITESPACE@85..86 " " 33 | OP_ASSIGN@86..87 "=" 34 | EXPR@87..99 35 | EXPR_BINARY@87..99 36 | EXPR@87..91 37 | WHITESPACE@87..88 " " 38 | EXPR_LIT@88..90 39 | LIT@88..90 40 | LIT_INT@88..90 "12" 41 | WHITESPACE@90..91 " " 42 | OP_ADD@91..92 "+" 43 | EXPR@92..99 44 | EXPR_BINARY@92..99 45 | EXPR@92..96 46 | WHITESPACE@92..93 " " 47 | EXPR_LIT@93..95 48 | LIT@93..95 49 | LIT_INT@93..95 "34" 50 | WHITESPACE@95..96 " " 51 | OP_MUL@96..97 "*" 52 | EXPR@97..99 53 | WHITESPACE@97..98 " " 54 | EXPR_LIT@98..99 55 | LIT@98..99 56 | LIT_INT@98..99 "5" 57 | PUNCT_SEMI@99..100 ";" 58 | WHITESPACE@100..102 "\n\n" 59 | STMT@102..111 60 | ITEM@102..110 61 | EXPR@102..110 62 | EXPR_CALL@102..110 63 | EXPR@102..107 64 | EXPR_IDENT@102..107 65 | IDENT@102..107 "print" 66 | ARG_LIST@107..110 67 | PUNCT_PAREN_START@107..108 "(" 68 | EXPR@108..109 69 | EXPR_IDENT@108..109 70 | IDENT@108..109 "x" 71 | PUNCT_PAREN_END@109..110 ")" 72 | PUNCT_SEMI@110..111 ";" 73 | WHITESPACE@111..112 "\n" 74 | 75 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@op3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 37 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..114 7 | COMMENT_LINE@0..41 "// This script runs a ..." 8 | WHITESPACE@41..43 "\n\n" 9 | STMT@43..78 10 | ITEM@43..77 11 | EXPR@43..77 12 | EXPR_CALL@43..77 13 | EXPR@43..48 14 | EXPR_IDENT@43..48 15 | IDENT@43..48 "print" 16 | ARG_LIST@48..77 17 | PUNCT_PAREN_START@48..49 "(" 18 | EXPR@49..76 19 | EXPR_LIT@49..76 20 | LIT@49..76 21 | LIT_STR@49..76 "\"The result should be ..." 22 | PUNCT_PAREN_END@76..77 ")" 23 | PUNCT_SEMI@77..78 ";" 24 | WHITESPACE@78..80 "\n\n" 25 | STMT@80..102 26 | ITEM@80..101 27 | EXPR@80..101 28 | EXPR_LET@80..101 29 | KW_LET@80..83 "let" 30 | WHITESPACE@83..84 " " 31 | IDENT@84..85 "x" 32 | WHITESPACE@85..86 " " 33 | OP_ASSIGN@86..87 "=" 34 | EXPR@87..101 35 | EXPR_BINARY@87..101 36 | EXPR@87..98 37 | WHITESPACE@87..88 " " 38 | EXPR_PAREN@88..97 39 | PUNCT_PAREN_START@88..89 "(" 40 | EXPR@89..96 41 | EXPR_BINARY@89..96 42 | EXPR@89..92 43 | EXPR_LIT@89..91 44 | LIT@89..91 45 | LIT_INT@89..91 "12" 46 | WHITESPACE@91..92 " " 47 | OP_ADD@92..93 "+" 48 | EXPR@93..96 49 | WHITESPACE@93..94 " " 50 | EXPR_LIT@94..96 51 | LIT@94..96 52 | LIT_INT@94..96 "34" 53 | PUNCT_PAREN_END@96..97 ")" 54 | WHITESPACE@97..98 " " 55 | OP_MUL@98..99 "*" 56 | EXPR@99..101 57 | WHITESPACE@99..100 " " 58 | EXPR_LIT@100..101 59 | LIT@100..101 60 | LIT_INT@100..101 "5" 61 | PUNCT_SEMI@101..102 ";" 62 | WHITESPACE@102..104 "\n\n" 63 | STMT@104..113 64 | ITEM@104..112 65 | EXPR@104..112 66 | EXPR_CALL@104..112 67 | EXPR@104..109 68 | EXPR_IDENT@104..109 69 | IDENT@104..109 "print" 70 | ARG_LIST@109..112 71 | PUNCT_PAREN_START@109..110 "(" 72 | EXPR@110..111 73 | EXPR_IDENT@110..111 74 | IDENT@110..111 "x" 75 | PUNCT_PAREN_END@111..112 ")" 76 | PUNCT_SEMI@112..113 ";" 77 | WHITESPACE@113..114 "\n" 78 | 79 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@optional_ops.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 66 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..73 7 | WHITESPACE@0..1 "\n" 8 | STMT@1..73 9 | ITEM@1..72 10 | EXPR@1..72 11 | EXPR_FN@1..72 12 | KW_FN@1..3 "fn" 13 | WHITESPACE@3..4 " " 14 | IDENT@4..10 "test_a" 15 | PARAM_LIST@10..12 16 | PUNCT_PAREN_START@10..11 "(" 17 | PUNCT_PAREN_END@11..12 ")" 18 | EXPR_BLOCK@12..72 19 | WHITESPACE@12..13 " " 20 | PUNCT_BRACE_START@13..14 "{" 21 | WHITESPACE@14..19 "\n " 22 | STMT@19..33 23 | ITEM@19..32 24 | EXPR@19..32 25 | EXPR_LET@19..32 26 | KW_LET@19..22 "let" 27 | WHITESPACE@22..23 " " 28 | IDENT@23..26 "foo" 29 | WHITESPACE@26..27 " " 30 | OP_ASSIGN@27..28 "=" 31 | EXPR@28..32 32 | WHITESPACE@28..29 " " 33 | EXPR_OBJECT@29..32 34 | PUNCT_MAP_START@29..31 "#{" 35 | PUNCT_BRACE_END@31..32 "}" 36 | PUNCT_SEMI@32..33 ";" 37 | WHITESPACE@33..38 "\n " 38 | STMT@38..70 39 | ITEM@38..69 40 | EXPR@38..69 41 | EXPR_LET@38..69 42 | KW_LET@38..41 "let" 43 | WHITESPACE@41..42 " " 44 | IDENT@42..45 "val" 45 | WHITESPACE@45..46 " " 46 | OP_ASSIGN@46..47 "=" 47 | EXPR@47..69 48 | EXPR_BINARY@47..69 49 | EXPR@47..65 50 | EXPR_INDEX@47..64 51 | EXPR@47..56 52 | EXPR_BINARY@47..56 53 | EXPR@47..51 54 | WHITESPACE@47..48 " " 55 | EXPR_IDENT@48..51 56 | IDENT@48..51 "foo" 57 | OP_NULL_ACCESS@51..53 "?." 58 | EXPR@53..56 59 | EXPR_IDENT@53..56 60 | IDENT@53..56 "bar" 61 | PUNCT_NULL_BRACKET_START@56..58 "?[" 62 | EXPR@58..63 63 | EXPR_LIT@58..63 64 | LIT@58..63 65 | LIT_STR@58..63 "\"baz\"" 66 | PUNCT_BRACKET_END@63..64 "]" 67 | WHITESPACE@64..65 " " 68 | OP_NULL_OR@65..67 "??" 69 | EXPR@67..69 70 | WHITESPACE@67..68 " " 71 | EXPR_LIT@68..69 72 | LIT@68..69 73 | LIT_INT@68..69 "2" 74 | PUNCT_SEMI@69..70 ";" 75 | WHITESPACE@70..71 "\n" 76 | PUNCT_BRACE_END@71..72 "}" 77 | WHITESPACE@72..73 "\n" 78 | 79 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@simple.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 37 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..103 7 | SHEBANG@0..17 "#!/bin/echo hello" 8 | WHITESPACE@17..19 "\n\n" 9 | COMMENT_LINE@19..28 "// It's a" 10 | WHITESPACE@28..29 "\n" 11 | STMT@29..41 12 | ITEM@29..40 13 | EXPR@29..40 14 | EXPR_LET@29..40 15 | KW_LET@29..32 "let" 16 | WHITESPACE@32..33 " " 17 | IDENT@33..34 "a" 18 | WHITESPACE@34..35 " " 19 | OP_ASSIGN@35..36 "=" 20 | EXPR@36..40 21 | WHITESPACE@36..37 " " 22 | EXPR_LIT@37..40 23 | LIT@37..40 24 | LIT_STR@37..40 "\"0\"" 25 | PUNCT_SEMI@40..41 ";" 26 | WHITESPACE@41..43 "\n\n" 27 | STMT@43..64 28 | ITEM@43..63 29 | DOC@43..53 30 | COMMENT_LINE_DOC@43..53 "/// It's b" 31 | WHITESPACE@53..54 "\n" 32 | EXPR@54..63 33 | EXPR_LET@54..63 34 | KW_LET@54..57 "let" 35 | WHITESPACE@57..58 " " 36 | IDENT@58..59 "b" 37 | WHITESPACE@59..60 " " 38 | OP_ASSIGN@60..61 "=" 39 | EXPR@61..63 40 | WHITESPACE@61..62 " " 41 | EXPR_IDENT@62..63 42 | IDENT@62..63 "a" 43 | PUNCT_SEMI@63..64 ";" 44 | WHITESPACE@64..66 "\n\n" 45 | STMT@66..78 46 | ITEM@66..77 47 | EXPR@66..77 48 | EXPR_CONST@66..77 49 | KW_CONST@66..71 "const" 50 | WHITESPACE@71..72 " " 51 | IDENT@72..73 "c" 52 | WHITESPACE@73..74 " " 53 | OP_ASSIGN@74..75 "=" 54 | EXPR@75..77 55 | WHITESPACE@75..76 " " 56 | EXPR_IDENT@76..77 57 | IDENT@76..77 "b" 58 | PUNCT_SEMI@77..78 ";" 59 | WHITESPACE@78..79 " " 60 | COMMENT_BLOCK@79..84 "/* */" 61 | WHITESPACE@84..86 "\n\n" 62 | STMT@86..102 63 | ITEM@86..101 64 | EXPR@86..101 65 | EXPR_CONST@86..101 66 | KW_CONST@86..91 "const" 67 | WHITESPACE@91..92 " " 68 | IDENT@92..97 "ident" 69 | WHITESPACE@97..98 " " 70 | OP_ASSIGN@98..99 "=" 71 | EXPR@99..101 72 | WHITESPACE@99..100 " " 73 | EXPR_LIT@100..101 74 | LIT@100..101 75 | LIT_INT@100..101 "2" 76 | PUNCT_SEMI@101..102 ";" 77 | WHITESPACE@102..103 "\n" 78 | 79 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@string_escape.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 62 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..62 7 | STMT@0..13 8 | ITEM@0..12 9 | EXPR@0..12 10 | EXPR_LET@0..12 11 | KW_LET@0..3 "let" 12 | WHITESPACE@3..4 " " 13 | IDENT@4..5 "a" 14 | WHITESPACE@5..6 " " 15 | OP_ASSIGN@6..7 "=" 16 | EXPR@7..12 17 | WHITESPACE@7..8 " " 18 | EXPR_LIT@8..12 19 | LIT@8..12 20 | LIT_STR@8..12 "\"\"\"\"" 21 | PUNCT_SEMI@12..13 ";" 22 | WHITESPACE@13..14 "\n" 23 | STMT@14..29 24 | ITEM@14..28 25 | EXPR@14..28 26 | EXPR_LET@14..28 27 | KW_LET@14..17 "let" 28 | WHITESPACE@17..18 " " 29 | IDENT@18..19 "a" 30 | WHITESPACE@19..20 " " 31 | OP_ASSIGN@20..21 "=" 32 | EXPR@21..28 33 | WHITESPACE@21..22 " " 34 | EXPR_LIT@22..28 35 | LIT@22..28 36 | LIT_STR@22..28 "\" \"\" \"" 37 | PUNCT_SEMI@28..29 ";" 38 | WHITESPACE@29..31 "\n\n" 39 | STMT@31..44 40 | ITEM@31..43 41 | EXPR@31..43 42 | EXPR_LET@31..43 43 | KW_LET@31..34 "let" 44 | WHITESPACE@34..35 " " 45 | IDENT@35..36 "b" 46 | WHITESPACE@36..37 " " 47 | OP_ASSIGN@37..38 "=" 48 | EXPR@38..43 49 | WHITESPACE@38..39 " " 50 | EXPR_LIT@39..43 51 | LIT@39..43 52 | LIT_STR_TEMPLATE@39..43 53 | LIT_STR@39..43 "````" 54 | PUNCT_SEMI@43..44 ";" 55 | WHITESPACE@44..45 "\n" 56 | STMT@45..60 57 | ITEM@45..59 58 | EXPR@45..59 59 | EXPR_LET@45..59 60 | KW_LET@45..48 "let" 61 | WHITESPACE@48..49 " " 62 | IDENT@49..50 "b" 63 | WHITESPACE@50..51 " " 64 | OP_ASSIGN@51..52 "=" 65 | EXPR@52..59 66 | WHITESPACE@52..53 " " 67 | EXPR_LIT@53..59 68 | LIT@53..59 69 | LIT_STR_TEMPLATE@53..59 70 | LIT_STR@53..59 "` `` `" 71 | PUNCT_SEMI@59..60 ";" 72 | WHITESPACE@60..62 "\n\n" 73 | 74 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@throw_try_catch.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 56 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..37 7 | STMT@0..37 8 | ITEM@0..37 9 | EXPR@0..37 10 | EXPR_TRY@0..37 11 | KW_TRY@0..3 "try" 12 | EXPR_BLOCK@3..26 13 | WHITESPACE@3..4 " " 14 | PUNCT_BRACE_START@4..5 "{" 15 | WHITESPACE@5..10 "\n " 16 | STMT@10..24 17 | ITEM@10..23 18 | EXPR@10..23 19 | EXPR_THROW@10..23 20 | KW_THROW@10..15 "throw" 21 | EXPR@15..23 22 | WHITESPACE@15..16 " " 23 | EXPR_LIT@16..23 24 | LIT@16..23 25 | LIT_STR@16..23 "\"hello\"" 26 | PUNCT_SEMI@23..24 ";" 27 | WHITESPACE@24..25 "\n" 28 | PUNCT_BRACE_END@25..26 "}" 29 | WHITESPACE@26..27 " " 30 | KW_CATCH@27..32 "catch" 31 | WHITESPACE@32..33 " " 32 | EXPR_BLOCK@33..37 33 | PUNCT_BRACE_START@33..34 "{" 34 | WHITESPACE@34..36 "\n\n" 35 | PUNCT_BRACE_END@36..37 "}" 36 | 37 | -------------------------------------------------------------------------------- /crates/rhai-rowan/tests/snapshots/smoke__parse_valid@while.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rowan/tests/smoke.rs 3 | assertion_line: 37 4 | expression: "format!(\"{:#?}\", parse.into_syntax())" 5 | --- 6 | RHAI@0..90 7 | COMMENT_LINE@0..33 "// This script runs a ..." 8 | WHITESPACE@33..35 "\n\n" 9 | STMT@35..46 10 | ITEM@35..45 11 | EXPR@35..45 12 | EXPR_LET@35..45 13 | KW_LET@35..38 "let" 14 | WHITESPACE@38..39 " " 15 | IDENT@39..40 "x" 16 | WHITESPACE@40..41 " " 17 | OP_ASSIGN@41..42 "=" 18 | EXPR@42..45 19 | WHITESPACE@42..43 " " 20 | EXPR_LIT@43..45 21 | LIT@43..45 22 | LIT_INT@43..45 "10" 23 | PUNCT_SEMI@45..46 ";" 24 | WHITESPACE@46..48 "\n\n" 25 | STMT@48..90 26 | ITEM@48..89 27 | EXPR@48..89 28 | EXPR_WHILE@48..89 29 | KW_WHILE@48..53 "while" 30 | EXPR@53..60 31 | EXPR_BINARY@53..60 32 | EXPR@53..56 33 | WHITESPACE@53..54 " " 34 | EXPR_IDENT@54..56 35 | IDENT@54..55 "x" 36 | WHITESPACE@55..56 " " 37 | OP_GT@56..57 ">" 38 | EXPR@57..60 39 | WHITESPACE@57..58 " " 40 | EXPR_LIT@58..59 41 | LIT@58..59 42 | LIT_INT@58..59 "0" 43 | WHITESPACE@59..60 " " 44 | EXPR_BLOCK@60..89 45 | PUNCT_BRACE_START@60..61 "{" 46 | WHITESPACE@61..66 "\n " 47 | STMT@66..75 48 | ITEM@66..74 49 | EXPR@66..74 50 | EXPR_CALL@66..74 51 | EXPR@66..71 52 | EXPR_IDENT@66..71 53 | IDENT@66..71 "print" 54 | ARG_LIST@71..74 55 | PUNCT_PAREN_START@71..72 "(" 56 | EXPR@72..73 57 | EXPR_IDENT@72..73 58 | IDENT@72..73 "x" 59 | PUNCT_PAREN_END@73..74 ")" 60 | PUNCT_SEMI@74..75 ";" 61 | WHITESPACE@75..80 "\n " 62 | STMT@80..87 63 | ITEM@80..86 64 | EXPR@80..86 65 | EXPR_BINARY@80..86 66 | EXPR@80..82 67 | EXPR_IDENT@80..82 68 | IDENT@80..81 "x" 69 | WHITESPACE@81..82 " " 70 | OP_SUB_ASSIGN@82..84 "-=" 71 | EXPR@84..86 72 | WHITESPACE@84..85 " " 73 | EXPR_LIT@85..86 74 | LIT@85..86 75 | LIT_INT@85..86 "1" 76 | PUNCT_SEMI@86..87 ";" 77 | WHITESPACE@87..88 "\n" 78 | PUNCT_BRACE_END@88..89 "}" 79 | WHITESPACE@89..90 "\n" 80 | 81 | -------------------------------------------------------------------------------- /crates/rhai-sourcegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-sourcegen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | prettyplease = "0.1.12" 11 | proc-macro2 = "1.0.29" 12 | quote = "1.0.9" 13 | syn = "1.0.96" 14 | ungrammar = "1.14.4" 15 | -------------------------------------------------------------------------------- /crates/rhai-sourcegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow( 3 | clippy::unused_async, 4 | clippy::single_match, 5 | clippy::wildcard_imports, 6 | clippy::too_many_lines, 7 | clippy::enum_glob_use, 8 | clippy::cast_possible_truncation, 9 | clippy::cast_lossless, 10 | clippy::module_name_repetitions, 11 | clippy::single_match_else, 12 | clippy::option_option, 13 | clippy::missing_errors_doc, 14 | clippy::missing_panics_doc 15 | )] 16 | 17 | pub mod syntax; 18 | pub mod util; 19 | -------------------------------------------------------------------------------- /crates/rhai-sourcegen/src/util.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[must_use] 4 | pub fn format_rust(src: &str) -> String { 5 | prettyplease::unparse(&syn::parse_str(src).unwrap()) 6 | } 7 | -------------------------------------------------------------------------------- /crates/rhai-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /crates/rhai-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhai-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | rhai-lsp = { version = "0.1.0", path = "../rhai-lsp" } 13 | rhai-common = { version = "0.1.0", path = "../rhai-common" } 14 | 15 | anyhow = "1.0.57" 16 | async-trait = "0.1.56" 17 | clap = { version = "4.0.0", features = ["derive"] } 18 | console_error_panic_hook = "0.1.7" 19 | futures = "0.3.21" 20 | js-sys = "0.3.57" 21 | serde = { version = "1.0.137", features = ["derive"] } 22 | serde_json = "1.0.81" 23 | tokio = "1.19.2" 24 | tracing = "0.1.35" 25 | url = "2.2.2" 26 | wasm-bindgen = { version = "0.2.80", features = ["serde-serialize"] } 27 | wasm-bindgen-futures = "0.4.30" 28 | lsp-async-stub = "0.6.0" 29 | 30 | [profile.release] 31 | opt-level = 's' 32 | lto = true 33 | -------------------------------------------------------------------------------- /crates/rhai-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use environment::WasmEnvironment; 2 | use rhai_common::log::setup_stderr_logging; 3 | use serde::Serialize; 4 | use wasm_bindgen::prelude::*; 5 | 6 | mod environment; 7 | mod lsp; 8 | 9 | #[derive(Serialize)] 10 | struct Range { 11 | start: u32, 12 | end: u32, 13 | } 14 | 15 | #[derive(Serialize)] 16 | struct LintError { 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | range: Option, 19 | error: String, 20 | } 21 | 22 | #[derive(Serialize)] 23 | struct LintResult { 24 | errors: Vec, 25 | } 26 | 27 | #[wasm_bindgen] 28 | pub fn initialize() { 29 | console_error_panic_hook::set_once(); 30 | } 31 | 32 | #[wasm_bindgen] 33 | pub fn create_lsp(env: JsValue, lsp_interface: JsValue) -> lsp::RhaiWasmLsp { 34 | let env = WasmEnvironment::from(env); 35 | setup_stderr_logging(env.clone(), false, false, None); 36 | 37 | lsp::RhaiWasmLsp { 38 | server: rhai_lsp::create_server(), 39 | world: rhai_lsp::create_world(env), 40 | lsp_interface: lsp::WasmLspInterface::from(lsp_interface), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/rhai-wasm/src/lsp.rs: -------------------------------------------------------------------------------- 1 | use crate::environment::WasmEnvironment; 2 | use futures::Sink; 3 | use js_sys::Function; 4 | use lsp_async_stub::{rpc, Server}; 5 | use std::{io, sync::Arc}; 6 | use rhai_lsp::WorldState; 7 | use wasm_bindgen::prelude::*; 8 | use wasm_bindgen_futures::spawn_local; 9 | 10 | #[wasm_bindgen] 11 | pub struct RhaiWasmLsp { 12 | pub(crate) server: Server>>, 13 | pub(crate) world: Arc>, 14 | pub(crate) lsp_interface: WasmLspInterface, 15 | } 16 | 17 | #[wasm_bindgen] 18 | impl RhaiWasmLsp { 19 | pub fn send(&self, message: JsValue) -> Result<(), JsError> { 20 | let message: lsp_async_stub::rpc::Message = message.into_serde()?; 21 | let world = self.world.clone(); 22 | let writer = self.lsp_interface.clone(); 23 | 24 | let msg_fut = self.server.handle_message(world, message, writer); 25 | 26 | spawn_local(async move { 27 | if let Err(err) = msg_fut.await { 28 | tracing::error!(error = %err, "lsp message error"); 29 | } 30 | }); 31 | 32 | Ok(()) 33 | } 34 | } 35 | 36 | #[derive(Clone)] 37 | pub(crate) struct WasmLspInterface { 38 | js_on_message: Function, 39 | } 40 | 41 | impl From for WasmLspInterface { 42 | fn from(val: JsValue) -> Self { 43 | Self { 44 | js_on_message: js_sys::Reflect::get(&val, &JsValue::from_str("js_on_message")) 45 | .unwrap() 46 | .into(), 47 | } 48 | } 49 | } 50 | 51 | impl Sink for WasmLspInterface { 52 | type Error = io::Error; 53 | 54 | fn poll_ready( 55 | self: std::pin::Pin<&mut Self>, 56 | _cx: &mut std::task::Context<'_>, 57 | ) -> std::task::Poll> { 58 | std::task::Poll::Ready(Ok(())) 59 | } 60 | 61 | fn start_send( 62 | self: std::pin::Pin<&mut Self>, 63 | message: rpc::Message, 64 | ) -> Result<(), Self::Error> { 65 | let this = JsValue::null(); 66 | self.js_on_message 67 | .call1(&this, &JsValue::from_serde(&message).unwrap()) 68 | .unwrap(); 69 | Ok(()) 70 | } 71 | 72 | fn poll_flush( 73 | self: std::pin::Pin<&mut Self>, 74 | _cx: &mut std::task::Context<'_>, 75 | ) -> std::task::Poll> { 76 | std::task::Poll::Ready(Ok(())) 77 | } 78 | 79 | fn poll_close( 80 | self: std::pin::Pin<&mut Self>, 81 | _cx: &mut std::task::Context<'_>, 82 | ) -> std::task::Poll> { 83 | std::task::Poll::Ready(Ok(())) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /editors/vscode/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | .vscode 7 | 8 | .yarn 9 | -------------------------------------------------------------------------------- /editors/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | dist/**/*.map 6 | src/** 7 | **/*.ts 8 | webpack.config.* 9 | .gitignore 10 | tsconfig.json 11 | tslint.json 12 | sample 13 | images 14 | node_modules 15 | .yarn -------------------------------------------------------------------------------- /editors/vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Unreleased 4 | -------------------------------------------------------------------------------- /editors/vscode/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ferenc Tamás 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /editors/vscode/README.md: -------------------------------------------------------------------------------- 1 | # Rhai LSP 2 | 3 | WIP 4 | -------------------------------------------------------------------------------- /editors/vscode/assets/rhai-icon-transparent-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /editors/vscode/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import path from "node:path"; 4 | import esbuild from "rollup-plugin-esbuild"; 5 | import replace from "@rollup/plugin-replace"; 6 | 7 | const onwarn = (warning, rollupWarn) => { 8 | const ignoredWarnings = [ 9 | { 10 | ignoredCode: "CIRCULAR_DEPENDENCY", 11 | ignoredPath: "node_modules/semver", 12 | }, 13 | ]; 14 | 15 | // only show warning when code and path don't match 16 | // anything in above list of ignored warnings 17 | if ( 18 | !ignoredWarnings.some( 19 | ({ ignoredCode, ignoredPath }) => 20 | warning.code === ignoredCode && 21 | warning.importer.includes(path.normalize(ignoredPath)) 22 | ) 23 | ) { 24 | rollupWarn(warning); 25 | } 26 | }; 27 | 28 | /** @type {import('rollup').RollupOptions} */ 29 | const options = { 30 | onwarn, 31 | input: { 32 | server: "src/server.ts", 33 | extension: "src/extension.ts", 34 | }, 35 | output: { 36 | sourcemap: false, 37 | format: "commonjs", 38 | dir: "dist", 39 | chunkFileNames: "[name].js", 40 | }, 41 | external: ["vscode"], 42 | preserveEntrySignatures: true, 43 | treeshake: "smallest", 44 | plugins: [ 45 | replace({ 46 | preventAssignment: true, 47 | "import.meta.env.BROWSER": "false", 48 | }), 49 | esbuild({ minify: true }), 50 | commonjs(), 51 | resolve({ 52 | preferBuiltins: true, 53 | }), 54 | ], 55 | }; 56 | 57 | export default options; 58 | -------------------------------------------------------------------------------- /editors/vscode/src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as client from "vscode-languageclient/node"; 3 | import type { Lsp } from "@rhaiscript/core"; 4 | 5 | export function registerCommands( 6 | context: vscode.ExtensionContext, 7 | client: client.BaseLanguageClient 8 | ) { 9 | context.subscriptions.push( 10 | vscode.commands.registerCommand("rhai.showHirDump", async () => { 11 | const editor = vscode.window.activeTextEditor; 12 | 13 | if (!editor) { 14 | return; 15 | } 16 | 17 | const wsUri = vscode.workspace.getWorkspaceFolder( 18 | editor.document.uri 19 | ).uri; 20 | 21 | const s: Lsp.Client.RequestMethod = "rhai/hirDump"; 22 | const params: Lsp.Client.RequestParams<"rhai/hirDump"> = { 23 | workspaceUri: wsUri.toString(), 24 | }; 25 | const res = await client.sendRequest>(s, params); 26 | 27 | if (res) { 28 | const doc = await vscode.workspace.openTextDocument({content: res.hir}); 29 | vscode.window.showTextDocument(doc); 30 | } 31 | }) 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /editors/vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { SyntaxTreeProvider } from "./syntax-tree"; 3 | 4 | import { createClient } from "./client"; 5 | import { getOutput } from "./util"; 6 | import { registerCommands } from "./commands"; 7 | 8 | export async function activate(context: vscode.ExtensionContext) { 9 | try { 10 | let c = await createClient(context); 11 | 12 | const syntaxTreeProvider = new SyntaxTreeProvider(context, c); 13 | 14 | const disposeProvider = vscode.window.registerTreeDataProvider( 15 | "rhaiSyntaxTree", 16 | syntaxTreeProvider 17 | ); 18 | 19 | syntaxTreeProvider.setEditor(vscode.window.activeTextEditor); 20 | 21 | context.subscriptions.push( 22 | disposeProvider, 23 | vscode.window.onDidChangeActiveTextEditor(editor => { 24 | if (!editor || editor.document.languageId !== "rhai") { 25 | syntaxTreeProvider.setEditor(undefined); 26 | return; 27 | } 28 | 29 | syntaxTreeProvider.setEditor(editor); 30 | }), 31 | vscode.workspace.onDidChangeTextDocument(() => { 32 | // Let the LSP parse the document. 33 | setTimeout(() => { 34 | syntaxTreeProvider.update(); 35 | }, 100); 36 | }) 37 | ); 38 | 39 | c.registerProposedFeatures(); 40 | 41 | context.subscriptions.push(getOutput(), c.start()); 42 | 43 | registerCommands(context, c); 44 | await c.onReady(); 45 | 46 | vscode.commands.executeCommand("setContext", "rhai.extensionActive", true); 47 | context.subscriptions.push({ 48 | dispose: () => { 49 | vscode.commands.executeCommand( 50 | "setContext", 51 | "rhai.extensionActive", 52 | false 53 | ); 54 | }, 55 | }); 56 | } catch (e) { 57 | getOutput().appendLine(e.message); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /editors/vscode/src/server.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import fsPromise from "fs/promises"; 3 | import path from "path"; 4 | import { exit } from "process"; 5 | import { RpcMessage, RhaiLsp } from "@rhaiscript/lsp"; 6 | import glob from "fast-glob"; 7 | 8 | let rhai: RhaiLsp; 9 | 10 | process.on("message", async (d: RpcMessage) => { 11 | if (d.method === "exit") { 12 | exit(0); 13 | } 14 | 15 | if (typeof rhai === "undefined") { 16 | rhai = await RhaiLsp.initialize( 17 | { 18 | cwd: () => process.cwd(), 19 | envVar: name => process.env[name], 20 | discoverRhaiConfig: from => { 21 | const fileNames = ["Rhai.toml"]; 22 | 23 | for (const name of fileNames) { 24 | try { 25 | const fullPath = path.join(from, name); 26 | fs.accessSync(fullPath); 27 | return fullPath; 28 | } catch {} 29 | } 30 | }, 31 | glob: p => glob.sync(p), 32 | isAbsolute: p => path.isAbsolute(p), 33 | readFile: path => fsPromise.readFile(path), 34 | writeFile: (path, data) => fsPromise.writeFile(path, data), 35 | stderr: process.stderr, 36 | stdErrAtty: () => process.stderr.isTTY, 37 | stdin: process.stdin, 38 | stdout: process.stdout, 39 | urlToFilePath: (url: string) => { 40 | const c = decodeURIComponent(url).slice("file://".length); 41 | 42 | if (process.platform === "win32" && c.startsWith("/")) { 43 | return c.slice(1); 44 | } 45 | 46 | return c; 47 | }, 48 | isDir: path => { 49 | try { 50 | return fs.statSync(path).isDirectory(); 51 | } catch { 52 | return false; 53 | } 54 | }, 55 | sleep: ms => new Promise(resolve => setTimeout(resolve, ms)), 56 | }, 57 | { 58 | onMessage(message) { 59 | process.send(message); 60 | }, 61 | } 62 | ); 63 | } 64 | 65 | rhai.send(d); 66 | }); 67 | 68 | // These are panics from Rust. 69 | process.on("unhandledRejection", up => { 70 | throw up; 71 | }); 72 | -------------------------------------------------------------------------------- /editors/vscode/src/util.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { BaseLanguageClient } from "vscode-languageclient"; 3 | 4 | let output: vscode.OutputChannel; 5 | 6 | export function getOutput(): vscode.OutputChannel { 7 | if (!output) { 8 | output = vscode.window.createOutputChannel("Rhai"); 9 | } 10 | 11 | return output; 12 | } 13 | 14 | export function allRange(doc: vscode.TextDocument): vscode.Range { 15 | let firstLine = doc.lineAt(0); 16 | let lastLine = doc.lineAt(doc.lineCount - 1); 17 | let textRange = new vscode.Range(firstLine.range.start, lastLine.range.end); 18 | return textRange; 19 | } 20 | 21 | export async function showMessage( 22 | params: { kind: "info" | "warn" | "error"; message: string }, 23 | c: BaseLanguageClient 24 | ) { 25 | let show: string | undefined; 26 | switch (params.kind) { 27 | case "info": 28 | show = await vscode.window.showInformationMessage( 29 | params.message, 30 | "Show Details" 31 | ); 32 | case "warn": 33 | show = await vscode.window.showWarningMessage( 34 | params.message, 35 | "Show Details" 36 | ); 37 | case "error": 38 | show = await vscode.window.showErrorMessage( 39 | params.message, 40 | "Show Details" 41 | ); 42 | } 43 | 44 | if (show) { 45 | c.outputChannel.show(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /editors/vscode/syntax/rhai.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | "brackets": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["(", ")"] 10 | ], 11 | "autoClosingPairs": [ 12 | { 13 | "open": "{", 14 | "close": "}" 15 | }, 16 | { 17 | "open": "[", 18 | "close": "]" 19 | }, 20 | { 21 | "open": "(", 22 | "close": ")" 23 | }, 24 | { 25 | "open": "'", 26 | "close": "'", 27 | "notIn": ["string", "comment"] 28 | }, 29 | { 30 | "open": "\"", 31 | "close": "\"", 32 | "notIn": ["string"] 33 | }, 34 | { 35 | "open": "`", 36 | "close": "`", 37 | "notIn": ["string"] 38 | }, 39 | { 40 | "open": "/*", 41 | "close": " */", 42 | "notIn": ["string"] 43 | } 44 | ], 45 | "autoCloseBefore": ";:.,=}])>` \n\t", 46 | "surroundingPairs": [ 47 | ["{", "}"], 48 | ["[", "]"], 49 | ["(", ")"], 50 | ["'", "'"], 51 | ["\"", "\""], 52 | ["`", "`"] 53 | ], 54 | "folding": { 55 | "markers": { 56 | "start": "^\\s*//\\s*#?region\\b", 57 | "end": "^\\s*//\\s*#?endregion\\b" 58 | } 59 | }, 60 | "indentationRules": { 61 | "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", 62 | "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /editors/vscode/syntax/rhai.markdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "markdown.rhai.codeblock", 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [ 5 | { 6 | "name": "markup.fenced_code.block.rhai", 7 | "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rhai)((\\s+|:|\\{)[^`~]*)?$)", 8 | "beginCaptures": { 9 | "3": { 10 | "name": "punctuation.definition.markdown" 11 | }, 12 | "4": { 13 | "name": "fenced_code.block.language.markdown" 14 | }, 15 | "5": { 16 | "name": "fenced_code.block.language.attributes.markdown" 17 | } 18 | }, 19 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", 20 | "endCaptures": { 21 | "3": { 22 | "name": "punctuation.definition.markdown" 23 | } 24 | }, 25 | "patterns": [ 26 | { 27 | "begin": "(^|\\G)(\\s*)(.*)", 28 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 29 | "contentName": "meta.embedded.block.rhai", 30 | "patterns": [ 31 | { 32 | "include": "source.rhai" 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /editors/vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["ES2019"], 5 | "sourceMap": false, 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "ESNext", 9 | "allowSyntheticDefaultImports": true, 10 | "strict": false 11 | }, 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /examples/bar.d.rhai: -------------------------------------------------------------------------------- 1 | /// Definition for "bar.rhai". 2 | /// 3 | /// Omitting the module path is the same as `module "./bar.rhai"`. 4 | module; 5 | 6 | // We know all items in "bar.rhai", 7 | // this file is only useful for the module description. 8 | // 9 | // In the future we can use this to define additional types 10 | // and function overloads. 11 | -------------------------------------------------------------------------------- /examples/bar.rhai: -------------------------------------------------------------------------------- 1 | /// Some constant. 2 | export const X = 10; 3 | 4 | /// This function is private. 5 | private fn cant_touch_this() {} 6 | 7 | /// This function is exported. 8 | fn hello() { 9 | print("hello"); 10 | } 11 | -------------------------------------------------------------------------------- /examples/external.d.rhai: -------------------------------------------------------------------------------- 1 | /// This definition file creates an `external` module. 2 | /// 3 | /// Only the item definitions are known, values are provided 4 | /// by the runtime. 5 | module external; 6 | 7 | /// Load a file into a byte array. 8 | fn load_file(path: String) -> [u8]; 9 | -------------------------------------------------------------------------------- /examples/foo.rhai: -------------------------------------------------------------------------------- 1 | // Simply re-export bar. 2 | // 3 | // Notice how this module has no definition file, thus no description. 4 | import "./bar.rhai" as bar; 5 | 6 | bar::hello(); 7 | 8 | /// A function that expects to have scoped-values 9 | /// can have definitions contiguous in doc comment blocks: 10 | /** 11 | 12 | ```rhai-scope 13 | 14 | /// Hello is always available when this function is called. 15 | let hello; 16 | 17 | ``` 18 | 19 | */ 20 | /// Only doc comment blocks are supported, 21 | /// the following is ignored: 22 | /// 23 | /// ```rhai-scope 24 | /// let hi; 25 | /// ``` 26 | fn scoped() { 27 | return hello; 28 | 29 | // error: 30 | // hi 31 | } 32 | -------------------------------------------------------------------------------- /examples/global.d.rhai: -------------------------------------------------------------------------------- 1 | /// The global module, this should be auto-magically 2 | /// generated in the HIR. 3 | /// 4 | /// It's here for demonstration purposes. 5 | module global; 6 | 7 | const VAL: string; 8 | -------------------------------------------------------------------------------- /examples/main.rhai: -------------------------------------------------------------------------------- 1 | import "./foo.rhai" as foo; 2 | 3 | let file = external::load_file(); 4 | 5 | foo::bar::hello(); 6 | 7 | let a = foo::bar::hello; 8 | 9 | // We can access nested modules defined inline as well. 10 | nested::inner::need::to::go_deeper::YEAH; 11 | 12 | // We can use custom operators from definitions. 13 | assert("rhai" is "awesome"); 14 | 15 | // Private function is not reachable. 16 | // Uncomment the line below to see the error. 17 | // foo::bar::cant_touch_this(); 18 | -------------------------------------------------------------------------------- /examples/nested.d.rhai: -------------------------------------------------------------------------------- 1 | module nested; 2 | 3 | // Nested modules can be defined as well. 4 | module inner { 5 | fn hey(); 6 | 7 | module need { 8 | module to { 9 | module go_deeper { 10 | const YEAH; 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/static2.d.rhai: -------------------------------------------------------------------------------- 1 | /// Same as `static.d.rhai`. 2 | /// 3 | /// All modules, including static can be extended 4 | /// in multiple definition files. 5 | module static; 6 | 7 | fn floor(number: f32) -> f32; 8 | 9 | const RUNTIME_VERSION: string; 10 | 11 | /// Magically compares two items. 12 | // The precedence of the operator is represented by 13 | // the binding the left and optionally the right binding powers 14 | // after the `with` keyword. 15 | // 16 | // If the right binding power is higher, 17 | // the operator is left-associative, 18 | // otherwise the operator is right-associative. 19 | op is(?, ?) -> bool with (1, 2); 20 | 21 | /// Asserts that the given expression is true. 22 | fn assert(expr: bool); 23 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.0.0" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2021" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | rhai-rowan = { path = "../crates/rhai-rowan" } 14 | 15 | [[bin]] 16 | name = "fuzz-parser" 17 | path = "fuzz_targets/fuzz-parser.rs" 18 | test = false 19 | doc = false 20 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz-parser.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use rhai_rowan::parser::Parser; 3 | 4 | #[macro_use] 5 | extern crate libfuzzer_sys; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | if let Ok(s) = std::str::from_utf8(data) { 9 | Parser::new(s).parse_script(); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /images/bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhaiscript/lsp/2f1fcd73f43b909d1d5e96123516e599b9aaaa88/images/bench.png -------------------------------------------------------------------------------- /js/core/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | target 4 | **/.local* 5 | *.tgz 6 | .yarn/cache -------------------------------------------------------------------------------- /js/core/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | target 3 | util 4 | **/.local* 5 | *.tgz 6 | tsconfig.json 7 | rollup.config.js 8 | yarn-error.log 9 | -------------------------------------------------------------------------------- /js/core/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhaiscript/lsp/2f1fcd73f43b909d1d5e96123516e599b9aaaa88/js/core/.yarn/install-state.gz -------------------------------------------------------------------------------- /js/core/README.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /js/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rhaiscript/core", 3 | "version": "0.1.0", 4 | "description": "Commonly used types and utilities for Rhai", 5 | "author": { 6 | "name": "tamasfe", 7 | "url": "https://github.com/tamasfe" 8 | }, 9 | "scripts": { 10 | "build": "yarn rollup --silent -c rollup.config.js" 11 | }, 12 | "types": "dist/index.d.ts", 13 | "main": "dist/index.js", 14 | "license": "MIT", 15 | "autoTag": { 16 | "enabled": true 17 | }, 18 | "devDependencies": { 19 | "@rollup/plugin-commonjs": "^22.0.0", 20 | "@rollup/plugin-node-resolve": "^13.3.0", 21 | "@types/node": "^17.0.41", 22 | "esbuild": "^0.14.45", 23 | "rollup": "^2.75.6", 24 | "rollup-plugin-esbuild": "^4.9.1", 25 | "rollup-plugin-typescript2": "^0.32.1", 26 | "tslib": "^2.4.0", 27 | "typescript": "^4.7.3" 28 | }, 29 | "packageManager": "yarn@3.2.1" 30 | } 31 | -------------------------------------------------------------------------------- /js/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import path from "path"; 4 | import process from "process"; 5 | import { minify } from "rollup-plugin-esbuild"; 6 | import typescript from "rollup-plugin-typescript2"; 7 | 8 | export default { 9 | input: { 10 | index: "src/index.ts", 11 | }, 12 | output: { 13 | sourcemap: false, 14 | name: "rhaiCore", 15 | format: "umd", 16 | dir: "dist", 17 | }, 18 | plugins: [ 19 | typescript(), 20 | commonjs(), 21 | resolve({ 22 | jsnext: true, 23 | preferBuiltins: true, 24 | rootDir: path.join(process.cwd(), ".."), 25 | }), 26 | minify(), 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /js/core/src/config.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | source?: SourceConfig; 3 | } 4 | 5 | export interface SourceConfig { 6 | include?: string[]; 7 | exclude?: string[]; 8 | } 9 | -------------------------------------------------------------------------------- /js/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./environment"; 2 | export * as Lsp from "./lsp"; 3 | export * from "./config"; 4 | 5 | /** 6 | * Byte range within a document. 7 | */ 8 | export interface Range { 9 | /** 10 | * Start byte index. 11 | */ 12 | start: number; 13 | /** 14 | * Exclusive end index. 15 | */ 16 | end: number; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /js/core/src/lsp.ts: -------------------------------------------------------------------------------- 1 | export interface Range { 2 | start: Position; 3 | end: Position; 4 | } 5 | 6 | export interface Position { 7 | line: number; 8 | character: number; 9 | } 10 | 11 | export interface SyntaxTree { 12 | kind: string; 13 | text_range: [number, number]; 14 | children?: Array; 15 | text?: string; 16 | } 17 | 18 | export namespace Server { 19 | interface ServerNotifications {} 20 | 21 | export type NotificationMethod = keyof ServerNotifications; 22 | 23 | export type NotificationParams = 24 | ServerNotifications[T] extends NotificationDescription 25 | ? ServerNotifications[T]["params"] 26 | : never; 27 | } 28 | 29 | export namespace Client { 30 | interface ClientNotifications {} 31 | 32 | interface ClientRequests { 33 | "rhai/hirDump": { 34 | params: { 35 | workspaceUri?: string; 36 | }; 37 | response: 38 | | { 39 | hir: string; 40 | } 41 | | null 42 | | undefined; 43 | }; 44 | "rhai/syntaxTree": { 45 | params: { 46 | /** 47 | * URI of the document. 48 | */ 49 | uri: string; 50 | }; 51 | response: { 52 | /** 53 | * Syntax tree textual representation. 54 | */ 55 | text: string; 56 | tree: SyntaxTree; 57 | } | null; 58 | }; 59 | "rhai/convertOffsets": { 60 | params: { 61 | /** 62 | * URI of the document. 63 | */ 64 | uri: string; 65 | ranges?: Array<[number, number]>; 66 | positions?: Array; 67 | }; 68 | response: { 69 | ranges?: Array; 70 | positions?: Array; 71 | } | null; 72 | }; 73 | } 74 | 75 | export type NotificationMethod = keyof ClientNotifications; 76 | 77 | export type NotificationParams = 78 | ClientNotifications[T] extends NotificationDescription 79 | ? ClientNotifications[T]["params"] 80 | : never; 81 | 82 | export type RequestMethod = keyof ClientRequests; 83 | 84 | export type RequestParams = 85 | ClientRequests[T] extends RequestDescription 86 | ? ClientRequests[T]["params"] 87 | : never; 88 | 89 | export type RequestResponse = 90 | ClientRequests[T] extends RequestDescription 91 | ? ClientRequests[T]["response"] 92 | : never; 93 | } 94 | 95 | interface NotificationDescription { 96 | readonly params: any; 97 | } 98 | 99 | interface RequestDescription { 100 | readonly params: any; 101 | readonly response: any; 102 | } 103 | 104 | export type AssociationRule = 105 | | { glob: string } 106 | | { regex: string } 107 | | { url: string }; 108 | 109 | export interface SchemaInfo { 110 | url: string; 111 | meta: any; 112 | } 113 | -------------------------------------------------------------------------------- /js/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["ES2019", "dom"], 5 | "sourceMap": false, 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "ESNext", 9 | "allowSyntheticDefaultImports": true, 10 | "declaration": true, 11 | "strict": false 12 | }, 13 | "exclude": ["node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /js/lsp/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | target 4 | **/.local* 5 | *.tgz 6 | yarn-error.log 7 | .yarn/cache 8 | -------------------------------------------------------------------------------- /js/lsp/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | target 3 | util 4 | **/.local* 5 | *.tgz 6 | tsconfig.json 7 | rollup.config.js 8 | yarn-error.log 9 | -------------------------------------------------------------------------------- /js/lsp/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhaiscript/lsp/2f1fcd73f43b909d1d5e96123516e599b9aaaa88/js/lsp/.yarn/install-state.gz -------------------------------------------------------------------------------- /js/lsp/README.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /js/lsp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rhaiscript/lsp", 3 | "version": "0.1.0", 4 | "description": "A JavaScript wrapper for the Rhai language server.", 5 | "scripts": { 6 | "build": "yarn rollup --silent -c rollup.config.js" 7 | }, 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "homepage": "https://rhai.rs/", 11 | "repository": "https://github.com/rhaiscript/lsp", 12 | "author": { 13 | "name": "tamasfe", 14 | "url": "https://github.com/tamasfe" 15 | }, 16 | "license": "MIT", 17 | "autoTag": { 18 | "enabled": true 19 | }, 20 | "devDependencies": { 21 | "@rollup/plugin-commonjs": "^22.0.0", 22 | "@rollup/plugin-node-resolve": "^13.3.0", 23 | "@types/node": "^17.0.41", 24 | "@wasm-tool/rollup-plugin-rust": "^2.2.2", 25 | "esbuild": "^0.14.45", 26 | "rollup": "^2.75.6", 27 | "rollup-plugin-esbuild": "^4.9.1", 28 | "rollup-plugin-typescript2": "^0.32.1", 29 | "tslib": "^2.4.0", 30 | "typescript": "^4.7.3" 31 | }, 32 | "dependencies": { 33 | "@rhaiscript/core": "file:../core" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /js/lsp/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import rust from "@wasm-tool/rollup-plugin-rust"; 4 | import path from "path"; 5 | import process from "process"; 6 | import { minify } from "rollup-plugin-esbuild"; 7 | import typescript from "rollup-plugin-typescript2"; 8 | 9 | export default { 10 | input: { 11 | index: "src/index.ts", 12 | }, 13 | output: { 14 | sourcemap: false, 15 | name: "rhaiLsp", 16 | format: "umd", 17 | dir: "dist", 18 | }, 19 | plugins: [ 20 | typescript(), 21 | rust({ 22 | debug: process.env["RELEASE"] !== "true", 23 | nodejs: true, 24 | inlineWasm: true, 25 | }), 26 | commonjs(), 27 | resolve({ 28 | jsnext: true, 29 | preferBuiltins: true, 30 | rootDir: path.join(process.cwd(), ".."), 31 | }), 32 | minify(), 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /js/lsp/src/index.ts: -------------------------------------------------------------------------------- 1 | import loadRhai from "../../../crates/rhai-wasm/Cargo.toml"; 2 | import { convertEnv, Environment } from "@rhaiscript/core"; 3 | 4 | export interface RpcMessage { 5 | jsonrpc: "2.0"; 6 | method?: string; 7 | id?: string | number; 8 | params?: any; 9 | result?: any; 10 | error?: any; 11 | } 12 | 13 | export interface LspInterface { 14 | /** 15 | * Handler for RPC messages set from the LSP server. 16 | */ 17 | onMessage: (message: RpcMessage) => void; 18 | } 19 | 20 | export class RhaiLsp { 21 | private static rhai: any | undefined; 22 | private static initializing: boolean = false; 23 | 24 | private constructor(private lspInner: any) { 25 | if (!RhaiLsp.initializing) { 26 | throw new Error( 27 | `an instance of RhaiLsp can only be created by calling the "initialize" static method` 28 | ); 29 | } 30 | } 31 | 32 | public static async initialize( 33 | env: Environment, 34 | lspInterface: LspInterface 35 | ): Promise { 36 | if (typeof RhaiLsp.rhai === "undefined") { 37 | RhaiLsp.rhai = await loadRhai(); 38 | } 39 | RhaiLsp.rhai.initialize(); 40 | 41 | RhaiLsp.initializing = true; 42 | const t = new RhaiLsp( 43 | RhaiLsp.rhai.create_lsp(convertEnv(env), { 44 | js_on_message: lspInterface.onMessage, 45 | }) 46 | ); 47 | RhaiLsp.initializing = false; 48 | 49 | return t; 50 | } 51 | 52 | public send(message: RpcMessage) { 53 | this.lspInner.send(message); 54 | } 55 | 56 | public dispose() { 57 | this.lspInner.free(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /js/lsp/src/rust.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*/Cargo.toml" { 2 | const mod: any; 3 | export default mod; 4 | } 5 | -------------------------------------------------------------------------------- /js/lsp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["ES2019"], 5 | "sourceMap": false, 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "ESNext", 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": false 12 | }, 13 | "exclude": ["node_modules", "dist"] 14 | } 15 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | force_explicit_abi = false 2 | -------------------------------------------------------------------------------- /taplo.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhaiscript/lsp/2f1fcd73f43b909d1d5e96123516e599b9aaaa88/taplo.toml -------------------------------------------------------------------------------- /testdata/benchmarks/array.rhai: -------------------------------------------------------------------------------- 1 | let x = [1, 2, 3]; 2 | 3 | print("x[1] should be 2:"); 4 | print(x[1]); 5 | 6 | x[1] = 5; 7 | 8 | print(`x[1] should be 5: ${x[1]}`); 9 | -------------------------------------------------------------------------------- /testdata/benchmarks/assignment.rhai: -------------------------------------------------------------------------------- 1 | print("x should be 78:"); 2 | 3 | let x = 78; 4 | 5 | print(x); 6 | -------------------------------------------------------------------------------- /testdata/benchmarks/comments.rhai: -------------------------------------------------------------------------------- 1 | // I am a single line comment! 2 | 3 | let /* I am a spy in a variable declaration! */ x = 5; 4 | 5 | /* I am a simple 6 | multi-line 7 | comment */ 8 | 9 | /* look /* at /* that, /* multi-line */ comments */ can be */ nested */ 10 | 11 | /* surrounded by */ let this_is_not_a_comment = true // comments 12 | -------------------------------------------------------------------------------- /testdata/benchmarks/fibonacci.rhai: -------------------------------------------------------------------------------- 1 | // This script calculates the n-th Fibonacci number using a really dumb algorithm 2 | // to test the speed of the scripting engine. 3 | 4 | const TARGET = 28; 5 | const REPEAT = 5; 6 | 7 | fn fib(n) { 8 | if n < 2 { 9 | n 10 | } else { 11 | fib(n-1) + fib(n-2) 12 | } 13 | } 14 | 15 | print(`Running Fibonacci(28) x ${REPEAT} times...`); 16 | print("Ready... Go!"); 17 | 18 | let result; 19 | let now = timestamp(); 20 | 21 | for n in range(0, REPEAT) { 22 | result = fib(TARGET); 23 | } 24 | 25 | print(`Finished. Run time = ${now.elapsed} seconds.`); 26 | 27 | print(`Fibonacci number #${TARGET} = ${result}`); 28 | 29 | if result != 317_811 { 30 | print("The answer is WRONG! Should be 317,811!"); 31 | } 32 | -------------------------------------------------------------------------------- /testdata/benchmarks/for1.rhai: -------------------------------------------------------------------------------- 1 | // This script runs for-loops 2 | 3 | let arr = [1, true, 123.456, "hello", 3, 42]; 4 | 5 | for (a, i) in arr { 6 | for (b, j) in ['x', 42, (), 123, 99, 0.5] { 7 | if b > 100 { continue; } 8 | 9 | print(`(${i}, ${j}) = (${a}, ${b})`); 10 | } 11 | 12 | if a == 3 { break; } 13 | } 14 | //print(a); // <- if you uncomment this line, the script will fail to run 15 | // because 'a' is not defined here 16 | 17 | for i in range(5, 0, -1) { // runs from 5 down to 1 18 | print(i); 19 | } 20 | -------------------------------------------------------------------------------- /testdata/benchmarks/for2.rhai: -------------------------------------------------------------------------------- 1 | const MAX = 1_000_000; 2 | 3 | print(`Iterating an array with ${MAX} items...`); 4 | 5 | print("Ready... Go!"); 6 | 7 | let now = timestamp(); 8 | 9 | let list = []; 10 | 11 | for i in range(0, MAX) { 12 | list.push(i); 13 | } 14 | 15 | print(`Time = ${now.elapsed} seconds...`); 16 | 17 | let sum = 0; 18 | 19 | for i in list { 20 | sum += i; 21 | } 22 | 23 | print(`Sum = ${sum}`); 24 | print(`Finished. Total run time = ${now.elapsed} seconds.`); 25 | -------------------------------------------------------------------------------- /testdata/benchmarks/function_decl1.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function and calls it 2 | 3 | fn bob() { 4 | return 3; 5 | } 6 | 7 | let result = bob(); 8 | 9 | print(`bob() should be 3: ${result}`); 10 | -------------------------------------------------------------------------------- /testdata/benchmarks/function_decl2.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function with two parameters 2 | 3 | let a = 3; 4 | 5 | fn addme(a, b) { 6 | a = 42; // notice that 'a' is passed by value 7 | a + b; // notice that the last value is returned even if terminated by a semicolon 8 | } 9 | 10 | let result = addme(a, 4); 11 | 12 | print(`addme(a, 4) should be 46: ${result}`); 13 | 14 | print(`a should still be 3: ${a}`); // should print 3 - 'a' is never changed 15 | -------------------------------------------------------------------------------- /testdata/benchmarks/function_decl3.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function with many parameters and calls it 2 | 3 | const KEY = 38; 4 | 5 | fn f(a, b, c, d, e, f) { 6 | a - b * c - d * e - f + global::KEY 7 | } 8 | 9 | let result = f(100, 5, 2, 9, 6, 32); 10 | 11 | print(`result should be 42: ${result}`); 12 | -------------------------------------------------------------------------------- /testdata/benchmarks/function_decl4.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function that acts as a method 2 | 3 | // Use 'this' to refer to the object of a method call 4 | fn action(x, y) { 5 | this = this.abs() + x * y; // 'this' can be modified 6 | } 7 | 8 | let obj = -40; 9 | 10 | obj.action(1, 2); // call 'action' as method 11 | 12 | print(`obj should now be 42: ${obj}`); 13 | -------------------------------------------------------------------------------- /testdata/benchmarks/if1.rhai: -------------------------------------------------------------------------------- 1 | let a = 42; 2 | let b = 123; 3 | let x = 999; 4 | 5 | if a > b { 6 | print("Oops! a > b"); 7 | } else if a < b { 8 | print("a < b, x should be 0"); 9 | 10 | let x = 0; // this 'x' shadows the global 'x' 11 | print(x); // should print 0 12 | } else { 13 | print("Oops! a == b"); 14 | } 15 | -------------------------------------------------------------------------------- /testdata/benchmarks/if2.rhai: -------------------------------------------------------------------------------- 1 | let a = 42; 2 | let b = 123; 3 | 4 | let x = if a <= b { // if-expression 5 | b - a 6 | } else { 7 | a - b 8 | } * 10; 9 | 10 | print(`x should be 810: ${x}`); 11 | -------------------------------------------------------------------------------- /testdata/benchmarks/loop.rhai: -------------------------------------------------------------------------------- 1 | // This script runs an infinite loop, ending it with a break statement 2 | 3 | let x = 10; 4 | 5 | // simulate do..while using loop 6 | loop { 7 | print(x); 8 | 9 | x -= 1; 10 | 11 | if x <= 0 { break; } 12 | } 13 | 14 | export x as foo; 15 | -------------------------------------------------------------------------------- /testdata/benchmarks/mat_mul.rhai: -------------------------------------------------------------------------------- 1 | const SIZE = 50; 2 | 3 | fn new_mat(x, y) { 4 | let row = []; 5 | row.pad(y, 0.0); 6 | 7 | let matrix = []; 8 | matrix.pad(x, row); 9 | 10 | matrix 11 | } 12 | 13 | fn mat_gen() { 14 | const n = global::SIZE; 15 | const tmp = 1.0 / n / n; 16 | let m = new_mat(n, n); 17 | 18 | for i in range(0, n) { 19 | for j in range(0, n) { 20 | m[i][j] = tmp * (i - j) * (i + j); 21 | } 22 | } 23 | 24 | m 25 | } 26 | 27 | fn mat_mul(a, b) { 28 | let b2 = new_mat(a[0].len, b[0].len); 29 | 30 | for i in range(0, a[0].len) { 31 | for j in range(0, b[0].len) { 32 | b2[j][i] = b[i][j]; 33 | } 34 | } 35 | 36 | let c = new_mat(a.len, b[0].len); 37 | 38 | for i in range(0, c.len) { 39 | for j in range(0, c[i].len) { 40 | c[i][j] = 0.0; 41 | 42 | for z in range(0, a[i].len) { 43 | c[i][j] += a[i][z] * b2[j][z]; 44 | } 45 | } 46 | } 47 | 48 | c 49 | } 50 | 51 | const now = timestamp(); 52 | 53 | const a = mat_gen(); 54 | const b = mat_gen(); 55 | const c = mat_mul(a, b); 56 | 57 | /* 58 | for i in range(0, SIZE) { 59 | print(c[i]); 60 | } 61 | */ 62 | 63 | print(`Finished. Run time = ${now.elapsed} seconds.`); 64 | -------------------------------------------------------------------------------- /testdata/benchmarks/module.rhai: -------------------------------------------------------------------------------- 1 | import "loop" as x; 2 | 3 | print(`Module test! foo = ${x::foo}`); 4 | -------------------------------------------------------------------------------- /testdata/benchmarks/oop.rhai: -------------------------------------------------------------------------------- 1 | // This script simulates object-oriented programming (OOP) techniques using closures. 2 | 3 | // External variable that will be captured. 4 | let last_value = (); 5 | 6 | // Define object 7 | let obj1 = #{ 8 | _data: 42, // data field 9 | get_data: || this._data, // property getter 10 | action: || print(`Data=${this._data}`), // method 11 | update: |x| { // property setter 12 | this._data = x; 13 | last_value = this._data; // capture 'last_value' 14 | this.action(); 15 | } 16 | }; 17 | 18 | if obj1.get_data() > 0 { // property access 19 | obj1.update(123); // call method 20 | } else { 21 | print("we have a problem here"); 22 | } 23 | 24 | // Define another object based on the first object 25 | let obj2 = #{ 26 | _data: 0, // data field - new value 27 | update: |x| { // property setter - another function 28 | this._data = x * 2; 29 | last_value = this._data; // capture 'last_value' 30 | this.action(); 31 | } 32 | }; 33 | obj2.fill_with(obj1); // add all other fields from obj1 34 | 35 | if obj2.get_data() > 0 { // property access 36 | print("we have another problem here"); 37 | } else { 38 | obj2.update(42); // call method 39 | } 40 | 41 | print(`Should be 84: ${last_value}`); 42 | -------------------------------------------------------------------------------- /testdata/benchmarks/op1.rhai: -------------------------------------------------------------------------------- 1 | print("The result should be 46:"); 2 | 3 | print(34 + 12); 4 | -------------------------------------------------------------------------------- /testdata/benchmarks/op2.rhai: -------------------------------------------------------------------------------- 1 | print("The result should be 182:"); 2 | 3 | let x = 12 + 34 * 5; 4 | 5 | print(x); 6 | -------------------------------------------------------------------------------- /testdata/benchmarks/op3.rhai: -------------------------------------------------------------------------------- 1 | print("The result should be 230:"); 2 | 3 | let x = (12 + 34) * 5; 4 | 5 | print(x); 6 | -------------------------------------------------------------------------------- /testdata/benchmarks/primes.rhai: -------------------------------------------------------------------------------- 1 | // This script uses the Sieve of Eratosthenes to calculate prime numbers. 2 | 3 | let now = timestamp(); 4 | 5 | const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000 6 | 7 | let prime_mask = []; 8 | prime_mask.pad(MAX_NUMBER_TO_CHECK, true); 9 | 10 | prime_mask[0] = false; 11 | prime_mask[1] = false; 12 | 13 | let total_primes_found = 0; 14 | 15 | for p in range(2, MAX_NUMBER_TO_CHECK) { 16 | if !prime_mask[p] { continue; } 17 | 18 | //print(p); 19 | 20 | total_primes_found += 1; 21 | 22 | for i in range(2 * p, MAX_NUMBER_TO_CHECK, p) { 23 | prime_mask[i] = false; 24 | } 25 | } 26 | 27 | print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`); 28 | print(`Run time = ${now.elapsed} seconds.`); 29 | 30 | if total_primes_found != 78_498 { 31 | print("The answer is WRONG! Should be 78,498!"); 32 | } 33 | -------------------------------------------------------------------------------- /testdata/benchmarks/speed_test.rhai: -------------------------------------------------------------------------------- 1 | // This script runs 1 million iterations 2 | // to test the speed of the scripting engine. 3 | 4 | let now = timestamp(); 5 | let x = 1_000_000; 6 | 7 | print("Ready... Go!"); 8 | 9 | while x > 0 { 10 | x -= 1; 11 | } 12 | 13 | print(`Finished. Run time = ${now.elapsed} seconds.`); 14 | -------------------------------------------------------------------------------- /testdata/benchmarks/string.rhai: -------------------------------------------------------------------------------- 1 | // This script tests string operations 2 | 3 | print("hello"); 4 | print("this\nis \\ nice"); // escape sequences 5 | print("0x40 hex is \x40"); // hex escape sequence 6 | print("Unicode fun: \u2764"); // Unicode escape sequence 7 | print("more fun: \U0001F603"); // Unicode escape sequence 8 | print("foo" + " " + "bar"); // string building using strings 9 | print("foo" < "bar"); // string comparison 10 | print("foo" >= "bar"); // string comparison 11 | print("the answer is " + 42); // string building using non-string types 12 | 13 | let s = "\u2764 hello, world! \U0001F603"; // string variable 14 | print(`length=${s.len}`); // should be 17 15 | 16 | s[s.len-3] = '?'; // change the string 17 | print(`Question: ${s}`); // should print 'Question: hello, world?' 18 | 19 | // Line continuation: 20 | let s = "This is a long \ 21 | string constructed using \ 22 | line continuation"; 23 | 24 | // String interpolation 25 | print(`One string: ${s}`); 26 | 27 | // Multi-line literal string: 28 | let s = ` 29 | \U0001F603 This is a multi-line 30 | "string" with \t\x20\r\n 31 | made using multi-line literal 32 | string syntax. 33 | `; 34 | 35 | print(s); 36 | 37 | // Interpolation 38 | let s = `This is interpolation ${ 39 | let x = `within ${let y = "yet another level \ 40 | of interpolation!"; y} interpolation`; 41 | x 42 | } within literal string.`; 43 | 44 | print(s); 45 | 46 | print(">>> END <<<"); 47 | -------------------------------------------------------------------------------- /testdata/benchmarks/switch.rhai: -------------------------------------------------------------------------------- 1 | let arr = [42, 123.456, "hello", true, 'x', 999, 1]; 2 | 3 | for item in arr { 4 | switch item { 5 | 42 => print("The Answer!"), 6 | 123.456 => print(`Floating point... ${item}`), 7 | "hello" => print(`${item} world!`), 8 | 999 => print(`A number: ${item}`), 9 | _ => print(`Something else: <${item}>`) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /testdata/benchmarks/while.rhai: -------------------------------------------------------------------------------- 1 | // This script runs a while loop 2 | 3 | let x = 10; 4 | 5 | while x > 0 { 6 | print(x); 7 | x -= 1; 8 | } 9 | -------------------------------------------------------------------------------- /testdata/valid/array.rhai: -------------------------------------------------------------------------------- 1 | let x = [1, 2, 3]; 2 | 3 | print("x[1] should be 2:"); 4 | print(x[1]); 5 | 6 | x[1] = 5; 7 | 8 | print(`x[1] should be 5: ${x[1]}`); 9 | -------------------------------------------------------------------------------- /testdata/valid/assignment.rhai: -------------------------------------------------------------------------------- 1 | // This script contains a single assignment statement. 2 | 3 | let x = 78; 4 | 5 | print(`x should be 78: ${x}`); 6 | -------------------------------------------------------------------------------- /testdata/valid/char.rhai: -------------------------------------------------------------------------------- 1 | let val = 'a'; 2 | let val = '\n'; 3 | -------------------------------------------------------------------------------- /testdata/valid/comments.rhai: -------------------------------------------------------------------------------- 1 | // I am a single line comment! 2 | 3 | let /* I am a spy in a variable declaration! */ x = 5; 4 | 5 | /* I am a simple 6 | multi-line 7 | comment */ 8 | 9 | /* look /* at /* that, /* multi-line */ comments */ can be */ nested */ 10 | 11 | /* surrounded by */ let this_is_not_a_comment = true // comments 12 | -------------------------------------------------------------------------------- /testdata/valid/doc-comments.rhai: -------------------------------------------------------------------------------- 1 | /// The function `foo`, which prints `hello, world!` and a magic number, 2 | /// accepts three parameters. 3 | /// 4 | /// # Parameters 5 | /// 6 | /// * `x` - `i64` 7 | /// * `y` - `string` 8 | /// * `z` - `bool` 9 | /// 10 | /// # Notes 11 | /// 12 | /// This is a doc-comment. It can be obtained with the `metadata` feature. 13 | /// 14 | /// An example is the `rhai-doc` app. 15 | /// 16 | /// # Example 17 | /// 18 | /// ```rhai 19 | /// let x = foo(42, "hello", true); 20 | /// 21 | /// print(x); // prints 47 22 | /// ``` 23 | fn foo(x, y, z) { 24 | print(`hello, world! ${if z { x + y.len() } else { x } }`); 25 | } 26 | 27 | foo(39, "bar", true); 28 | -------------------------------------------------------------------------------- /testdata/valid/fibonacci.rhai: -------------------------------------------------------------------------------- 1 | // This script calculates the n-th Fibonacci number using a really dumb algorithm 2 | // to test the speed of the scripting engine. 3 | 4 | const TARGET = 28; 5 | const REPEAT = 5; 6 | const ANSWER = 317_811; 7 | 8 | fn fib(n) { 9 | if n < 2 { 10 | n 11 | } else { 12 | fib(n-1) + fib(n-2) 13 | } 14 | } 15 | 16 | print(`Running Fibonacci(${TARGET}) x ${REPEAT} times...`); 17 | print("Ready... Go!"); 18 | 19 | let result; 20 | let now = timestamp(); 21 | 22 | for n in 0..REPEAT { 23 | result = fib(TARGET); 24 | } 25 | 26 | print(`Finished. Run time = ${now.elapsed} seconds.`); 27 | 28 | print(`Fibonacci number #${TARGET} = ${result}`); 29 | 30 | if result != ANSWER { 31 | print(`The answer is WRONG! Should be ${ANSWER}!`); 32 | } 33 | -------------------------------------------------------------------------------- /testdata/valid/for1.rhai: -------------------------------------------------------------------------------- 1 | // This script runs for-loops. 2 | 3 | let arr = [1, true, 123.456, "hello", 3, 42]; 4 | 5 | // Loop over array with counter 6 | for (a, i) in arr { 7 | for (b, j) in ['x', 42, (), 123, 99, 0.5] { 8 | if b > 100 { continue; } 9 | 10 | print(`(${i}, ${j}) = (${a}, ${b})`); 11 | } 12 | 13 | if a == 3 { break; } 14 | } 15 | //print(a); // <- if you uncomment this line, the script will fail to compile 16 | // because 'a' is not defined here 17 | 18 | for i in range(5, 0, -1) { // runs from 5 down to 1 19 | print(i); 20 | } 21 | -------------------------------------------------------------------------------- /testdata/valid/for2.rhai: -------------------------------------------------------------------------------- 1 | // This script runs for-loops 2 | 3 | const MAX = 1_000_000; 4 | 5 | print(`Iterating an array with ${MAX} items...`); 6 | 7 | print("Ready... Go!"); 8 | 9 | let now = timestamp(); 10 | 11 | let list = []; 12 | 13 | // Loop over range 14 | for i in 0..MAX { 15 | list.push(i); 16 | } 17 | 18 | print(`Time = ${now.elapsed} seconds...`); 19 | 20 | let sum = 0; 21 | 22 | // Loop over array 23 | for i in list { 24 | sum += i; 25 | } 26 | 27 | print(`Sum = ${sum}`); 28 | print(`Finished. Total run time = ${now.elapsed} seconds.`); 29 | -------------------------------------------------------------------------------- /testdata/valid/for3.rhai: -------------------------------------------------------------------------------- 1 | // This script runs for-loops with closures. 2 | 3 | const MAX = 100; 4 | const CHECK = ((MAX - 1) ** 2) * MAX; 5 | 6 | print("Ready... Go!"); 7 | 8 | let now = timestamp(); 9 | 10 | print(`Creating ${MAX} closures...`); 11 | 12 | let list = []; 13 | 14 | // Loop over range 15 | for i in 0..MAX { 16 | list.push(|| i ** 2); 17 | } 18 | 19 | print(`Time = ${now.elapsed} seconds...`); 20 | print(`Summing ${MAX} closures...`); 21 | 22 | let sum = 0; 23 | 24 | // Loop over array 25 | for f in list { 26 | sum += f.call(); 27 | } 28 | 29 | print(`Sum = ${sum} (should be ${CHECK})`); 30 | print(`Finished. Total run time = ${now.elapsed} seconds.`); 31 | -------------------------------------------------------------------------------- /testdata/valid/function_decl1.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function and calls it. 2 | 3 | fn call_me() { 4 | return 3; 5 | } 6 | 7 | let result = call_me(); 8 | 9 | print(`call_me() should be 3: ${result}`); 10 | -------------------------------------------------------------------------------- /testdata/valid/function_decl2.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function with two parameters and local variables. 2 | 3 | let a = 3; 4 | 5 | fn add(a, b) { 6 | a = 42; // notice that 'a' is passed by value 7 | a + b; // notice that the last value is returned even if terminated by a semicolon 8 | } 9 | 10 | let result = add(a, 4); 11 | 12 | print(`add(a, 4) should be 46: ${result}`); 13 | 14 | print(`a should still be 3: ${a}`); // prints 3: 'a' is never changed 15 | -------------------------------------------------------------------------------- /testdata/valid/function_decl3.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function with many parameters. 2 | // 3 | 4 | const KEY = 38; 5 | 6 | fn f(a, b, c, d, e, f) { 7 | let x = global::KEY; // <- access global module 8 | a - b * c - d * e - f + x 9 | } 10 | 11 | let result = f(100, 5, 2, 9, 6, 32); 12 | 13 | print(`result should be 42: ${result}`); 14 | -------------------------------------------------------------------------------- /testdata/valid/function_decl4.rhai: -------------------------------------------------------------------------------- 1 | // This script defines a function that acts as a method. 2 | 3 | // Use 'this' to refer to the object of a method call 4 | fn action(x, y) { 5 | this = this.abs() + x * y; // 'this' can be modified 6 | } 7 | 8 | let obj = -40; 9 | 10 | obj.action(1, 2); // call 'action' as method 11 | 12 | print(`obj should now be 42: ${obj}`); 13 | -------------------------------------------------------------------------------- /testdata/valid/if1.rhai: -------------------------------------------------------------------------------- 1 | // This script runs if statements. 2 | 3 | let a = 42; 4 | let b = 123; 5 | let x = 999; 6 | 7 | if a > b { 8 | print("Oops! a > b"); 9 | } else if a < b { 10 | print("a < b, x should be 0"); 11 | 12 | let x = 0; // <- this 'x' shadows the global 'x' 13 | print(x); // should print 0 14 | } else { 15 | print("Oops! a == b"); 16 | } 17 | -------------------------------------------------------------------------------- /testdata/valid/if2.rhai: -------------------------------------------------------------------------------- 1 | // This script runs an if expression. 2 | 3 | let a = 42; 4 | let b = 123; 5 | 6 | let x = if a <= b { // <- if-expression 7 | b - a 8 | } else { 9 | a - b 10 | } * 10; 11 | 12 | print(`x should be 810: ${x}`); 13 | -------------------------------------------------------------------------------- /testdata/valid/loop.rhai: -------------------------------------------------------------------------------- 1 | // This script runs an infinite loop, ending it with a break statement. 2 | 3 | let x = 10; 4 | 5 | // simulate do..while using loop 6 | loop { 7 | print(x); 8 | 9 | x -= 1; 10 | 11 | if x <= 0 { break; } 12 | } 13 | 14 | export x as foo; 15 | -------------------------------------------------------------------------------- /testdata/valid/mat_mul.rhai: -------------------------------------------------------------------------------- 1 | // This script simulates multi-dimensional matrix calculations. 2 | 3 | const SIZE = 50; 4 | 5 | fn new_mat(x, y) { 6 | let row = []; 7 | row.pad(y, 0.0); 8 | 9 | let matrix = []; 10 | matrix.pad(x, row); 11 | 12 | matrix 13 | } 14 | 15 | fn mat_gen() { 16 | const n = global::SIZE; 17 | const tmp = 1.0 / n / n; 18 | let m = new_mat(n, n); 19 | 20 | for i in 0..n { 21 | for j in 0..n { 22 | m[i][j] = tmp * (i - j) * (i + j); 23 | } 24 | } 25 | 26 | m 27 | } 28 | 29 | fn mat_mul(a, b) { 30 | let b2 = new_mat(a[0].len, b[0].len); 31 | 32 | for i in 0..a[0].len { 33 | for j in 0..b[0].len { 34 | b2[j][i] = b[i][j]; 35 | } 36 | } 37 | 38 | let c = new_mat(a.len, b[0].len); 39 | 40 | for i in 0..c.len { 41 | for j in 0..c[i].len { 42 | c[i][j] = 0.0; 43 | 44 | for z in 0..a[i].len { 45 | c[i][j] += a[i][z] * b2[j][z]; 46 | } 47 | } 48 | } 49 | 50 | c 51 | } 52 | 53 | const now = timestamp(); 54 | 55 | const a = mat_gen(); 56 | const b = mat_gen(); 57 | const c = mat_mul(a, b); 58 | 59 | /* 60 | for i in 0..SIZE) { 61 | print(c[i]); 62 | } 63 | */ 64 | 65 | print(`Finished. Run time = ${now.elapsed} seconds.`); 66 | -------------------------------------------------------------------------------- /testdata/valid/module.rhai: -------------------------------------------------------------------------------- 1 | // This script imports an external script as a module. 2 | 3 | import "loop" as x; 4 | 5 | print(`Module test! foo = ${x::foo}`); 6 | -------------------------------------------------------------------------------- /testdata/valid/oop.rhai: -------------------------------------------------------------------------------- 1 | // This script simulates object-oriented programming (OOP) techniques using closures. 2 | 3 | // External variable that will be captured. 4 | let last_value = (); 5 | 6 | // Define object 7 | let obj1 = #{ 8 | _data: 42, // data field 9 | get_data: || this._data, // property getter 10 | action: || print(`Data=${this._data}`), // method 11 | update: |x| { // property setter 12 | this._data = x; 13 | last_value = this._data; // capture 'last_value' 14 | this.action(); 15 | } 16 | }; 17 | 18 | if obj1.get_data() > 0 { // property access 19 | obj1.update(123); // call method 20 | } else { 21 | print("we have a problem here"); 22 | } 23 | 24 | // Define another object based on the first object 25 | let obj2 = #{ 26 | _data: 0, // data field - new value 27 | update: |x| { // property setter - another function 28 | this._data = x * 2; 29 | last_value = this._data; // capture 'last_value' 30 | this.action(); 31 | } 32 | }; 33 | obj2.fill_with(obj1); // add all other fields from obj1 34 | 35 | if obj2.get_data() > 0 { // property access 36 | print("we have another problem here"); 37 | } else { 38 | obj2.update(42); // call method 39 | } 40 | 41 | print(`Should be 84: ${last_value}`); 42 | -------------------------------------------------------------------------------- /testdata/valid/op1.rhai: -------------------------------------------------------------------------------- 1 | // This script runs a single expression. 2 | 3 | print("The result should be 46:"); 4 | 5 | print(34 + 12); 6 | -------------------------------------------------------------------------------- /testdata/valid/op2.rhai: -------------------------------------------------------------------------------- 1 | // This script runs a complex expression. 2 | 3 | print("The result should be 182:"); 4 | 5 | let x = 12 + 34 * 5; 6 | 7 | print(x); 8 | -------------------------------------------------------------------------------- /testdata/valid/op3.rhai: -------------------------------------------------------------------------------- 1 | // This script runs a complex expression. 2 | 3 | print("The result should be 230:"); 4 | 5 | let x = (12 + 34) * 5; 6 | 7 | print(x); 8 | -------------------------------------------------------------------------------- /testdata/valid/operators.rhai: -------------------------------------------------------------------------------- 1 | let a = 3 in 4; 2 | 3 | let custom_operator = 3 over 4; 4 | -------------------------------------------------------------------------------- /testdata/valid/optional_ops.rhai: -------------------------------------------------------------------------------- 1 | 2 | fn test_a() { 3 | let foo = #{}; 4 | let val = foo?.bar?["baz"] ?? 2; 5 | } 6 | -------------------------------------------------------------------------------- /testdata/valid/primes.rhai: -------------------------------------------------------------------------------- 1 | // This script uses the Sieve of Eratosthenes to calculate prime numbers. 2 | 3 | let now = timestamp(); 4 | 5 | const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000 6 | 7 | let prime_mask = []; 8 | prime_mask.pad(MAX_NUMBER_TO_CHECK + 1, true); 9 | 10 | prime_mask[0] = false; 11 | prime_mask[1] = false; 12 | 13 | let total_primes_found = 0; 14 | 15 | for p in 2..=MAX_NUMBER_TO_CHECK { 16 | if !prime_mask[p] { continue; } 17 | 18 | //print(p); 19 | 20 | total_primes_found += 1; 21 | 22 | for i in range(2 * p, MAX_NUMBER_TO_CHECK + 1, p) { 23 | prime_mask[i] = false; 24 | } 25 | } 26 | 27 | print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`); 28 | print(`Run time = ${now.elapsed} seconds.`); 29 | 30 | if total_primes_found != 78_498 { 31 | print("The answer is WRONG! Should be 78,498!"); 32 | } 33 | -------------------------------------------------------------------------------- /testdata/valid/simple.rhai: -------------------------------------------------------------------------------- 1 | #!/bin/echo hello 2 | 3 | // It's a 4 | let a = "0"; 5 | 6 | /// It's b 7 | let b = a; 8 | 9 | const c = b; /* */ 10 | 11 | const ident = 2; 12 | -------------------------------------------------------------------------------- /testdata/valid/speed_test.rhai: -------------------------------------------------------------------------------- 1 | // This script runs 1 million iterations to test the speed of the scripting engine. 2 | 3 | let now = timestamp(); 4 | let x = 1_000_000; 5 | 6 | print("Ready... Go!"); 7 | 8 | while x > 0 { 9 | x -= 1; 10 | } 11 | 12 | print(`Finished. Run time = ${now.elapsed} seconds.`); 13 | -------------------------------------------------------------------------------- /testdata/valid/string.rhai: -------------------------------------------------------------------------------- 1 | // This script tests string operations. 2 | 3 | print("hello"); 4 | print("this\nis \\ nice"); // escape sequences 5 | print("0x40 hex is \x40"); // hex escape sequence 6 | print("Unicode fun: \u2764"); // Unicode escape sequence 7 | print("more fun: \U0001F603"); // Unicode escape sequence 8 | print("foo" + " " + "bar"); // string building using strings 9 | print("foo" < "bar"); // string comparison 10 | print("foo" >= "bar"); // string comparison 11 | print("the answer is " + 42); // string building using non-string types 12 | 13 | let s = "\u2764 hello, world! \U0001F603"; // string variable 14 | print(`length=${s.len}`); // should be 17 15 | 16 | s[s.len-3] = '?'; // change the string 17 | print(`Question: ${s}`); // should print 'Question: hello, world?' 18 | 19 | // Line continuation: 20 | let s = "This is a long \ 21 | string constructed using \ 22 | line continuation"; 23 | 24 | // String interpolation 25 | print(`One string: ${s}`); 26 | 27 | // Multi-line literal string: 28 | let s = ` 29 | \U0001F603 This is a multi-line 30 | "string" with \t\x20\r\n 31 | made using multi-line literal 32 | string syntax. 33 | `; 34 | 35 | print(s); 36 | 37 | // Interpolation 38 | let s = `This is interpolation ${ 39 | let x = `within ${let y = "yet another level \ 40 | of interpolation!"; y} interpolation`; 41 | x 42 | } within literal string.`; 43 | 44 | print(s); 45 | 46 | print(">>> END <<<"); 47 | -------------------------------------------------------------------------------- /testdata/valid/string_escape.rhai: -------------------------------------------------------------------------------- 1 | let a = """"; 2 | let a = " "" "; 3 | 4 | let b = ````; 5 | let b = ` `` `; 6 | 7 | -------------------------------------------------------------------------------- /testdata/valid/switch.rhai: -------------------------------------------------------------------------------- 1 | // This script runs a switch statement in a for-loop. 2 | 3 | let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4]; 4 | 5 | for item in arr { 6 | switch item { 7 | // Match single integer 8 | 42 => print("The Answer!"), 9 | // Match single floating-point number 10 | 123.456 => print(`Floating point... ${item}`), 11 | // Match single string 12 | "hello" => print(`${item} world!`), 13 | // Match another integer 14 | 999 => print(`Got 999: ${item}`), 15 | // Match range with condition 16 | 0..100 if item % 2 == 0 => print(`A small even number: ${item}`), 17 | // Match another range 18 | 0..100 => print(`A small odd number: ${item}`), 19 | // Default case 20 | _ => print(`Something else: <${item}> is ${type_of(item)}`), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /testdata/valid/template.rhai: -------------------------------------------------------------------------------- 1 | let hi = 2; 2 | `${2}💩 💩 asd abc${3 + 2 + 1 + `${""}`}`; 3 | 4 | let a = `with \interpolation ${hi} `; 5 | let a = `${hi}`; 6 | 7 | let a = `multiple ${hi}${hi} ${hi} \interpolations ${hi + 2} and more complex ${ 8 | { 9 | let a = 2; 10 | let b = `nested ${hi} interpolation ${ 3**3 + 4}`; 11 | let c = `with escaped `` but you cannot escape the \`` `; 12 | a + a 13 | } 14 | } expressions`; 15 | -------------------------------------------------------------------------------- /testdata/valid/throw_try_catch.rhai: -------------------------------------------------------------------------------- 1 | try { 2 | throw "hello"; 3 | } catch { 4 | 5 | } -------------------------------------------------------------------------------- /testdata/valid/unary_ops.rhai: -------------------------------------------------------------------------------- 1 | let a = !a + 2; 2 | 3 | let b = 3 + !+ + +4 + -2 * 3; 4 | 5 | let b = !!!!foo.bar; 6 | 7 | let hm = !a.b; 8 | -------------------------------------------------------------------------------- /testdata/valid/while.rhai: -------------------------------------------------------------------------------- 1 | // This script runs a while loop. 2 | 3 | let x = 10; 4 | 5 | while x > 0 { 6 | print(x); 7 | x -= 1; 8 | } 9 | --------------------------------------------------------------------------------