Add some basic error handling.
TODO: This, better
This commit is contained in:
parent
d1d8c45bdb
commit
8f8cfd4cb5
182
src/lib.rs
182
src/lib.rs
@ -1,5 +1,4 @@
|
|||||||
use std::{iter::Peekable, str::CharIndices};
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
use unicode_ident::*;
|
use unicode_ident::*;
|
||||||
|
|
||||||
/// Rule = ident '=' Either? ';' ;
|
/// Rule = ident '=' Either? ';' ;
|
||||||
@ -94,6 +93,9 @@ impl<'a> Parser<'a> {
|
|||||||
tail: 0,
|
tail: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn error(&mut self, kind: ErrorKind) -> error::Error {
|
||||||
|
error::Error::new(self.head, self.tail, kind)
|
||||||
|
}
|
||||||
pub fn start(&mut self) -> &mut Self {
|
pub fn start(&mut self) -> &mut Self {
|
||||||
self.space();
|
self.space();
|
||||||
self.head = self.tail;
|
self.head = self.tail;
|
||||||
@ -105,8 +107,11 @@ impl<'a> Parser<'a> {
|
|||||||
} = self;
|
} = self;
|
||||||
&text[head..tail]
|
&text[head..tail]
|
||||||
}
|
}
|
||||||
pub fn peek(&mut self) -> Option<char> {
|
pub fn peek(&mut self) -> Result<char> {
|
||||||
self.chars.peek().map(|(_, c)| *c)
|
self.chars
|
||||||
|
.peek()
|
||||||
|
.map(|(_, c)| *c)
|
||||||
|
.ok_or_else(|| self.error(ErrorKind::EndOfInput))
|
||||||
}
|
}
|
||||||
pub fn take(&mut self) -> Option<(usize, char)> {
|
pub fn take(&mut self) -> Option<(usize, char)> {
|
||||||
let out = self.chars.next();
|
let out = self.chars.next();
|
||||||
@ -116,14 +121,19 @@ impl<'a> Parser<'a> {
|
|||||||
};
|
};
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
pub fn take_one(&mut self, f: fn(char) -> bool) -> Option<&mut Self> {
|
pub fn take_one(&mut self, f: fn(char) -> bool) -> Result<&mut Self> {
|
||||||
self.chars.peek().filter(|(_, c)| f(*c)).is_some().then(|| {
|
let Some(&(_, c)) = self.chars.peek() else {
|
||||||
|
Err(self.error(ErrorKind::EndOfInput))?
|
||||||
|
};
|
||||||
|
if f(c) {
|
||||||
self.take();
|
self.take();
|
||||||
self
|
Ok(self)
|
||||||
})
|
} else {
|
||||||
|
Err(self.error(ErrorKind::Unexpected(c)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn take_many(&mut self, f: fn(char) -> bool) -> &mut Self {
|
pub fn take_many(&mut self, f: fn(char) -> bool) -> &mut Self {
|
||||||
while self.take_one(f).is_some() {}
|
while self.take_one(f).is_ok() {}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn space(&mut self) -> &mut Self {
|
pub fn space(&mut self) -> &mut Self {
|
||||||
@ -132,9 +142,9 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
pub fn rule(&mut self) -> Option<Rule<'a>> {
|
pub fn rule(&mut self) -> Result<Rule<'a>> {
|
||||||
let out = Rule {
|
let out = Rule {
|
||||||
comment: self.comment(),
|
comment: self.comment().ok(),
|
||||||
name: self.ident()?,
|
name: self.ident()?,
|
||||||
body: {
|
body: {
|
||||||
self.space()
|
self.space()
|
||||||
@ -143,102 +153,91 @@ impl<'a> Parser<'a> {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if self.space().take_one(|c| ';' == c).is_none() {
|
self.space().take_one(|c| ';' == c).map(|_| out)
|
||||||
panic!("Rule should end in ';': {}..{}", self.head, self.tail)
|
|
||||||
}
|
|
||||||
Some(out)
|
|
||||||
}
|
}
|
||||||
pub fn either(&mut self) -> Option<RuleKind<'a>> {
|
pub fn either(&mut self) -> Result<RuleKind<'a>> {
|
||||||
let mut out = vec![self.follow()?];
|
let mut out = vec![self.follow()?];
|
||||||
while self.space().take_one(|c| '|' == c).is_some() {
|
while self.space().take_one(|c| '|' == c).is_ok() {
|
||||||
out.push(self.follow()?)
|
out.push(self.follow()?)
|
||||||
}
|
}
|
||||||
match out.len() {
|
Ok(match out.len() {
|
||||||
1 => out.pop(),
|
1 => out.pop().expect("pop should succeed when length is 1"),
|
||||||
_ => Some(RuleKind::Either(out)),
|
_ => RuleKind::Either(out),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
pub fn follow(&mut self) -> Option<RuleKind<'a>> {
|
pub fn follow(&mut self) -> Result<RuleKind<'a>> {
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
while let Some(rule) = self.repeat() {
|
while let Ok(rule) = self.repeat() {
|
||||||
out.push(rule)
|
out.push(rule)
|
||||||
}
|
}
|
||||||
match out.len() {
|
Ok(match out.len() {
|
||||||
1 => out.pop(),
|
1 => out.pop().expect("pop should succeed when length is 1"),
|
||||||
_ => Some(RuleKind::Follow(out)),
|
_ => RuleKind::Follow(out),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
pub fn repeat(&mut self) -> Option<RuleKind<'a>> {
|
pub fn repeat(&mut self) -> Result<RuleKind<'a>> {
|
||||||
let out = self.not()?;
|
let out = self.not()?;
|
||||||
let out = match self.space().peek() {
|
let out = match self.space().peek() {
|
||||||
Some('*') => RuleKind::Any(out.into()),
|
Ok('*') => RuleKind::Any(out.into()),
|
||||||
Some('+') => RuleKind::Many(out.into()),
|
Ok('+') => RuleKind::Many(out.into()),
|
||||||
Some('?') => RuleKind::Maybe(out.into()),
|
Ok('?') => RuleKind::Maybe(out.into()),
|
||||||
_ => return Some(out),
|
_ => return Ok(out),
|
||||||
};
|
};
|
||||||
self.take();
|
self.take();
|
||||||
Some(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
pub fn not(&mut self) -> Option<RuleKind<'a>> {
|
pub fn not(&mut self) -> Result<RuleKind<'a>> {
|
||||||
match self.space().take_one(|c| '!' == c) {
|
match self.space().take_one(|c| '!' == c) {
|
||||||
Some(_) => Some(RuleKind::Not(self.prime()?.into())),
|
Ok(_) => Ok(RuleKind::Not(self.prime()?.into())),
|
||||||
_ => self.prime(),
|
_ => self.prime(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn prime(&mut self) -> Option<RuleKind<'a>> {
|
pub fn prime(&mut self) -> Result<RuleKind<'a>> {
|
||||||
Some(match self.space().peek()? {
|
Ok(match self.space().peek()? {
|
||||||
'(' => return self.group(),
|
'(' => return self.group(),
|
||||||
'"' => RuleKind::Str(self.str()?),
|
'"' => RuleKind::Str(self.str()?),
|
||||||
'\'' => RuleKind::Chr(self.chr()?),
|
'\'' => RuleKind::Chr(self.chr()?),
|
||||||
_ => RuleKind::Ident(self.ident()?),
|
_ => RuleKind::Ident(self.ident()?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn group(&mut self) -> Option<RuleKind<'a>> {
|
pub fn group(&mut self) -> Result<RuleKind<'a>> {
|
||||||
self.take_one(|c| '(' == c)?;
|
self.take_one(|c| '(' == c)?;
|
||||||
let out = self.either()?;
|
let out = self.either()?;
|
||||||
if self.take_one(|c| ')' == c).is_none() {
|
self.take_one(|c| ')' == c)
|
||||||
panic!("Groups should have terminating ')': {}", self.tail)
|
.map(|_| RuleKind::Group(out.into()))
|
||||||
}
|
|
||||||
Some(RuleKind::Group(out.into()))
|
|
||||||
}
|
}
|
||||||
pub fn ident(&mut self) -> Option<&'a str> {
|
pub fn ident(&mut self) -> Result<&'a str> {
|
||||||
self.start().take_one(is_xid_start)?;
|
self.start().take_one(is_xid_start)?;
|
||||||
self.take_many(is_xid_continue);
|
self.take_many(is_xid_continue);
|
||||||
Some(self.fragment())
|
Ok(self.fragment())
|
||||||
}
|
}
|
||||||
pub fn chr(&mut self) -> Option<&'a str> {
|
pub fn chr(&mut self) -> Result<&'a str> {
|
||||||
self.space().take_one(|c| '\'' == c)?;
|
self.space().take_one(|c| '\'' == c)?;
|
||||||
self.start().take_many(|c| '\'' != c);
|
self.start().take_many(|c| '\'' != c);
|
||||||
let out = self.fragment();
|
let out = self.fragment();
|
||||||
if self.take_one(|c| '\'' == c).is_none() {
|
self.take_one(|c| '\'' == c).map(|_| out)
|
||||||
panic!("chr should have terminating '\'': {}", self.tail)
|
|
||||||
}
|
|
||||||
Some(out)
|
|
||||||
}
|
}
|
||||||
pub fn str(&mut self) -> Option<&'a str> {
|
pub fn str(&mut self) -> Result<&'a str> {
|
||||||
self.space().take_one(|c| '\"' == c)?;
|
self.space().take_one(|c| '\"' == c)?;
|
||||||
self.start().take_many(|c| '\"' != c);
|
self.start().take_many(|c| '\"' != c);
|
||||||
let out = self.fragment();
|
let out = self.fragment();
|
||||||
if self.take_one(|c| '\"' == c).is_none() {
|
self.take_one(|c| '"' == c).map(|_| out)
|
||||||
panic!("str should have terminating '\"': {}", self.tail)
|
|
||||||
}
|
|
||||||
Some(out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn comment(&mut self) -> Option<&'a str> {
|
pub fn comment(&mut self) -> Result<&'a str> {
|
||||||
let start = self.tail;
|
let start = self.tail;
|
||||||
while self.space().take_one(|c| '(' == c).is_some() {
|
while self.space().take_one(|c| '(' == c).is_ok() {
|
||||||
self.take_one(|c| '*' == c)?;
|
self.take_one(|c| '*' == c)?;
|
||||||
while let Some(c) = self.peek() {
|
loop {
|
||||||
match c {
|
match self.peek()? {
|
||||||
'*' => {
|
'*' => {
|
||||||
self.take_one(|c| '*' == c)?;
|
self.take_one(|c| '*' == c)?;
|
||||||
if self.take_one(|c| ')' == c).is_some() {
|
if self.take_one(|c| ')' == c).is_ok() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'(' => {
|
'(' => {
|
||||||
self.comment();
|
let _ = self.comment();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.take();
|
self.take();
|
||||||
@ -247,6 +246,69 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let out = &self.text[start..self.tail];
|
let out = &self.text[start..self.tail];
|
||||||
(out.len() > 1).then_some(out)
|
if out.len() < 2 {
|
||||||
|
Ok(out)
|
||||||
|
} else {
|
||||||
|
Err(self.error(ErrorKind::NoComment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use error::{ErrorKind, Result};
|
||||||
|
pub mod error {
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
place: (usize, usize),
|
||||||
|
kind: ErrorKind,
|
||||||
|
}
|
||||||
|
impl Error {
|
||||||
|
pub fn new(start: usize, end: usize, kind: ErrorKind) -> Self {
|
||||||
|
Self {
|
||||||
|
place: (start, end),
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_end(&self) -> bool {
|
||||||
|
matches!(self.kind, ErrorKind::EndOfInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Self {
|
||||||
|
place: (start, end),
|
||||||
|
kind,
|
||||||
|
} = self;
|
||||||
|
write!(f, "[{start}..{end}]: {kind} at {end}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
Unterminated(Terminable),
|
||||||
|
Unexpected(char),
|
||||||
|
EndOfInput,
|
||||||
|
NoComment,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ErrorKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorKind::Unterminated(t) => write!(f, "unterminated {t:?}"),
|
||||||
|
ErrorKind::Unexpected(c) => write!(f, "unexpected character {c}"),
|
||||||
|
ErrorKind::EndOfInput => write!(f, "end of input"),
|
||||||
|
ErrorKind::NoComment => write!(f, "no comment"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Terminable {
|
||||||
|
Comment,
|
||||||
|
Group,
|
||||||
|
Str,
|
||||||
|
Chr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
src/main.rs
22
src/main.rs
@ -6,17 +6,27 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
for file in std::env::args().skip(1) {
|
for file in std::env::args().skip(1) {
|
||||||
let file = std::fs::read_to_string(file)?;
|
let file = std::fs::read_to_string(file)?;
|
||||||
let mut p = Parser::new(&file);
|
let mut p = Parser::new(&file);
|
||||||
while let Some(rule) = p.rule() {
|
parse(&mut p);
|
||||||
println!("{rule}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for line in std::io::stdin().lines() {
|
for line in std::io::stdin().lines() {
|
||||||
let line = line?;
|
let line = line?;
|
||||||
let mut p = Parser::new(&line);
|
let mut p = Parser::new(&line);
|
||||||
while let Some(rule) = p.rule() {
|
parse(&mut p);
|
||||||
println!("{} = {{ {} }}", rule.name, rule.body);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse(p: &mut Parser) {
|
||||||
|
loop {
|
||||||
|
match p.rule() {
|
||||||
|
Ok(rule) => {
|
||||||
|
println!("{rule}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) if e.is_end() => {}
|
||||||
|
Err(e) => eprintln!("Error {e}"),
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user