From 3a65c33b15b6faf198f693e5a0bec4bbe5f08161 Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 18 Dec 2023 18:04:21 +0100 Subject: [PATCH] feat(trixy-lang_parser): Add first parser pass --- trixy/trixy-lang_parser/.gitignore | 2 +- trixy/trixy-lang_parser/README.md | 6 + trixy/trixy-lang_parser/docs/grammar.ebnf | 17 ++ trixy/trixy-lang_parser/docs/grammar.pdf | Bin 0 -> 32641 bytes trixy/trixy-lang_parser/example/empty.tri | 0 trixy/trixy-lang_parser/example/failing.tri | 9 + trixy/trixy-lang_parser/example/full.tri | 126 ++++++++++++ .../{example_simple.tri => multiple.tri} | 4 +- .../example/{example.tri => simple.tri} | 0 trixy/trixy-lang_parser/generate_docs | 9 + .../src/command_spec/checked.rs | 58 ++++++ .../trixy-lang_parser/src/command_spec/mod.rs | 38 +--- .../src/command_spec/unchecked.rs | 48 +++++ trixy/trixy-lang_parser/src/error.rs | 184 ++++++++++++++++- trixy/trixy-lang_parser/src/lexing/error.rs | 121 +++++------ trixy/trixy-lang_parser/src/lexing/mod.rs | 194 ++++++++++++++++-- trixy/trixy-lang_parser/src/lexing/test.rs | 4 +- .../trixy-lang_parser/src/lexing/tokenizer.rs | 65 +----- trixy/trixy-lang_parser/src/lib.rs | 87 ++++---- trixy/trixy-lang_parser/src/main.rs | 29 ++- trixy/trixy-lang_parser/src/parsing/error.rs | 93 +++++++++ trixy/trixy-lang_parser/src/parsing/mod.rs | 4 + trixy/trixy-lang_parser/src/parsing/test.rs | 88 ++++++++ .../src/parsing/unchecked.rs | 167 +++++++++++++++ 24 files changed, 1127 insertions(+), 226 deletions(-) create mode 100644 trixy/trixy-lang_parser/README.md create mode 100644 trixy/trixy-lang_parser/docs/grammar.ebnf create mode 100644 trixy/trixy-lang_parser/docs/grammar.pdf create mode 100644 trixy/trixy-lang_parser/example/empty.tri create mode 100644 trixy/trixy-lang_parser/example/failing.tri create mode 100644 trixy/trixy-lang_parser/example/full.tri rename trixy/trixy-lang_parser/example/{example_simple.tri => multiple.tri} (80%) rename trixy/trixy-lang_parser/example/{example.tri => simple.tri} (100%) create mode 100755 trixy/trixy-lang_parser/generate_docs create mode 100644 trixy/trixy-lang_parser/src/command_spec/checked.rs create mode 100644 trixy/trixy-lang_parser/src/command_spec/unchecked.rs create mode 100644 trixy/trixy-lang_parser/src/parsing/error.rs create mode 100644 trixy/trixy-lang_parser/src/parsing/mod.rs create mode 100644 trixy/trixy-lang_parser/src/parsing/test.rs create mode 100644 trixy/trixy-lang_parser/src/parsing/unchecked.rs diff --git a/trixy/trixy-lang_parser/.gitignore b/trixy/trixy-lang_parser/.gitignore index 72fc7e3..20c0ba9 100644 --- a/trixy/trixy-lang_parser/.gitignore +++ b/trixy/trixy-lang_parser/.gitignore @@ -2,5 +2,5 @@ /target /result -# lua_macros is a library +# This crate is a library Cargo.lock diff --git a/trixy/trixy-lang_parser/README.md b/trixy/trixy-lang_parser/README.md new file mode 100644 index 0000000..0a6f6bb --- /dev/null +++ b/trixy/trixy-lang_parser/README.md @@ -0,0 +1,6 @@ +# trixy-lang_parser +This crate contains a parser (and lexer) for the Trixy language. +The corresponding grammar is in the grammar file [here](./docs/grammar.ebnf) encoded in [Extended Backus-Naur Form](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form). + +## Docs +Run `./generate_docs` to turn the grammar file into railroad diagrams. diff --git a/trixy/trixy-lang_parser/docs/grammar.ebnf b/trixy/trixy-lang_parser/docs/grammar.ebnf new file mode 100644 index 0000000..749149c --- /dev/null +++ b/trixy/trixy-lang_parser/docs/grammar.ebnf @@ -0,0 +1,17 @@ +# (* +# Trixy is fully whitespace independent, this means that you can +# interleave whitespace in the definitions. +# The same applies to comments: +# - Line comments (`// \n`) and +# - Block comments (`/* */`). +# *) + +CommandSpec = { Function | Namespace } ; +Function = "fn" Identifier "(" {Identifier ":" Type} ")" [ "->" Type ] ";" ; +Namespace = "nasp" Identifier "{" {Function | Namespace} "}" ; +Type = "String" | "Integer" ; # (* This corresponds to the CommandTransferValue *) +Identifier = CHARACTER { NUMBER | CHARACTER } ; + +# (* +# vim: ft=ebnf +# *) diff --git a/trixy/trixy-lang_parser/docs/grammar.pdf b/trixy/trixy-lang_parser/docs/grammar.pdf new file mode 100644 index 0000000000000000000000000000000000000000..54cad09d87574d9864c5cf9ccd9bc1e95ac12834 GIT binary patch literal 32641 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%0AAcVCFW*Qf5o==aa)!b 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 String; ; +} + + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/full.tri b/trixy/trixy-lang_parser/example/full.tri new file mode 100644 index 0000000..d0ca0b1 --- /dev/null +++ b/trixy/trixy-lang_parser/example/full.tri @@ -0,0 +1,126 @@ +/// Prints to the output, with a newline. +// HACK(@soispha): The stdlib Lua `print()` function has stdout as output hardcoded, +// redirecting stdout seems too much like a hack thus we are just redefining the print function +// to output to a controlled output. <2023-09-09> +fn print(input: CommandTransferValue); + +nasp trinitrix { + /// Language specific functions, which mirror the `trinitrix.api` namespace. + /// That is, if you have to choose between a `std` and a `api` function choose the `std` + /// one as it will most likely be more high-level and easier to use (as it isn't abstracted + /// over multiple languages). Feel free to drop down to the lower level api, if you feel + /// like that more, it should be as stable and user-oriented as the `std` functions + nasp std {} + + /// Debug only functions, these are effectively useless + nasp debug { + /// Greets the user + fn greet(input: String) -> String; + + /// Returns a table of greeted users + fn greet_multiple() -> Table; + } + + /// General API to change stuff in Trinitrix + nasp api { + /// Closes the application + fn exit(); + + /// Send a message to the current room + /// The send message is interpreted literally. + fn room_message_send(msg: String); + + /// Open the help pages at the first occurrence of + /// the input string if it is Some, otherwise open + /// the help pages at the start + fn help(input: Option); + + // Register a function to be used with the Trinitrix API + // (This function is actually implemented in the std namespace) + /* fn register_function(function: RawFunction); */ + + /// Function that change the UI, or UI state + nasp ui { + /// Shows the command line + fn command_line_show(); + + /// Hides the command line + fn command_line_hide(); + + /// Go to the next plane + fn cycle_planes(); + /// Go to the previous plane + fn cycle_planes_rev(); + + /// Sets the current app mode to Normal / navigation mode + fn set_mode_normal(); + /// Sets the current app mode to Insert / editing mode + fn set_mode_insert(); + } + + /// Manipulate keymappings, the mode is specified as a String build up of all mode + /// the keymapping should be active in. The mapping works as follows: + /// n => normal Mode + /// c => command Mode + /// i => insert Mode + /// + /// The key works in a similar matter, specifying the required keypresses to trigger the + /// callback. For example "aba" for require the user to press "a" then "b" then "a" again + /// to trigger the mapping. Special characters are encoded as follows: + /// "ba" => "Ctrl+a" then "b" then "a" + /// "" => "A" or "Shift+a" + /// "A" => "A" + /// " " => "Alt+a" () or "Meta+a"() (most terminals can't really differentiate between these characters) + /// "a" => "a" then "Ctrl+b" then "Ctrl+a" (also works for Shift, Alt and Super) + /// "" => "Ctrl+Shift+Alt+b" (the ordering doesn't matter) + /// "a " => "a" then a literal space (" ") + /// "å🙂" => "å" then "🙂" (full Unicode support!) + /// "" => escape key + /// "" => F3 key + /// "" => backspace key (and so forth) + /// "" => a literal "-" + /// "" or "" => a literal "<" + /// "" or "" => a literal ">" + /// + /// The callback MUST be registered first by calling + /// `trinitrix.api.register_function()` the returned value can than be used to + /// set the keymap. + nasp keymaps { + /// Add a new keymapping + fn add(mode: String, key: String, callback: Function); + + /// Remove a keymapping + /// + /// Does nothing, if the keymapping doesn't exists + fn remove((/* mode: */ String, /* key: */ String)); + + /// List declared keymappings + fn get(mode: String); + } + + /// Functions only used internally within Trinitrix + nasp raw { + /// Send an error to the default error output + fn raise_error(input: String); + + /// Send output to the default output + /// This is mainly used to display the final + /// output of evaluated lua commands. + fn display_output(input: String); + + /// Input a character without checking for possible keymaps + /// If the current state does not expect input, this character is ignored + /// The encoding is the same as in the `trinitrix.api.keymaps` commands + fn send_input_unprocessed(input: String); + + /// This namespace is used to store some command specific data (like functions, as + /// ensuring memory locations stay allocated in garbage collected language is hard) + /// + /// Treat it as an implementation detail + nasp __private {} + } + } +} + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/example_simple.tri b/trixy/trixy-lang_parser/example/multiple.tri similarity index 80% rename from trixy/trixy-lang_parser/example/example_simple.tri rename to trixy/trixy-lang_parser/example/multiple.tri index 8cdb691..a0d01ad 100644 --- a/trixy/trixy-lang_parser/example/example_simple.tri +++ b/trixy/trixy-lang_parser/example/multiple.tri @@ -3,7 +3,9 @@ fn print(message: CommandTransferValue); nasp trinitrix { fn hi(name: String) -> String; } -namespace commands { >- + +nasp trinitrix { + fn ho(name: String) -> String; } diff --git a/trixy/trixy-lang_parser/example/example.tri b/trixy/trixy-lang_parser/example/simple.tri similarity index 100% rename from trixy/trixy-lang_parser/example/example.tri rename to trixy/trixy-lang_parser/example/simple.tri diff --git a/trixy/trixy-lang_parser/generate_docs b/trixy/trixy-lang_parser/generate_docs new file mode 100755 index 0000000..e48d336 --- /dev/null +++ b/trixy/trixy-lang_parser/generate_docs @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + + + +ebnf2pdf "./docs/grammar.ebnf" +mv out.pdf ./docs/grammar.pdf + + +# vim: ft=sh diff --git a/trixy/trixy-lang_parser/src/command_spec/checked.rs b/trixy/trixy-lang_parser/src/command_spec/checked.rs new file mode 100644 index 0000000..c47bf73 --- /dev/null +++ b/trixy/trixy-lang_parser/src/command_spec/checked.rs @@ -0,0 +1,58 @@ +//! This module contains the already type checked types. +//! +//! + +use crate::lexing::{Keyword, TokenKind}; +pub enum PrimitiveTypes { + String, + /// Nothing + Void, +} + +impl From for Identifier { + fn from(value: TokenKind) -> Self { + match value { + TokenKind::Identifier(ident) => Identifier(ident), + TokenKind::Keyword(_) + | TokenKind::Colon + | TokenKind::Semicolon + | TokenKind::Comma + | TokenKind::Arrow + | TokenKind::BraceOpen + | TokenKind::BraceClose + | TokenKind::ParenOpen + | TokenKind::Dummy + | TokenKind::ParenClose => { + panic!("Tried to convert a non Identifier TokenKind to a Identefier. This is a bug") + } + } + } +} + +/// An Identifier +/// These include +/// - Variable names +/// - Function names +/// - Namespace names +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Identifier(String); + +impl From for Keyword { + fn from(value: TokenKind) -> Self { + match value { + TokenKind::Keyword(keyword) => keyword, + TokenKind::Identifier(_) + | TokenKind::Colon + | TokenKind::Semicolon + | TokenKind::Comma + | TokenKind::Arrow + | TokenKind::BraceOpen + | TokenKind::BraceClose + | TokenKind::ParenOpen + | TokenKind::Dummy + | TokenKind::ParenClose => { + panic!("Tried to convert a non Keyword TokenKind to a Keyword. This is a bug") + } + } + } +} diff --git a/trixy/trixy-lang_parser/src/command_spec/mod.rs b/trixy/trixy-lang_parser/src/command_spec/mod.rs index 2832a12..1bf868c 100644 --- a/trixy/trixy-lang_parser/src/command_spec/mod.rs +++ b/trixy/trixy-lang_parser/src/command_spec/mod.rs @@ -1,36 +1,2 @@ -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct CommandSpec { - pub(crate) declarations: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct Declaration { - pub(crate) namespace: Vec, - pub(crate) genus: Genus, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct Namespace { - pub(crate) name: String, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum Genus { - Function { - name: String, - inputs: Vec, - output: Type, - }, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct NamedType { - pub(crate) name: String, - pub(crate) base: Type, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum Type { - String, - Void, -} +pub mod checked; +pub mod unchecked; diff --git a/trixy/trixy-lang_parser/src/command_spec/unchecked.rs b/trixy/trixy-lang_parser/src/command_spec/unchecked.rs new file mode 100644 index 0000000..ec2fa66 --- /dev/null +++ b/trixy/trixy-lang_parser/src/command_spec/unchecked.rs @@ -0,0 +1,48 @@ +//! This module contains the not type checked types. +//! These are generated on the first pass of the parser, to be later converted into the checked +//! ones. + +use crate::lexing::Token; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandSpec { + pub declarations: Vec, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Declaration { + pub namespace: Vec, // Will later be turned into Namespace + pub genus: Genus, +} + +impl Declaration { + pub fn new_function(function: Function, namespace: Vec) -> Self { + Declaration { namespace, genus: Genus::Function(function) } + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Namespace { + pub name: Token, // Will later become an Identifier +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Genus { + /// Not actually a genus, but used in parsing to accommodate multiple errors + Dummy, + /// A function + Function(Function), +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Function { + pub identifier: Token, // Will later become an Identifier + pub inputs: Vec, + pub output: Option, // Will later become an Type +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct FunctionInput { + pub name: Token, // Will later become an Identifier + pub r#type: Token, // Will later become an Type +} diff --git a/trixy/trixy-lang_parser/src/error.rs b/trixy/trixy-lang_parser/src/error.rs index e0681a3..fcf441d 100644 --- a/trixy/trixy-lang_parser/src/error.rs +++ b/trixy/trixy-lang_parser/src/error.rs @@ -1,9 +1,191 @@ +use core::fmt; + use thiserror::Error; -use crate::lexing::error::SpannedLexingError; +use crate::lexing::{error::SpannedLexingError, TokenSpan}; #[derive(Error, Debug)] pub enum TrixyError { #[error(transparent)] Parsing(#[from] SpannedLexingError), } + +/// The context of an Error. +#[derive(Debug)] +pub struct ErrorContext { + /// The span of the error in the source file + pub span: TokenSpan, + /// The span of the error in the context line relative to the context line + pub contexted_span: TokenSpan, + /// The line above the error + pub line_above: String, + /// The line below the error + pub line_below: String, + /// The line in which the error occurred + pub line: String, + /// The line number of the main error line + pub line_number: usize, +} + +impl ErrorContext { + pub fn from_span(span: TokenSpan, original_file: &str) -> Self { + let line_number = original_file + .chars() + .take(span.start) + .filter(|a| a == &'\n') + .count() + // This is here, as we are missing one newline with the method above + + 1; + + let lines: Vec<_> = original_file.lines().collect(); + + let line = (*lines + .get(line_number - 1) + .expect("This should work, as have *at least* one (index = 0) line")) + .to_owned(); + + let contexted_span = { + let matched_line: Vec<_> = original_file.match_indices(&line).collect(); + let (index, matched_line) = matched_line.get(0).expect("This first index should always match, as we took the line from the string in the first place"); + debug_assert_eq!(matched_line, &&line); + TokenSpan { + start: span.start - index, + end: span.end - index, + } + }; + + let line_above; + if line_number == 0 { + // We only have one line, so no line above + line_above = "".to_owned(); + } else { + line_above = (*lines + .get((line_number - 1) - 1) + .expect("We checked that this should work")) + .to_owned(); + } + + let line_below; + if lines.len() - 1 > line_number { + // We have a line after the current line + line_below = (*lines + .get((line_number + 1) - 1) + .expect("We checked that this should work")) + .to_owned(); + } else { + line_below = "".to_owned(); + } + + Self { + span, + contexted_span, + line_above, + line_below, + line, + line_number, + } + } + + pub fn from_index(start: usize, orginal_file: &str) -> Self { + let span = TokenSpan { + start, + end: start, + }; + Self::from_span(span, orginal_file) + } + + pub fn get_error_line(&self, source_error: &str) -> String { + // deconstruct the structure + let ErrorContext { + contexted_span, + line_number, + .. + } = self; + + let mut output = String::new(); + output.push_str("\x1b[92;1m"); + + // pad to accommodate the line number printing. + // 32 -> needs two spaces padding to print it + line_number.to_string().chars().for_each(|_| { + output.push(' '); + }); + + // pad to the beginning of the error + for _ in 0..contexted_span.start { + output.push(' '); + } + + // push the error markers + for _ in contexted_span.start..contexted_span.end { + output.push('^'); + } + + // // pad until end of line + // for _ in contexted_span.end..(line.len() - 1) { + // output.push('-'); + // } + // + // additional space to avoid having to end with a '-' + output.push(' '); + + output.push_str("help: "); + + output.push_str(source_error); + output.push_str("\x1b[0m"); + output + } +} + +pub trait AdditionalHelp { + fn additional_help(&self) -> String; +} + +pub trait ErrorContextDisplay: fmt::Display { + type Error; + + fn error_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + where + ::Error: std::fmt::Display + AdditionalHelp, + { + let error_line = self + .context() + .get_error_line(&self.source().additional_help()); + + writeln!(f, "\x1b[31;1merror: \x1b[37;1m{}\x1b[0m", self.source())?; + + if !self.line_above().is_empty() { + writeln!( + f, + "\x1b[32;1m{} |\x1b[0m {}", + self.line_number() - 1, + self.line_above() + )?; + } + writeln!( + f, + "\x1b[36;1m{} |\x1b[0m {}", + self.line_number(), + self.line() + )?; + writeln!(f, " {}", error_line)?; + if !self.line_below().is_empty() { + writeln!( + f, + "\x1b[32;1m{} |\x1b[0m {}", + self.line_number() + 1, + self.line_below() + ) + } else { + write!(f, "") + } + } + + // getters + fn context(&self) -> &ErrorContext; + fn source(&self) -> &Self::Error; + fn line_number(&self) -> usize; + fn line_above(&self) -> &str; + fn line_below(&self) -> &str; + fn line(&self) -> &str; +} diff --git a/trixy/trixy-lang_parser/src/lexing/error.rs b/trixy/trixy-lang_parser/src/lexing/error.rs index 12177fb..98f3699 100644 --- a/trixy/trixy-lang_parser/src/lexing/error.rs +++ b/trixy/trixy-lang_parser/src/lexing/error.rs @@ -1,6 +1,8 @@ use std::{error::Error, fmt::Display}; use thiserror::Error; +use crate::error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}; + #[derive(Error, Debug)] pub enum LexingError { #[error("No matches were found")] @@ -13,88 +15,61 @@ pub enum LexingError { ExpectedArrow, } +impl AdditionalHelp for LexingError { + fn additional_help(& self) -> String { + let out = match self { + LexingError::NoMatchesTaken => "This token does not produce a possible match".to_owned(), + LexingError::UnexpectedEOF => "This eof was completely unexpected".to_owned(), + LexingError::ExpectedArrow => "The `-` token is interpretet as a started arrow (`->`), but we could not find the arrow tip (`>`)".to_owned(), + LexingError::UnknownCharacter(char) => { + format!("This char: `{char}`; is not a valid token") + }, + }; + out + } +} + #[derive(Debug)] -pub enum SpannedLexingError { - Error { - source: LexingError, - /// The starting char index of the error in the source file - start: usize, - /// The starting char index of the error in the context line - contexted_start: usize, - /// The line above the error - line_above: String, - /// The line below the error - line_below: String, - /// The line in which the error occurred - line: String, - /// The line number of the main error line - line_number: usize, - }, +pub struct SpannedLexingError { + pub source: LexingError, + pub context: ErrorContext, } impl Error for SpannedLexingError { fn source(&self) -> Option<&(dyn Error + 'static)> { - let Self::Error { source, .. } = self; - Some(source) + Some(&self.source) } } +impl ErrorContextDisplay for SpannedLexingError { + type Error = LexingError; + + fn context(&self) -> &crate::error::ErrorContext { + &self.context + } + + fn line_number(&self) -> usize { + self.context.line_number + } + + fn line_above(&self) -> &str { + &self.context.line_above + } + + fn line_below(&self) -> &str { + &self.context.line_below + } + + fn line(&self) -> &str { + &self.context.line + } + + fn source(&self) -> &::Error { + &self.source + } +} impl Display for SpannedLexingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self::Error { - source, - line_above, - line_below, - line, - line_number, - contexted_start, - .. - } = self; - let error_line = { - let mut output = String::new(); - output.push_str("\x1b[92;1m"); - for _ in 0..(*contexted_start) { - output.push(' '); - } - line_number.to_string().chars().for_each(|_| { - output.push(' '); - }); - output.push('^'); - for _ in *contexted_start..(line.len() - 1) { - output.push('-'); - } - output.push(' '); - let appandig_str = match source { - LexingError::NoMatchesTaken => "This token does not produce a possible match".to_owned(), - LexingError::UnexpectedEOF => "This eof was completely unexpected".to_owned(), - LexingError::UnknownCharacter(char) => format!("This char: `{char}`; is not a valid token"), - LexingError::ExpectedArrow => "The `-` token is interpretet as a started arrow (`->`), but we could not find the arrow tip (`>`)".to_owned(), - }; - output.push_str(&appandig_str); - output.push_str("\x1b[0m"); - output - }; - - writeln!(f, "\x1b[31;1merror: \x1b[37;1m{}\x1b[0m", source)?; - if !line_above.is_empty() { - writeln!( - f, - "\x1b[32;1m{} |\x1b[0m {}", - line_number - 1, - line_above - )?; - } - writeln!(f, "\x1b[36;1m{} |\x1b[0m {}", line_number, line)?; - writeln!(f, " {}", error_line)?; - if !line_below.is_empty() { - writeln!( - f, - "\x1b[32;1m{} |\x1b[0m {}", - line_number + 1, - line_below - ) - } else { - write!(f, "") - } + self.error_fmt(f) } } diff --git a/trixy/trixy-lang_parser/src/lexing/mod.rs b/trixy/trixy-lang_parser/src/lexing/mod.rs index d601cb6..989ec8b 100644 --- a/trixy/trixy-lang_parser/src/lexing/mod.rs +++ b/trixy/trixy-lang_parser/src/lexing/mod.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use self::{error::SpannedLexingError, tokenizer::Tokenizer}; pub mod error; @@ -8,7 +10,7 @@ mod test; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] pub struct TokenStream { - original_file: String, + pub original_file: String, tokens: Vec, } @@ -31,34 +33,83 @@ impl TokenStream { original_file: src.to_owned(), }) } + + /// Get a token by index + pub fn get(&self, index: usize) -> Option<&Token> { + self.tokens.get(index) + } + + /// Get a reference to the uppermost token, without modifying the token list + pub fn peek(&self) -> &Token { + self.tokens.last().expect("This should not be emtpy") + } + + /// Remove to the uppermost token + pub fn pop(&mut self) -> Token { + self.tokens.pop().expect("This should not be emtpy") + } + + /// Reverses the underlying tokes vector + /// This is facilitates using the pop and peek methods to parse the tokens from the beginning, + /// not the end + pub fn reverse(&mut self) { + self.tokens.reverse() + } + + /// Check if the TokenStream is empty. + pub fn is_empty(&self) -> bool { + self.tokens.is_empty() + } } /// A token span is recorded in chars starting from the beginning of the file: /// A token span like this, for example: -/// ```no_run +/// ```dont_run +///# use trixy_lang_parser::lexing::TokenSpan; /// TokenSpan { /// start: 20, /// end: 23, /// } /// ``` /// signals, that the token starts at the 20th char in the source file and ends on the 23rd. -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)] pub struct TokenSpan { - start: usize, /// The start of the token span - end: usize, + pub start: usize, + /// The end of the token span + pub end: usize, } /// A Token -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)] pub struct Token { /// The token's original location in the source file - span: TokenSpan, - kind: TokenKind, + pub span: TokenSpan, + pub kind: TokenKind, +} + +impl Token { + /// Return the TokenKind of a token + pub fn kind(&self) -> &TokenKind { + &self.kind + } + + /// Return the TokenSpan of a token + pub fn span(&self) -> &TokenSpan { + &self.span + } + + /// Get a dummy token, this is intended for error handling + pub fn get_dummy() -> Token { + Self { + span: TokenSpan { start: 0, end: 0 }, + kind: TokenKind::Dummy, + } + } } /// Possibly kinds of tokens -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum TokenKind { Keyword(Keyword), Identifier(String), @@ -68,12 +119,49 @@ pub enum TokenKind { Arrow, BraceOpen, BraceClose, - ParenthesisOpen, - ParenthesisClose, + ParenOpen, + ParenClose, + /// This is not a real TokenKind, but only used for error handling + Dummy, +} + +impl TokenKind { + pub fn same_kind(&self, other: &TokenKind) -> bool { + if let TokenKind::Identifier(_) = self { + if let TokenKind::Identifier(_) = other { + return true; + } + } + self == other + } +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TokenKind::Keyword(word) => write!(f, "KEYWORD({})", word), + TokenKind::Identifier(ident) => { + if ident == "" { + write!(f, "IDENTIFIER") + } else { + write!(f, "IDENTIFIER({})", ident) + } + } + TokenKind::Colon => f.write_str("COLON"), + TokenKind::Semicolon => f.write_str("SEMICOLON"), + TokenKind::Comma => f.write_str("COMMA"), + TokenKind::Arrow => f.write_str("ARROW"), + TokenKind::BraceOpen => f.write_str("BRACEOPEN"), + TokenKind::BraceClose => f.write_str("BRACECLOSE"), + TokenKind::ParenOpen => f.write_str("PARENOPEN"), + TokenKind::ParenClose => f.write_str("PARENCLOSE"), + TokenKind::Dummy => f.write_str("DUMMY"), + } + } } /// Keywords used in the language -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)] pub enum Keyword { /// Start a namespace declaration #[allow(non_camel_case_types)] @@ -82,3 +170,85 @@ pub enum Keyword { #[allow(non_camel_case_types)] r#fn, } + +impl Display for Keyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Keyword::nasp => f.write_str("nasp"), + Keyword::r#fn => f.write_str("fn"), + } + } +} + +/// Shorthand macro for generating a token from *anything* which can be +/// converted into a `TokenKind`, or any of the `TokenKind` variants. +/// +/// # Examples +/// +/// ``` +/// use trixy_lang_parser::token; +/// # fn main() { +/// token![nasp]; +/// token![;]; +/// token![Arrow]; +/// # } +/// ``` +#[macro_export] +macro_rules! token { + [Semicolon] => { $crate::lexing::TokenKind::Semicolon }; + [;] => { $crate::lexing::TokenKind::Semicolon }; + [Colon] => { $crate::lexing::TokenKind::Colon }; + [:] => { $crate::lexing::TokenKind::Colon }; + [Comma] => { $crate::lexing::TokenKind::Comma }; + [,] => { $crate::lexing::TokenKind::Comma }; + [Arrow] => { $crate::lexing::TokenKind::Arrow }; + [->] => { $crate::lexing::TokenKind::Arrow }; + [BraceOpen] => { $crate::lexing::TokenKind::BraceOpen }; + // [{] => { $crate::lexing::TokenKind::BraceOpen }; + [BraceClose] => { $crate::lexing::TokenKind::BraceClose }; + // [}] => { $crate::lexing::TokenKind::BraceClose }; + [ParenOpen] => { $crate::lexing::TokenKind::ParenOpen }; + // [(] => { $crate::lexing::TokenKind::ParenthesisOpen }; + [ParenClose] => { $crate::lexing::TokenKind::ParenClose }; + // [)] => { $crate::lexing::TokenKind::ParenthesisClose }; + + [nasp] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::nasp) }; + [fn] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#fn) }; + + // This is only works for checking for a identifier + // see the `same_kind` method on TokenKind + [Ident] => { $crate::lexing::TokenKind::Identifier("".to_owned()) }; + [Identifier] => { $crate::lexing::TokenKind::Identifier("".to_owned()) }; +} + +#[cfg(test)] +mod tests { + use super::TokenKind; + use crate::token; + + macro_rules! token_macro_test { + ($name:ident, $from:tt, => $to:expr) => { + #[test] + fn $name() { + let got: TokenKind = token![$from]; + let should_be = $to; + + assert_eq!(got, should_be); + } + }; + ($name:ident, $from:tt, => $to:expr) => { + #[test] + fn $name() { + let got: TokenKind = token![$from]; + let should_be = $to; + + assert_eq!(got, should_be); + } + }; + } + + token_macro_test!(tok_expands_to_arrow, ->, => TokenKind::Arrow); + token_macro_test!(tok_expands_to_semicolon, Semicolon, => TokenKind::Semicolon); + token_macro_test!(tok_expands_to_nasp, nasp, => TokenKind::Keyword(crate::lexing::Keyword::nasp)); + token_macro_test!(tok_expands_to_fn, fn, => TokenKind::Keyword(crate::lexing::Keyword::r#fn)); +} diff --git a/trixy/trixy-lang_parser/src/lexing/test.rs b/trixy/trixy-lang_parser/src/lexing/test.rs index 43665ad..396f1cb 100644 --- a/trixy/trixy-lang_parser/src/lexing/test.rs +++ b/trixy/trixy-lang_parser/src/lexing/test.rs @@ -36,7 +36,7 @@ nasp commands { }, Token { span: TokenSpan { start: 30, end: 31 }, - kind: TokenKind::ParenthesisOpen, + kind: TokenKind::ParenOpen, }, Token { span: TokenSpan { start: 31, end: 36 }, @@ -52,7 +52,7 @@ nasp commands { }, Token { span: TokenSpan { start: 44, end: 45 }, - kind: TokenKind::ParenthesisClose, + kind: TokenKind::ParenClose, }, Token { span: TokenSpan { start: 46, end: 48 }, diff --git a/trixy/trixy-lang_parser/src/lexing/tokenizer.rs b/trixy/trixy-lang_parser/src/lexing/tokenizer.rs index 67986e1..af46d43 100644 --- a/trixy/trixy-lang_parser/src/lexing/tokenizer.rs +++ b/trixy/trixy-lang_parser/src/lexing/tokenizer.rs @@ -1,6 +1,9 @@ // This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html -use crate::lexing::{Keyword, TokenSpan}; +use crate::{ + error::ErrorContext, + lexing::{Keyword, TokenSpan}, +}; use super::{ error::{LexingError, SpannedLexingError}, @@ -29,61 +32,11 @@ impl<'a> Tokenizer<'a> { let start = self.current_index; let (token_kind, index) = self.get_next_tokenkind().map_err(|e| { - let (line_above, line, line_below, contexted_start, line_number) = { - let line_number = self - .original_text - .chars() - .take(start) - .filter(|a| a == &'\n') - .count(); - let lines: Vec<_> = self.original_text.lines().collect(); + let context = ErrorContext::from_index(start, self.original_text); - let line = (*lines - .get(line_number) - .expect("This should work, as have *at least* one (index = 0) line")) - .to_owned(); - - let contexted_start = { - let matched_line: Vec<_> = self.original_text.match_indices(&line).collect(); - let (index, matched_line) = matched_line.get(0).expect("This first index should always match, as we took the line from the string in the first place"); - debug_assert_eq!(matched_line, &&line); - start - index - }; - - let line_above; - if line_number == 0 { - // We only have one line, so no line above - line_above = "".to_owned(); - } else { - line_above = (*lines - .get(line_number - 1) - .expect("We checked that this should work")) - .to_owned(); - } - - let line_below; - if lines.len() - 1 > line_number { - // We have a line after the current line - line_below = (*lines - .get(line_number + 1) - .expect("We checked that this should work")) - .to_owned(); - } else { - line_below = "".to_owned(); - } - - (line_above, line, line_below, contexted_start, line_number) - }; - SpannedLexingError::Error { - source: e, - start, - contexted_start, - line_above, - line_below, - line_number, - line, - } + SpannedLexingError { source: e, context } })?; + self.chomp(index); // end - start let end = self.current_index; Ok(Some(Token { @@ -100,8 +53,8 @@ impl<'a> Tokenizer<'a> { }; let (tok, length) = match next { - '(' => (TokenKind::ParenthesisOpen, 1), - ')' => (TokenKind::ParenthesisClose, 1), + '(' => (TokenKind::ParenOpen, 1), + ')' => (TokenKind::ParenClose, 1), '{' => (TokenKind::BraceOpen, 1), '}' => (TokenKind::BraceClose, 1), ':' => (TokenKind::Colon, 1), diff --git a/trixy/trixy-lang_parser/src/lib.rs b/trixy/trixy-lang_parser/src/lib.rs index 1167aea..247fa8b 100644 --- a/trixy/trixy-lang_parser/src/lib.rs +++ b/trixy/trixy-lang_parser/src/lib.rs @@ -2,11 +2,12 @@ use error::TrixyError; use crate::lexing::TokenStream; -use self::command_spec::CommandSpec; +use self::command_spec::unchecked::CommandSpec; mod command_spec; pub mod error; pub mod lexing; +pub mod parsing; pub fn parse_trixy_lang(input: &str) -> Result { let input_tokens = TokenStream::lex(input)?; @@ -14,45 +15,45 @@ pub fn parse_trixy_lang(input: &str) -> Result { todo!() } -#[cfg(test)] -mod test { - use crate::{ - command_spec::{CommandSpec, Declaration, Genus, NamedType, Namespace, Type}, - parse_trixy_lang, - }; - - #[test] - fn test_function_with_namespace() { - let expected = parse_trixy_lang( - " - nasp commands { - fn say_something(name_to_greet: String, what_to_say: String) -> String; - } - ", - ) - .unwrap(); - let correct: CommandSpec = { - let declarations = vec![Declaration { - namespace: vec![Namespace { - name: "commands".to_owned(), - }], - genus: Genus::Function { - name: "say_something".to_owned(), - inputs: vec![ - NamedType { - name: "name_to_greet".to_owned(), - base: Type::String, - }, - NamedType { - name: "what_to_say".to_owned(), - base: Type::String, - }, - ], - output: Type::String, - }, - }]; - CommandSpec { declarations } - }; - assert_eq!(expected, correct); - } -} +// #[cfg(test)] +// mod test { +// use crate::{ +// command_spec::unchecked::{CommandSpec, Declaration, Genus, Namespace}, +// parse_trixy_lang, +// }; +// +// #[test] +// fn test_function_with_namespace() { +// let expected = parse_trixy_lang( +// " +// nasp commands { +// fn say_something(name_to_greet: String, what_to_say: String) -> String; +// } +// ", +// ) +// .unwrap(); +// let correct: CommandSpec = { +// let declarations = vec![Declaration { +// namespace: vec![Namespace { +// name: "commands".to_owned(), +// }], +// genus: Genus::Function { +// name: "say_something".to_owned(), +// inputs: vec![ +// NamedType { +// name: "name_to_greet".to_owned(), +// base: Type::String, +// }, +// NamedType { +// name: "what_to_say".to_owned(), +// base: Type::String, +// }, +// ], +// output: Type::String, +// }, +// }]; +// CommandSpec { declarations } +// }; +// assert_eq!(expected, correct); +// } +// } diff --git a/trixy/trixy-lang_parser/src/main.rs b/trixy/trixy-lang_parser/src/main.rs index c6f8104..277fb76 100644 --- a/trixy/trixy-lang_parser/src/main.rs +++ b/trixy/trixy-lang_parser/src/main.rs @@ -23,6 +23,11 @@ pub enum Command { /// The file containing the trixy code to tokenize file: PathBuf, }, + Parse { + #[clap(value_parser)] + /// The file containing the trixy code to parse + file: PathBuf, + }, } pub fn main() { @@ -34,12 +39,34 @@ pub fn main() { let input_tokens = match TokenStream::lex(&input) { Ok(err) => err, Err(ok) => { - println!("{}", ok); + eprintln!("{}", ok); exit(1); } }; println!("{:#?}", input_tokens); } + Command::Parse { file } => { + let input = fs::read_to_string(file).unwrap(); + + let input_tokens = match TokenStream::lex(&input) { + Ok(ok) => ok, + Err(err) => { + eprintln!("Error while tokenizing:"); + eprintln!("{}", err); + exit(1); + } + }; + + let parsed = match input_tokens.parse_unchecked() { + Ok(ok) => ok, + Err(err) => { + eprintln!("Error while doing the first (unchecked) parsing run:"); + eprintln!("{}", err); + exit(1) + } + }; + println!("{:#?}", parsed); + } } } diff --git a/trixy/trixy-lang_parser/src/parsing/error.rs b/trixy/trixy-lang_parser/src/parsing/error.rs new file mode 100644 index 0000000..a6036e3 --- /dev/null +++ b/trixy/trixy-lang_parser/src/parsing/error.rs @@ -0,0 +1,93 @@ +use std::{error::Error, fmt::Display}; +use thiserror::Error; + +use crate::{ + error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}, + lexing::{TokenKind, TokenSpan}, +}; + +#[derive(Error, Debug)] +pub enum ParsingError { + #[error("Expected '{expected}' but received '{actual}'")] + ExpectedDifferentToken { + expected: TokenKind, + actual: TokenKind, + span: TokenSpan, + }, + + #[error("Expected a Keyword to start a new declaration, but found: '{actual}'")] + ExpectedKeyword { actual: TokenKind, span: TokenSpan }, +} + +impl ParsingError { + pub fn get_span(&self) -> TokenSpan { + match self { + ParsingError::ExpectedDifferentToken { span, .. } => *span, + ParsingError::ExpectedKeyword { span, .. } => *span, + } + } +} + +impl AdditionalHelp for ParsingError { + fn additional_help(&self) -> String { + match self { + ParsingError::ExpectedDifferentToken { + expected, + actual, + .. + } => format!( + "I expected a '{}' here, but you put a '{}' there!", + expected, actual + ), + ParsingError::ExpectedKeyword { actual, .. } => format!( + "I expected a keyword (that is something like 'fn' or 'nasp') but you put a '{}' there!", + actual), + } + } +} + +#[derive(Debug)] +pub struct SpannedParsingError { + pub source: ParsingError, + pub context: ErrorContext, +} + +impl Error for SpannedParsingError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} + +impl Display for SpannedParsingError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.error_fmt(f) + } +} + +impl ErrorContextDisplay for SpannedParsingError { + type Error = ParsingError; + + fn context(&self) -> &crate::error::ErrorContext { + &self.context + } + + fn line_number(&self) -> usize { + self.context.line_number + } + + fn line_above(&self) -> &str { + &self.context.line_above + } + + fn line_below(&self) -> &str { + &self.context.line_below + } + + fn line(&self) -> &str { + &self.context.line + } + + fn source(&self) -> &::Error { + &self.source + } +} diff --git a/trixy/trixy-lang_parser/src/parsing/mod.rs b/trixy/trixy-lang_parser/src/parsing/mod.rs new file mode 100644 index 0000000..435b2bc --- /dev/null +++ b/trixy/trixy-lang_parser/src/parsing/mod.rs @@ -0,0 +1,4 @@ +mod error; +mod unchecked; +#[cfg(test)] +mod test; diff --git a/trixy/trixy-lang_parser/src/parsing/test.rs b/trixy/trixy-lang_parser/src/parsing/test.rs new file mode 100644 index 0000000..2f73978 --- /dev/null +++ b/trixy/trixy-lang_parser/src/parsing/test.rs @@ -0,0 +1,88 @@ +use crate::{ + command_spec::unchecked::{CommandSpec, Declaration, Function, FunctionInput, Genus}, + lexing::{Token, TokenKind, TokenSpan, TokenStream}, +}; + +use super::error::ParsingError; + +use pretty_assertions::assert_eq; + +#[test] +fn test_failing() { + let input = " +fn print(message: CommandTransferValue); + +nasp trinitrix { {} + fn hi honner(name: String) -> String; ; +} + +"; + let parsed = TokenStream::lex(input).unwrap().parse_unchecked(); + let err = parsed.unwrap_err().source; + match err { + ParsingError::ExpectedDifferentToken { .. } => panic!("Wrong error"), + ParsingError::ExpectedKeyword { .. } => {} + } +} + +#[test] +fn test_full() { + let input = "fn print(message: CommandTransferValue); + +nasp trinitrix { + fn hi(name: String) -> String; +} +"; + let parsed = TokenStream::lex(input).unwrap().parse_unchecked().unwrap(); + let expected = CommandSpec { + declarations: vec![ + Declaration { + namespace: vec![], + genus: Genus::Function(Function { + identifier: Token { + span: TokenSpan { start: 3, end: 8 }, + kind: TokenKind::Identifier("print".to_owned()), + }, + inputs: vec![FunctionInput { + name: Token { + span: TokenSpan { start: 9, end: 16 }, + kind: TokenKind::Identifier("message".to_owned()), + }, + r#type: Token { + span: TokenSpan { start: 18, end: 38 }, + kind: TokenKind::Identifier("CommandTransferValue".to_owned()), + }, + }], + output: None, + }), + }, + Declaration { + namespace: vec![Token { + span: TokenSpan { start: 47, end: 56 }, + kind: TokenKind::Identifier("trinitrix".to_owned()), + }], + genus: Genus::Function(Function { + identifier: Token { + span: TokenSpan { start: 66, end: 68 }, + kind: TokenKind::Identifier("hi".to_owned()), + }, + inputs: vec![FunctionInput { + name: Token { + span: TokenSpan { start: 69, end: 73 }, + kind: TokenKind::Identifier("name".to_owned()), + }, + r#type: Token { + span: TokenSpan { start: 75, end: 81 }, + kind: TokenKind::Identifier("String".to_owned()), + }, + }], + output: Some(Token { + span: TokenSpan { start: 86, end: 92 }, + kind: TokenKind::Identifier("String".to_owned()), + }), + }), + }, + ], + }; + assert_eq!(parsed, expected); +} diff --git a/trixy/trixy-lang_parser/src/parsing/unchecked.rs b/trixy/trixy-lang_parser/src/parsing/unchecked.rs new file mode 100644 index 0000000..9d12a9e --- /dev/null +++ b/trixy/trixy-lang_parser/src/parsing/unchecked.rs @@ -0,0 +1,167 @@ +use crate::{ + command_spec::unchecked::{CommandSpec, Declaration, Function, FunctionInput}, + error::ErrorContext, + lexing::{Token, TokenKind, TokenStream}, + token, +}; + +use super::error::{ParsingError, SpannedParsingError}; + +impl TokenStream { + pub fn parse_unchecked(self) -> Result { + let mut parser = Parser::new(self); + parser.parse() + } +} + +pub(super) struct Parser { + token_stream: TokenStream, + current_namespaces: Vec, // This should in the second pass turn into Identifiers +} + +impl Parser { + fn new(mut token_stream: TokenStream) -> Self { + token_stream.reverse(); + Self { + token_stream, + current_namespaces: vec![], + } + } + + fn parse(&mut self) -> Result { + let mut declarations = vec![]; + while !self.token_stream.is_empty() { + let mut next = self.parse_next().map_err(|err| { + let span = err.get_span(); + SpannedParsingError { + source: err, + context: ErrorContext::from_span(span, &self.token_stream.original_file), + } + })?; + + declarations.append(&mut next); + } + + Ok(CommandSpec { declarations }) + } + + fn parse_next(&mut self) -> Result, ParsingError> { + match self.peek().kind() { + token![nasp] => Ok(self.parse_namespace()?), + token![fn] => Ok(vec![Declaration::new_function( + self.parse_function()?, + self.current_namespaces.clone(), + )]), + _ => { + let err = ParsingError::ExpectedKeyword { + span: *self.peek().span(), + actual: self.peek().kind().clone(), + }; + + return Err(err); + } + } + } + + fn parse_namespace(&mut self) -> Result, ParsingError> { + self.expect(token![nasp])?; + let namespace_name = self.expect(token![Ident])?; + self.current_namespaces.push(namespace_name); + self.expect(token![BraceOpen])?; + + let mut declarations = vec![]; + while !self.expect_peek(token![BraceClose]) { + declarations.append(&mut self.parse_next()?); + } + + self.expect(token![BraceClose])?; + self.current_namespaces.pop(); + Ok(declarations) + } + + fn parse_function(&mut self) -> Result { + self.expect(token![fn])?; + let name = self.expect(token![Ident])?; + self.expect(token![ParenOpen])?; + let mut inputs = vec![]; + + while self.expect_peek(token![Ident]) { + let input_name = self.expect(token![Ident])?; + self.expect(token![Colon])?; + let input_type = self.expect(token![Ident])?; + inputs.push(FunctionInput { + name: input_name, + r#type: input_type, + }) + } + + self.expect(token![ParenClose])?; + let mut output_type = None; + if self.expect_peek(token![->]) { + self.expect(token![->])?; + output_type = Some(self.expect(token![Ident])?); + } + self.expect(token![;])?; + Ok(Function { + identifier: name, + inputs, + output: output_type, + }) + } + + /// Expect a token in the next input position: + /// For example: + /// + /// ```dont_run + /// use trixy_lang_parser::{ + /// lexing::{Keyword, TokenKind, TokenStream}, + /// parsing::unchecked::Parser, + /// token, + /// }; + /// + /// # fn main() { + /// let token_stream = TokenStream::lex("nasp {}").unwrap(); + /// let parser = Parser::new(token_stream); + /// assert_eq!(parser.expect(token![nasp]).unwrap(), TokenKind::Keyword(Keyword::nasp)); + /// assert_eq!(parser.expect(token![BraceOpen]).unwrap(), TokenKind::BraceOpen); + /// assert_eq!(parser.expect(token![BraceClose]).unwrap(), TokenKind::BraceClose); + /// assert!(parser.expect(token![BraceClose]).is_err()); + /// # } + /// ``` + /// + pub(super) fn expect(&mut self, token: TokenKind) -> Result { + let actual_token = self.peek(); + if actual_token.kind().same_kind(&token) { + Ok(self.pop()) + } else { + let err = ParsingError::ExpectedDifferentToken { + expected: token, + actual: actual_token.kind().clone(), + span: *actual_token.span(), + }; + + Err(err) + } + } + + /// Check if the next token is of the specified TokenKind. + /// Does not alter the token_stream + fn expect_peek(&self, token: TokenKind) -> bool { + let actual_token = self.peek(); + if actual_token.kind().same_kind(&token) { + true + } else { + false + } + } + + /// Looks at the next token without removing it + fn peek(&self) -> &Token { + self.token_stream.peek() + } + + /// Removes the next token + fn pop(&mut self) -> Token { + self.token_stream.pop() + } +}