Skip to content

Instantly share code, notes, and snippets.

@travisstaloch
Forked from eyelash/peg.rs
Last active November 14, 2018 17:37
Show Gist options
  • Save travisstaloch/0ce44a4fd5060dff7f2f1482765acdec to your computer and use it in GitHub Desktop.
Save travisstaloch/0ce44a4fd5060dff7f2f1482765acdec to your computer and use it in GitHub Desktop.
#![feature(ptr_offset_from)]
type ParseResult<'a> = Option<&'a str>;
trait Peg<'a> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a>;
}
struct Wrap<T>(T);
impl<'a, T: Peg<'a>> Peg<'a> for Wrap<T> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
self.0.p(s)
}
}
impl<'a> Peg<'a> for &'a str {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
if s.starts_with(self) {
*s = &s[self.len()..];
return Some(&s[..self.len() - 1]);
}
None
}
}
impl<'a, F: Fn(char) -> ParseResult<'a>> Peg<'a> for F {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
if let Some(c) = s.chars().next() {
if self(c).is_some() {
*s = &s[c.len_utf8()..];
return Some(&s[..c.len_utf8() - 1]);
}
}
None
}
}
impl<'a> Peg<'a> for char {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
(|c| if c == *self { Some(&s[..1]) } else { None }).p(&mut &s[1..])
}
}
// note that ranges are inclusive here
impl<'a> Peg<'a> for std::ops::Range<char> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
(|c| {
if c >= self.start && c <= self.end {
Some(&s[..1])
} else {
None
}
}).p(&mut &s[1..])
}
}
struct Nothing();
impl<'a> Peg<'a> for Nothing {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
Some("")
}
}
// sequence
struct Seq<P1, P2>(P1, P2);
impl<'a, P1: Peg<'a>, P2: Peg<'a>> Peg<'a> for Seq<P1, P2> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
let original = *s;
if self.0.p(s).is_none() {
return None;
}
if self.1.p(s).is_none() {
*s = original;
return None;
}
Some(&s[..ptr_offset(original, s)])
}
}
impl<'a, P1: Peg<'a>, P2: Peg<'a>> std::ops::Add<Wrap<P2>> for Wrap<P1> {
type Output = Wrap<Seq<P1, P2>>;
fn add(self, rhs: Wrap<P2>) -> Wrap<Seq<P1, P2>> {
Wrap(Seq(self.0, rhs.0))
}
}
// ordered choice
struct Choice<P1, P2>(P1, P2);
impl<'a, P1: Peg<'a>, P2: Peg<'a>> Peg<'a> for Choice<P1, P2> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
let original = *s;
if self.0.p(s).is_some() || self.1.p(s).is_some() {
Some(&s[..ptr_offset(original, s)])
} else {
None
}
}
}
impl<'a, P1: Peg<'a>, P2: Peg<'a>> std::ops::BitOr<Wrap<P2>> for Wrap<P1> {
type Output = Wrap<Choice<P1, P2>>;
fn bitor(self, rhs: Wrap<P2>) -> Wrap<Choice<P1, P2>> {
Wrap(Choice(self.0, rhs.0))
}
}
struct ZeroOrMore<P>(P);
impl<'a, P: Peg<'a>> Peg<'a> for ZeroOrMore<P> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
let original = *s;
while self.0.p(s).is_some() {}
Some(&s[..ptr_offset(original, s)])
}
}
fn zero_or_more<'a, P: Peg<'a>>(p: P) -> Wrap<ZeroOrMore<P>> {
Wrap(ZeroOrMore(p))
}
struct OneOrMore<P>(P);
impl<'a, P: Peg<'a>> Peg<'a> for OneOrMore<P> {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
if self.0.p(s).is_none() {
return None;
}
let original = *s;
while self.0.p(s).is_some() {}
Some(&s[..ptr_offset(original, s)])
}
}
fn one_or_more<'a, P: Peg<'a>>(p: P) -> Wrap<OneOrMore<P>> {
Wrap(OneOrMore(p))
}
fn optional<'a, P: Peg<'a>>(p: P) -> Wrap<Choice<P, Nothing>> {
Wrap(Choice(p, Nothing()))
}
struct End();
impl<'a> Peg<'a> for End {
fn p(&self, s: &mut &'a str) -> ParseResult<'a> {
if s.is_empty() {
None
} else {
Some(s)
}
}
}
fn end() -> Wrap<End> {
Wrap(End())
}
fn ptr_offset(orig: &str, s: &str) -> usize {
let ptro: *const u8 = orig.as_ptr();
let ptrs: *const u8 = s.as_ptr();
(unsafe { ptrs.offset_from(ptro) }) as usize
}
#[test]
fn itworks() {
let p = optional('-')
+ (Wrap('0') | one_or_more('0'..'9'))
+ optional(Wrap('.') + one_or_more(char::is_numeric))
+ optional(
(Wrap('e') | Wrap('E')) + optional(Wrap('+') | Wrap('-')) + one_or_more(char::is_numeric),
)
+ end();
assert!(p.p(&mut "9.81"));
assert!(p.p(&mut "6.022e23"));
assert!(!p.p(&mut "0815"));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment