use std; use std::str::FromStr; use nom; use std::net::SocketAddr; use nom::{IResult,Input,Consumer,ConsumerState,Move,Producer,HexDisplay}; use nom::{alpha,alphanumeric,space,multispace,line_ending,not_line_ending}; #[derive(Debug,PartialEq,Eq)] enum Expr<'a> { Keyval(&'a str,&'a str), Interface(&'a str), Close, Comment } named!(parse_itf<&[u8],Expr>, chain!( tag!("interface") ~ space ~ val: map_res!(alpha, std::str::from_utf8) ~ opt!(space) ~ tag!("{"), ||{ Expr::Interface(val) } )); named!(parse_keyval<&[u8],Expr>, chain!( key: map_res!(alphanumeric, std::str::from_utf8) ~ space ~ val: map_res!(not_line_ending, std::str::from_utf8) ~ line_ending, ||{ Expr::Keyval(key, val.trim()) } )); named!(parse_expr<&[u8],Expr>, alt!( value!(Expr::Comment, multispace) | value!(Expr::Comment, delimited!(tag!("#"), not_line_ending, line_ending)) | value!(Expr::Close, delimited!(tag!("}"), opt!(space), line_ending)) | parse_itf | parse_keyval // Must be last, matches a lot )); #[derive(Debug,Clone,Default)] pub struct Interface { pub addr: Vec } #[derive(Debug,Clone,Default)] pub struct Config { pub itf: Vec } #[derive(Debug)] pub enum Err { Io(std::io::Error), Parse(()) // TODO: error info } enum ParseState { Root, Interface } struct ConfigConsumer { c_state: ConsumerState<(), (), nom::Move>, state: ParseState, cfg: Config } impl ConfigConsumer { fn handle_root(&mut self, e: Expr) { match e { Expr::Interface(_) => { self.cfg.itf.push(Default::default()); self.state = ParseState::Interface }, _ => { panic!("Not implemented yet.") } } } fn handle_itf(&mut self, e: Expr) { let ref mut itf = self.cfg.itf.last_mut().unwrap(); match e { Expr::Keyval("listen", a) => { // TODO: More flexible input format (e.g. ":80" or dns "localhost:8080") itf.addr.push(SocketAddr::from_str(a).expect("I crash on invalid strings")); }, Expr::Close => { if itf.addr.len() < 1 { panic!("No interface address configured"); } self.state = ParseState::Root }, _ => { panic!("Not implemented yet in interface: {:?}.", e) } } } fn token(&mut self, e: Expr) { if e == Expr::Comment { return; } match self.state { ParseState::Root => self.handle_root(e), ParseState::Interface => self.handle_itf(e) } } } impl<'a> Consumer<&'a[u8], (), (), nom::Move> for ConfigConsumer { fn handle(&mut self, input: Input<&[u8]>) -> &ConsumerState<(), (), nom::Move> { match input { Input::Eof(None) => { self.c_state = ConsumerState::Done(Move::Consume(0), ()); }, Input::Empty => { self.c_state = ConsumerState::Continue(Move::Consume(0)); }, Input::Element(x) | Input::Eof(Some(x)) => { match parse_expr(x) { IResult::Done(i, e) => { self.token(e); self.c_state = ConsumerState::Continue(Move::Consume(x.offset(i))); }, IResult::Error(_) => { self.c_state = ConsumerState::Error(()); }, IResult::Incomplete(i) => { self.c_state = ConsumerState::Continue(Move::Await(i)); } } } } &self.c_state } fn state(&self) -> &ConsumerState<(), (), nom::Move> { &self.c_state } } impl Config { pub fn parse(file: &str) -> Result { let mut p = try!(nom::FileProducer::new(file, 1024).map_err(Err::Io)); let mut c = ConfigConsumer{ state: ParseState::Root, c_state: ConsumerState::Continue(Move::Consume(0)), cfg: Default::default() }; loop { match *p.apply(&mut c) { ConsumerState::Done(_, _) => { return Ok(c.cfg) } ConsumerState::Error(e) => { return Result::Err(Err::Parse(e)) } ConsumerState::Continue(_) => {} } } } } #[test] fn test_parse_expr() { let x = |e| { IResult::Done(&b"x"[..], e) }; let e = parse_expr; assert_eq!(e(b" \r\n\tx"), x(Expr::Comment)); assert_eq!(e(b"\nx"), x(Expr::Comment)); assert_eq!(e(b"#\nx"), x(Expr::Comment)); assert_eq!(e(b"##/!@$% \nx"), x(Expr::Comment)); assert_eq!(e(b"}\nx"), x(Expr::Close)); assert_eq!(e(b"key val\nx"), x(Expr::Keyval("key", "val"))); assert_eq!(e(b"ke\t v a \nx"), x(Expr::Keyval("ke", "v a"))); assert_eq!(e(b"interface http {x"), x(Expr::Interface("http"))); assert_eq!(e(b"interface \thttp{x"), x(Expr::Interface("http"))); let l: &[&'static str] = &[ "", "something", "#something", ][..]; for s in l { if let IResult::Done(_,_) = e(s.as_bytes()) { panic!("'{}' did not error", s); } } }