///|
/// Cirru uses nested Vecters and Strings as data structure
pub(all) enum Cirru {
/// Leaf node, with a string
Leaf(String)
/// List node, with a list of children
List(Array[Cirru])
} derive(Eq)
///|
pub impl Hash for Cirru with hash(self) {
let mut i = 0
match self {
Leaf(s) => i = s.length()
List(xs) =>
for _ in xs {
i = i + 1 // TODO: how to hash a list?
}
}
i
}
///|
pub impl Hash for Cirru with hash_combine(self, hasher) {
match self {
Leaf(s) => hasher.combine_string(s)
List(xs) =>
for x in xs {
hasher.combine(x)
}
}
}
///|
pub impl ToJson for Cirru with to_json(self : Cirru) -> Json {
match self {
Leaf(s) => Json::string(s)
List(xs) => {
let out = Array::new()
for x in xs {
out.push(x.to_json())
}
Json::array(out)
}
}
}
///|
pub impl @json.FromJson for Cirru with from_json(json, path) {
match json {
String(a) => Cirru::Leaf(a)
Array(xs) => {
let ys = []
for idx, x in xs {
let v = @json.FromJson::from_json(x, path.add_index(idx))
ys.push(v)
}
List(ys)
}
// _ => @json.decode_error!((path, "Char::from_json: expected string"))
_ =>
raise @json.JsonDecodeError(
(path, "Cirru::from_json: expected string or array"),
)
}
}
///|
pub impl Show for Cirru with output(self : Cirru, logger : &Logger) -> Unit {
match self {
Leaf(s) =>
if CirruLexItem::is_normal_str(s) {
logger.write_string(s)
} else {
logger.write_string(escape_cirru_leaf(s))
}
List(xs) => {
let mut out = ""
for i = 0; i < xs.length(); i = i + 1 {
let x = xs[i]
out = "\{out}\{x}"
if i < xs.length() - 1 {
out = "\{out} "
}
}
logger.write_string(out)
}
}
}
///|
pub impl Compare for Cirru with compare(self, other) -> Int {
match (self, other) {
(Leaf(a), Leaf(b)) => a.compare(b)
(List(xs), List(ys)) => {
let size = @cmp.minimum(xs.length(), ys.length())
for i = 0; i < size; i = i + 1 {
let x = xs[i]
let y = ys[i]
let c = x.compare(y)
if c != 0 {
return c
}
}
return xs.length().compare(ys.length())
}
(Leaf(_), List(_)) => -1
(List(_), Leaf(_)) => 1
}
}
///|
pub fn Cirru::length(self : Cirru) -> Int {
match self {
Leaf(s) => s.length()
List(xs) => xs.length()
}
}
///|
pub fn Cirru::is_empty(self : Cirru) -> Bool {
match self {
Leaf(s) => s.length() == 0
List(xs) => xs.length() == 0
}
}
///|
pub fn Cirru::is_nested(self : Cirru) -> Bool {
match self {
Leaf(_) => false
List(xs) => {
for i = 0; i < xs.length(); i = i + 1 {
let x = xs[i]
if x.is_nested() {
return true
}
}
return false
}
}
}
///|
pub fn Cirru::is_comment(self : Cirru) -> Bool {
match self {
Leaf(s) => s.length() > 0 && s[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
}
///|
/// 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 {
let content = match self {
Open => "("
Close => ")"
Indent(size) => "indent(\{size})"
Str(s) => s
}
logger.write_string(content)
}
///|
fn CirruLexItem::is_normal_str(tok : String) -> Bool {
let size = tok.length()
if size == 0 {
return false
}
let mut i = 0
while i < size {
let c = tok[i]
match c {
'A'..='Z' => ()
'a'..='z' => ()
'0'..='9' => ()
'-' => ()
'?' => ()
'!' => ()
'+' => ()
'*' => ()
'$' => ()
'@' => ()
'#' => ()
'%' => ()
'&' => ()
'_' => ()
'=' => ()
'|' => ()
':' => ()
'.' => ()
'<' => ()
'>' => ()
_ => return false
}
i = i + 1
}
return true
}
///|
fn escape_cirru_leaf(s : String) -> String {
if CirruLexItem::is_normal_str(s) {
return "\"\{s}\""
} else {
let mut out = "\""
let size = s.length()
let mut i = 0
while i < size {
let c = s[i]
match c {
'\n' => out += "\\n"
'\t' => out += "\\t"
'"' => out += "\\\""
'\\' => out += "\\\\"
'\'' => out += "\\'"
_ => out += "\{c}"
}
i = i + 1
}
out += "\""
return out
}
}