make TCS compiler GIR control validation and lowering
This commit is contained in:
parent
14ffe45caa
commit
6e89b82d88
5 changed files with 271 additions and 17 deletions
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{json, Value as JsonValue};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fs,
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
|
@ -608,13 +608,26 @@ pub fn validate_program(document: &TcsDocument) -> Result<(), TcsError> {
|
|||
"Stage-0 executable subset accepts PROGRAM only",
|
||||
));
|
||||
}
|
||||
for section in PROGRAM_SECTIONS {
|
||||
let sections = PROGRAM_SECTIONS
|
||||
.iter()
|
||||
.map(|value| (*value).to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let operations = BTreeSet::from(["CORE.ECHO".to_owned()]);
|
||||
validate_program_contract(document, §ions, &operations)
|
||||
}
|
||||
|
||||
fn validate_program_contract(
|
||||
document: &TcsDocument,
|
||||
sections: &[String],
|
||||
registered_operations: &BTreeSet<String>,
|
||||
) -> Result<(), TcsError> {
|
||||
for section in sections {
|
||||
document.body.get(section).ok_or_else(|| {
|
||||
TcsError::new("TCS-E1004", format!("required section {section} missing"))
|
||||
})?;
|
||||
}
|
||||
for section in document.body.keys() {
|
||||
if !PROGRAM_SECTIONS.contains(§ion.as_str()) {
|
||||
if !sections.contains(section) {
|
||||
return Err(TcsError::new(
|
||||
"TCS-E1003",
|
||||
format!("unknown PROGRAM section {section}"),
|
||||
|
|
@ -647,7 +660,7 @@ pub fn validate_program(document: &TcsDocument) -> Result<(), TcsError> {
|
|||
for (action_id, action) in actions {
|
||||
let action = action.object(action_id)?;
|
||||
let operation = required_text(action, "operation")?;
|
||||
if operation != "CORE.ECHO" {
|
||||
if !registered_operations.contains(operation) {
|
||||
return Err(TcsError::new(
|
||||
"TCS-E2101",
|
||||
format!("unregistered TCS operation {operation}"),
|
||||
|
|
@ -665,6 +678,99 @@ pub fn validate_program(document: &TcsDocument) -> Result<(), TcsError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CompilerPolicy {
|
||||
required_sections: Vec<String>,
|
||||
registered_operations: BTreeSet<String>,
|
||||
lowerings: BTreeMap<String, String>,
|
||||
output_schema: String,
|
||||
}
|
||||
|
||||
fn compiler_policy(compiler: &JsonValue) -> Result<CompilerPolicy, TcsError> {
|
||||
fn text_array(value: &JsonValue, pointer: &str) -> Result<Vec<String>, TcsError> {
|
||||
value
|
||||
.pointer(pointer)
|
||||
.and_then(JsonValue::as_array)
|
||||
.ok_or_else(|| TcsError::new("TCS-E3002", format!("missing array {pointer}")))?
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item.as_str().map(str::to_owned).ok_or_else(|| {
|
||||
TcsError::new("TCS-E3002", format!("non-text item in {pointer}"))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
let required_sections = text_array(
|
||||
compiler,
|
||||
"/compiler_definition/program_validation/required_sections",
|
||||
)?;
|
||||
let registered_operations = text_array(
|
||||
compiler,
|
||||
"/compiler_definition/program_validation/registered_operations",
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<BTreeSet<_>>();
|
||||
if required_sections.is_empty() || registered_operations.is_empty() {
|
||||
return Err(TcsError::new(
|
||||
"TCS-E3002",
|
||||
"compiler policy must not be empty",
|
||||
));
|
||||
}
|
||||
let lowering_rules = compiler
|
||||
.pointer("/compiler_definition/lowering_to_gir")
|
||||
.and_then(JsonValue::as_object)
|
||||
.ok_or_else(|| TcsError::new("TCS-E3002", "lowering rules missing"))?;
|
||||
let mut lowerings = BTreeMap::new();
|
||||
for rule in lowering_rules.values() {
|
||||
let Some(from) = rule.get("from").and_then(JsonValue::as_str) else {
|
||||
continue;
|
||||
};
|
||||
let to = rule
|
||||
.get("to")
|
||||
.and_then(JsonValue::as_str)
|
||||
.ok_or_else(|| TcsError::new("TCS-E3002", "lowering target missing"))?;
|
||||
if document_section(from) && lowerings.insert(from.to_owned(), to.to_owned()).is_some() {
|
||||
return Err(TcsError::new("TCS-E3002", "duplicate lowering source"));
|
||||
}
|
||||
}
|
||||
let required_lowerings = [
|
||||
"subject",
|
||||
"target",
|
||||
"inputs",
|
||||
"outputs",
|
||||
"conditions",
|
||||
"actions",
|
||||
"authority",
|
||||
"resources",
|
||||
"failure",
|
||||
"stop",
|
||||
"cleanup",
|
||||
"rollback",
|
||||
"receipt",
|
||||
];
|
||||
if required_lowerings
|
||||
.iter()
|
||||
.any(|field| !lowerings.contains_key(*field))
|
||||
{
|
||||
return Err(TcsError::new(
|
||||
"TCS-E3002",
|
||||
"compiler lowering map is incomplete",
|
||||
));
|
||||
}
|
||||
Ok(CompilerPolicy {
|
||||
required_sections,
|
||||
registered_operations,
|
||||
lowerings,
|
||||
output_schema: json_text(compiler, "/compiler_definition/output_contract/schema")?
|
||||
.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn document_section(value: &str) -> bool {
|
||||
!matches!(value, "header" | "source" | "all")
|
||||
}
|
||||
|
||||
fn validate_exact_sections(document: &TcsDocument, expected: &[&str]) -> Result<(), TcsError> {
|
||||
for section in expected {
|
||||
document.body.get(*section).ok_or_else(|| {
|
||||
|
|
@ -759,6 +865,7 @@ pub fn sha256_hex(bytes: impl AsRef<[u8]>) -> String {
|
|||
format!("{:x}", Sha256::digest(bytes.as_ref()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "bootstrap")]
|
||||
fn lower_program(
|
||||
document: &TcsDocument,
|
||||
source_sha256: &str,
|
||||
|
|
@ -798,6 +905,44 @@ fn lower_program(
|
|||
}))
|
||||
}
|
||||
|
||||
fn lower_program_with_policy(
|
||||
document: &TcsDocument,
|
||||
source_sha256: &str,
|
||||
compiler_id: &str,
|
||||
policy: &CompilerPolicy,
|
||||
) -> Result<JsonValue, TcsError> {
|
||||
let mut gir = json!({
|
||||
"schema": policy.output_schema,
|
||||
"identity": {
|
||||
"gir_id": format!("GIR-{}", document.declaration_id),
|
||||
"program_id": document.declaration_id,
|
||||
"language_version": document.language_version,
|
||||
},
|
||||
"compiled_from": {
|
||||
"source_sha256": source_sha256,
|
||||
"compiler_id": compiler_id,
|
||||
"compiler_state": "TCS_COMPILER_GIR_EXECUTED",
|
||||
},
|
||||
"unresolved_natural_language": false,
|
||||
"native_self_hosted": true,
|
||||
});
|
||||
let object = gir
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| TcsError::new("TCS-E8001", "GIR root must be object"))?;
|
||||
for (from, to) in &policy.lowerings {
|
||||
let value = document
|
||||
.body
|
||||
.get(from)
|
||||
.ok_or_else(|| TcsError::new("TCS-E3002", format!("lowering source {from} missing")))?;
|
||||
object.insert(
|
||||
to.clone(),
|
||||
serde_json::to_value(value)
|
||||
.map_err(|error| TcsError::new("TCS-E8001", error.to_string()))?,
|
||||
);
|
||||
}
|
||||
Ok(gir)
|
||||
}
|
||||
|
||||
fn compiler_definition_sha256(document: &TcsDocument) -> Result<String, TcsError> {
|
||||
let definition = serde_json::to_vec(&document.body)
|
||||
.map_err(|error| TcsError::new("TCS-E8001", error.to_string()))?;
|
||||
|
|
@ -922,18 +1067,20 @@ pub fn compile_with_compiler_gir(
|
|||
source: &str,
|
||||
) -> Result<JsonValue, TcsError> {
|
||||
let (compiler_id, _) = verify_compiler_gir(compiler)?;
|
||||
let policy = compiler_policy(compiler)?;
|
||||
let document = parse(source)?;
|
||||
let source_sha256 = sha256_hex(source.as_bytes());
|
||||
match document.declaration_kind.as_str() {
|
||||
"PROGRAM" => {
|
||||
validate_program(&document)?;
|
||||
lower_program(
|
||||
if document.language_version != "0.1" {
|
||||
return Err(TcsError::new("TCS-E2102", "unsupported TCS version"));
|
||||
}
|
||||
validate_program_contract(
|
||||
&document,
|
||||
&source_sha256,
|
||||
compiler_id,
|
||||
"TCS_COMPILER_GIR_EXECUTED",
|
||||
true,
|
||||
)
|
||||
&policy.required_sections,
|
||||
&policy.registered_operations,
|
||||
)?;
|
||||
lower_program_with_policy(&document, &source_sha256, compiler_id, &policy)
|
||||
}
|
||||
"COMPILER" => {
|
||||
validate_compiler(&document)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue