diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ee10d35..38b8d37 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -13,7 +13,6 @@ jobs: - uses: actions/checkout@v4 - name: install - if: ${{ matrix.os != 'windows-latest' }} run: | curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash echo "$HOME/.moon/bin" >> $GITHUB_PATH diff --git a/.gitignore b/.gitignore index c673e77..a66830c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target/ .mooncakes/ trace.json +.DS_Store diff --git a/README.md b/README.md index 93172b8..c293306 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ Docs: https://mooncakes.io/docs/#/tiye/cirru-parser/lib/members moon add tiye/cirru-parser ``` -```moon +```moonbit +typealias @cirru_parser.Cirru + // parse Cirru code -@cirru_parser.parse(code: String) : Array[Cirru]!CirruParseError +Cirru::parse(code: String) : Array[Cirru]!CirruParseError // format Cirru code -@cirru_parser.format(cirru: Array[Cirru], {use_inline: false}) : String!FormatCirruError +Cirru::format(cirru: Array[Cirru], {use_inline: false}) : String!FormatCirruError ``` ### License diff --git a/moon.mod.json b/moon.mod.json index 4b24836..ca0a064 100644 --- a/moon.mod.json +++ b/moon.mod.json @@ -1,6 +1,6 @@ { "name": "tiye/cirru-parser", - "version": "0.0.13", + "version": "0.1.0", "deps": {}, "readme": "README.md", "repository": "https://github.com/Cirru/parser.mbt", diff --git a/src/lib/parser.mbt b/src/lib/parser.mbt index ebda89f..4e9e6ed 100644 --- a/src/lib/parser.mbt +++ b/src/lib/parser.mbt @@ -1,5 +1,7 @@ ///| -fn build_exprs(tokens : Array[CirruLexItem]) -> Array[Cirru]!CirruParseError { +fn build_exprs( + tokens : Array[CirruLexItem] +) -> Array[Cirru] raise CirruParseError { let acc : Array[Cirru] = Array::new() let mut idx = 0 let pull_token = fn() -> CirruLexItem? { @@ -17,40 +19,39 @@ fn build_exprs(tokens : Array[CirruLexItem]) -> Array[Cirru]!CirruParseError { None => return acc Some(ck) => match ck { - CirruLexItem::Open => { - let mut pointer : Array[Cirru] = Array::new() + Open => { + let mut pointer : Array[Cirru] = Array::new(capacity=8) // guess a nested level of 16 - let pointer_stack : Array[Array[Cirru]] = Array::new() + let pointer_stack : Array[Array[Cirru]] = Array::new(capacity=16) for { let cursor = pull_token() match cursor { None => raise CirruParseError("unexpected end of file") Some(c) => match c { - CirruLexItem::Close => + Close => match pointer_stack.pop() { None => { - acc.push(Cirru::List(pointer)) + acc.push(List(pointer)) break } Some(v) => { let prev_p = pointer pointer = v - pointer.push(Cirru::List(prev_p)) + pointer.push(List(prev_p)) } } - CirruLexItem::Open => { + Open => { pointer_stack.push(pointer) pointer = Array::new() } - CirruLexItem::Str(s) => pointer.push(Cirru::Leaf(s)) - CirruLexItem::Indent(n) => - raise CirruParseError("unknown indent: \{n}") + Str(s) => pointer.push(Leaf(s)) + Indent(n) => raise CirruParseError("unknown indent: \{n}") } } } } - CirruLexItem::Close => raise CirruParseError("unexpected \")\"") + Close => raise CirruParseError("unexpected \")\"") a => raise CirruParseError("unknown item: \{a}") } } @@ -58,16 +59,16 @@ fn build_exprs(tokens : Array[CirruLexItem]) -> Array[Cirru]!CirruParseError { } ///| main function to parse Cirru code into syntax tree -pub fn parse(code : String) -> Array[Cirru]!CirruParseError { - let tokens = resolve_indentations(lex!(code)) +pub fn Cirru::parse(code : String) -> Array[Cirru] raise CirruParseError { + let tokens = resolve_indentations(lex(code)) // println("tokens: \{tokens}") - let tree = build_exprs!(tokens) + let tree = build_exprs(tokens) // println("tree: \{tree}") resolve_comma(resolve_dollar(tree)) } ///| -type! CirruParseError String +suberror CirruParseError String ///| display Cirru parse error pub fn to_string(self : CirruParseError) -> String { @@ -77,18 +78,18 @@ pub fn to_string(self : CirruParseError) -> String { } ///| -fn parse_indentation(size : Int) -> CirruLexItem!CirruParseError { +fn parse_indentation(size : Int) -> CirruLexItem raise CirruParseError { if size % 2 == 0 { - CirruLexItem::Indent(size >> 1) + Indent(size >> 1) } else { raise CirruParseError("odd indentation size: \{size}") } } ///| lexer is a simpler state machine to tokenize Cirru code -fn lex(initial_code : String) -> Array[CirruLexItem]!CirruParseError { +fn lex(initial_code : String) -> Array[CirruLexItem] raise CirruParseError { let acc = [] - let mut state = CirruLexState::Indent + let mut state : CirruLexState = Indent let mut buffer = "" let code = initial_code.to_array() for idx, c in code { @@ -97,103 +98,103 @@ fn lex(initial_code : String) -> Array[CirruLexItem]!CirruParseError { // "lexing \{c.to_string().escape()} in \{state} and \{buffer.escape()}", // ) match state { - CirruLexState::Space => + Space => match c { ' ' => { - state = CirruLexState::Space + state = Space buffer = "" } '\n' => { - state = CirruLexState::Indent + state = Indent buffer = "" } '(' => { - acc.push(CirruLexItem::Open) - state = CirruLexState::Space + acc.push(Open) + state = Space buffer = "" } ')' => { - acc.push(CirruLexItem::Close) - state = CirruLexState::Space + acc.push(Close) + state = Space buffer = "" } '"' => { - state = CirruLexState::Str + state = Str buffer = "" } _ => { - state = CirruLexState::Token + state = Token buffer = c.to_string() } } - CirruLexState::Token => + Token => match c { ' ' => { - acc.push(CirruLexItem::Str(buffer)) - state = CirruLexState::Space + acc.push(Str(buffer)) + state = Space buffer = "" } '"' => { - acc.push(CirruLexItem::Str(buffer)) - state = CirruLexState::Str + acc.push(Str(buffer)) + state = Str buffer = "" } '\n' => { - acc.push(CirruLexItem::Str(buffer)) - state = CirruLexState::Indent + acc.push(Str(buffer)) + state = Indent buffer = "" } '(' => { - acc.push(CirruLexItem::Str(buffer)) - acc.push(CirruLexItem::Open) - state = CirruLexState::Space + acc.push(Str(buffer)) + acc.push(Open) + state = Space buffer = "" } ')' => { - acc.push(CirruLexItem::Str(buffer)) - acc.push(CirruLexItem::Close) - state = CirruLexState::Space + acc.push(Str(buffer)) + acc.push(Close) + state = Space buffer = "" } _ => { - state = CirruLexState::Token + state = Token buffer += c.to_string() } } - CirruLexState::Str => + Str => match c { '"' => { - acc.push(CirruLexItem::Str(buffer)) - state = CirruLexState::Space + acc.push(Str(buffer)) + state = Space buffer = "" } - '\\' => state = CirruLexState::Escape + '\\' => state = Escape '\n' => raise CirruParseError("unexpected newline in string") _ => { - state = CirruLexState::Str + state = Str buffer += c.to_string() } } - CirruLexState::Escape => + Escape => match c { '"' => { - state = CirruLexState::Str + state = Str buffer += "\"" } '\'' => { - state = CirruLexState::Str + state = Str buffer += "'" } 't' => { - state = CirruLexState::Str + state = Str buffer += "\t" } 'n' => { - state = CirruLexState::Str + state = Str buffer += "\n" } 'r' => { - state = CirruLexState::Str + state = Str buffer += "\r" } 'u' => { @@ -210,10 +211,10 @@ fn lex(initial_code : String) -> Array[CirruLexItem]!CirruParseError { } println("Unicode escaping is not supported yet: \{preview} ...") buffer += "\\u" - state = CirruLexState::Str + state = Str } '\\' => { - state = CirruLexState::Str + state = Str buffer += "\\" } _ => @@ -224,31 +225,31 @@ fn lex(initial_code : String) -> Array[CirruLexItem]!CirruParseError { Indent => match c { ' ' => { - state = CirruLexState::Indent + state = Indent buffer += c.to_string() } '\n' => { - state = CirruLexState::Indent + state = Indent buffer = "" } '"' => { - let level = parse_indentation!(buffer.length()) + let level = parse_indentation(buffer.length()) acc.push(level) - state = CirruLexState::Str + state = Str buffer = "" } '(' => { - let level = parse_indentation!(buffer.length()) + let level = parse_indentation(buffer.length()) acc.push(level) acc.push(CirruLexItem::Open) - state = CirruLexState::Space + state = Space buffer = "" } ')' => raise CirruParseError("unexpected ) at line start") _ => { - let level = parse_indentation!(buffer.length()) + let level = parse_indentation(buffer.length()) acc.push(level) - state = CirruLexState::Token + state = Token buffer = c.to_string() } } @@ -256,14 +257,14 @@ fn lex(initial_code : String) -> Array[CirruLexItem]!CirruParseError { } // println("end of file in lex \{acc}") match state { - CirruLexState::Space => acc - CirruLexState::Token => { + Space => acc + Token => { acc.push(CirruLexItem::Str(buffer)) acc } - CirruLexState::Escape => raise CirruParseError("unknown escape") - CirruLexState::Indent => acc - CirruLexState::Str => raise CirruParseError("finished at string") + Escape => raise CirruParseError("unknown escape") + Indent => acc + Str => raise CirruParseError("finished at string") } } @@ -278,32 +279,32 @@ fn resolve_indentations(tokens : Array[CirruLexItem]) -> Array[CirruLexItem] { if acc.is_empty() { return Array::new() } - acc.insert(0, CirruLexItem::Open) + acc.insert(0, Open) for i = 0; i < level; i = i + 1 { - acc.push(CirruLexItem::Close) + acc.push(Close) } - acc.push(CirruLexItem::Close) + acc.push(Close) return acc } match tokens[pointer] { - CirruLexItem::Str(s) => { - acc.push(CirruLexItem::Str(s)) + Str(s) => { + acc.push(Str(s)) pointer += 1 } - CirruLexItem::Open => { - acc.push(CirruLexItem::Open) + Open => { + acc.push(Open) pointer += 1 } - CirruLexItem::Close => { - acc.push(CirruLexItem::Close) + Close => { + acc.push(Close) pointer += 1 } - CirruLexItem::Indent(n) => + Indent(n) => if n > level { let delta = n - level // for _ in 0..delta { for i = 0; i < delta; i = i + 1 { - acc.push(CirruLexItem::Open) + acc.push(Open) } pointer += 1 level = n @@ -311,18 +312,18 @@ fn resolve_indentations(tokens : Array[CirruLexItem]) -> Array[CirruLexItem] { let delta = level - n // for _ in 0..delta { for i = 0; i < delta; i = i + 1 { - acc.push(CirruLexItem::Close) + acc.push(Close) } - acc.push(CirruLexItem::Close) - acc.push(CirruLexItem::Open) + acc.push(Close) + acc.push(Open) pointer += 1 level = n } else { if acc.is_empty() { acc = Array::new() } else { - acc.push(CirruLexItem::Close) - acc.push(CirruLexItem::Open) + acc.push(Close) + acc.push(Open) } pointer += 1 } diff --git a/src/lib/parser_test.mbt b/src/lib/parser_test.mbt index bc6be5b..b70b01f 100644 --- a/src/lib/parser_test.mbt +++ b/src/lib/parser_test.mbt @@ -1,9 +1,12 @@ +///| +typealias @lib.Cirru + +///| test "parser" { - assert_eq!(@lib.parse!("def a"), [ - @lib.Cirru::List([@lib.Cirru::Leaf("def"), @lib.Cirru::Leaf("a")]), - ]) + assert_eq(Cirru::parse("def a"), [List([Leaf("def"), Leaf("a")])]) } +///| test "existed demos" { let demos = [ "comma", "demo", "folding", "html", "indent-twice", "indent", "let", "line", @@ -13,15 +16,15 @@ test "existed demos" { for demo in demos { let demo_file = fsReadSync("./test/cirru/\{demo}.cirru") let json_file = fsReadSync("./test/data/\{demo}.json") - let tree = @lib.parse?(demo_file).unwrap().to_json().stringify() + let tree = Cirru::parse?(demo_file).unwrap().to_json().stringify() let defined = @json.parse?(json_file).unwrap() let defined_str = defined.stringify() - assert_eq!(tree, defined_str) - let formatted = @lib.format!(@json.from_json!(defined), { + assert_eq(tree, defined_str) + let formatted = Cirru::format(@json.from_json(defined), { use_inline: false, }) - let ret = @lib.parse?(formatted).unwrap().to_json().stringify() - assert_eq!(ret, defined_str) + let ret = Cirru::parse?(formatted).unwrap().to_json().stringify() + assert_eq(ret, defined_str) } } diff --git a/src/lib/primes.mbt b/src/lib/primes.mbt index f214a9c..6f7e41a 100644 --- a/src/lib/primes.mbt +++ b/src/lib/primes.mbt @@ -10,8 +10,8 @@ pub(all) enum Cirru { pub impl Hash for Cirru with hash(self) { let mut i = 0 match self { - Cirru::Leaf(s) => i = s.length() - Cirru::List(xs) => + Leaf(s) => i = s.length() + List(xs) => for _ in xs { i = i + 1 // TODO: how to hash a list? } @@ -22,8 +22,8 @@ pub impl Hash for Cirru with hash(self) { ///| pub impl Hash for Cirru with hash_combine(self, hasher) { match self { - Cirru::Leaf(s) => hasher.combine_string(s) - Cirru::List(xs) => + Leaf(s) => hasher.combine_string(s) + List(xs) => for x in xs { hasher.combine(x) } @@ -33,8 +33,8 @@ pub impl Hash for Cirru with hash_combine(self, hasher) { ///| pub impl ToJson for Cirru with to_json(self : Cirru) -> Json { match self { - Cirru::Leaf(s) => Json::string(s) - Cirru::List(xs) => { + Leaf(s) => Json::string(s) + List(xs) => { let out = Array::new() for x in xs { out.push(x.to_json()) @@ -51,10 +51,10 @@ pub impl @json.FromJson for Cirru with from_json(json, path) { Array(xs) => { let ys = [] for idx, x in xs { - let v = @json.FromJson::from_json!(x, path.add_index(idx)) + let v = @json.FromJson::from_json(x, path.add_index(idx)) ys.push(v) } - Cirru::List(ys) + List(ys) } // _ => @json.decode_error!((path, "Char::from_json: expected string")) _ => @@ -65,25 +65,20 @@ pub impl @json.FromJson for Cirru with from_json(json, path) { } ///| -fn Cirru::default() -> Cirru { - Cirru::List(Array::new()) +impl Default for Cirru with default() -> Cirru { + List(Array::new()) } ///| pub impl Show for Cirru with output(self : Cirru, logger : &Logger) -> Unit { - logger.write_string(self.to_string()) -} - -///| -pub fn Cirru::to_string(self : Cirru) -> String { match self { - Cirru::Leaf(s) => + Leaf(s) => if CirruLexItem::is_normal_str(s) { - return s + logger.write_string(s) } else { - return escape_cirru_leaf(s) + logger.write_string(escape_cirru_leaf(s)) } - Cirru::List(xs) => { + List(xs) => { let mut out = "" for i = 0; i < xs.length(); i = i + 1 { let x = xs[i] @@ -92,16 +87,16 @@ pub fn Cirru::to_string(self : Cirru) -> String { out = "\{out} " } } - return out + logger.write_string(out) } } } ///| -pub fn compare(self : Cirru, other : Cirru) -> Int { +pub impl Compare for Cirru with compare(self, other) -> Int { match (self, other) { - (Cirru::Leaf(a), Cirru::Leaf(b)) => a.compare(b) - (Cirru::List(xs), Cirru::List(ys)) => { + (Leaf(a), Leaf(b)) => a.compare(b) + (List(xs), List(ys)) => { let size = @math.minimum(xs.length(), ys.length()) for i = 0; i < size; i = i + 1 { let x = xs[i] @@ -113,13 +108,13 @@ pub fn compare(self : Cirru, other : Cirru) -> Int { } return xs.length().compare(ys.length()) } - (Cirru::Leaf(_), Cirru::List(_)) => -1 - (Cirru::List(_), Cirru::Leaf(_)) => 1 + (Leaf(_), List(_)) => -1 + (List(_), Leaf(_)) => 1 } } ///| -pub fn length(self : Cirru) -> Int { +pub fn Cirru::length(self : Cirru) -> Int { match self { Leaf(s) => s.length() List(xs) => xs.length() @@ -127,7 +122,7 @@ pub fn length(self : Cirru) -> Int { } ///| -pub fn is_empty(self : Cirru) -> Bool { +pub fn Cirru::is_empty(self : Cirru) -> Bool { match self { Leaf(s) => s.length() == 0 List(xs) => xs.length() == 0 @@ -135,7 +130,7 @@ pub fn is_empty(self : Cirru) -> Bool { } ///| -pub fn is_nested(self : Cirru) -> Bool { +pub fn Cirru::is_nested(self : Cirru) -> Bool { match self { Leaf(_) => false List(xs) => { @@ -151,43 +146,48 @@ pub fn is_nested(self : Cirru) -> Bool { } ///| -pub fn is_comment(self : Cirru) -> Bool { +pub fn Cirru::is_comment(self : Cirru) -> Bool { match self { - Leaf(s) => s[0] == ';' + Leaf(s) => s.char_at(0) == ';' _ => false } } -///| +///| lexer is a simpler state machine to tokenize Cirru code priv enum CirruLexState { + /// Space, waiting for next token Space + /// Token, a normal token Token + /// Escape, waiting for a character or some special sequence Escape + /// Working on indentations Indent + /// Working on a string Str } derive(Show) -///| +///| lexer is a simpler state machine to tokenize Cirru code priv enum CirruLexItem { + /// `(`, start of a list Open + /// `)`, end of a list Close + /// Indentation level, used for nested lists Indent(Int) + /// A string, which can be a normal string or a special one Str(String) } ///| impl Show for CirruLexItem with output(self : CirruLexItem, logger : &Logger) -> Unit { - logger.write_string(self.to_string()) -} - -///| -fn CirruLexItem::to_string(self : CirruLexItem) -> String { - match self { - CirruLexItem::Open => "(" - CirruLexItem::Close => ")" - CirruLexItem::Indent(size) => "indent(\{size})" - CirruLexItem::Str(s) => s + let content = match self { + Open => "(" + Close => ")" + Indent(size) => "indent(\{size})" + Str(s) => s } + logger.write_string(content) } ///| @@ -198,29 +198,29 @@ fn CirruLexItem::is_normal_str(tok : String) -> Bool { } let mut i = 0 while i < size { - let c = tok[i] - if (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - c == '-' || - c == '?' || - c == '!' || - c == '+' || - c == '*' || - c == '$' || - c == '@' || - c == '#' || - c == '%' || - c == '&' || - c == '_' || - c == '=' || - c == '|' || - c == ':' || - c == '.' || - c == '<' || - c == '>' { - i = i + 1 - } else { - return false + let c = tok.char_at(i) + match c { + 'A'..='Z' => () + 'a'..='z' => () + '0'..='9' => () + '-' => () + '?' => () + '!' => () + '+' => () + '*' => () + '$' => () + '@' => () + '#' => () + '%' => () + '&' => () + '_' => () + '=' => () + '|' => () + ':' => () + '.' => () + '<' => () + '>' => () + _ => return false } i = i + 1 } @@ -236,23 +236,18 @@ fn escape_cirru_leaf(s : String) -> String { let size = s.length() let mut i = 0 while i < size { - let c = s[i] - if c == '\n' { - out = out + "\\n" - } else if c == '\t' { - out = out + "\\t" - } else if c == '"' { - out = out + "\\\"" - } else if c == '\\' { - out = out + "\\\\" - } else if c == '\'' { - out = out + "\\'" - } else { - out = "\{out}\{c}" + let c = s.char_at(i) + match c { + '\n' => out += "\\n" + '\t' => out += "\\t" + '"' => out += "\\\"" + '\\' => out += "\\\\" + '\'' => out += "\\'" + _ => out += "\{c}" } i = i + 1 } - out = out + "\"" + out += "\"" return out } } diff --git a/src/lib/s_expr.mbt b/src/lib/s_expr.mbt index 1fb6bd2..145e5cd 100644 --- a/src/lib/s_expr.mbt +++ b/src/lib/s_expr.mbt @@ -1,20 +1,20 @@ ///| -type! FormatError String +suberror FormatError String ///| format program to Cirru to Lisp-like code -pub fn format_to_lisp(xs : Array[Cirru]) -> String!FormatError { +pub fn format_to_lisp(xs : Array[Cirru]) -> String raise FormatError { let mut content : String = "\n" for expr in xs { - content = content + expr.format_lispy_expr!(0) + content = content + expr.format_lispy_expr(0) } content } ///| format single expression to Lisp-like code -fn format_lispy_expr(self : Cirru, indent : Int) -> String!FormatError { +fn format_lispy_expr(self : Cirru, indent : Int) -> String raise FormatError { let emptySpace = " " match self { - Cirru::List(xs) => + List(xs) => if xs.length() > 0 && xs[0].is_comment() { let mut chunk : String = "\{gen_newline(indent)};;" for idx, x in xs { @@ -32,7 +32,7 @@ fn format_lispy_expr(self : Cirru, indent : Int) -> String!FormatError { if x.is_nested() { chunk = "\{chunk.trim_end(emptySpace)}\{gen_newline(indent + 1)}" } - let next = format_lispy_expr!(x, indent + 1) + let next = x.format_lispy_expr(indent + 1) if next.has_prefix("\n") { chunk = "\{chunk.trim_end(emptySpace)}\{next}" } else { @@ -45,11 +45,11 @@ fn format_lispy_expr(self : Cirru, indent : Int) -> String!FormatError { } "\{chunk})" } - Cirru::Leaf(token) => + Leaf(token) => if token.is_empty() { raise FormatError("empty string is invalid") } else { - let s0 = token[0] + let s0 = token.char_at(0) if s0 == '|' || s0 == '"' { "\"" + escape_string(token.substring(start=1)) + "\"" } else if token.contains(" ") || diff --git a/src/lib/tree.mbt b/src/lib/tree.mbt index 6634d39..261c484 100644 --- a/src/lib/tree.mbt +++ b/src/lib/tree.mbt @@ -9,7 +9,7 @@ fn resolve_comma(xs : Array[Cirru]) -> Array[Cirru] { ///| fn comma_helper(initial_after : Array[Cirru]) -> Array[Cirru] { - let before : Array[Cirru] = [] + let before : Array[Cirru] = Array::new(capacity=initial_after.length()) let after : Array[Cirru] = initial_after let mut pointer = 0 for { @@ -17,21 +17,21 @@ fn comma_helper(initial_after : Array[Cirru]) -> Array[Cirru] { return before } match after[pointer] { - Cirru::List(xs) => + List(xs) => if not(xs.is_empty()) { match xs[0] { - Cirru::List(_) => before.push(Cirru::List(resolve_comma(xs))) - Cirru::Leaf(s) => + List(_) => before.push(List(resolve_comma(xs))) + Leaf(s) => if s == "," { - before.push_iter(resolve_comma(xs[1:].iter().to_array()).iter()) + before.push_iter(resolve_comma(xs[1:].to_array()).iter()) } else { - before.push(Cirru::List(resolve_comma(xs))) + before.push(List(resolve_comma(xs))) } } } else { - before.push(Cirru::List([])) + before.push(List([])) } - Cirru::Leaf(_) => before.push(after[pointer]) + Leaf(_) => before.push(after[pointer]) } pointer += 1 } @@ -48,7 +48,7 @@ fn resolve_dollar(xs : Array[Cirru]) -> Array[Cirru] { ///| fn dollar_helper(initial_after : Array[Cirru]) -> Array[Cirru] { - let before : Array[Cirru] = [] + let before : Array[Cirru] = Array::new(capacity=initial_after.length()) let after : Array[Cirru] = initial_after let mut pointer = 0 for { @@ -56,18 +56,18 @@ fn dollar_helper(initial_after : Array[Cirru]) -> Array[Cirru] { return before } else { match after[pointer] { - Cirru::List(xs) => { - before.push(Cirru::List(resolve_dollar(xs))) + List(xs) => { + before.push(List(resolve_dollar(xs))) pointer += 1 } - Cirru::Leaf(s) => + Leaf(s) => if s == "$" { before.push( - Cirru::List(resolve_dollar(after[pointer + 1:].iter().to_array())), + List(resolve_dollar(after[pointer + 1:].iter().to_array())), ) pointer = after.length() } else { - before.push(Cirru::Leaf(s)) + before.push(Leaf(s)) pointer += 1 } } diff --git a/src/lib/writer.mbt b/src/lib/writer.mbt index a4b8eb9..49b22f8 100644 --- a/src/lib/writer.mbt +++ b/src/lib/writer.mbt @@ -1,19 +1,24 @@ ///| priv enum WriterNode { + /// Nil node, which is used to indicate no previous node Nil + /// Leaf node, which is a string Leaf + /// SimpleExpr node, which is a simple expression with no nested lists SimpleExpr + /// Simple expression, which is a list with no nested lists BoxedExpr + /// Expr node, which can be complex Expr } derive(Eq) -///| +///| `)`, end of a list let char_close : Char = ')' -///| +///| `(`, start of a list) let char_open : Char = '(' -///| +///| Allowed characters in a leaf node, which can be used to generate a string safely let allowed_chars : String = "$-:<>[]{}*=+.,\\/!?~_@#&%^|;'" ///| @@ -25,39 +30,36 @@ fn is_a_digit(c : Char) -> Bool { ///| fn is_a_letter(c : Char) -> Bool { - let n = c.to_int() - if n >= 65 && n <= 90 { - return true + match c { + 'A'..='Z' => true + 'a'..='z' => true + _ => false } - if n >= 97 && n <= 122 { - return true - } - false } -///| +///| Check if the expression is simple, which means it has no nested lists fn is_simple_expr(ys : Array[Cirru]) -> Bool { for y in ys { match y { - Cirru::List(_) => return false - Cirru::Leaf(_) => () + List(_) => return false + Leaf(_) => () } } true } -///| +///| Check if the expression is boxed, which means it has no nested lists fn is_boxed(ys : Array[Cirru]) -> Bool { for y in ys { match y { - Cirru::List(_) => return false - Cirru::Leaf(_) => () + Leaf(_) => return false + List(_) => () } } true } -///| +///| Check if the character is a simple character, which means it is a letter or a digit fn is_simple_char(x : Char) -> Bool { is_a_letter(x) || is_a_digit(x) } @@ -70,7 +72,7 @@ fn is_char_allowed(x : Char) -> Bool { allowed_chars.contains_char(x) } -///| +///| Generate a leaf node, which is a string that may contain special characters fn generate_leaf(s : String) -> String { let mut all_allowed = true for x in s { @@ -83,19 +85,19 @@ fn generate_leaf(s : String) -> String { s.to_string() } else { let mut ret = "" - ret = ret + "\"" + ret += "\"" for c in s { - ret = ret + - (match c { - '\n' => "\\n" - '\t' => "\\t" - '\"' => "\\\"" - '\\' => "\\\\" - '\'' => "\\'" - _ => "\{c}" - }) + let content = match c { + '\n' => "\\n" + '\t' => "\\t" + '\"' => "\\\"" + '\\' => "\\\\" + '\'' => "\\'" + _ => "\{c}" + } + ret += content } - ret = ret + "\"" + ret += "\"" ret } } @@ -110,26 +112,23 @@ fn generate_inline_expr(xs : Array[Cirru]) -> String { let mut result = char_open.to_string() for idx, x in xs { if idx > 0 { - result = result + " " + result += " " } let piece = match x { - Cirru::Leaf(s) => generate_leaf(s) - Cirru::List(ys) => generate_inline_expr(ys) + Leaf(s) => generate_leaf(s) + List(ys) => generate_inline_expr(ys) } - result = result + piece + result += piece } - result = result + char_close.to_string() + result += char_close.to_string() result } ///| by 2 spaces fn push_spaces(buf : String, n : Int) -> String { let mut ret = buf - // for _ in 0..n { - // buf.push_str(" "); - // } for i = 0; i < n; i = i + 1 { - ret = ret + " " + ret += " " } return ret } @@ -137,7 +136,7 @@ fn push_spaces(buf : String, n : Int) -> String { ///| fn render_newline(n : Int) -> String { let mut ret = "" - ret = ret + "\n" + ret += "\n" ret = push_spaces(ret, n) ret } @@ -148,31 +147,24 @@ pub(all) struct CirruWriterOptions { } ///| kind for writer nodes -fn get_node_kind(self : Cirru) -> WriterNode { +fn Cirru::get_node_kind(self : Cirru) -> WriterNode { match self { - Cirru::Leaf(_) => WriterNode::Leaf - Cirru::List(xs) => + Leaf(_) => Leaf + List(xs) => if xs.is_empty() { - WriterNode::Leaf + Leaf } else if is_simple_expr(xs) { - WriterNode::SimpleExpr + SimpleExpr } else if is_boxed(xs) { - WriterNode::BoxedExpr + BoxedExpr } else { - WriterNode::Expr + Expr } } } ///| -pub(all) type! FormatCirruError String - -///| -pub fn FormatCirruError::to_string(self : FormatCirruError) -> String { - match self { - FormatCirruError(s) => s - } -} +pub(all) suberror FormatCirruError String derive(Show) ///| fn generate_tree( @@ -181,18 +173,17 @@ fn generate_tree( options : CirruWriterOptions, base_level : Int, in_tail : Bool -) -> String!FormatCirruError { - let mut prev_kind = WriterNode::Nil +) -> String raise FormatCirruError { + let mut prev_kind : WriterNode = Nil let mut level = base_level let mut result = "" for idx, cursor in xs { - let kind = get_node_kind(cursor) + let kind = cursor.get_node_kind() let next_level = level + 1 - let child_insist_head = prev_kind == WriterNode::BoxedExpr || - prev_kind == WriterNode::Expr + let child_insist_head = prev_kind == BoxedExpr || prev_kind == Expr let at_tail = idx != 0 && not(in_tail) && - prev_kind == WriterNode::Leaf && + prev_kind == Leaf && idx == xs.length() - 1 // println!("\nloop {:?} {:?}", prev_kind, kind); @@ -200,19 +191,19 @@ fn generate_tree( // println!("{:?}", result); let child : String = match cursor { - Cirru::Leaf(s) => generate_leaf(s) - Cirru::List(ys) => + Leaf(s) => generate_leaf(s) + List(ys) => if at_tail { if ys.is_empty() { "$" } else { let mut ret = "$ " - ret = ret + generate_tree!(ys, false, options, level, at_tail) + ret = ret + generate_tree(ys, false, options, level, at_tail) ret } } else if idx == 0 && insist_head { generate_inline_expr(ys) - } else if kind == WriterNode::Leaf { + } else if kind == Leaf { if idx == 0 { let mut ret = render_newline(level) ret = ret + generate_empty_expr() @@ -220,21 +211,21 @@ fn generate_tree( } else { generate_empty_expr() // special since empty expr is treated as leaf } - } else if kind == WriterNode::SimpleExpr { - if prev_kind == WriterNode::Leaf { + } else if kind == SimpleExpr { + if prev_kind == Leaf { generate_inline_expr(ys) - } else if options.use_inline && prev_kind == WriterNode::SimpleExpr { + } else if options.use_inline && prev_kind == SimpleExpr { let mut ret = " " ret = ret + generate_inline_expr(ys) ret } else { let mut ret = render_newline(next_level) ret = ret + - generate_tree!(ys, child_insist_head, options, next_level, false) + generate_tree(ys, child_insist_head, options, next_level, false) ret } - } else if kind == WriterNode::Expr { - let content = generate_tree!( + } else if kind == Expr { + let content = generate_tree( ys, child_insist_head, options, next_level, false, ) if content.has_prefix("\n") { @@ -244,13 +235,11 @@ fn generate_tree( ret = ret + content ret } - } else if kind == WriterNode::BoxedExpr { - let content = generate_tree!( + } else if kind == 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 prev_kind == Nil || prev_kind == Leaf || prev_kind == SimpleExpr { content } else { let mut ret = render_newline(next_level) @@ -261,12 +250,11 @@ fn generate_tree( raise FormatCirruError("Unpected condition") } } - let bended = kind == WriterNode::Leaf && - (prev_kind == WriterNode::BoxedExpr || prev_kind == WriterNode::Expr) + let bended = kind == Leaf && (prev_kind == BoxedExpr || prev_kind == Expr) let chunk = if at_tail || - (prev_kind == WriterNode::Leaf && kind == WriterNode::Leaf) || - (prev_kind == WriterNode::Leaf && kind == WriterNode::SimpleExpr) || - (prev_kind == WriterNode::SimpleExpr && kind == WriterNode::Leaf) { + (prev_kind == Leaf && kind == Leaf) || + (prev_kind == Leaf && kind == SimpleExpr) || + (prev_kind == SimpleExpr && kind == Leaf) { let mut ret = " " ret = ret + child ret @@ -281,19 +269,19 @@ fn generate_tree( // update writer states - if kind == WriterNode::SimpleExpr { + if kind == SimpleExpr { if idx == 0 && insist_head { - prev_kind = WriterNode::SimpleExpr + prev_kind = SimpleExpr } else if options.use_inline { - if prev_kind == WriterNode::Leaf || prev_kind == WriterNode::SimpleExpr { - prev_kind = WriterNode::SimpleExpr + if prev_kind == Leaf || prev_kind == SimpleExpr { + prev_kind = SimpleExpr } else { - prev_kind = WriterNode::Expr + prev_kind = Expr } - } else if prev_kind == WriterNode::Leaf { - prev_kind = WriterNode::SimpleExpr + } else if prev_kind == Leaf { + prev_kind = SimpleExpr } else { - prev_kind = WriterNode::Expr + prev_kind = Expr } } else { prev_kind = kind @@ -312,15 +300,15 @@ fn generate_tree( fn generate_statements( ys : Array[Cirru], options : CirruWriterOptions -) -> String!FormatCirruError { +) -> String raise FormatCirruError { let mut zs = "" for y in ys { match y { - Cirru::Leaf(_) => raise FormatCirruError("expected an exprs at top level") - Cirru::List(cs) => { - zs = zs + "\n" - zs = zs + generate_tree!(cs, true, options, 0, false) - zs = zs + "\n" + Leaf(_) => raise FormatCirruError("expected an exprs at top level") + List(cs) => { + zs += "\n" + zs += generate_tree(cs, true, options, 0, false) + zs += "\n" } } } @@ -328,9 +316,9 @@ fn generate_statements( } ///| format Cirru code, use options to control `use_inline` option -pub fn format( +pub fn Cirru::format( xs : Array[Cirru], options : CirruWriterOptions -) -> String!FormatCirruError { - generate_statements!(xs, options) +) -> String raise FormatCirruError { + generate_statements(xs, options) } diff --git a/src/main/main.mbt b/src/main/main.mbt index 2160713..046a915 100644 --- a/src/main/main.mbt +++ b/src/main/main.mbt @@ -1,26 +1,29 @@ +///| +typealias @lib.Cirru + ///| pub fn main_parser() -> Unit { // let tree = @lib.parse?("DEMO") // match tree { - // Result::Ok(tree) => { + // Ok(tree) => { // println(tree.to_json().stringify()) // println(tree)} - // Result::Err(err) => println(err.to_string()) + // Err(err) => println(err.to_string()) // } let demo_file = fsReadSync("./test/cirru/unfolding.cirru") let json_file = fsReadSync("./test/data/unfolding.json") - let tree = @lib.parse?(demo_file) + let tree = Cirru::parse?(demo_file) match tree { - Result::Ok(tree) => { + Ok(tree) => { println(tree.to_json().stringify()) match @json.parse?(json_file) { - Result::Ok(json) => println(json.stringify()) - Result::Err(err) => println(err.to_string()) + Ok(json) => println(json.stringify()) + Err(err) => println(err.to_string()) } // println(tree) } - Result::Err(err) => println(err.to_string()) + Err(err) => println(err.to_string()) } } @@ -30,12 +33,12 @@ pub fn main_writer() -> Unit { let demo_file = fsReadSync("./test/cirru/\{demo}.cirru") let json_file = fsReadSync("./test/data/\{demo}.json") let defined = @json.parse?(json_file).unwrap() - let tree : Array[@lib.Cirru] = @json.from_json?(defined).unwrap() + let tree : Array[Cirru] = @json.from_json?(defined).unwrap() println("TREE:") for item in tree { println(item.to_json().stringify()) } - let ret = @lib.format?(tree, { use_inline: false }).unwrap() + let ret = Cirru::format?(tree, { use_inline: false }).unwrap() println("Generated:") println(ret) println("Expected:")