feat(parser): implement parser
This commit is contained in:
commit
02f19d91ee
73 changed files with 3072 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
245
Cargo.lock
generated
Normal file
245
Cargo.lock
generated
Normal file
|
@ -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",
|
||||
]
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
members = [ "crates/*" ]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
<h1 align="center">oyster</h1>
|
||||
<p align="center"><i>The world's your oyster</i></p>
|
||||
<hr><p align="center">
|
||||
<img alt="Stars" src="https://img.shields.io/github/stars/buffet/oyster.svg?label=Stars&style=flat" />
|
||||
<a href="https://github.com/buffet/oyster/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/buffet/oyster.svg"/></a>
|
||||
<a href="https://github.com/buffet/oyster/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/buffet/oyster"></a>
|
||||
</p>
|
||||
|
||||
oyster is a shell.
|
||||
Better description soon.
|
6
crates/oyster/Cargo.toml
Normal file
6
crates/oyster/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "oyster"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
3
crates/oyster/src/main.rs
Normal file
3
crates/oyster/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
1
crates/oyster/tests/it/main.rs
Normal file
1
crates/oyster/tests/it/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod tidy;
|
33
crates/oyster/tests/it/tidy.rs
Normal file
33
crates/oyster/tests/it/tidy.rs
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
13
crates/oyster_parser/Cargo.toml
Normal file
13
crates/oyster_parser/Cargo.toml
Normal file
|
@ -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"
|
136
crates/oyster_parser/src/ast.rs
Normal file
136
crates/oyster_parser/src/ast.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use crate::{NodeKind, ParseError, ParseEvent, Parser};
|
||||
|
||||
/// Abtract, lossy representation of the syntax.
|
||||
#[derive(Debug)]
|
||||
pub struct Code<'a>(pub Vec<Statement<'a>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Statement<'a> {
|
||||
Pipeline(Pipeline<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline<'a>(pub Vec<Command<'a>>);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Redirect {
|
||||
None,
|
||||
Stdout,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Command<'a>(pub Vec<Word<'a>>, pub Redirect);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Word<'a>(pub Vec<WordPart<'a>>);
|
||||
|
||||
#[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<Self, Self::Error> {
|
||||
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<Code<'a>, 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<Pipeline<'a>, 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<Command<'a>, 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<Word<'a>, 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))
|
||||
}
|
47
crates/oyster_parser/src/cst.rs
Normal file
47
crates/oyster_parser/src/cst.rs
Normal file
|
@ -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<ParseTree>,
|
||||
},
|
||||
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<ParseTree> {
|
||||
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
|
||||
}
|
137
crates/oyster_parser/src/lexer.rs
Normal file
137
crates/oyster_parser/src/lexer.rs
Normal file
|
@ -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<char> {
|
||||
self.chars.clone().next()
|
||||
}
|
||||
|
||||
/// Retrieve the next character from the input stream.
|
||||
fn next_char(&mut self) -> Option<char> {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
crates/oyster_parser/src/lib.rs
Normal file
23
crates/oyster_parser/src/lib.rs
Normal file
|
@ -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};
|
188
crates/oyster_parser/src/parser.rs
Normal file
188
crates/oyster_parser/src/parser.rs
Normal file
|
@ -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<ParseEvent>,
|
||||
stack: Vec<NodeKind>,
|
||||
}
|
||||
|
||||
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<Self::Item> {
|
||||
// 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!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
145
crates/oyster_parser/tests/it/ast.rs
Normal file
145
crates/oyster_parser/tests/it/ast.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use insta::assert_debug_snapshot as assert_snapshot;
|
||||
use oyster_parser::{ast, ParseError};
|
||||
|
||||
fn parse(s: &str) -> Result<ast::Code, ParseError> {
|
||||
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);
|
||||
}
|
145
crates/oyster_parser/tests/it/cst.rs
Normal file
145
crates/oyster_parser/tests/it/cst.rs
Normal file
|
@ -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);
|
||||
}
|
83
crates/oyster_parser/tests/it/lexer.rs
Normal file
83
crates/oyster_parser/tests/it/lexer.rs
Normal file
|
@ -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);
|
||||
}
|
4
crates/oyster_parser/tests/it/main.rs
Normal file
4
crates/oyster_parser/tests/it/main.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
mod ast;
|
||||
mod cst;
|
||||
mod lexer;
|
||||
mod parser;
|
145
crates/oyster_parser/tests/it/parser.rs
Normal file
145
crates/oyster_parser/tests/it/parser.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use insta::assert_debug_snapshot as assert_snapshot;
|
||||
use oyster_parser::{ParseEvent, Parser};
|
||||
|
||||
fn parse(s: &str) -> Vec<ParseEvent> {
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Ok(
|
||||
Code(
|
||||
[],
|
||||
),
|
||||
)
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Ok(
|
||||
Code(
|
||||
[],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Ok(
|
||||
Code(
|
||||
[
|
||||
Pipeline(
|
||||
Pipeline(
|
||||
[
|
||||
Command(
|
||||
[
|
||||
Word(
|
||||
[
|
||||
Text(
|
||||
"whoami",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
None,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Err(
|
||||
UnexpectedPipe,
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Err(
|
||||
UnexpectedPipe,
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Err(
|
||||
UnexpectedSemicolon,
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Err(
|
||||
UnexpectedEof,
|
||||
)
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Ok(
|
||||
Code(
|
||||
[
|
||||
Pipeline(
|
||||
Pipeline(
|
||||
[
|
||||
Command(
|
||||
[
|
||||
Word(
|
||||
[
|
||||
Text(
|
||||
"谚语",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
None,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
28
crates/oyster_parser/tests/it/snapshots/it__ast__word.snap
Normal file
28
crates/oyster_parser/tests/it/snapshots/it__ast__word.snap
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/ast.rs
|
||||
expression: actual
|
||||
---
|
||||
Ok(
|
||||
Code(
|
||||
[
|
||||
Pipeline(
|
||||
Pipeline(
|
||||
[
|
||||
Command(
|
||||
[
|
||||
Word(
|
||||
[
|
||||
Text(
|
||||
"word",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
None,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/cst.rs
|
||||
expression: actual
|
||||
---
|
||||
Tree {
|
||||
kind: Program,
|
||||
children: [],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
28
crates/oyster_parser/tests/it/snapshots/it__cst__word.snap
Normal file
28
crates/oyster_parser/tests/it/snapshots/it__cst__word.snap
Normal file
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: Comment,
|
||||
len: 5,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: Eof,
|
||||
len: 0,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: EscapedChar,
|
||||
len: 2,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: Newlines,
|
||||
len: 3,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: Pipe,
|
||||
len: 1,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: PlainWord,
|
||||
len: 6,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: Semicolon,
|
||||
len: 1,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: Whitespace,
|
||||
len: 6,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/lexer.rs
|
||||
expression: actual
|
||||
---
|
||||
Token {
|
||||
kind: PlainWord,
|
||||
len: 13,
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/parser.rs
|
||||
expression: actual
|
||||
---
|
||||
[
|
||||
NewLeaf(
|
||||
Comment,
|
||||
3,
|
||||
),
|
||||
NewLeaf(
|
||||
Newlines,
|
||||
1,
|
||||
),
|
||||
NewLeaf(
|
||||
Comment,
|
||||
3,
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: crates/oyster_parser/tests/it/parser.rs
|
||||
expression: actual
|
||||
---
|
||||
[]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
Loading…
Reference in a new issue