#![cfg(feature = "wasm")]
use crate::admission::RejectionCode;
use crate::catalog::Catalog;
use crate::kernel::Kernel;
use crate::types::{
Atom8, EpochId, FactBlock8, FactRow8, PredicateId, ProofMode, QueryAtom8, SourceId, TermId,
};
use serde_json::json;
use wasm_bindgen::prelude::*;
const MAX_INPUT_LEN: usize = 10 * 1024 * 1024;
const MAX_ANSWERS: usize = 128;
fn js_err(message: &str) -> JsValue {
let json = serde_json::to_string(&json!({"error": message}))
.unwrap_or_else(|_| format!(r#"{{"error":"{}"}}"#, message));
JsValue::from_str(&json)
}
fn to_js_str<T: serde::Serialize>(v: &T) -> Result<JsValue, JsValue> {
serde_json::to_string(v)
.map(|s| JsValue::from_str(&s))
.map_err(|e| js_err(&format!("serialize: {e}")))
}
#[derive(serde::Deserialize)]
struct FactRowInput {
pred_id: u32,
arity: u8,
args: Vec<u32>,
#[serde(default)]
source_id: u32,
}
impl FactRowInput {
fn into_fact_row(self) -> FactRow8 {
let pid = PredicateId(self.pred_id);
let args: Vec<TermId> = self.args.iter().map(|&v| TermId(v)).collect();
FactRow8::new(pid, self.arity, &args, SourceId(self.source_id))
}
}
#[derive(serde::Deserialize)]
struct FactBlockInput {
pred_id: u32,
arity: u8,
#[serde(default)]
rows: Vec<FactRowInput>,
}
impl FactBlockInput {
fn into_fact_block(self) -> FactBlock8 {
let pid = PredicateId(self.pred_id);
let rows: Vec<FactRow8> = self.rows.into_iter().map(|r| r.into_fact_row()).collect();
FactBlock8::new(pid, self.arity, rows)
}
}
#[derive(serde::Deserialize)]
struct AtomInput {
pred_id: u32,
arity: u8,
#[serde(default)]
args: Vec<u32>,
#[serde(default)]
binding_mask: u8,
}
impl AtomInput {
fn into_atom(self) -> Atom8 {
let pid = PredicateId(self.pred_id);
let args: Vec<TermId> = self.args.iter().map(|&v| TermId(v)).collect();
let mut atom = Atom8::new(pid, self.arity, &args);
atom.binding_mask = self.binding_mask;
atom
}
}
#[derive(serde::Deserialize)]
struct QueryInput {
atom: AtomInput,
#[serde(default)]
binding_mask: u8,
#[serde(default)]
output_mask: u8,
#[serde(default = "default_proof_mode")]
proof_mode: ProofMode,
#[serde(default)]
epoch: u64,
}
fn default_proof_mode() -> ProofMode {
ProofMode::PositiveOnly
}
impl QueryInput {
fn into_query_atom(mut self) -> QueryAtom8 {
self.atom.binding_mask = self.binding_mask;
QueryAtom8 {
atom: self.atom.into_atom(),
output_mask: self.output_mask,
proof_mode: self.proof_mode,
epoch: EpochId(self.epoch),
}
}
}
#[wasm_bindgen]
pub fn prolog8_show() -> Result<JsValue, JsValue> {
to_js_str(&json!({
"engine": crate::ENGINE_NAME,
"version": crate::ENGINE_VERSION,
"caps": {
"arity": crate::ARITY_CAP,
"body": crate::BODY_CAP,
"vars": crate::VAR_CAP,
"binding_patterns": crate::BINDING_PATTERNS,
"max_answers": MAX_ANSWERS,
}
}))
}
#[wasm_bindgen]
pub fn prolog8_query(input_json: &str) -> Result<JsValue, JsValue> {
if input_json.len() > MAX_INPUT_LEN {
return Err(js_err(&format!(
"input exceeds {MAX_INPUT_LEN} bytes (got {})",
input_json.len()
)));
}
#[derive(serde::Deserialize)]
struct Input {
catalog: Catalog,
#[serde(default)]
facts: Vec<FactBlockInput>,
#[serde(default)]
rules: Vec<crate::types::Rule8>,
query: QueryInput,
}
let parsed: Input =
serde_json::from_str(input_json).map_err(|e| js_err(&format!("schema rejected: {e}")))?;
let mut kernel = Kernel::new(parsed.catalog);
for block in parsed.facts {
kernel
.load_facts(block.into_fact_block())
.map_err(|c| js_err(&format!("fact admission rejected: {}", c)))?;
}
for rule in parsed.rules {
kernel
.load_rule(rule)
.map_err(|c| js_err(&format!("rule admission rejected: {}", c)))?;
}
let query = parsed.query.into_query_atom();
match kernel.query(&query) {
crate::kernel::QueryResult::Answered(mut answers) => {
if answers.len() > MAX_ANSWERS {
answers.truncate(MAX_ANSWERS);
to_js_str(&json!({ "TruncatedAnswers": answers }))
} else {
to_js_str(&json!({ "Answered": answers }))
}
}
other => to_js_str(&other),
}
}
#[wasm_bindgen]
pub fn prolog8_replay(input_json: &str) -> Result<JsValue, JsValue> {
if input_json.len() > MAX_INPUT_LEN {
return Err(js_err("input exceeds 10MiB"));
}
#[derive(serde::Deserialize)]
struct Input {
catalog: Catalog,
#[serde(default)]
facts: Vec<FactBlockInput>,
#[serde(default)]
rules: Vec<crate::types::Rule8>,
query: QueryInput,
receipt: crate::types::Receipt,
}
let parsed: Input =
serde_json::from_str(input_json).map_err(|e| js_err(&format!("schema rejected: {e}")))?;
let mut kernel = Kernel::new(parsed.catalog);
for block in parsed.facts {
kernel
.load_facts(block.into_fact_block())
.map_err(|c: RejectionCode| js_err(&format!("fact admission rejected: {}", c)))?;
}
for rule in parsed.rules {
kernel
.load_rule(rule)
.map_err(|c: RejectionCode| js_err(&format!("rule admission rejected: {}", c)))?;
}
let query = parsed.query.into_query_atom();
let status = crate::replay::replay(&kernel, &query, &parsed.receipt);
to_js_str(&status)
}