feat(parser): implement parser

This commit is contained in:
buffet 2022-07-14 22:26:57 +00:00
commit 02f19d91ee
73 changed files with 3072 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

245
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,6 @@
[workspace]
members = [ "crates/*" ]
[profile.release]
codegen-units = 1
lto = true

10
README.md Normal file
View 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
View file

@ -0,0 +1,6 @@
[package]
name = "oyster"
version = "0.1.0"
edition = "2021"
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -0,0 +1 @@
mod tidy;

View 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");
}
}

View 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"

View 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))
}

View 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
}

View 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 }
}
}
}
}

View 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};

View 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!(),
},
}
}
}

View 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);
}

View 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);
}

View 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);
}

View file

@ -0,0 +1,4 @@
mod ast;
mod cst;
mod lexer;
mod parser;

View 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);
}

View file

@ -0,0 +1,9 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Ok(
Code(
[],
),
)

View file

@ -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,
),
],
),
),
],
),
)

View file

@ -0,0 +1,9 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Ok(
Code(
[],
),
)

View file

@ -0,0 +1,28 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Ok(
Code(
[
Pipeline(
Pipeline(
[
Command(
[
Word(
[
Text(
"whoami",
),
],
),
],
None,
),
],
),
),
],
),
)

View file

@ -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,
),
],
),
),
],
),
)

View file

@ -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,
),
],
),
),
],
),
)

View file

@ -0,0 +1,7 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Err(
UnexpectedPipe,
)

View file

@ -0,0 +1,7 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Err(
UnexpectedPipe,
)

View file

@ -0,0 +1,7 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Err(
UnexpectedSemicolon,
)

View file

@ -0,0 +1,7 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Err(
UnexpectedEof,
)

View file

@ -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,
),
],
),
),
],
),
)

View file

@ -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,
),
],
),
),
],
),
)

View file

@ -0,0 +1,28 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Ok(
Code(
[
Pipeline(
Pipeline(
[
Command(
[
Word(
[
Text(
"谚语",
),
],
),
],
None,
),
],
),
),
],
),
)

View file

@ -0,0 +1,28 @@
---
source: crates/oyster_parser/tests/it/ast.rs
expression: actual
---
Ok(
Code(
[
Pipeline(
Pipeline(
[
Command(
[
Word(
[
Text(
"word",
),
],
),
],
None,
),
],
),
),
],
),
)

View file

@ -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,
),
],
),
),
],
),
)

View file

@ -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,
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/cst.rs
expression: actual
---
Tree {
kind: Program,
children: [],
}

View file

@ -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,
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View 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: 6,
},
],
},
],
},
],
},
],
}

View 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,
},
],
},
],
},
],
},
],
}

View file

@ -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,
},
],
},
],
},
],
},
],
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: Comment,
len: 5,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: Eof,
len: 0,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: EscapedChar,
len: 2,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: Newlines,
len: 3,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: Pipe,
len: 1,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: PlainWord,
len: 6,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: Semicolon,
len: 1,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: Whitespace,
len: 6,
}

View file

@ -0,0 +1,8 @@
---
source: crates/oyster_parser/tests/it/lexer.rs
expression: actual
---
Token {
kind: PlainWord,
len: 13,
}

View file

@ -0,0 +1,18 @@
---
source: crates/oyster_parser/tests/it/parser.rs
expression: actual
---
[
NewLeaf(
Comment,
3,
),
NewLeaf(
Newlines,
1,
),
NewLeaf(
Comment,
3,
),
]

View file

@ -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,
]

View file

@ -0,0 +1,5 @@
---
source: crates/oyster_parser/tests/it/parser.rs
expression: actual
---
[]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]

View file

@ -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,
]