Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

[package]
name = "cirru_parser"
version = "0.2.5"
version = "0.2.8"
authors = ["jiyinyiyong <jiyinyiyong@gmail.com>"]
edition = "2024"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use tree::{resolve_comma, resolve_dollar};

pub use primes::{Cirru, CirruLexItem, CirruLexItemList, escape_cirru_leaf};
pub use s_expr::format_to_lisp;
pub use writer::{CirruOneLinerExt, CirruWriterOptions, format, format_expr_one_liner};
pub use writer::{CirruOneLinerExt, CirruWriterOptions, format, format_expr_one_liner, generate_leaf};

/// Helper function to format and print a detailed error
pub fn print_error(error: &CirruError, source_code: Option<&str>) {
Expand Down
26 changes: 26 additions & 0 deletions src/primes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,32 @@ impl Cirru {
Cirru::Leaf(s) => &(**s) == ";" || &(**s) == ";;",
}
}

/// Returns `true` if this node is a `Leaf`.
pub fn is_leaf(&self) -> bool {
matches!(self, Self::Leaf(_))
}

/// Returns `true` if this node is a `List`.
pub fn is_list(&self) -> bool {
matches!(self, Self::List(_))
}

/// Returns the leaf string slice if this node is a `Leaf`, otherwise `None`.
pub fn as_leaf_str(&self) -> Option<&str> {
match self {
Self::Leaf(s) => Some(s),
_ => None,
}
}

/// Returns the first child node if this is a non-empty `List`, otherwise `None`.
pub fn head(&self) -> Option<&Cirru> {
match self {
Self::List(xs) => xs.first(),
_ => None,
}
}
}

#[derive(fmt::Debug, PartialEq, Eq)]
Expand Down
33 changes: 15 additions & 18 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ fn is_char_allowed(x: char) -> bool {
ALLOWED_CHARS.find(x).is_some()
}

fn generate_leaf(s: &str) -> String {
/// Format a Cirru leaf token: returns the bare string if all characters are allowed
/// in Cirru without quoting; otherwise wraps in double quotes with escape sequences.
/// This mirrors the exact quoting behaviour used by the Cirru formatter when emitting
/// leaf nodes, so callers outside this crate get consistent output.
pub fn generate_leaf(s: &str) -> String {
let mut all_allowed = true;
for x in s.chars() {
if !is_char_allowed(x) {
Expand Down Expand Up @@ -182,14 +186,6 @@ fn get_node_kind(cursor: &Cirru) -> WriterNode {
}
}

fn should_insist_nested_head(ys: &[Cirru], idx: usize, prev_kind: WriterNode) -> bool {
if prev_kind == WriterNode::BoxedExpr || prev_kind == WriterNode::Expr {
return true;
}

idx > 1 && matches!(ys.first(), Some(Cirru::List(head)) if head.len() > 1)
}

fn generate_tree(
xs: &[Cirru],
insist_head: bool,
Expand All @@ -204,6 +200,7 @@ fn generate_tree(
for (idx, cursor) in xs.iter().enumerate() {
let kind = get_node_kind(cursor);
let next_level = level + 1;
let child_insist_head = (prev_kind == WriterNode::BoxedExpr) || (prev_kind == WriterNode::Expr) || idx > 1;
let at_tail = idx != 0 && !in_tail && prev_kind == WriterNode::Leaf && idx == xs.len() - 1;

// println!("\nloop {:?} {:?}", prev_kind, kind);
Expand All @@ -213,7 +210,6 @@ fn generate_tree(
let child: String = match cursor {
Cirru::Leaf(s) => generate_leaf(s),
Cirru::List(ys) => {
let child_insist_head = should_insist_nested_head(ys, idx, prev_kind);
if at_tail {
if ys.is_empty() {
String::from("$")
Expand Down Expand Up @@ -242,12 +238,8 @@ fn generate_tree(
generate_empty_expr() // special since empty expr is treated as leaf
}
} else if kind == WriterNode::SimpleExpr {
if prev_kind == WriterNode::Leaf && (idx == 1 || level > base_level || xs.len().saturating_sub(idx) <= 2) {
if prev_kind == WriterNode::Leaf {
generate_inline_expr(ys)
} else if prev_kind == WriterNode::Leaf {
let mut ret = render_newline(next_level);
ret.push_str(&generate_tree(ys, child_insist_head, options, next_level, false)?);
ret
} else if options.use_inline && prev_kind == WriterNode::SimpleExpr {
let mut ret = String::from(" ");
ret.push_str(&generate_inline_expr(ys));
Expand All @@ -268,15 +260,20 @@ fn generate_tree(
}
} else if kind == WriterNode::BoxedExpr {
let content = generate_tree(ys, child_insist_head, options, next_level, false)?;
if prev_kind == WriterNode::Nil || prev_kind == WriterNode::Leaf || prev_kind == WriterNode::SimpleExpr {
if child_insist_head {
// special case for boxed expr when it insists head, it has both indentation and brackets
let mut ret = render_newline(next_level);
ret.push_str(&content);
ret
Comment on lines +265 to +267
} else if prev_kind == WriterNode::Nil || prev_kind == WriterNode::Leaf || prev_kind == WriterNode::SimpleExpr {
content
} else {
let mut ret = render_newline(next_level);
ret.push_str(&content);
ret
}
} else {
return Err(String::from("Unpected condition"));
return Err(String::from("Unexpected condition"));
}
}
};
Expand All @@ -285,7 +282,7 @@ fn generate_tree(

let chunk = if at_tail
|| (prev_kind == WriterNode::Leaf && kind == WriterNode::Leaf)
|| (prev_kind == WriterNode::Leaf && kind == WriterNode::SimpleExpr && !child.starts_with('\n'))
|| (prev_kind == WriterNode::Leaf && kind == WriterNode::SimpleExpr)
|| prev_kind == WriterNode::SimpleExpr && kind == WriterNode::Leaf
{
let mut ret = String::from(" ");
Expand Down
2 changes: 1 addition & 1 deletion tests/cirru/match.cirru
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

match x
:dyn 1
(:dyn) 1
(:dyn x) 2
(:dyn x y) 3
(:dyn x y z) 4
2 changes: 1 addition & 1 deletion tests/data/match.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[
"match",
"x",
[":dyn", "1"],
[[":dyn"], "1"],
[[":dyn", "x"], "2"],
[[":dyn", "x", "y"], "3"],
[[":dyn", "x", "y", "z"], "4"]
Expand Down
5 changes: 2 additions & 3 deletions tests/writer_cirru/comma-indent.cirru
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

c $ d (e)
a
, d (f)
g
(a) d (f)
g
2 changes: 1 addition & 1 deletion tests/writer_cirru/indent.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
a $ b (c)

e f
g
(g)
h
2 changes: 1 addition & 1 deletion tests/writer_cirru/match.cirru
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

match x
:dyn 1
(:dyn) 1
(:dyn x) 2
(:dyn x y) 3
(:dyn x y z) 4
2 changes: 1 addition & 1 deletion tests/writer_data/match.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[
"match",
"x",
[":dyn", "1"],
[[":dyn"], "1"],
[[":dyn", "x"], "2"],
[[":dyn", "x", "y"], "3"],
[[":dyn", "x", "y", "z"], "4"]
Expand Down
7 changes: 2 additions & 5 deletions tests/writer_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ fn format_match_without_bending_later_clauses() -> Result<(), String> {
let xs = vec![Cirru::List(vec![
Cirru::leaf("match"),
Cirru::leaf("x"),
Cirru::List(vec![Cirru::leaf(":dyn"), Cirru::leaf("1")]),
Cirru::List(vec![Cirru::List(vec![Cirru::leaf(":dyn")]), Cirru::leaf("1")]),
Cirru::List(vec![Cirru::List(vec![Cirru::leaf(":dyn"), Cirru::leaf("x")]), Cirru::leaf("2")]),
Cirru::List(vec![
Cirru::List(vec![Cirru::leaf(":dyn"), Cirru::leaf("x"), Cirru::leaf("y")]),
Expand All @@ -220,10 +220,7 @@ fn format_match_without_bending_later_clauses() -> Result<(), String> {

let rendered = format(&xs, CirruWriterOptions::from(false))?;

assert_eq!(
"\nmatch x\n :dyn 1\n (:dyn x) 2\n (:dyn x y) 3\n (:dyn x y z) 4\n",
rendered
);
assert_eq!("\nmatch x\n (:dyn) 1\n (:dyn x) 2\n (:dyn x y) 3\n (:dyn x y z) 4\n", rendered);
Ok(())
}

Expand Down
Loading