DEV: Handle comments between statements.

This commit is contained in:
Jeremy Wall 2019-05-21 20:34:42 -05:00
parent d884ea9385
commit 957d0c6102
8 changed files with 181 additions and 15 deletions

View File

@ -756,6 +756,7 @@ impl fmt::Display for Expression {
/// Encodes a let statement in the UCG AST. /// Encodes a let statement in the UCG AST.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct LetDef { pub struct LetDef {
pub pos: Position,
pub name: Token, pub name: Token,
pub value: Expression, pub value: Expression,
} }
@ -770,8 +771,19 @@ pub enum Statement {
Let(LetDef), Let(LetDef),
// Assert statement // Assert statement
Assert(Expression), Assert(Position, Expression),
// Identify an Expression for output. // Identify an Expression for output.
Output(Position, Token, Expression), Output(Position, Token, Expression),
} }
impl Statement {
fn pos(&self) -> &Position {
match self {
Statement::Expression(ref e) => e.pos(),
Statement::Let(ref def) => &def.pos,
Statement::Assert(ref pos, _) => pos,
Statement::Output(ref pos, _, _) => pos,
}
}
}

View File

@ -29,6 +29,8 @@ where
// Indexed by line that the comment was on. // Indexed by line that the comment was on.
// We use this to determine when to print a comment in our AstPrinter // We use this to determine when to print a comment in our AstPrinter
comment_map: Option<&'a CommentMap>, comment_map: Option<&'a CommentMap>,
last_line: usize,
comment_group_lines: Vec<usize>,
} }
// TODO(jwall): At some point we probably want to be more aware of line length // TODO(jwall): At some point we probably want to be more aware of line length
@ -43,10 +45,14 @@ where
curr_indent: 0, curr_indent: 0,
comment_map: None, comment_map: None,
w: w, w: w,
last_line: 0,
comment_group_lines: Vec::new(),
} }
} }
pub fn with_comment_map(mut self, map: &'a CommentMap) -> Self { pub fn with_comment_map(mut self, map: &'a CommentMap) -> Self {
self.comment_group_lines = map.keys().cloned().collect();
self.comment_group_lines.reverse();
self.comment_map = Some(map); self.comment_map = Some(map);
self self
} }
@ -77,6 +83,47 @@ where
return true; return true;
} }
fn print_comment_group(&mut self, line: usize) -> std::io::Result<()> {
if let Some(ref map) = self.comment_map {
let empty: Vec<Token> = Vec::new();
//eprintln!("comment line candidate: {}", line);
let cg = map.get(&line).unwrap_or(&empty);
//eprintln!("comment_group: {:?}", cg);
for c in cg.iter() {
write!(self.w, "// {}\n", c.fragment.trim())?;
}
self.comment_group_lines.pop();
}
Ok(())
}
fn render_missed_comments(&mut self, line: usize) -> std::io::Result<()> {
loop {
if let Some(next_comment_line) = self.comment_group_lines.last() {
let next_comment_line = *next_comment_line;
if next_comment_line < line {
self.print_comment_group(next_comment_line)?;
} else {
break;
}
if next_comment_line < line - 1 {
write!(self.w, "\n")?;
}
continue;
}
break;
}
Ok(())
}
fn render_comment_if_needed(&mut self, line: usize) -> std::io::Result<()> {
if line > self.last_line {
self.render_missed_comments(line)?;
self.last_line = line;
}
Ok(())
}
fn render_list_def(&mut self, def: &ListDef) -> std::io::Result<()> { fn render_list_def(&mut self, def: &ListDef) -> std::io::Result<()> {
write!(self.w, "[")?; write!(self.w, "[")?;
self.curr_indent += self.indent_size; self.curr_indent += self.indent_size;
@ -351,6 +398,8 @@ where
pub fn render_stmt(&mut self, stmt: &Statement) -> std::io::Result<()> { pub fn render_stmt(&mut self, stmt: &Statement) -> std::io::Result<()> {
// All statements start at the beginning of a line. // All statements start at the beginning of a line.
let line = stmt.pos().line;
self.render_comment_if_needed(line)?;
match stmt { match stmt {
Statement::Let(def) => { Statement::Let(def) => {
write!(&mut self.w, "let {} = ", def.name.fragment)?; write!(&mut self.w, "let {} = ", def.name.fragment)?;
@ -359,7 +408,7 @@ where
Statement::Expression(_expr) => { Statement::Expression(_expr) => {
self.render_expr(&_expr)?; self.render_expr(&_expr)?;
} }
Statement::Assert(def) => { Statement::Assert(_, def) => {
write!(&mut self.w, "assert ")?; write!(&mut self.w, "assert ")?;
self.render_expr(&def)?; self.render_expr(&def)?;
} }
@ -369,6 +418,7 @@ where
} }
}; };
write!(self.w, ";\n")?; write!(self.w, ";\n")?;
self.last_line = line;
Ok(()) Ok(())
} }
@ -376,6 +426,12 @@ where
for v in stmts { for v in stmts {
self.render_stmt(v)?; self.render_stmt(v)?;
} }
if let Some(last_comment_line) = self.comment_group_lines.first() {
eprintln!("last_comment_line is: {}", last_comment_line);
eprintln!("comment_map is: {:?}", self.comment_map);
eprintln!("coment_group_lines is: {:?}", self.comment_group_lines);
self.render_missed_comments(*last_comment_line + 1)?;
}
Ok(()) Ok(())
} }
} }

View File

@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::collections::BTreeMap;
use crate::ast::printer::*; use crate::ast::printer::*;
use crate::iter::OffsetStrIter; use crate::iter::OffsetStrIter;
@ -351,3 +352,53 @@ fn test_format_expr_list_arg_printing() {
assert!(printer.render(&stmts).is_ok()); assert!(printer.render(&stmts).is_ok());
assert_eq!(String::from_utf8(buffer).unwrap(), format!("{}\n", input)); assert_eq!(String::from_utf8(buffer).unwrap(), format!("{}\n", input));
} }
#[test]
fn test_statement_with_comment_printing() {
let mut comment_map = BTreeMap::new();
let input = "// add 1 + 1\n1 + 1;";
let stmts = assert_parse(input, Some(&mut comment_map));
let mut buffer: Vec<u8> = Vec::new();
let mut printer = AstPrinter::new(2, &mut buffer).with_comment_map(&comment_map);
assert!(printer.render(&stmts).is_ok());
assert_eq!(String::from_utf8(buffer).unwrap(), format!("{}\n", input));
}
#[test]
fn test_statement_with_comment_printing_groups() {
let mut comment_map = BTreeMap::new();
let input = "// add 1\n// and 1\n1 + 1;";
let stmts = assert_parse(input, Some(&mut comment_map));
let mut buffer: Vec<u8> = Vec::new();
let mut printer = AstPrinter::new(2, &mut buffer).with_comment_map(&comment_map);
assert!(printer.render(&stmts).is_ok());
assert_eq!(String::from_utf8(buffer).unwrap(), format!("{}\n", input));
}
#[test]
fn test_statement_with_comment_printing_multiple_groups() {
let mut comment_map = BTreeMap::new();
let input = "\n// group 1\n// more group 1\n\n// group 2\n// more group 2\n1 + 1;";
let stmts = assert_parse(input, Some(&mut comment_map));
let mut buffer: Vec<u8> = Vec::new();
let mut printer = AstPrinter::new(2, &mut buffer).with_comment_map(&comment_map);
assert!(printer.render(&stmts).is_ok());
assert_eq!(
String::from_utf8(buffer).unwrap(),
format!("{}\n", input.trim())
);
}
#[test]
fn test_statement_with_comment_printing_comments_at_end() {
let mut comment_map = BTreeMap::new();
let input = "// group 1\n1 + 1;\n// group 2\n\n";
let stmts = assert_parse(input, Some(&mut comment_map));
let mut buffer: Vec<u8> = Vec::new();
let mut printer = AstPrinter::new(2, &mut buffer).with_comment_map(&comment_map);
assert!(printer.render(&stmts).is_ok());
assert_eq!(
String::from_utf8(buffer).unwrap(),
format!("{}\n", input.trim())
);
}

View File

@ -16,7 +16,7 @@ pub trait Walker {
Statement::Expression(ref mut expr) => { Statement::Expression(ref mut expr) => {
self.walk_expression(expr); self.walk_expression(expr);
} }
Statement::Assert(ref mut expr) => { Statement::Assert(_, ref mut expr) => {
self.walk_expression(expr); self.walk_expression(expr);
} }
Statement::Output(_, _, ref mut expr) => { Statement::Output(_, _, ref mut expr) => {

View File

@ -511,7 +511,7 @@ impl<'a> FileBuilder<'a> {
fn eval_stmt(&mut self, stmt: &Statement) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_stmt(&mut self, stmt: &Statement) -> Result<Rc<Val>, Box<dyn Error>> {
let child_scope = self.scope.clone(); let child_scope = self.scope.clone();
match stmt { match stmt {
&Statement::Assert(ref expr) => self.eval_assert(&expr, &child_scope), &Statement::Assert(_, ref expr) => self.eval_assert(&expr, &child_scope),
&Statement::Let(ref def) => self.eval_let(def), &Statement::Let(ref def) => self.eval_let(def),
&Statement::Expression(ref expr) => self.eval_expr(expr, &child_scope), &Statement::Expression(ref expr) => self.eval_expr(expr, &child_scope),
// Only one output can be used per file. Right now we enforce this by // Only one output can be used per file. Right now we enforce this by

View File

@ -793,21 +793,19 @@ make_fn!(
) )
); );
fn tuple_to_let(tok: Token, expr: Expression) -> Statement {
Statement::Let(LetDef {
name: tok,
value: expr,
})
}
make_fn!( make_fn!(
let_stmt_body<SliceIter<Token>, Statement>, let_stmt_body<SliceIter<Token>, Statement>,
do_each!( do_each!(
pos => pos,
name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"), name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"),
_ => punct!("="), _ => punct!("="),
val => wrap_err!(trace_parse!(expression), "Expected Expression"), val => wrap_err!(trace_parse!(expression), "Expected Expression"),
_ => punct!(";"), _ => punct!(";"),
(tuple_to_let(name, val)) (Statement::Let(LetDef {
pos: pos,
name: name,
value: val,
}))
) )
); );
@ -823,10 +821,11 @@ make_fn!(
make_fn!( make_fn!(
assert_statement<SliceIter<Token>, Statement>, assert_statement<SliceIter<Token>, Statement>,
do_each!( do_each!(
pos => pos,
_ => word!("assert"), _ => word!("assert"),
expr => wrap_err!(must!(expression), "Expected Tuple {ok=<bool>, desc=<str>}"), expr => wrap_err!(must!(expression), "Expected Tuple {ok=<bool>, desc=<str>}"),
_ => must!(punct!(";")), _ => must!(punct!(";")),
(Statement::Assert(expr)) (Statement::Assert(pos, expr))
) )
); );

View File

@ -24,7 +24,7 @@ use crate::error::StackPrinter;
use crate::iter::OffsetStrIter; use crate::iter::OffsetStrIter;
pub type CommentGroup = Vec<Token>; pub type CommentGroup = Vec<Token>;
pub type CommentMap = std::collections::HashMap<usize, CommentGroup>; pub type CommentMap = std::collections::BTreeMap<usize, CommentGroup>;
fn is_symbol_char<'a>(i: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, u8> { fn is_symbol_char<'a>(i: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, u8> {
let mut _i = i.clone(); let mut _i = i.clone();
@ -353,6 +353,12 @@ fn comment(input: OffsetStrIter) -> Result<OffsetStrIter, Token> {
) )
) { ) {
Result::Complete(rest, cmt) => { Result::Complete(rest, cmt) => {
// Eat the new lines here before continuing
let rest =
match optional!(rest, either!(text_token!("\r\n"), text_token!("\n"))) {
Result::Complete(next_rest, _) => next_rest,
_ => rest,
};
return Result::Complete(rest, make_tok!(CMT => cmt.to_string(), input)); return Result::Complete(rest, make_tok!(CMT => cmt.to_string(), input));
} }
// If we didn't find a new line then we just grab everything. // If we didn't find a new line then we just grab everything.
@ -504,7 +510,9 @@ pub fn tokenize<'a>(
continue; continue;
} }
(&mut Some(ref mut map), _) => { (&mut Some(ref mut map), _) => {
out.push(tok); if tok.typ != TokenType::WS {
out.push(tok);
}
if let Some(tok) = comment_was_last { if let Some(tok) = comment_was_last {
map.insert(tok.pos.line, comment_group); map.insert(tok.pos.line, comment_group);
comment_group = Vec::new(); comment_group = Vec::new();

View File

@ -1,3 +1,5 @@
use std::collections::BTreeMap;
use super::*; use super::*;
use abortable_parser::{Offsetable, Result, SliceIter}; use abortable_parser::{Offsetable, Result, SliceIter};
@ -167,6 +169,23 @@ fn test_tokenize_one_of_each() {
assert_eq!(v[38].typ, TokenType::END); assert_eq!(v[38].typ, TokenType::END);
} }
#[test]
fn test_tokenize_one_of_each_comment_map_path() {
let input = OffsetStrIter::new(
"map out filter assert let import func select as => [ ] { } ; = % / * \
+ - . ( ) , 1 . foo \"bar\" // comment\n ; true false == < > <= >= !=",
);
let mut comment_map = BTreeMap::new();
let result = tokenize(input.clone(), Some(&mut comment_map));
assert!(result.is_ok(), format!("result {:?} is not ok", result));
let v = result.unwrap();
for (i, t) in v.iter().enumerate() {
println!("{}: {:?}", i, t);
}
assert_eq!(v.len(), 39);
assert_eq!(v[38].typ, TokenType::END);
}
#[test] #[test]
fn test_parse_has_end() { fn test_parse_has_end() {
let input = OffsetStrIter::new("foo"); let input = OffsetStrIter::new("foo");
@ -327,3 +346,24 @@ fn test_match_type() {
res => assert!(false, format!("Fail: {:?}", res)), res => assert!(false, format!("Fail: {:?}", res)),
} }
} }
#[test]
fn test_tokenize_builds_comment_map() {
let input = OffsetStrIter::new("// comment 1\n\n//comment 2");
let mut comment_map = BTreeMap::new();
let result = tokenize(input.clone(), Some(&mut comment_map));
assert!(result.is_ok(), format!("result {:?} is not ok", result));
assert_eq!(comment_map.len(), 2);
}
#[test]
fn test_tokenize_builds_comment_map_groups() {
let input = OffsetStrIter::new("// first part\n// comment 1\n\n//comment 2");
let mut comment_map = BTreeMap::new();
let result = tokenize(input.clone(), Some(&mut comment_map));
assert!(result.is_ok(), format!("result {:?} is not ok", result));
assert_eq!(comment_map.len(), 2);
assert_eq!(comment_map[&2].len(), 2);
}