diff --git a/README.md b/README.md index 15371da..05521e6 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,15 @@ using @cirru_parser {type Cirru} // parse Cirru code Cirru::parse(code: String) : Array[Cirru] raise CirruParseError +// parse a one-line Cirru expression +Cirru::parse_expr_one_liner(code: String) : Cirru raise CirruParseError + // format Cirru code Cirru::format(cirru: Array[Cirru], use_inline=false) : String raise FormatCirruError + +// format one expression into one line +Cirru::format_expr_one_liner(expr: Cirru) : String raise FormatCirruError +Cirru::format_one_liner(expr: Cirru) : String raise FormatCirruError ``` ### License diff --git a/src/parser.mbt b/src/parser.mbt index 0f8f995..015ee57 100644 --- a/src/parser.mbt +++ b/src/parser.mbt @@ -13,7 +13,7 @@ fn build_exprs( idx += 1 Some(tokens[pos]) } - for { + for ;; { let chunk = pull_token() match chunk { None => return acc @@ -23,7 +23,7 @@ fn build_exprs( let mut pointer : Array[Cirru] = Array::new(capacity=8) // guess a nested level of 16 let pointer_stack : Array[Array[Cirru]] = Array::new(capacity=16) - for { + for ;; { let cursor = pull_token() match cursor { None => raise CirruParseError("unexpected end of file") @@ -68,6 +68,18 @@ pub fn Cirru::parse(code : String) -> Array[Cirru] raise CirruParseError { resolve_comma(resolve_dollar(tree)) } +///| +/// parse a one-line Cirru expression into exactly one expression +pub fn Cirru::parse_expr_one_liner( + code : String, +) -> Cirru raise CirruParseError { + let xs = Cirru::parse(code) + if xs.length() != 1 { + raise CirruParseError("expected 1 expression, got \{xs.length()}") + } + xs[0] +} + ///| suberror CirruParseError { CirruParseError(String) @@ -280,7 +292,7 @@ fn resolve_indentations(tokens : Array[CirruLexItem]) -> Array[CirruLexItem] { let mut acc : Array[CirruLexItem] = Array::new() let mut level = 0 let mut pointer = 0 - for { + for ;; { if pointer >= size { if acc.is_empty() { return Array::new() diff --git a/src/parser_test.mbt b/src/parser_test.mbt index 3946a9c..15b651b 100644 --- a/src/parser_test.mbt +++ b/src/parser_test.mbt @@ -3,7 +3,91 @@ using @lib {type Cirru} ///| test "parser" { - assert_eq(Cirru::parse("def a"), [List([Leaf("def"), Leaf("a")])]) + assert_eq(Cirru::parse("def a"), [ + Cirru::List([Cirru::Leaf("def"), Cirru::Leaf("a")]), + ]) +} + +///| +test "parse and format one-liner expression" { + let tree = Cirru::List([ + Cirru::Leaf("defn"), + Cirru::Leaf("main"), + Cirru::List([]), + Cirru::List([Cirru::Leaf("println"), Cirru::Leaf("Hello, world!")]), + ]) + + let one_liner = try! tree.format_one_liner() + assert_eq(one_liner, "defn main () $ println \"Hello, world!\"") + + let parsed = try! Cirru::parse_expr_one_liner(one_liner) + assert_eq(parsed, tree) +} + +///| +test "reject multiple expressions in one-liner parser" { + let result = try? Cirru::parse_expr_one_liner("a\nb") + match result { + Err(err) => assert_eq(err.to_string(), "expected 1 expression, got 2") + Ok(_) => assert_eq("ok", "err") + } +} + +///| +test "format complex one-liner expression" { + let tree = Cirru::List([ + Cirru::Leaf("a"), + Cirru::List([Cirru::Leaf("b"), Cirru::List([Cirru::Leaf("c")])]), + Cirru::Leaf("d"), + ]) + + let one_liner = try! tree.format_one_liner() + assert_eq(one_liner, "a (b (c)) d") + + let parsed = try! Cirru::parse_expr_one_liner(one_liner) + assert_eq(parsed, tree) +} + +///| +test "format tail expression one-liner" { + let tree = Cirru::List([ + Cirru::Leaf("defn"), + Cirru::Leaf("main"), + Cirru::List([]), + Cirru::List([Cirru::Leaf("println"), Cirru::Leaf("Hello")]), + ]) + + let one_liner = try! tree.format_one_liner() + assert_eq(one_liner, "defn main () $ println Hello") + + let parsed = try! Cirru::parse_expr_one_liner(one_liner) + assert_eq(parsed, tree) +} + +///| +test "format nested tail expression one-liner" { + let tree = Cirru::List([ + Cirru::Leaf("if"), + Cirru::Leaf("condition"), + Cirru::List([Cirru::Leaf("do"), Cirru::List([Cirru::Leaf("action")])]), + ]) + + let one_liner = try! tree.format_one_liner() + assert_eq(one_liner, "if condition $ do $ action") + + let parsed = try! Cirru::parse_expr_one_liner(one_liner) + assert_eq(parsed, tree) +} + +///| +test "format empty tail expression one-liner" { + let tree = Cirru::List([Cirru::Leaf("a"), Cirru::Leaf("b"), Cirru::List([])]) + + let one_liner = try! tree.format_one_liner() + assert_eq(one_liner, "a b $") + + let parsed = try! Cirru::parse_expr_one_liner(one_liner) + assert_eq(parsed, tree) } ///| diff --git a/src/primes.mbt b/src/primes.mbt index ba67bca..7a0cd3c 100644 --- a/src/primes.mbt +++ b/src/primes.mbt @@ -149,6 +149,12 @@ pub fn Cirru::is_comment(self : Cirru) -> Bool { } } +///| +/// format this expression into a single line of Cirru code +pub fn Cirru::format_one_liner(self : Cirru) -> String raise FormatCirruError { + Cirru::format_expr_one_liner(self) +} + ///| /// lexer is a simpler state machine to tokenize Cirru code priv enum CirruLexState { diff --git a/src/s_expr.mbt b/src/s_expr.mbt index 15eb5c3..45d3e88 100644 --- a/src/s_expr.mbt +++ b/src/s_expr.mbt @@ -46,7 +46,7 @@ fn Cirru::format_lispy_expr( chunk = "\{chunk}\{next}" } // TODO dirty way, but intuitive for now - if idx < xs.length() - 1 && not(ends_with_newline(chunk)) { + if idx < xs.length() - 1 && !ends_with_newline(chunk) { chunk = "\{chunk} " } } @@ -58,7 +58,7 @@ fn Cirru::format_lispy_expr( } else { let s0 = token[0] if s0 == '|' || s0 == '"' { - let sliced = (try! token[1:]).to_string() + let sliced = token[1:].to_owned() "\"" + escape_string(sliced) + "\"" } else if token.contains(" ") || token.contains("\n") || diff --git a/src/tree.mbt b/src/tree.mbt index 261c484..bb77ea1 100644 --- a/src/tree.mbt +++ b/src/tree.mbt @@ -12,18 +12,18 @@ fn comma_helper(initial_after : Array[Cirru]) -> Array[Cirru] { let before : Array[Cirru] = Array::new(capacity=initial_after.length()) let after : Array[Cirru] = initial_after let mut pointer = 0 - for { + for ;; { if pointer >= after.length() { return before } match after[pointer] { List(xs) => - if not(xs.is_empty()) { + if !xs.is_empty() { match xs[0] { List(_) => before.push(List(resolve_comma(xs))) Leaf(s) => if s == "," { - before.push_iter(resolve_comma(xs[1:].to_array()).iter()) + before.push_iter(resolve_comma(xs[1:].to_owned()).iter()) } else { before.push(List(resolve_comma(xs))) } @@ -51,7 +51,7 @@ fn dollar_helper(initial_after : Array[Cirru]) -> Array[Cirru] { let before : Array[Cirru] = Array::new(capacity=initial_after.length()) let after : Array[Cirru] = initial_after let mut pointer = 0 - for { + for ;; { if pointer >= after.length() { return before } else { diff --git a/src/writer.mbt b/src/writer.mbt index 5cad15c..547864b 100644 --- a/src/writer.mbt +++ b/src/writer.mbt @@ -83,7 +83,7 @@ fn is_char_allowed(x : Char) -> Bool { fn generate_leaf(s : String) -> String { let mut all_allowed = true for x in s { - if not(is_char_allowed(x)) { + if !is_char_allowed(x) { all_allowed = false break } @@ -131,6 +131,33 @@ fn generate_inline_expr(xs : Array[Cirru]) -> String { result } +///| +fn generate_statement_one_liner(xs : Array[Cirru]) -> String { + let mut ret = "" + let size = xs.length() + for idx, cursor in xs { + if idx > 0 { + ret += " " + } + let at_tail = idx > 0 && idx == size - 1 + match cursor { + Leaf(s) => ret += generate_leaf(s) + List(ys) => + if at_tail { + if ys.is_empty() { + ret += "$" + } else { + ret += "$ " + ret += generate_statement_one_liner(ys) + } + } else { + ret += generate_inline_expr(ys) + } + } + } + ret +} + ///| /// by 2 spaces fn push_spaces(buf : String, n : Int) -> String { @@ -170,7 +197,7 @@ fn Cirru::get_node_kind(self : Cirru) -> WriterNode { ///| pub(all) suberror FormatCirruError { FormatCirruError(String) -} derive(Show) +} derive(Debug) ///| fn generate_tree( @@ -188,7 +215,7 @@ fn generate_tree( let next_level = level + 1 let child_insist_head = prev_kind == BoxedExpr || prev_kind == Expr let at_tail = idx != 0 && - not(in_tail) && + !in_tail && prev_kind == Leaf && idx == xs.length() - 1 @@ -343,3 +370,15 @@ pub fn Cirru::format( ) -> String raise FormatCirruError { generate_statements(xs, use_inline~) } + +///| +/// format a single Cirru expression as a single line +pub fn Cirru::format_expr_one_liner( + expr : Cirru, +) -> String raise FormatCirruError { + match expr { + Leaf(_) => + raise FormatCirruError("format_expr_one_liner expects an expr (list)") + List(xs) => generate_statement_one_liner(xs) + } +}