///|
using @cirru_parser {type Cirru}
///|
pub(all) suberror EdnCommonError {
EdnCommonError(String)
} derive(Eq, Hash, Show)
///|
pub impl @strconv.FromStr for Edn with from_str(s) {
Edn::parse(s.to_string())
}
///|
/// parse Cirru code into data
pub fn Edn::parse(s : String) -> Edn raise @strconv.StrConvError {
let xs = try! Cirru::parse(s)
if xs.length() == 1 {
match xs[0] {
Leaf(s) =>
raise @strconv.StrConvError("expected expr for data, got leaf: " + s)
List(_) => extract_cirru_edn(xs[0])
}
} else {
raise @strconv.StrConvError(
"Expected 1 expr for edn, got length \{xs.length()}",
)
}
}
///|
fn extract_cirru_edn(
node : @cirru_parser.Cirru,
) -> Edn raise @strconv.StrConvError {
match node {
Leaf(s) =>
match s {
"nil" => Nil
"true" => Bool(true)
"false" => Bool(false)
"" => raise @strconv.StrConvError("empty string is invalid for edn")
s1 =>
match s1.get_char(0).unwrap() {
'\'' => Symbol(try! s1[1:].to_string())
':' => Tag(try! s1[1:].to_string())
'"' | '|' => Str(try! s1[1:].to_string())
_ =>
try
@strconv.parse_double(s1.trim(char_set=" ").to_string())
catch {
e =>
match e {
@strconv.StrConvError(s) =>
raise @strconv.StrConvError(
"failed to parse number: " + s,
)
}
} noraise {
f => Number(f)
}
}
}
List(xs) =>
if xs.is_empty() {
raise @strconv.StrConvError("empty expr is invalid for edn")
} else {
// println("extracting list: " + xs.to_string())
match xs[0] {
Leaf(s) =>
match s {
"quote" =>
if xs.length() == 2 {
Quote(xs[1])
} else {
raise @strconv.StrConvError("missing edn quote value")
}
"do" => {
let mut ret : Edn? = None
for x in xs[1:] {
if is_comment(x) {
continue
}
match ret {
Some(_) =>
raise @strconv.StrConvError("multiple values in do")
None => ret = Some(extract_cirru_edn(x))
}
}
match ret {
None => raise @strconv.StrConvError("missing edn do value")
Some(_v) => ()
}
ret.unwrap_or_error(
@strconv.StrConvError("missing edn do value"),
)
}
"atom" =>
if xs.length() == 2 {
Atom(extract_cirru_edn(xs[1]))
} else {
raise @strconv.StrConvError("missing edn atom value")
}
"::" => {
let mut tag : Edn? = None
let extra : Array[Edn] = []
for x in xs[1:] {
if is_comment(x) {
continue
}
match tag {
Some(_) => {
extra.push(extract_cirru_edn(x))
continue
}
None => tag = Some(extract_cirru_edn(x))
}
}
match tag {
Some(x0) => Tuple({ tag: x0, extra })
None =>
raise @strconv.StrConvError("missing edn :: fst value")
}
}
"[]" => {
let ys : Array[Edn] = []
for x in xs[1:] {
if is_comment(x) {
continue
}
try extract_cirru_edn(x) catch {
v => raise @strconv.StrConvError(v.to_string())
} noraise {
v => ys.push(v)
}
}
List(EdnListView(ys))
}
"#{}" => {
let ys : @hashset.HashSet[Edn] = @hashset.new()
for x in xs[1:] {
if is_comment(x) {
continue
}
try extract_cirru_edn(x) catch {
v => raise @strconv.StrConvError(v.to_string())
} noraise {
v => ys.add(v)
}
}
Edn::Set(EdnSetView(ys))
}
"{}" => {
let zs : Map[Edn, Edn] = {}
for x in xs[1:] {
if is_comment(x) {
continue
}
match x {
Leaf(s) =>
raise @strconv.StrConvError(
"expected a pair, invalid map entry: " + s,
)
List(ys) =>
if ys.length() == 2 {
match
(
try? extract_cirru_edn(ys[0]),
try? extract_cirru_edn(ys[1]),
) {
(Ok(k), Ok(v)) => zs.set(k, v)
(Err(e), _) =>
raise @strconv.StrConvError(
"invalid map entry `\{e}` from `\{ys[0]}`",
)
(Ok(k), Err(e)) =>
raise @strconv.StrConvError(
"invalid map entry for `\{k}`, got \{e}",
)
}
}
}
}
Edn::Map(EdnMapView(zs))
}
"%{}" =>
if xs.length() >= 3 {
let name = match xs[1] {
Leaf(s) =>
EdnTag::new(s.trim_start(char_set=":").to_string())
List(e) =>
raise @strconv.StrConvError(
"expected record name in string: " + e.to_string(),
)
}
let entries : Array[(EdnTag, Edn)] = []
for x in xs[2:] {
if is_comment(x) {
continue
}
match x {
Leaf(s) =>
raise @strconv.StrConvError(
"expected record, invalid record entry: " + s,
)
List(ys) =>
if ys.length() == 2 {
match (ys[0], try? extract_cirru_edn(ys[1])) {
(Leaf(s), Ok(v)) =>
entries.push(
(
EdnTag::new(
s.trim_start(char_set=":").to_string(),
),
v,
),
)
(Leaf(s), Err(e)) =>
raise @strconv.StrConvError(
"invalid record value for `\{s}`, got: \{e}",
)
(List(zs), _) =>
raise @strconv.StrConvError(
"invalid list as record key: \{zs}",
)
}
} else {
raise @strconv.StrConvError(
"expected pair of 2: " + ys.to_string(),
)
}
}
}
if entries.is_empty() {
raise @strconv.StrConvError("empty record is invalid")
}
Edn::Record({ tag: name, extra: entries })
} else {
raise @strconv.StrConvError(
"insufficient items for edn record",
)
}
"buf" => {
let ys : Array[UInt] = []
for x in xs[1:] {
if is_comment(x) {
continue
}
match x {
Leaf(y) =>
if y.length() == 2 {
try @strconv.parse_int(y) catch {
e =>
match e {
@strconv.StrConvError(s) =>
raise @strconv.StrConvError(
"expected length 2 hex string in buffer, got: \{y} \{s}",
)
}
} noraise {
b => ys.push(b.reinterpret_as_uint())
}
} else {
raise @strconv.StrConvError(
"expected length 2 hex string in buffer, got: \{y}",
)
}
_ =>
raise @strconv.StrConvError(
"expected hex string in buffer, got: " + x.to_string(),
)
}
}
Edn::Buffer(ys)
}
a => raise @strconv.StrConvError("invalid operator for edn: " + a)
}
List(a) =>
raise @strconv.StrConvError(
"invalid nodes for edn: " + a.to_string(),
)
}
}
}
}
///|
fn is_comment(node : @cirru_parser.Cirru) -> Bool {
match node {
Leaf(_) => false
List(xs) => xs[0] == @cirru_parser.Cirru::Leaf(";")
}
}
///|
fn assemble_cirru_node(data : Edn) -> @cirru_parser.Cirru raise EdnCommonError {
match data {
Nil => Leaf("nil")
Bool(v) => Leaf(v.to_string())
Number(n) => Leaf(n.to_string())
Symbol(s) => Leaf("'\{s}")
Tag(s) => Leaf(":" + s.0)
Str(s) => Leaf("|" + s)
Quote(v) => List([Leaf("quote"), v])
List(xs) => {
let ys : Array[@cirru_parser.Cirru] = []
ys.push(Leaf("[]"))
for x in xs {
ys.push(assemble_cirru_node(x))
}
List(ys)
}
Set(xs) => {
let ys : Array[@cirru_parser.Cirru] = []
ys.push(@cirru_parser.Cirru::Leaf("#{}"))
let items = xs.0
// items.sort()
for x in items {
ys.push(assemble_cirru_node(x))
}
@cirru_parser.Cirru::List(ys)
}
Map(xs) => {
let ys : Array[@cirru_parser.Cirru] = []
ys.push(@cirru_parser.Cirru::Leaf("{}"))
let items = Array::from_iter(xs.0.iter())
items.sort_by(fn(left, right) {
let (a1, a2) = left
let (b1, b2) = right
match
(a1.is_literal(), b1.is_literal(), a2.is_literal(), b2.is_literal()) {
(true, true, true, false) => -1
(true, true, false, true) => 1
(true, false, _, _) => -1
(false, true, _, _) => 1
// _ => a1.cmp(b1)
_ => a1.compare(b1)
}
})
for pair in items.iter() {
let (k, v) = pair
ys.push(List([assemble_cirru_node(k), assemble_cirru_node(v)]))
}
List(ys)
}
Record({ tag: name, extra: entries }) => {
let ys : Array[@cirru_parser.Cirru] = []
ys.push(Leaf("%{}"))
ys.push(Leaf(":" + name.0))
let ordered_entries = entries
ordered_entries.sort_by(fn(left, right) {
let (_a1, a2) = left
let (_b1, b2) = right
match (a2.is_literal(), b2.is_literal()) {
(true, false) => -1
(false, true) => 1
_ => 0
}
})
for entry in ordered_entries {
let v = entry.1
ys.push(List([Leaf(":" + entry.0.0), assemble_cirru_node(v)]))
}
List(ys)
}
Tuple({ tag, extra }) => {
let ys : Array[@cirru_parser.Cirru] = [
Leaf("::"),
assemble_cirru_node(tag),
]
for item in extra {
ys.push(assemble_cirru_node(item))
}
List(ys)
}
Buffer(buf) => {
let ys : Array[@cirru_parser.Cirru] = []
ys.push(Leaf("buf"))
for b in buf.0 {
ys.push(
Leaf(BigInt::from_int(b.reinterpret_as_int()).to_string(radix=16)),
)
}
List(ys)
}
// AnyRef(..) => unreachable!("AnyRef is not serializable"),
Atom(v) => {
let ys : Array[@cirru_parser.Cirru] = []
ys.push(Leaf("atom"))
ys.push(assemble_cirru_node(v))
List(ys)
}
}
}
///|
/// generate Cirru code from Edn
pub fn Edn::format(
data : Self,
use_inline? : Bool = false,
) -> String raise EdnCommonError {
let ret = match assemble_cirru_node(data) {
Leaf(s) => try? Cirru::format([List([Leaf("do"), Leaf(s)])], use_inline~)
List(xs) => try? Cirru::format([List(xs)], use_inline~)
}
match ret {
Ok(s) => s
Err(e) => raise EdnCommonError(e.to_string())
}
}