From 02f19d91ee3408c04d57ebfdfd075decc00d751c Mon Sep 17 00:00:00 2001 From: Charlotte Meyer Date: Thu, 14 Jul 2022 22:26:57 +0000 Subject: [PATCH] feat(parser): implement parser --- .gitignore | 1 + Cargo.lock | 245 ++++++++++++++++++ Cargo.toml | 6 + README.md | 10 + crates/oyster/Cargo.toml | 6 + crates/oyster/src/main.rs | 3 + crates/oyster/tests/it/main.rs | 1 + crates/oyster/tests/it/tidy.rs | 33 +++ crates/oyster_parser/Cargo.toml | 13 + crates/oyster_parser/src/ast.rs | 136 ++++++++++ crates/oyster_parser/src/cst.rs | 47 ++++ crates/oyster_parser/src/lexer.rs | 137 ++++++++++ crates/oyster_parser/src/lib.rs | 23 ++ crates/oyster_parser/src/parser.rs | 188 ++++++++++++++ crates/oyster_parser/tests/it/ast.rs | 145 +++++++++++ crates/oyster_parser/tests/it/cst.rs | 145 +++++++++++ crates/oyster_parser/tests/it/lexer.rs | 83 ++++++ crates/oyster_parser/tests/it/main.rs | 4 + crates/oyster_parser/tests/it/parser.rs | 145 +++++++++++ .../tests/it/snapshots/it__ast__comment.snap | 9 + .../it__ast__comment_in_pipeline.snap | 47 ++++ .../tests/it/snapshots/it__ast__empty.snap | 9 + .../it/snapshots/it__ast__inline_comment.snap | 28 ++ .../it__ast__multiline_pipeline.snap | 47 ++++ .../tests/it/snapshots/it__ast__pipeline.snap | 40 +++ .../it__ast__reject_double_pipe.snap | 7 + .../it__ast__reject_leading_pipe.snap | 7 + .../it__ast__reject_pipe_semicolon.snap | 7 + .../it__ast__reject_trailing_pipe.snap | 7 + .../it/snapshots/it__ast__semicolon.snap | 46 ++++ .../it/snapshots/it__ast__simple_command.snap | 35 +++ .../tests/it/snapshots/it__ast__unicode.snap | 28 ++ .../tests/it/snapshots/it__ast__word.snap | 28 ++ .../snapshots/it__ast__word_with_escape.snap | 34 +++ .../tests/it/snapshots/it__cst__comment.snap | 21 ++ .../it__cst__comment_in_pipeline.snap | 75 ++++++ .../tests/it/snapshots/it__cst__empty.snap | 8 + .../it/snapshots/it__cst__inline_comment.snap | 36 +++ .../it__cst__multiline_pipeline.snap | 67 +++++ .../tests/it/snapshots/it__cst__pipeline.snap | 54 ++++ .../it__cst__reject_double_pipe.snap | 62 +++++ .../it__cst__reject_leading_pipe.snap | 59 +++++ .../it__cst__reject_pipe_semicolon.snap | 67 +++++ .../it__cst__reject_trailing_pipe.snap | 40 +++ .../it/snapshots/it__cst__semicolon.snap | 55 ++++ .../it/snapshots/it__cst__simple_command.snap | 41 +++ .../tests/it/snapshots/it__cst__unicode.snap | 28 ++ .../tests/it/snapshots/it__cst__word.snap | 28 ++ .../snapshots/it__cst__word_with_escape.snap | 36 +++ .../it/snapshots/it__lexer__comment.snap | 8 + .../tests/it/snapshots/it__lexer__eof.snap | 8 + .../it/snapshots/it__lexer__escaped_hash.snap | 8 + .../it/snapshots/it__lexer__newlines.snap | 8 + .../tests/it/snapshots/it__lexer__pipe.snap | 8 + .../it/snapshots/it__lexer__plain_word.snap | 8 + .../it/snapshots/it__lexer__semicolon.snap | 8 + .../it/snapshots/it__lexer__whitespace.snap | 8 + .../snapshots/it__lexer__word_with_hash.snap | 8 + .../it/snapshots/it__parser__comment.snap | 18 ++ .../it__parser__comment_in_pipeline.snap | 66 +++++ .../tests/it/snapshots/it__parser__empty.snap | 5 + .../snapshots/it__parser__inline_comment.snap | 30 +++ .../it__parser__multiline_pipeline.snap | 58 +++++ .../it/snapshots/it__parser__pipeline.snap | 46 ++++ .../it__parser__reject_double_pipe.snap | 54 ++++ .../it__parser__reject_leading_pipe.snap | 50 ++++ .../it__parser__reject_pipe_semicolon.snap | 58 +++++ .../it__parser__reject_trailing_pipe.snap | 34 +++ .../it/snapshots/it__parser__semicolon.snap | 46 ++++ .../snapshots/it__parser__simple_command.snap | 34 +++ .../it/snapshots/it__parser__unicode.snap | 22 ++ .../tests/it/snapshots/it__parser__word.snap | 22 ++ .../it__parser__word_with_escape.snap | 30 +++ 73 files changed, 3072 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 crates/oyster/Cargo.toml create mode 100644 crates/oyster/src/main.rs create mode 100644 crates/oyster/tests/it/main.rs create mode 100644 crates/oyster/tests/it/tidy.rs create mode 100644 crates/oyster_parser/Cargo.toml create mode 100644 crates/oyster_parser/src/ast.rs create mode 100644 crates/oyster_parser/src/cst.rs create mode 100644 crates/oyster_parser/src/lexer.rs create mode 100644 crates/oyster_parser/src/lib.rs create mode 100644 crates/oyster_parser/src/parser.rs create mode 100644 crates/oyster_parser/tests/it/ast.rs create mode 100644 crates/oyster_parser/tests/it/cst.rs create mode 100644 crates/oyster_parser/tests/it/lexer.rs create mode 100644 crates/oyster_parser/tests/it/main.rs create mode 100644 crates/oyster_parser/tests/it/parser.rs create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__comment_in_pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__empty.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__inline_comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__multiline_pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__reject_double_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__reject_leading_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__reject_pipe_semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__reject_trailing_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__simple_command.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__unicode.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__word.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__ast__word_with_escape.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__comment_in_pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__empty.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__inline_comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__multiline_pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__reject_double_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__reject_leading_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__reject_pipe_semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__reject_trailing_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__simple_command.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__unicode.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__word.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__cst__word_with_escape.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__eof.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__escaped_hash.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__newlines.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__plain_word.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__whitespace.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__lexer__word_with_hash.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__comment_in_pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__empty.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__inline_comment.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__multiline_pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__pipeline.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__reject_double_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__reject_leading_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__reject_pipe_semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__reject_trailing_pipe.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__semicolon.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__simple_command.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__unicode.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__word.snap create mode 100644 crates/oyster_parser/tests/it/snapshots/it__parser__word_with_escape.snap diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..55cc2a4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,245 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "console" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "terminal_size", + "winapi", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "hashbrown" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "insta" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126dd76ebfe2561486a1bd6738a33d2029ffb068a99ac446b7f8c77b2e58dbc" +dependencies = [ + "console", + "once_cell", + "serde", + "serde_json", + "serde_yaml", + "similar", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "oyster" +version = "0.1.0" + +[[package]] +name = "oyster_parser" +version = "0.0.0" +dependencies = [ + "insta", + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "serde" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec0091e1f5aa338283ce049bd9dfefd55e1f168ac233e85c1ffe0038fb48cbe" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "similar" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cfc3f83 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ "crates/*" ] + +[profile.release] +codegen-units = 1 +lto = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a95c68 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +

oyster

+

The world's your oyster

+

+ Stars + GitHub issues + GitHub contributors +

+ +oyster is a shell. +Better description soon. diff --git a/crates/oyster/Cargo.toml b/crates/oyster/Cargo.toml new file mode 100644 index 0000000..9cdf166 --- /dev/null +++ b/crates/oyster/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "oyster" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/crates/oyster/src/main.rs b/crates/oyster/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/crates/oyster/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/crates/oyster/tests/it/main.rs b/crates/oyster/tests/it/main.rs new file mode 100644 index 0000000..acd0afa --- /dev/null +++ b/crates/oyster/tests/it/main.rs @@ -0,0 +1 @@ +mod tidy; diff --git a/crates/oyster/tests/it/tidy.rs b/crates/oyster/tests/it/tidy.rs new file mode 100644 index 0000000..1ac52c8 --- /dev/null +++ b/crates/oyster/tests/it/tidy.rs @@ -0,0 +1,33 @@ +use std::{ + path::PathBuf, + process::{Command, Stdio}, +}; + +fn project_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .to_owned() +} + +#[test] +fn code_is_formatted() { + let output = Command::new("cargo") + .args(["fmt", "--", "--check"]) + .current_dir(project_root()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .unwrap(); + + if !output.status.success() { + Command::new("cargo") + .arg("fmt") + .current_dir(project_root()) + .output() + .unwrap(); + panic!("code wasn't formatted"); + } +} diff --git a/crates/oyster_parser/Cargo.toml b/crates/oyster_parser/Cargo.toml new file mode 100644 index 0000000..6ced133 --- /dev/null +++ b/crates/oyster_parser/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "oyster_parser" +version = "0.0.0" +edition = "2021" + +[lib] +doctest = false + +[dependencies] +thiserror = "^1.0.35" + +[dev-dependencies] +insta = "^1.15.0" diff --git a/crates/oyster_parser/src/ast.rs b/crates/oyster_parser/src/ast.rs new file mode 100644 index 0000000..83b915d --- /dev/null +++ b/crates/oyster_parser/src/ast.rs @@ -0,0 +1,136 @@ +use crate::{NodeKind, ParseError, ParseEvent, Parser}; + +/// Abtract, lossy representation of the syntax. +#[derive(Debug)] +pub struct Code<'a>(pub Vec>); + +#[derive(Debug)] +pub enum Statement<'a> { + Pipeline(Pipeline<'a>), +} + +#[derive(Debug)] +pub struct Pipeline<'a>(pub Vec>); + +#[derive(Debug, Clone, Copy)] +pub enum Redirect { + None, + Stdout, +} + +#[derive(Debug)] +pub struct Command<'a>(pub Vec>, pub Redirect); + +#[derive(Debug)] +pub struct Word<'a>(pub Vec>); + +#[derive(Debug)] +pub enum WordPart<'a> { + Text(&'a str), +} + +impl<'a> TryFrom<&'a str> for Code<'a> { + type Error = ParseError; + + fn try_from(source: &'a str) -> Result { + let mut parser = Parser::new(source); + let mut pos = 0; + build_tree_program(&mut parser, source, &mut pos) + } +} + +fn build_tree_program<'a>( + parser: &mut Parser, + source: &'a str, + pos: &mut usize, +) -> Result, ParseError> { + let mut children = vec![]; + + while let Some(ev) = parser.next() { + match ev { + ParseEvent::StartNode(NodeKind::Pipeline) => children.push(Statement::Pipeline( + build_tree_pipeline(parser, source, pos)?, + )), + ParseEvent::EndNode => break, + ParseEvent::NewLeaf(_, len) => *pos += len, + ParseEvent::Error(err, _) => return Err(err), + _ => unreachable!(), + } + } + + Ok(Code(children)) +} + +fn build_tree_pipeline<'a>( + parser: &mut Parser, + source: &'a str, + pos: &mut usize, +) -> Result, ParseError> { + let mut children = vec![]; + + while let Some(ev) = parser.next() { + match ev { + ParseEvent::StartNode(NodeKind::Command) => { + children.push(build_tree_command(parser, source, pos)?) + } + ParseEvent::EndNode => break, + ParseEvent::NewLeaf(NodeKind::Pipe, _) => { + *pos += 1; + children.last_mut().unwrap().1 = Redirect::Stdout; + } + ParseEvent::NewLeaf(_, len) => *pos += len, + ParseEvent::Error(err, _) => return Err(err), + _ => unreachable!(), + } + } + + Ok(Pipeline(children)) +} + +fn build_tree_command<'a>( + parser: &mut Parser, + source: &'a str, + pos: &mut usize, +) -> Result, ParseError> { + let mut children = vec![]; + + while let Some(ev) = parser.next() { + match ev { + ParseEvent::StartNode(NodeKind::Word) => { + children.push(build_tree_word(parser, source, pos)?) + } + ParseEvent::EndNode => break, + ParseEvent::NewLeaf(_, len) => *pos += len, + ParseEvent::Error(err, _) => return Err(err), + _ => unreachable!(), + } + } + + Ok(Command(children, Redirect::None)) +} + +fn build_tree_word<'a>( + parser: &mut Parser, + source: &'a str, + pos: &mut usize, +) -> Result, ParseError> { + let mut children = vec![]; + + for ev in parser { + match ev { + ParseEvent::NewLeaf(NodeKind::PlainWord, len) => { + children.push(WordPart::Text(&source[*pos..*pos + len])); + *pos += len + } + ParseEvent::NewLeaf(NodeKind::EscapedChar, len) => { + children.push(WordPart::Text(&source[*pos + 1..*pos + len])); + *pos += len + } + ParseEvent::EndNode => break, + ParseEvent::Error(err, _) => return Err(err), + _ => unreachable!(), + } + } + + Ok(Word(children)) +} diff --git a/crates/oyster_parser/src/cst.rs b/crates/oyster_parser/src/cst.rs new file mode 100644 index 0000000..f2e045e --- /dev/null +++ b/crates/oyster_parser/src/cst.rs @@ -0,0 +1,47 @@ +use crate::{NodeKind, ParseError, ParseEvent, Parser}; + +/// A concrete, loss-less representation of the parsed program. +#[derive(Debug)] +pub enum ParseTree { + Tree { + kind: NodeKind, + children: Vec, + }, + Leaf { + kind: NodeKind, + len: usize, + }, + Error { + kind: ParseError, + len: usize, + }, +} + +impl<'a> From<&'a str> for ParseTree { + fn from(source: &str) -> Self { + let mut parser = Parser::new(source); + + ParseTree::Tree { + kind: NodeKind::Program, + children: build_tree(&mut parser), + } + } +} + +fn build_tree(parser: &mut Parser) -> Vec { + let mut children = vec![]; + + while let Some(ev) = parser.next() { + match ev { + ParseEvent::EndNode => break, + ParseEvent::StartNode(kind) => children.push(ParseTree::Tree { + kind, + children: build_tree(parser), + }), + ParseEvent::Error(kind, len) => children.push(ParseTree::Error { kind, len }), + ParseEvent::NewLeaf(kind, len) => children.push(ParseTree::Leaf { kind, len }), + } + } + + children +} diff --git a/crates/oyster_parser/src/lexer.rs b/crates/oyster_parser/src/lexer.rs new file mode 100644 index 0000000..14091e9 --- /dev/null +++ b/crates/oyster_parser/src/lexer.rs @@ -0,0 +1,137 @@ +use std::str::Chars; + +/// The different kinds of tokens for later consumption by the parser. +#[derive(Debug)] +pub enum TokenKind { + // Command mode + /// Any non-newline whitespace. + Whitespace, + /// One or more newline characters. + Newlines, + /// A semicolon. + Semicolon, + /// A pipe. + Pipe, + /// A plain, unquoted word. + PlainWord, + /// A backslash followed by another character. + EscapedChar, + /// A line comment, from # to newline. + Comment, + + /// End of file. + Eof, +} + +/// Tokens represent the lowest abstraction over the input string. +/// They only contain their length, not their position, or text. +/// The parser is responsible for getting that information, if required. +#[derive(Debug)] +pub struct Token { + pub kind: TokenKind, + pub len: usize, +} + +/// The lexer splits up a &str into a stream of tokens. +/// The lexer for oyster specifically supports multiple entry points, +/// which each parse a different sublanguage. +/// This makes handling strings, and substitutions in strings, and strings in substitutions in +/// strings way easier. +#[derive(Debug)] +pub struct Lexer<'a> { + chars: Chars<'a>, + remaining: usize, +} + +impl Lexer<'_> { + /// Create a new lexer. + pub fn new(source: &str) -> Lexer { + let chars = source.chars(); + let remaining = chars.as_str().len(); + + Lexer { chars, remaining } + } + + /// Peek at the next character in the input stream. + fn peek_char(&mut self) -> Option { + self.chars.clone().next() + } + + /// Retrieve the next character from the input stream. + fn next_char(&mut self) -> Option { + self.chars.next() + } + + /// Consume input while `pred` holds true. + fn eat_while(&mut self, pred: fn(char) -> bool) { + loop { + match self.peek_char() { + Some(c) => { + if !pred(c) { + return; + } + } + None => return, + } + + self.next_char(); + } + } + + /// Returns the length of the current token. + /// Also updates remaining characters in input. + fn token_len(&mut self) -> usize { + let old = self.remaining; + self.remaining = self.chars.as_str().len(); + old - self.remaining + } + + /// Get the next token in the command sublanguage. + pub fn next_command_token(&mut self) -> Token { + fn is_whitespace(c: char) -> bool { + [' ', '\t'].contains(&c) + } + + match self.next_char() { + None => Token { + kind: TokenKind::Eof, + len: 0, + }, + Some(c) => { + let kind = match c { + ';' => TokenKind::Semicolon, + '|' => TokenKind::Pipe, + + c if is_whitespace(c) => { + self.eat_while(is_whitespace); + TokenKind::Whitespace + } + + '\n' => { + self.eat_while(|c| c == '\n'); + TokenKind::Newlines + } + + '#' => { + self.eat_while(|c| c != '\n'); + TokenKind::Comment + } + + '\\' => { + self.next_char(); + TokenKind::EscapedChar + } + + _ => { + self.eat_while(|c| ![' ', '\t', '\n', ';', '|', '\\'].contains(&c)); + TokenKind::PlainWord + } + }; + + let len = self.token_len(); + + Token { kind, len } + } + } + } +} diff --git a/crates/oyster_parser/src/lib.rs b/crates/oyster_parser/src/lib.rs new file mode 100644 index 0000000..9b9bc8e --- /dev/null +++ b/crates/oyster_parser/src/lib.rs @@ -0,0 +1,23 @@ +//! Parser for the oyster shell, the grammar is outlined below, `extras` are allowed everywhere, +//! *except* inside `word`. +//! `_` means that the rule does not produce an explicit node. +//! +//! ```grammar +//! program ::= (statement terminator)* +//! _statement ::= pipeline +//! _terminator ::= SEMICOLON | NEWLINES | EOF +//! pipeline ::= command (PIPE NEWLINES? command)* +//! command ::= word+ +//! word ::= (PLAIN_WORD)+ +//! +//! extras ::= COMMENT | WHITESPACE | BACKSLASH_N +//! ``` + +pub mod ast; +mod cst; +mod lexer; +mod parser; + +pub use cst::ParseTree; +pub use lexer::{Lexer, Token, TokenKind}; +pub use parser::{NodeKind, ParseError, ParseEvent, Parser}; diff --git a/crates/oyster_parser/src/parser.rs b/crates/oyster_parser/src/parser.rs new file mode 100644 index 0000000..7135c19 --- /dev/null +++ b/crates/oyster_parser/src/parser.rs @@ -0,0 +1,188 @@ +use thiserror::Error; + +use crate::{lexer::Lexer, Token, TokenKind}; + +/// Errors that might occur during parsing. +#[derive(Debug, Error)] +pub enum ParseError { + #[error("unexpected pipe")] + UnexpectedPipe, + #[error("unexpected end of file")] + UnexpectedEof, + #[error("unexpected semicolon in the middle of statement")] + UnexpectedSemicolon, +} + +/// Type of the node. +#[derive(Debug)] +pub enum NodeKind { + Whitespace, + Newlines, + Semicolon, + Pipe, + PlainWord, + EscapedChar, + Comment, + + Program, + Pipeline, + Command, + Word, + + /// Read a pipe but didn't start word yet. + PipelineCont, +} + +/// Events required to build a syntax tree. +#[derive(Debug)] +pub enum ParseEvent { + Error(ParseError, usize), + StartNode(NodeKind), + EndNode, + NewLeaf(NodeKind, usize), +} + +/// Parse a given source code string. +/// The parsing is decoupled from building the AST and parse tree, +/// instead of returning the tree directly, it returns a linear representation, +/// containing instructions on how to construct the trees. +/// StartNode(Program) and EndNode at the end is implied. +#[derive(Debug)] +pub struct Parser<'a> { + lex: Lexer<'a>, + lookahead: Token, + buffer: Option, + stack: Vec, +} + +impl Parser<'_> { + /// Create a new parser. + pub fn new(source: &str) -> Parser { + let mut lex = Lexer::new(source); + let lookahead = lex.next_command_token(); + let mut stack = Vec::with_capacity(5); + stack.push(NodeKind::Program); + + Parser { + lex, + lookahead, + buffer: None, + stack, + } + } +} + +impl Iterator for Parser<'_> { + type Item = ParseEvent; + + fn next(&mut self) -> Option { + // This is essentially a recursive descent parser. + // The macros somewhat represent things you would do in a true RD implementation. + + macro_rules! chain { + ($a:expr, $b:expr) => {{ + $b; + $a + }}; + } + + macro_rules! chain_buf { + ($a:expr, $b:expr) => {{ + self.buffer = $b; + $a + }}; + } + + macro_rules! call { + ($rule:expr) => {{ + use NodeKind::*; + self.stack.push($rule); + Some(ParseEvent::StartNode($rule)) + }}; + } + + macro_rules! ret { + () => {{ + self.stack.pop(); + Some(ParseEvent::EndNode) + }}; + } + + macro_rules! tailcall { + ($rule:expr) => {{ + use NodeKind::*; + self.stack.pop(); + self.stack.push($rule); + Some(ParseEvent::StartNode($rule)) + }}; + } + + macro_rules! leaf { + ($type:expr) => {{ + use NodeKind::*; + let len = self.lookahead.len; + self.lookahead = self.lex.next_command_token(); + Some(ParseEvent::NewLeaf($type, len)) + }}; + } + + macro_rules! error { + ($type:expr) => {{ + use ParseError::*; + let len = self.lookahead.len; + self.lookahead = self.lex.next_command_token(); + Some(ParseEvent::Error($type, len)) + }}; + } + + use TokenKind::*; + + if let Some(ev) = self.buffer.take() { + return Some(ev); + } + + match self.stack.last() { + None => None, + Some(nt) => match nt { + NodeKind::Program => match self.lookahead.kind { + Whitespace => leaf!(Whitespace), + Newlines => leaf!(Newlines), + Semicolon => leaf!(Semicolon), + Comment => leaf!(Comment), + PlainWord | EscapedChar => call!(Pipeline), + Pipe => error!(UnexpectedPipe), + Eof => chain!(None, ret!()), // return silently + }, + NodeKind::Pipeline => match self.lookahead.kind { + Whitespace => leaf!(Whitespace), + Comment => leaf!(Comment), + Pipe => chain!(leaf!(Pipe), call!(PipelineCont)), + PlainWord | EscapedChar => call!(Command), + Newlines | Semicolon | Eof => ret!(), + }, + NodeKind::PipelineCont => match self.lookahead.kind { + Whitespace => leaf!(Whitespace), + Newlines => leaf!(Newlines), + Comment => leaf!(Comment), + PlainWord | EscapedChar => tailcall!(Command), + Semicolon => chain_buf!(chain!(error!(UnexpectedSemicolon), ret!()), ret!()), + Pipe => chain!(error!(UnexpectedPipe), ret!()), + Eof => chain!(error!(UnexpectedEof), ret!()), + }, + NodeKind::Command => match self.lookahead.kind { + Whitespace => leaf!(Whitespace), + Comment => leaf!(Comment), + PlainWord | EscapedChar => call!(Word), + Newlines | Semicolon | Eof => ret!(), + Pipe => ret!(), + }, + NodeKind::Word => match self.lookahead.kind { + PlainWord => leaf!(PlainWord), + EscapedChar => leaf!(EscapedChar), + Comment | Whitespace | Newlines | Semicolon | Pipe | Eof => ret!(), + }, + _ => unreachable!(), + }, + } + } +} diff --git a/crates/oyster_parser/tests/it/ast.rs b/crates/oyster_parser/tests/it/ast.rs new file mode 100644 index 0000000..c1bc542 --- /dev/null +++ b/crates/oyster_parser/tests/it/ast.rs @@ -0,0 +1,145 @@ +use insta::assert_debug_snapshot as assert_snapshot; +use oyster_parser::{ast, ParseError}; + +fn parse(s: &str) -> Result { + ast::Code::try_from(s) +} + +#[test] +fn empty() { + let source = ""; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn word() { + let source = "word"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn word_with_escape() { + let source = r"hello\#world"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn unicode() { + let source = "谚语"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn simple_command() { + let source = "echo hi"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn semicolon() { + let source = "hello; hi"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn comment() { + let source = r"# a +# b"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn inline_comment() { + let source = "whoami # hello"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn pipeline() { + let source = "whoami | cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn multiline_pipeline() { + let source = r"whoami | +wc -l"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn comment_in_pipeline() { + let source = r"whoami | # comment +wc -l"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_leading_pipe() { + let source = r"whoami +| cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_trailing_pipe() { + let source = "whoami |"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_pipe_semicolon() { + let source = "whoami | ; cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_double_pipe() { + let source = "whoami | | cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} diff --git a/crates/oyster_parser/tests/it/cst.rs b/crates/oyster_parser/tests/it/cst.rs new file mode 100644 index 0000000..f75d039 --- /dev/null +++ b/crates/oyster_parser/tests/it/cst.rs @@ -0,0 +1,145 @@ +use insta::assert_debug_snapshot as assert_snapshot; +use oyster_parser::ParseTree; + +fn parse(s: &str) -> ParseTree { + ParseTree::from(s) +} + +#[test] +fn empty() { + let source = ""; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn word() { + let source = "word"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn word_with_escape() { + let source = r"hello\#world"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn unicode() { + let source = "谚语"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn simple_command() { + let source = "echo hi"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn semicolon() { + let source = "hello; hi"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn comment() { + let source = r"# a +# b"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn inline_comment() { + let source = "whoami # hello"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn pipeline() { + let source = "whoami | cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn multiline_pipeline() { + let source = r"whoami | +wc -l"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn comment_in_pipeline() { + let source = r"whoami | # comment +wc -l"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_leading_pipe() { + let source = r"whoami +| cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_trailing_pipe() { + let source = "whoami |"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_pipe_semicolon() { + let source = "whoami | ; cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_double_pipe() { + let source = "whoami | | cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} diff --git a/crates/oyster_parser/tests/it/lexer.rs b/crates/oyster_parser/tests/it/lexer.rs new file mode 100644 index 0000000..98c78e4 --- /dev/null +++ b/crates/oyster_parser/tests/it/lexer.rs @@ -0,0 +1,83 @@ +use insta::assert_debug_snapshot as assert_snapshot; +use oyster_parser::Lexer; + +#[test] +fn eof() { + let source = ""; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn whitespace() { + let source = " \t \t\t"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn newlines() { + let source = "\n\n\n"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn semicolon() { + let source = ";"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn pipe() { + let source = "|"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn plain_word() { + let source = "whoami"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn word_with_hash() { + let source = "nixpkgs#hello"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn escaped_hash() { + let source = r"\#"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} + +#[test] +fn comment() { + let source = "# hey"; + + let actual = Lexer::new(source).next_command_token(); + + assert_snapshot!(actual); +} diff --git a/crates/oyster_parser/tests/it/main.rs b/crates/oyster_parser/tests/it/main.rs new file mode 100644 index 0000000..940aa49 --- /dev/null +++ b/crates/oyster_parser/tests/it/main.rs @@ -0,0 +1,4 @@ +mod ast; +mod cst; +mod lexer; +mod parser; diff --git a/crates/oyster_parser/tests/it/parser.rs b/crates/oyster_parser/tests/it/parser.rs new file mode 100644 index 0000000..f81cdb0 --- /dev/null +++ b/crates/oyster_parser/tests/it/parser.rs @@ -0,0 +1,145 @@ +use insta::assert_debug_snapshot as assert_snapshot; +use oyster_parser::{ParseEvent, Parser}; + +fn parse(s: &str) -> Vec { + Parser::new(s).collect() +} + +#[test] +fn empty() { + let source = ""; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn word() { + let source = "word"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn word_with_escape() { + let source = "hello\\world"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn unicode() { + let source = "谚语"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn simple_command() { + let source = "echo hi"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn semicolon() { + let source = "hello; hi"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn comment() { + let source = r"# a +# b"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn inline_comment() { + let source = "whoami # hello"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn pipeline() { + let source = "whoami | cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn multiline_pipeline() { + let source = r"whoami | +wc -l"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn comment_in_pipeline() { + let source = r"whoami | # comment +wc -l"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_leading_pipe() { + let source = r"whoami +| cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_trailing_pipe() { + let source = "whoami |"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_pipe_semicolon() { + let source = "whoami | ; cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} + +#[test] +fn reject_double_pipe() { + let source = "whoami | | cat"; + + let actual = parse(source); + + assert_snapshot!(actual); +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__comment.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__comment.snap new file mode 100644 index 0000000..c2e4091 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__comment.snap @@ -0,0 +1,9 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__comment_in_pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__comment_in_pipeline.snap new file mode 100644 index 0000000..644d5e5 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__comment_in_pipeline.snap @@ -0,0 +1,47 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "whoami", + ), + ], + ), + ], + Stdout, + ), + Command( + [ + Word( + [ + Text( + "wc", + ), + ], + ), + Word( + [ + Text( + "-l", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__empty.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__empty.snap new file mode 100644 index 0000000..c2e4091 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__empty.snap @@ -0,0 +1,9 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__inline_comment.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__inline_comment.snap new file mode 100644 index 0000000..48f1338 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__inline_comment.snap @@ -0,0 +1,28 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "whoami", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__multiline_pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__multiline_pipeline.snap new file mode 100644 index 0000000..644d5e5 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__multiline_pipeline.snap @@ -0,0 +1,47 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "whoami", + ), + ], + ), + ], + Stdout, + ), + Command( + [ + Word( + [ + Text( + "wc", + ), + ], + ), + Word( + [ + Text( + "-l", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__pipeline.snap new file mode 100644 index 0000000..4e80f91 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__pipeline.snap @@ -0,0 +1,40 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "whoami", + ), + ], + ), + ], + Stdout, + ), + Command( + [ + Word( + [ + Text( + "cat", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__reject_double_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_double_pipe.snap new file mode 100644 index 0000000..c72f30e --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_double_pipe.snap @@ -0,0 +1,7 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Err( + UnexpectedPipe, +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__reject_leading_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_leading_pipe.snap new file mode 100644 index 0000000..c72f30e --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_leading_pipe.snap @@ -0,0 +1,7 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Err( + UnexpectedPipe, +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__reject_pipe_semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_pipe_semicolon.snap new file mode 100644 index 0000000..b220fdf --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_pipe_semicolon.snap @@ -0,0 +1,7 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Err( + UnexpectedSemicolon, +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__reject_trailing_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_trailing_pipe.snap new file mode 100644 index 0000000..9254c8f --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__reject_trailing_pipe.snap @@ -0,0 +1,7 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Err( + UnexpectedEof, +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__semicolon.snap new file mode 100644 index 0000000..59764ca --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__semicolon.snap @@ -0,0 +1,46 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "hello", + ), + ], + ), + ], + None, + ), + ], + ), + ), + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "hi", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__simple_command.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__simple_command.snap new file mode 100644 index 0000000..1e82546 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__simple_command.snap @@ -0,0 +1,35 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "echo", + ), + ], + ), + Word( + [ + Text( + "hi", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__unicode.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__unicode.snap new file mode 100644 index 0000000..ea808e7 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__unicode.snap @@ -0,0 +1,28 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "谚语", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__word.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__word.snap new file mode 100644 index 0000000..d118100 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__word.snap @@ -0,0 +1,28 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "word", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__ast__word_with_escape.snap b/crates/oyster_parser/tests/it/snapshots/it__ast__word_with_escape.snap new file mode 100644 index 0000000..578f4f6 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__ast__word_with_escape.snap @@ -0,0 +1,34 @@ +--- +source: crates/oyster_parser/tests/it/ast.rs +expression: actual +--- +Ok( + Code( + [ + Pipeline( + Pipeline( + [ + Command( + [ + Word( + [ + Text( + "hello", + ), + Text( + "#", + ), + Text( + "world", + ), + ], + ), + ], + None, + ), + ], + ), + ), + ], + ), +) diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__comment.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__comment.snap new file mode 100644 index 0000000..7fe4eee --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__comment.snap @@ -0,0 +1,21 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Leaf { + kind: Comment, + len: 3, + }, + Leaf { + kind: Newlines, + len: 1, + }, + Leaf { + kind: Comment, + len: 3, + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__comment_in_pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__comment_in_pipeline.snap new file mode 100644 index 0000000..1b37eb7 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__comment_in_pipeline.snap @@ -0,0 +1,75 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + ], + }, + Leaf { + kind: Pipe, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Leaf { + kind: Comment, + len: 9, + }, + Leaf { + kind: Newlines, + len: 1, + }, + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 2, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 2, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__empty.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__empty.snap new file mode 100644 index 0000000..bb2c4c3 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__empty.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__inline_comment.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__inline_comment.snap new file mode 100644 index 0000000..c89743f --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__inline_comment.snap @@ -0,0 +1,36 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Leaf { + kind: Comment, + len: 7, + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__multiline_pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__multiline_pipeline.snap new file mode 100644 index 0000000..bc7aa64 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__multiline_pipeline.snap @@ -0,0 +1,67 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + ], + }, + Leaf { + kind: Pipe, + len: 1, + }, + Leaf { + kind: Newlines, + len: 1, + }, + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 2, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 2, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__pipeline.snap new file mode 100644 index 0000000..68bf0c1 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__pipeline.snap @@ -0,0 +1,54 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + ], + }, + Leaf { + kind: Pipe, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 3, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__reject_double_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_double_pipe.snap new file mode 100644 index 0000000..e78c9dd --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_double_pipe.snap @@ -0,0 +1,62 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + ], + }, + Leaf { + kind: Pipe, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Error { + kind: UnexpectedPipe, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 3, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__reject_leading_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_leading_pipe.snap new file mode 100644 index 0000000..d625082 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_leading_pipe.snap @@ -0,0 +1,59 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + ], + }, + ], + }, + Leaf { + kind: Newlines, + len: 1, + }, + Error { + kind: UnexpectedPipe, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 3, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__reject_pipe_semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_pipe_semicolon.snap new file mode 100644 index 0000000..4a2da24 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_pipe_semicolon.snap @@ -0,0 +1,67 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + ], + }, + Leaf { + kind: Pipe, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Error { + kind: UnexpectedSemicolon, + len: 1, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 3, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__reject_trailing_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_trailing_pipe.snap new file mode 100644 index 0000000..fc08fce --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__reject_trailing_pipe.snap @@ -0,0 +1,40 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + ], + }, + Leaf { + kind: Pipe, + len: 1, + }, + Error { + kind: UnexpectedEof, + len: 0, + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__semicolon.snap new file mode 100644 index 0000000..28bf307 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__semicolon.snap @@ -0,0 +1,55 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 5, + }, + ], + }, + ], + }, + ], + }, + Leaf { + kind: Semicolon, + len: 1, + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 2, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__simple_command.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__simple_command.snap new file mode 100644 index 0000000..a4f5be9 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__simple_command.snap @@ -0,0 +1,41 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 4, + }, + ], + }, + Leaf { + kind: Whitespace, + len: 1, + }, + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 2, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__unicode.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__unicode.snap new file mode 100644 index 0000000..525fc37 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__unicode.snap @@ -0,0 +1,28 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 6, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__word.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__word.snap new file mode 100644 index 0000000..d3f9fcc --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__word.snap @@ -0,0 +1,28 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 4, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__cst__word_with_escape.snap b/crates/oyster_parser/tests/it/snapshots/it__cst__word_with_escape.snap new file mode 100644 index 0000000..dea8584 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__cst__word_with_escape.snap @@ -0,0 +1,36 @@ +--- +source: crates/oyster_parser/tests/it/cst.rs +expression: actual +--- +Tree { + kind: Program, + children: [ + Tree { + kind: Pipeline, + children: [ + Tree { + kind: Command, + children: [ + Tree { + kind: Word, + children: [ + Leaf { + kind: PlainWord, + len: 5, + }, + Leaf { + kind: EscapedChar, + len: 2, + }, + Leaf { + kind: PlainWord, + len: 5, + }, + ], + }, + ], + }, + ], + }, + ], +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__comment.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__comment.snap new file mode 100644 index 0000000..4b30a2c --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__comment.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: Comment, + len: 5, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__eof.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__eof.snap new file mode 100644 index 0000000..a0bc053 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__eof.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: Eof, + len: 0, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__escaped_hash.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__escaped_hash.snap new file mode 100644 index 0000000..b482977 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__escaped_hash.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: EscapedChar, + len: 2, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__newlines.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__newlines.snap new file mode 100644 index 0000000..4dc66fb --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__newlines.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: Newlines, + len: 3, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__pipe.snap new file mode 100644 index 0000000..367022a --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__pipe.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: Pipe, + len: 1, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__plain_word.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__plain_word.snap new file mode 100644 index 0000000..68dd11a --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__plain_word.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: PlainWord, + len: 6, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__semicolon.snap new file mode 100644 index 0000000..1e2a05c --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__semicolon.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: Semicolon, + len: 1, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__whitespace.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__whitespace.snap new file mode 100644 index 0000000..7fc7390 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__whitespace.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: Whitespace, + len: 6, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__lexer__word_with_hash.snap b/crates/oyster_parser/tests/it/snapshots/it__lexer__word_with_hash.snap new file mode 100644 index 0000000..ee41b05 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__lexer__word_with_hash.snap @@ -0,0 +1,8 @@ +--- +source: crates/oyster_parser/tests/it/lexer.rs +expression: actual +--- +Token { + kind: PlainWord, + len: 13, +} diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__comment.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__comment.snap new file mode 100644 index 0000000..ca14bcf --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__comment.snap @@ -0,0 +1,18 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + NewLeaf( + Comment, + 3, + ), + NewLeaf( + Newlines, + 1, + ), + NewLeaf( + Comment, + 3, + ), +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__comment_in_pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__comment_in_pipeline.snap new file mode 100644 index 0000000..e625cdd --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__comment_in_pipeline.snap @@ -0,0 +1,66 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + EndNode, + NewLeaf( + Pipe, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + NewLeaf( + Comment, + 9, + ), + NewLeaf( + Newlines, + 1, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 2, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 2, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__empty.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__empty.snap new file mode 100644 index 0000000..53546c4 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__empty.snap @@ -0,0 +1,5 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__inline_comment.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__inline_comment.snap new file mode 100644 index 0000000..22ec3dd --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__inline_comment.snap @@ -0,0 +1,30 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + NewLeaf( + Comment, + 7, + ), + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__multiline_pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__multiline_pipeline.snap new file mode 100644 index 0000000..271b05b --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__multiline_pipeline.snap @@ -0,0 +1,58 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + EndNode, + NewLeaf( + Pipe, + 1, + ), + NewLeaf( + Newlines, + 1, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 2, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 2, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__pipeline.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__pipeline.snap new file mode 100644 index 0000000..9e2485b --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__pipeline.snap @@ -0,0 +1,46 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + EndNode, + NewLeaf( + Pipe, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 3, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__reject_double_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_double_pipe.snap new file mode 100644 index 0000000..43a9897 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_double_pipe.snap @@ -0,0 +1,54 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + EndNode, + NewLeaf( + Pipe, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + Error( + UnexpectedPipe, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 3, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__reject_leading_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_leading_pipe.snap new file mode 100644 index 0000000..2aa98d8 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_leading_pipe.snap @@ -0,0 +1,50 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + EndNode, + EndNode, + NewLeaf( + Newlines, + 1, + ), + Error( + UnexpectedPipe, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 3, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__reject_pipe_semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_pipe_semicolon.snap new file mode 100644 index 0000000..11abca2 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_pipe_semicolon.snap @@ -0,0 +1,58 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + EndNode, + NewLeaf( + Pipe, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + Error( + UnexpectedSemicolon, + 1, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 3, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__reject_trailing_pipe.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_trailing_pipe.snap new file mode 100644 index 0000000..83917fa --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__reject_trailing_pipe.snap @@ -0,0 +1,34 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + EndNode, + NewLeaf( + Pipe, + 1, + ), + Error( + UnexpectedEof, + 0, + ), + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__semicolon.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__semicolon.snap new file mode 100644 index 0000000..8cb4932 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__semicolon.snap @@ -0,0 +1,46 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 5, + ), + EndNode, + EndNode, + EndNode, + NewLeaf( + Semicolon, + 1, + ), + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 2, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__simple_command.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__simple_command.snap new file mode 100644 index 0000000..59e1d21 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__simple_command.snap @@ -0,0 +1,34 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 4, + ), + EndNode, + NewLeaf( + Whitespace, + 1, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 2, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__unicode.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__unicode.snap new file mode 100644 index 0000000..1feddf1 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__unicode.snap @@ -0,0 +1,22 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 6, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__word.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__word.snap new file mode 100644 index 0000000..59ba7d0 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__word.snap @@ -0,0 +1,22 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 4, + ), + EndNode, + EndNode, + EndNode, +] diff --git a/crates/oyster_parser/tests/it/snapshots/it__parser__word_with_escape.snap b/crates/oyster_parser/tests/it/snapshots/it__parser__word_with_escape.snap new file mode 100644 index 0000000..f6b6ea6 --- /dev/null +++ b/crates/oyster_parser/tests/it/snapshots/it__parser__word_with_escape.snap @@ -0,0 +1,30 @@ +--- +source: crates/oyster_parser/tests/it/parser.rs +expression: actual +--- +[ + StartNode( + Pipeline, + ), + StartNode( + Command, + ), + StartNode( + Word, + ), + NewLeaf( + PlainWord, + 5, + ), + NewLeaf( + EscapedChar, + 2, + ), + NewLeaf( + PlainWord, + 4, + ), + EndNode, + EndNode, + EndNode, +]