///|
pub fn Value::json(self : Value) -> Json {
match self {
Number(n) => Json::number(n.to_double())
Flag(b) => Json::boolean(b)
Text(s) => Json::string(s)
}
}
///|
fn field(o : Map[String, Json], key : String) -> String raise StoryError {
match o.get(key) {
Some(String(s)) => s
_ => raise Runtime("request", key + " must be a string")
}
}
///|
fn diagnostic_json(d : Diagnostic) -> Json {
Json::object(
Map([
("code", Json::string(d.code)),
("node", Json::string(d.node)),
("line", Json::number(d.line.to_double())),
("message", Json::string(d.message)),
]),
)
}
///|
/// Metadata includes structural edges for host graph displays, without evaluating conditions.
pub fn Story::json(self : Story) -> Json {
let nodes = []
for name in self.order {
let edges = []
let mut line = 1
for ins in self.code {
if ins.node != name {
continue
}
if self.labels.get(name) is Some(pc) {
line = self.code[pc].line
}
match ins.op {
Jump(t) =>
edges.push(
Json::object(
Map([("target", Json::string(t)), ("kind", Json::string("jump"))]),
),
)
Branch(_, t) =>
edges.push(
Json::object(
Map([("target", Json::string(t)), ("kind", Json::string("if"))]),
),
)
Pick(id, text, t, _) =>
edges.push(
Json::object(
Map([
("target", Json::string(t)),
("kind", Json::string("choice")),
("id", Json::string(id)),
("text", Json::string(text)),
]),
),
)
_ => ()
}
}
nodes.push(
Json::object(
Map([
("name", Json::string(name)),
("line", Json::number(line.to_double())),
("edges", Json::array(edges)),
]),
),
)
}
Json::object(
Map([
("id", Json::string(self.id)),
("title", Json::string(self.title)),
("instructions", Json::number(self.code.length().to_double())),
("nodes", Json::array(nodes)),
("diagnostics", Json::array(self.diagnostics().map(diagnostic_json))),
]),
)
}
///|
pub fn Session::json(self : Session) -> Json {
let frame = match self.current {
Line(speaker, text) =>
Json::object(
Map([
("kind", Json::string("line")),
("speaker", Json::string(speaker)),
("text", Json::string(text)),
]),
)
Choices(cs) =>
Json::object(
Map([
("kind", Json::string("choices")),
(
"choices",
Json::array(
cs.map(fn(c) {
Json::object(
Map([
("id", Json::string(c.id)),
("text", Json::string(c.text)),
("target", Json::string(c.target)),
]),
)
}),
),
),
]),
)
Ended(ending) =>
Json::object(
Map([("kind", Json::string("ended")), ("ending", Json::string(ending))]),
)
}
let vars : Map[String, Json] = Map([])
for k, v in self.vars {
vars[k] = v.json()
}
let events = self.delivered.map(fn(e) {
Json::object(
Map([
("sequence", Json::number(e.sequence.to_double())),
("name", Json::string(e.name)),
("value", e.value.json()),
]),
)
})
Json::object(
Map([
("frame", frame),
("node", Json::string(self.node())),
("line", Json::number(self.source_line().to_double())),
("variables", Json::object(vars)),
("events", Json::array(events)),
("actions", Json::number(self.actions.length().to_double())),
]),
)
}
///|
fn error_json(e : Error) -> Json {
let (code, line, column, message) = match e {
StoryError::Syntax(l, c, m) => ("syntax", l, c, m)
StoryError::Type(l, m) => ("type", l, 1, m)
StoryError::Runtime(code, m) => (code, 0, 0, m)
StoryError::Limit(m) => ("limit", 0, 0, m)
StoryError::Save(m) => ("save", 0, 0, m)
_ => ("request", 0, 0, "invalid JSON request")
}
Json::object(
Map([
("ok", Json::boolean(false)),
("code", Json::string(code)),
("line", Json::number(line.to_double())),
("column", Json::number(column.to_double())),
("error", Json::string(message)),
]),
)
}
///|
/// Stateless JSON host boundary. Runtime decisions and save validation remain in MoonBit.
pub fn run_json(request : String) -> String {
try {
if request.length() > 3000000 {
raise Limit("request exceeds 3000000 code units")
}
guard @json.parse(request) is Object(o) else {
raise Runtime("request", "request must be an object")
}
let story = compile(field(o, "source"))
let command = field(o, "command")
let result = if command == "check" {
story.json()
} else {
let state = if command == "start" {
start(story)
} else {
let loaded = restore(story, field(o, "save"))
match command {
"restore" => loaded
"advance" => loaded.advance()
"choose" => loaded.choose(field(o, "choice"))
_ => raise Runtime("request", "unknown command " + command)
}
}
Json::object(
Map([("state", state.json()), ("save", Json::string(state.save()))]),
)
}
Json::object(Map([("ok", Json::boolean(true)), ("result", result)])).stringify()
} catch {
error => error_json(error).stringify()
}
}