Skip to content

Instantly share code, notes, and snippets.

@nulldatamap
Last active August 29, 2015 14:11
Show Gist options
  • Select an option

  • Save nulldatamap/aac6ad5a071cb2c4bbba to your computer and use it in GitHub Desktop.

Select an option

Save nulldatamap/aac6ad5a071cb2c4bbba to your computer and use it in GitHub Desktop.
use std::str::Chars;
use std::io::IoError;
use std::io::fs::File;
use std::iter::Peekable;
use std::error::{FromError, Error};
struct Settings {
clips : Vec<(String, Path)>,
}
impl Settings {
fn load_from( fp : Path ) -> Result<Settings, SettingsError> {
Ok( Settings{ clips: try!( Parser::parse_file( fp ) ) } )
}
}
enum SettingsError {
Io( IoError ),
Parse( ParseError )
}
impl Error for SettingsError {
fn description( &self ) -> &str {
"Failed to load settings."
}
fn detail( &self ) -> Option<String> {
Some( format!( "Failed to load settings due to: {}"
, self.cause().unwrap().detail() ) )
}
fn cause( &self ) -> Option<&Error> {
Some( match self {
&SettingsError::Io( ref ioerr ) => ioerr as &Error,
&SettingsError::Parse( ref perr ) => perr as &Error
} )
}
}
impl FromError<IoError> for SettingsError {
fn from_error( err: IoError ) -> SettingsError {
SettingsError::Io( err )
}
}
impl FromError<ParseError> for SettingsError {
fn from_error( err: ParseError ) -> SettingsError {
SettingsError::Parse( err )
}
}
enum ParseErrorKind {
Eof,
Expected( &'static str )
}
struct ParseError {
kind : ParseErrorKind,
at : &'static str,
line : u32,
pos : u32,
path : Path
}
impl ParseError {
fn eof( parser : &Parser ) -> ParseError {
ParseError{ kind: ParseErrorKind::Eof
, at : parser.at
, line: parser.line
, pos : parser.pos
, path: parser.path.clone() }
}
fn expected( parser : &Parser, what : &'static str ) -> ParseError {
ParseError{ kind: ParseErrorKind::Expected( what )
, at : parser.at
, line: parser.line
, pos : parser.pos
, path: parser.path.clone() }
}
}
impl Error for ParseError {
fn description( &self ) -> &str {
match self.kind {
ParseErrorKind::Eof => "Reached unexpected EOF.",
ParseErrorKind::Expected( .. ) => "Reached unexpected character."
}
}
fn detail(&self) -> Option<String> {
Some( match self.kind {
ParseErrorKind::Eof =>
format!( "Reached unexpected end of file at {} {}:{} while parsing {}."
, self.path.as_str().unwrap_or( "<INVALID UTF-8>" )
, self.line
, self.pos
, self.at ),
ParseErrorKind::Expected( ex ) =>
format!( "Expected {}, at file at {} {}:{} while parsing {}."
, ex
, self.path.as_str().unwrap_or( "<INVALID UTF-8>" )
, self.line
, self.pos
, self.at ),
} )
}
fn cause(&self) -> Option<&Error> {
None
}
}
// Giving the parers a generic name since it's never
// going to escape this module anyway, so just for brevity.
struct Parser<'a> {
path : Path,
at : &'static str,
chars: Peekable<char, Chars<'a>>,
line : u32,
pos : u32
}
// SYNTAX := WS PAIR* EOF
// PAIR := STRING WS ":" WS STRING WS
// STRING := '"' (-'"')* '"'
// WS := (" " | "\t" | "\n" | "\r")*
impl<'a> Parser<'a> {
fn parse_file( path : Path ) -> Result<Vec<(String, Path)>, SettingsError> {
let mut file = try!( File::open( &path ) );
let src = try!( file.read_to_string() );
Parser::parse( src.as_slice()
, path.as_str().unwrap_or( "<INVALID UTF-8>" ) )
}
fn parse( src : &str, name : &str )
-> Result<Vec<(String, Path)>, SettingsError> {
let mut parser = Parser{ path : Path::new( name )
, at : "top level"
, chars: src.chars().peekable()
, line : 1
, pos : 1 };
let mut clips = Vec::new();
try!( parser.parse_whitespace() );
while !parser.chars.is_empty() {
clips.push( try!( parser.parse_pair() ) );
}
Ok( clips )
}
fn parse_pair( &mut self ) -> Result<(String, Path), SettingsError> {
self.at = "pair";
let clip_name = try!( self.parse_string() );
try!( self.parse_whitespace() );
try!( self.next_expected( ':' ) );
try!( self.parse_whitespace() );
let clip_path = Path::new( try!( self.parse_string() ) );
try!( self.parse_whitespace() );
Ok( (clip_name, clip_path) )
}
fn parse_string( &mut self ) -> Result<String, SettingsError> {
self.at = "string";
let mut buffer = String::new();
self.next_expected( '"' );
loop {
if self.chars.peek().map( |&v| v ) == Some( '"' ) {
break
}
buffer.push( try!( self.next() ) );
}
self.next_expected( '"' );
Ok( buffer )
}
fn parse_whitespace( &mut self ) -> Result<(), SettingsError> {
loop {
match self.chars.peek().map( |&v| v ) {
Some( ' ' ) => {},
Some( '\n' ) => {},
Some( '\r' ) => {},
Some( '\t' ) => {},
_ => break
}
try!( self.next() );
}
Ok( () )
}
fn next_expected( &mut self, chr : char ) -> Result<(), SettingsError> {
if try!( self.next() ) != chr {
Err( FromError::from_error( ParseError::expected( self, "'\"'" ) ) )
} else {
Ok( () )
}
}
fn next( &mut self ) -> Result<char, SettingsError> {
let v = match self.chars.next() {
Some( v ) => v,
None => {
return Err( FromError::from_error( ParseError::eof( self ) ) )
}
};
match v {
'\n' => {
self.line += 1;
self.pos = 1;
},
_ => {
self.pos += 1;
}
}
return Ok( v )
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment