use super::*;
struct SexpParserState<'a>{
root: Wood,
paren_stack: Vec<*mut Vec<Wood>>,
leaf_being_read_into: *mut String,
iter: std::iter::Peekable<std::str::Chars<'a>>,
line: isize,
column: isize,
mode: fn(&mut SexpParserState<'a>, Option<char>)-> Result<(), PositionedError>,
}
impl<'a> SexpParserState<'a> {
fn a_fail(&self, message:String)-> Result<(), PositionedError> { Err(PositionedError{
line: self.line,
column: self.column,
msg: message,
}) }
fn mkbranch(&self)-> Wood { Branchv(Branch{ line:self.line, column:self.column, v:Vec::new() }) }
fn branch_for_insert(&mut self)-> *mut Vec<Wood> {
*get_back_mut(&mut self.paren_stack)
}
fn open_paren(&mut self) {
let lti = self.mkbranch();
let branch_for_insert = self.branch_for_insert();
unsafe{(*branch_for_insert).push(lti)};
self.paren_stack.push(&mut assume_branch_mut(get_back_mut(unsafe{&mut *branch_for_insert})).v);
self.mode = Self::seeking_term;
}
fn close_paren(&mut self)-> Result<(), PositionedError> {
if self.paren_stack.len() > 1 {
self.paren_stack.pop();
self.mode = Self::seeking_term;
Ok(())
}else{
self.a_fail("unmatched paren".into())
}
}
fn begin_leaf(&mut self){
let to_push = Leafv(Leaf{line:self.line, column:self.column, v:String::new()});
let branch_for_insert = self.branch_for_insert();
unsafe{(*branch_for_insert).push(to_push)};
self.leaf_being_read_into = &mut unsafe{assume_leaf_mut(get_back_mut(&mut *branch_for_insert))}.v;
}
fn begin_quoted(&mut self){
self.begin_leaf();
match self.iter.peek() {
Some(&'\n') | Some(&'\r') => {
self.move_char_ptr_and_update_line_col();
}
_=> {}
}
self.mode = Self::eating_quoted_string;
}
fn start_reading_thing(&mut self, c:char)-> Result<(), PositionedError> {
match c {
')' => {
try!(self.close_paren());
},
'(' => {
self.open_paren();
},
'"'=> {
self.begin_quoted();
},
_=> {
self.begin_leaf();
self.mode = Self::eating_leaf;
try!(self.eating_leaf(Some(c)));
},
}
Ok(())
}
fn seeking_term(&mut self, co:Option<char>)-> Result<(), PositionedError> {
if let Some(c) = co {
match c {
' ' | '\t' => {},
'\n' | '\r' => {}
_=> {
try!(self.start_reading_thing(c));
}
}
}
Ok(())
}
fn read_escaped_char(&mut self)-> Result<(), PositionedError> {
let push = |slf:&mut Self, c:char| unsafe{((*slf.leaf_being_read_into)).push(c)};
let nco = self.move_char_ptr_and_update_line_col();
let match_fail_message = "escape slash must be followed by a valid escape character code";
if let Some(nc) = nco {
match nc {
'n'=> { push(self, '\n'); }
'r'=> { push(self, '\r'); }
't'=> { push(self, '\t'); }
'h'=> { push(self, '☃'); }
'"'=> { push(self, '"'); }
'\\'=> { push(self, '\\'); }
_=> { return self.a_fail(match_fail_message.into()); }
}
}else{
return self.a_fail(match_fail_message.into());
}
Ok(())
}
fn move_char_ptr_and_update_line_col(&mut self)-> Option<char> {
self.iter.next().and_then(|c|{
if c == '\n' || c == '\r' {
let mut ic = self.iter.clone();
if Some('\r') == ic.next() {
self.iter.next();
while Some('\r') == ic.next() {
self.iter.next();
self.line += 1;
}
}
self.line += 1;
self.column = 0;
Some('\n')
}else {
self.column += 1;
Some(c)
}
})
}
fn eating_quoted_string(&mut self, co:Option<char>)-> Result<(), PositionedError> {
let push_char = |slf:&mut Self, c:char| unsafe{((*slf.leaf_being_read_into)).push(c)};
if let Some(c) = co {
match c {
'\\'=> {
try!(self.read_escaped_char());
},
'"'=> {
self.mode = Self::seeking_term;
},
_=> {
push_char(self, c);
}
}
}
Ok(())
}
fn eating_leaf(&mut self, co:Option<char>)-> Result<(), PositionedError> {
let push_char = |slf:&mut Self, c:char| unsafe{((*slf.leaf_being_read_into)).push(c)};
if let Some(c) = co {
match c {
'\n' | '\r' => {
self.mode = Self::seeking_term;
}
' ' | '\t' => {
self.mode = Self::seeking_term;
}
'"'=> {
self.begin_quoted();
}
')' => {
try!(self.close_paren());
}
'(' => {
self.open_paren();
}
'\\'=> {
try!(self.read_escaped_char());
}
_=> {
push_char(self, c);
}
}
}
Ok(())
}
}
pub fn parse_multiline_woodslist<'a>(s:&'a str)-> Result<Wood, PositionedError> {
let mut state = SexpParserState::<'a>{
root: branch!(),
paren_stack: Vec::new(),
leaf_being_read_into: null_mut(),
iter: s.chars().peekable(),
line: 0,
column: 0,
mode: SexpParserState::<'a>::seeking_term,
};
state.paren_stack = vec!(&mut assume_branch_mut(&mut state.root).v);
loop {
let co = state.move_char_ptr_and_update_line_col();
try!((state.mode)(&mut state, co));
if co == None { break; }
}
Ok(state.root)
}
pub fn parse_woodslist<'a>(s:&'a str)-> Result<Wood, PositionedError> {
parse_multiline_woodslist(s).map(|t|{
let l = assume_branch(t);
if l.v.len() == 1 {
yank_first(l.v)
}else{
Branchv(l)
}
})
}
pub fn to_woodslist(w:&Wood)-> String {
let mut ret = String::new();
inline_stringify_woodslist(w, &mut ret);
ret
}
pub fn stringify_leaf_woodslist(v:&Leaf, s:&mut String){
let needs_quotes = v.v.chars().any(|c|{ c == ' ' || c == '\t' || c == '(' || c == ')' });
if needs_quotes { s.push('"'); }
push_escaped(s, v.v.as_str());
if needs_quotes { s.push('"'); }
}
pub fn inline_stringify_woodslist_branch(b:&Branch, s:&mut String){
s.push('(');
let mut i = b.v.iter();
if let Some(ref first) = i.next() {
inline_stringify_woodslist(first, s);
while let Some(ref nexto) = i.next() {
s.push(' ');
inline_stringify_woodslist(nexto, s);
}
}
s.push(')');
}
pub fn inline_stringify_woodslist(w:&Wood, s:&mut String){
match *w {
Branchv(ref b)=> {
inline_stringify_woodslist_branch(b, s);
}
Leafv(ref v)=> {
stringify_leaf_woodslist(v, s);
}
}
}
fn woodslist_inline_length_estimate_for_branch(b:&Branch)-> usize {
let mut ret = 2;
if b.v.len() > 0 {
let spaces_length = b.v.len() - 1;
ret += b.v.iter().fold(spaces_length, |n, w|{
n + woodslist_inline_length_estimate(w)
});
}
ret
}
fn woodslist_inline_length_estimate(w:&Wood)-> usize {
match w {
&Branchv(ref b)=> {
woodslist_inline_length_estimate_for_branch(b)
}
&Leafv(ref l)=> {
l.v.len()
}
}
}
fn maybe_inline_woodslist_stringification<'a>(w:&'a Wood, column_limit:usize, out:&mut String)-> Option<&'a Branch> {
match w {
&Branchv(ref b)=> {
if woodslist_inline_length_estimate_for_branch(b) > column_limit {
return Some(b);
}else{
inline_stringify_woodslist_branch(b, out);
}
}
&Leafv(ref l)=> {
stringify_leaf_woodslist(l, out);
}
}
None
}
fn do_woodslist_stringification(w:&Wood, indent:&str, indent_depth:usize, column_limit:usize, out:&mut String){
out.push('\n');
do_indent(indent, indent_depth, out);
if let Some(b) = maybe_inline_woodslist_stringification(w, column_limit, out) {
let mut bi = b.v.iter();
out.push('(');
if let Some(fw) = bi.next() {
if let Some(_fwb) = maybe_inline_woodslist_stringification(fw, column_limit - 1, out) {
out.push('\n');
for iw in b.v.iter() {
do_woodslist_stringification(iw, indent, indent_depth + 1, column_limit, out);
}
}else{
for iw in bi {
do_woodslist_stringification(iw, indent, indent_depth + 1, column_limit, out);
}
}
}
out.push('\n');
do_indent(indent, indent_depth, out);
out.push(')');
}
}
pub fn indented_woodslist_detail(w:&Wood, indent_is_tab:bool, tab_size:usize, column_limit:usize)-> String {
let indent_string:String;
let indent:&str;
if indent_is_tab {
indent = "\t";
}else{
indent_string = " ".repeat(tab_size);
indent = indent_string.as_str();
}
let mut ret = String::new();
if let Some(b) = maybe_inline_woodslist_stringification(w, column_limit, &mut ret) {
for iw in b.v.iter() {
do_woodslist_stringification(iw, indent, 0, column_limit, &mut ret);
}
}
ret
}
pub fn indented_woodslist(w:&Wood)-> String {
indented_woodslist_detail(w, false, 2, 73)
}