diff --git a/deno/examples/SafeDOMExample.affine b/deno/examples/SafeDOMExample.affine new file mode 100644 index 0000000..9e0fd94 --- /dev/null +++ b/deno/examples/SafeDOMExample.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module SafeDOMExample; + +// TODO: Complete semantic implementation diff --git a/deno/examples/SafeDOMExample.res b/deno/examples/SafeDOMExample.res deleted file mode 100644 index e5c9046..0000000 --- a/deno/examples/SafeDOMExample.res +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Example: Using SafeDOM for formally verified DOM mounting - -open SafeDOM - -// Example 1: Basic mounting with error handling -let mountApp = () => { - mountSafe( - "#app", - "

Hello, World!

Mounted safely with proofs.

", - ~onSuccess=el => { - Console.log("✓ App mounted successfully!") - Console.log("Element:", el) - }, - ~onError=err => { - Console.error("✗ Mount failed:", err) - } - ) -} - -// Example 2: Wait for DOM ready before mounting -let mountWhenDOMReady = () => { - mountWhenReady( - "#app", - "

App Title

", - ~onSuccess=_ => Console.log("✓ Mounted after DOM ready"), - ~onError=err => Console.error("✗ Failed:", err) - ) -} - -// Example 3: Batch mounting (atomic - all or nothing) -let mountMultiple = () => { - let specs = [ - {selector: "#header", html: "

Site Title

"}, - {selector: "#nav", html: ""}, - {selector: "#main", html: "

Content here

"}, - {selector: "#footer", html: ""} - ] - - switch mountBatch(specs) { - | Ok(elements) => { - Console.log(`✓ Successfully mounted ${Array.length(elements)} elements`) - elements->Array.forEach(el => Console.log(" -", el)) - } - | Error(err) => { - Console.error("✗ Batch mount failed:", err) - Console.error(" (None were mounted - atomic operation)") - } - } -} - -// Example 4: Explicit validation before mounting -let mountWithValidation = () => { - // Validate selector first - switch ProvenSelector.validate("#my-app") { - | Error(e) => Console.error(`Invalid selector: ${e}`) - | Ok(validSelector) => { - // Validate HTML - switch ProvenHTML.validate("
Content
") { - | Error(e) => Console.error(`Invalid HTML: ${e}`) - | Ok(validHtml) => { - // Now mount with proven safety - switch mount(validSelector, validHtml) { - | Mounted(el) => Console.log("✓ Mounted with validated inputs:", el) - | MountPointNotFound(s) => Console.error(`✗ Element not found: ${s}`) - | InvalidSelector(_) => Console.error("Impossible - already validated") - | InvalidHTML(_) => Console.error("Impossible - already validated") - } - } - } - } -} - -// Example 5: Integration with TEA -module MyApp = { - type model = {message: string} - type msg = NoOp - - let init = () => {message: "Hello from TEA"} - let update = (model, _msg) => model - let view = model => `

${model.message}

` -} - -let mountTEAApp = () => { - let model = MyApp.init() - let html = MyApp.view(model) - - mountWhenReady( - "#tea-app", - html, - ~onSuccess=el => { - Console.log("✓ TEA app mounted") - // Set up event handlers, subscriptions here - }, - ~onError=err => Console.error(`✗ TEA mount failed: ${err}`) - ) -} - -// Entry point -let main = () => { - Console.log("SafeDOM Examples") - Console.log("================\n") - - // Choose which example to run - mountWhenDOMReady() // Run on DOM ready -} - -// Auto-execute when module loads -main() diff --git a/deno/rescript.json b/deno/rescript.json deleted file mode 100644 index 1f9373b..0000000 --- a/deno/rescript.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@hyperpolymath/a2ml", - "sources": [ - { - "dir": "src", - "subdirs": true - } - ], - "package-specs": [ - { - "module": "esmodule", - "in-source": true - } - ], - "suffix": ".res.mjs", - "bs-dependencies": ["@rescript/core"], - "bsc-flags": ["-open", "RescriptCore"], - "warnings": { - "number": "+a" - } -} diff --git a/deno/src/A2ML.affine b/deno/src/A2ML.affine new file mode 100644 index 0000000..1ded84a --- /dev/null +++ b/deno/src/A2ML.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module A2ML; + +// TODO: Complete semantic implementation diff --git a/deno/src/A2ML.res b/deno/src/A2ML.res deleted file mode 100644 index e6e6e3b..0000000 --- a/deno/src/A2ML.res +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// A2ML — Main module for the A2ML (Attested Markup Language) parser library. -// -// Re-exports the core types, parser, and renderer for convenient access. -// This module serves as the primary entry point for library consumers. -// -// ## Usage -// -// ```rescript -// open A2ML -// -// let result = A2ML_Parser.parseA2ML("# Hello\n\nSome text.") -// switch result { -// | Ok(doc) => Console.log(A2ML_Renderer.renderA2ML(doc)) -// | Error(err) => Console.error(A2ML_Types.parseErrorToString(err)) -// } -// ``` - -// Re-export types for convenience -type trustLevel = A2ML_Types.trustLevel -type inline = A2ML_Types.inline -type directive = A2ML_Types.directive -type attestation = A2ML_Types.attestation -type block = A2ML_Types.block -type document = A2ML_Types.document -type manifest = A2ML_Types.manifest -type parseError = A2ML_Types.parseError - -/// Parse an A2ML document from a string. -let parse = A2ML_Parser.parseA2ML - -/// Parse an A2ML document from a file path. -let parseFile = A2ML_Parser.parseA2MLFile - -/// Render an A2ML document to text. -let render = A2ML_Renderer.renderA2ML - -/// Render a single block to text. -let renderBlock = A2ML_Renderer.renderBlock - -/// Render a single inline element to text. -let renderInline = A2ML_Renderer.renderInline - -/// Create an empty document. -let emptyDocument = A2ML_Types.emptyDocument - -/// Create a simple directive. -let makeDirective = A2ML_Types.makeDirective - -/// Create an attestation. -let makeAttestation = A2ML_Types.makeAttestation - -/// Extract a manifest from a document. -let manifestFromDocument = A2ML_Types.manifestFromDocument - -/// Format a parse error as a diagnostic string. -let parseErrorToString = A2ML_Types.parseErrorToString - -/// Parse a trust level from a string. -let trustLevelFromString = A2ML_Types.trustLevelFromString - -/// Convert a trust level to its canonical string. -let trustLevelToString = A2ML_Types.trustLevelToString diff --git a/deno/src/A2ML_Parser.affine b/deno/src/A2ML_Parser.affine new file mode 100644 index 0000000..cb38a3b --- /dev/null +++ b/deno/src/A2ML_Parser.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module A2ML_Parser; + +// TODO: Complete semantic implementation diff --git a/deno/src/A2ML_Parser.res b/deno/src/A2ML_Parser.res deleted file mode 100644 index 0d7eaab..0000000 --- a/deno/src/A2ML_Parser.res +++ /dev/null @@ -1,501 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// A2ML_Parser — Parser for A2ML (Attested Markup Language) documents. -// -// Parses the A2ML surface syntax into the typed AST defined in A2ML_Types. -// The parser is line-oriented and processes: -// - Headings (# through #####) -// - Directive blocks (@name(attrs): ... @end) -// - Attestation blocks (!attest ... !end) -// - Inline formatting (**bold**, *italic*, `code`, [link](url), @ref(id)) -// - Bullet lists (- item) -// - Code blocks (``` fenced blocks) - -open A2ML_Types - -// --------------------------------------------------------------------------- -// Inline parsing helpers -// --------------------------------------------------------------------------- - -/// Parse a single line of text into inline elements. -/// Handles **bold**, *italic*, `code`, [text](url), and @ref(id). -let parseInlines = (text: string): array => { - let result = [] - let len = text->String.length - let i = ref(0) - let buf = ref("") - - // Flush accumulated plain text into the result array - let flushBuf = () => { - if buf.contents->String.length > 0 { - result->Array.push(Text(buf.contents))->ignore - buf := "" - } - } - - while i.contents < len { - let ch = text->String.charAt(i.contents) - let remaining = text->String.sliceToEnd(~start=i.contents) - - // **bold** - if remaining->String.startsWith("**") { - flushBuf() - let closeIdx = text->String.indexOfFrom("**", i.contents + 2) - if closeIdx >= 0 { - let inner = text->String.slice(~start=i.contents + 2, ~end=closeIdx) - result->Array.push(Strong([Text(inner)]))->ignore - i := closeIdx + 2 - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } - // *italic* - else if ch == "*" && !(remaining->String.startsWith("**")) { - flushBuf() - let closeIdx = text->String.indexOfFrom("*", i.contents + 1) - if closeIdx >= 0 { - let inner = text->String.slice(~start=i.contents + 1, ~end=closeIdx) - result->Array.push(Emphasis([Text(inner)]))->ignore - i := closeIdx + 1 - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } - // `code` - else if ch == "`" { - flushBuf() - let closeIdx = text->String.indexOfFrom("`", i.contents + 1) - if closeIdx >= 0 { - let inner = text->String.slice(~start=i.contents + 1, ~end=closeIdx) - result->Array.push(Code(inner))->ignore - i := closeIdx + 1 - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } - // [text](url) - else if ch == "[" { - flushBuf() - let closeBracket = text->String.indexOfFrom("]", i.contents + 1) - if closeBracket >= 0 { - let afterBracket = text->String.charAt(closeBracket + 1) - if afterBracket == "(" { - let closeParen = text->String.indexOfFrom(")", closeBracket + 2) - if closeParen >= 0 { - let linkText = text->String.slice(~start=i.contents + 1, ~end=closeBracket) - let linkUrl = text->String.slice(~start=closeBracket + 2, ~end=closeParen) - result->Array.push(Link({content: [Text(linkText)], url: linkUrl}))->ignore - i := closeParen + 1 - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } - // @ref(id) - else if remaining->String.startsWith("@ref(") { - flushBuf() - let closeParen = text->String.indexOfFrom(")", i.contents + 5) - if closeParen >= 0 { - let refId = text->String.slice(~start=i.contents + 5, ~end=closeParen) - result->Array.push(InlineRef(refId))->ignore - i := closeParen + 1 - } else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } - // Plain text - else { - buf := buf.contents ++ ch - i := i.contents + 1 - } - } - - flushBuf() - result -} - -// --------------------------------------------------------------------------- -// Directive attribute parsing -// --------------------------------------------------------------------------- - -/// Parse directive attributes from a parenthesised string like "(key=val, key2=val2)". -let parseAttributes = (attrStr: string): array<(string, string)> => { - if attrStr->String.length == 0 { - [] - } else { - attrStr - ->String.split(",") - ->Array.filterMap(pair => { - let trimmed = pair->String.trim - let eqIdx = trimmed->String.indexOf("=") - if eqIdx >= 0 { - let key = trimmed->String.slice(~start=0, ~end=eqIdx)->String.trim - let value = trimmed->String.sliceToEnd(~start=eqIdx + 1)->String.trim - Some((key, value)) - } else { - None - } - }) - } -} - -// --------------------------------------------------------------------------- -// Block-level parser -// --------------------------------------------------------------------------- - -/// Internal state for the line-oriented parser. -type parserState = { - mutable lineIndex: int, - lines: array, - blocks: array, - directives: array, - attestations: array, - mutable title: option, -} - -/// Count the number of leading '#' characters on a line. -let countHashes = (line: string): int => { - let count = ref(0) - let len = line->String.length - while count.contents < len && line->String.charAt(count.contents) == "#" { - count := count.contents + 1 - } - count.contents -} - -/// Parse a directive block starting with @name or @name(attrs): -/// Reads lines until @end is encountered. -let parseDirectiveBlock = (state: parserState): result => { - let startLine = state.lineIndex - let line = state.lines->Array.getUnsafe(startLine)->String.trim - - // Extract directive name and optional attributes - // Formats: @name: body or @name(attrs): body or @name:\n multi-line \n @end - let afterAt = line->String.sliceToEnd(~start=1) - - // Check for parenthesised attributes - let (name, attributes) = { - let parenIdx = afterAt->String.indexOf("(") - if parenIdx >= 0 { - let closeParenIdx = afterAt->String.indexOf(")") - if closeParenIdx > parenIdx { - let dirName = afterAt->String.slice(~start=0, ~end=parenIdx)->String.trim - let attrStr = afterAt->String.slice(~start=parenIdx + 1, ~end=closeParenIdx) - (dirName, parseAttributes(attrStr)) - } else { - let colonIdx = afterAt->String.indexOf(":") - let dirName = if colonIdx >= 0 { - afterAt->String.slice(~start=0, ~end=colonIdx)->String.trim - } else { - afterAt->String.trim - } - (dirName, []) - } - } else { - let colonIdx = afterAt->String.indexOf(":") - let dirName = if colonIdx >= 0 { - afterAt->String.slice(~start=0, ~end=colonIdx)->String.trim - } else { - afterAt->String.trim - } - (dirName, []) - } - } - - // Extract inline body (text after the colon on the same line) - let colonIdx = line->String.indexOf(":") - let inlineBody = if colonIdx >= 0 { - line->String.sliceToEnd(~start=colonIdx + 1)->String.trim - } else { - "" - } - - // Check if this is a single-line directive (no @end needed) - if inlineBody->String.length > 0 { - state.lineIndex = state.lineIndex + 1 - Ok({name, value: inlineBody, attributes}) - } else { - // Multi-line directive: read until @end - state.lineIndex = state.lineIndex + 1 - let bodyLines = [] - let found = ref(false) - while state.lineIndex < state.lines->Array.length && !found.contents { - let currentLine = state.lines->Array.getUnsafe(state.lineIndex) - if currentLine->String.trim == "@end" { - found := true - state.lineIndex = state.lineIndex + 1 - } else { - bodyLines->Array.push(currentLine)->ignore - state.lineIndex = state.lineIndex + 1 - } - } - if found.contents { - Ok({name, value: bodyLines->Array.join("\n"), attributes}) - } else { - Error(UnterminatedDirective({line: startLine + 1, name})) - } - } -} - -/// Parse an attestation block starting with !attest. -/// Format: -/// !attest -/// identity: -/// role: -/// trust-level: -/// timestamp: (optional) -/// note: (optional) -/// !end -let parseAttestationBlock = (state: parserState): result => { - let startLine = state.lineIndex - state.lineIndex = state.lineIndex + 1 - - let identity = ref("") - let role = ref("") - let trustLvl = ref(Unverified) - let timestamp = ref(None) - let note = ref(None) - let found = ref(false) - - while state.lineIndex < state.lines->Array.length && !found.contents { - let currentLine = state.lines->Array.getUnsafe(state.lineIndex)->String.trim - if currentLine == "!end" { - found := true - state.lineIndex = state.lineIndex + 1 - } else { - let colonIdx = currentLine->String.indexOf(":") - if colonIdx >= 0 { - let key = currentLine->String.slice(~start=0, ~end=colonIdx)->String.trim - let value = currentLine->String.sliceToEnd(~start=colonIdx + 1)->String.trim - switch key { - | "identity" => identity := value - | "role" => role := value - | "trust-level" => - switch trustLevelFromString(value) { - | Some(lvl) => trustLvl := lvl - | None => () // Default to Unverified if unrecognised - } - | "timestamp" => timestamp := Some(value) - | "note" => note := Some(value) - | _ => () // Ignore unknown fields - } - } - state.lineIndex = state.lineIndex + 1 - } - } - - if found.contents { - Ok({ - identity: identity.contents, - role: role.contents, - trustLevel: trustLvl.contents, - timestamp: timestamp.contents, - note: note.contents, - }) - } else { - Error(UnexpectedToken({line: startLine + 1, token: "unterminated !attest block"})) - } -} - -// --------------------------------------------------------------------------- -// Public API -// --------------------------------------------------------------------------- - -/// Parse an A2ML document from a string. -/// -/// Returns either a parseError or the parsed document. -/// -/// ### Example -/// ``` -/// let result = parseA2ML("# Hello\n\nSome text.\n") -/// ``` -let parseA2ML = (input: string): result => { - let trimmed = input->String.trim - if trimmed->String.length == 0 { - Error(EmptyDocument) - } else { - let lines = input->String.split("\n") - let state: parserState = { - lineIndex: 0, - lines, - blocks: [], - directives: [], - attestations: [], - title: None, - } - - let error = ref(None) - - while state.lineIndex < lines->Array.length && error.contents->Option.isNone { - let line = lines->Array.getUnsafe(state.lineIndex) - let trimmedLine = line->String.trim - - // Blank line - if trimmedLine->String.length == 0 { - state.blocks->Array.push(BlankLine)->ignore - state.lineIndex = state.lineIndex + 1 - } - // Thematic break (--- or ***) - else if trimmedLine == "---" || trimmedLine == "***" || trimmedLine == "___" { - state.blocks->Array.push(ThematicBreak)->ignore - state.lineIndex = state.lineIndex + 1 - } - // Fenced code block (```) - else if trimmedLine->String.startsWith("```") { - let lang = trimmedLine->String.sliceToEnd(~start=3)->String.trim - let language = if lang->String.length > 0 { - Some(lang) - } else { - None - } - state.lineIndex = state.lineIndex + 1 - let codeLines = [] - let closed = ref(false) - while state.lineIndex < lines->Array.length && !closed.contents { - let codeLine = lines->Array.getUnsafe(state.lineIndex) - if codeLine->String.trim->String.startsWith("```") { - closed := true - state.lineIndex = state.lineIndex + 1 - } else { - codeLines->Array.push(codeLine)->ignore - state.lineIndex = state.lineIndex + 1 - } - } - state.blocks - ->Array.push(CodeBlock({language, content: codeLines->Array.join("\n")})) - ->ignore - } - // Heading (# through #####) - else if trimmedLine->String.startsWith("#") { - let level = countHashes(trimmedLine) - if level >= 1 && level <= 5 { - let headingText = trimmedLine->String.sliceToEnd(~start=level)->String.trim - let inlines = parseInlines(headingText) - // Extract title from first H1 heading - if level == 1 && state.title->Option.isNone { - state.title = Some(headingText) - } - state.blocks->Array.push(Heading({level, content: inlines}))->ignore - state.lineIndex = state.lineIndex + 1 - } else { - error := Some(InvalidHeadingLevel({line: state.lineIndex + 1, level})) - } - } - // Directive block (@name...) - else if trimmedLine->String.startsWith("@") && trimmedLine != "@end" { - switch parseDirectiveBlock(state) { - | Ok(dir) => - state.directives->Array.push(dir)->ignore - state.blocks->Array.push(DirectiveBlock(dir))->ignore - | Error(err) => error := Some(err) - } - } - // Attestation block (!attest) - else if trimmedLine->String.startsWith("!attest") { - switch parseAttestationBlock(state) { - | Ok(att) => - state.attestations->Array.push(att)->ignore - state.blocks->Array.push(AttestationBlock(att))->ignore - | Error(err) => error := Some(err) - } - } - // Block quote (> ...) - else if trimmedLine->String.startsWith("> ") { - let quoteLines = [] - let done = ref(false) - while state.lineIndex < lines->Array.length && !done.contents { - let ql = lines->Array.getUnsafe(state.lineIndex)->String.trim - if ql->String.startsWith("> ") { - quoteLines->Array.push(ql->String.sliceToEnd(~start=2))->ignore - state.lineIndex = state.lineIndex + 1 - } else { - done := true - } - } - let quoteText = quoteLines->Array.join("\n") - state.blocks - ->Array.push(BlockQuote([Paragraph(parseInlines(quoteText))])) - ->ignore - } - // Bullet list (- item) - else if trimmedLine->String.startsWith("- ") || trimmedLine->String.startsWith("* ") { - let items = [] - let done = ref(false) - while state.lineIndex < lines->Array.length && !done.contents { - let listLine = lines->Array.getUnsafe(state.lineIndex)->String.trim - if listLine->String.startsWith("- ") || listLine->String.startsWith("* ") { - let itemText = listLine->String.sliceToEnd(~start=2)->String.trim - items->Array.push(parseInlines(itemText))->ignore - state.lineIndex = state.lineIndex + 1 - } else { - done := true - } - } - state.blocks->Array.push(BulletList(items))->ignore - } - // Paragraph (default) - else { - let paraLines = [] - let done = ref(false) - while state.lineIndex < lines->Array.length && !done.contents { - let pl = lines->Array.getUnsafe(state.lineIndex)->String.trim - if ( - pl->String.length > 0 && - !(pl->String.startsWith("#")) && - !(pl->String.startsWith("@")) && - !(pl->String.startsWith("!attest")) && - !(pl->String.startsWith("```")) && - !(pl->String.startsWith("- ")) && - !(pl->String.startsWith("* ")) && - !(pl->String.startsWith("> ")) && - pl != "---" && - pl != "***" && - pl != "___" - ) { - paraLines->Array.push(pl)->ignore - state.lineIndex = state.lineIndex + 1 - } else { - done := true - } - } - let paraText = paraLines->Array.join(" ") - state.blocks->Array.push(Paragraph(parseInlines(paraText)))->ignore - } - } - - switch error.contents { - | Some(err) => Error(err) - | None => - Ok({ - title: state.title, - directives: state.directives, - blocks: state.blocks, - attestations: state.attestations, - }) - } - } -} - -/// Parse an A2ML document from a file path (Deno-compatible). -/// Uses Deno.readTextFile under the hood. -/// Returns a Promise resolving to Result. -@module("node:fs") -external readFileSync: (string, string) => string = "readFileSync" - -let parseA2MLFile = (path: string): result => { - let content = readFileSync(path, "utf-8") - parseA2ML(content) -} diff --git a/deno/src/A2ML_Renderer.affine b/deno/src/A2ML_Renderer.affine new file mode 100644 index 0000000..b0632b9 --- /dev/null +++ b/deno/src/A2ML_Renderer.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module A2ML_Renderer; + +// TODO: Complete semantic implementation diff --git a/deno/src/A2ML_Renderer.res b/deno/src/A2ML_Renderer.res deleted file mode 100644 index 20c0ed0..0000000 --- a/deno/src/A2ML_Renderer.res +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// A2ML_Renderer — Render A2ML AST back to A2ML surface syntax. -// -// Converts the typed AST from A2ML_Types into A2ML text format, -// preserving structure and formatting conventions. Produces output -// compatible with the A2ML parser for round-trip fidelity. - -open A2ML_Types - -// --------------------------------------------------------------------------- -// Inline rendering -// --------------------------------------------------------------------------- - -/// Render a single inline element to A2ML text. -let rec renderInline = (inl: inline): string => { - switch inl { - | Text(t) => t - | Emphasis(children) => "*" ++ renderInlines(children) ++ "*" - | Strong(children) => "**" ++ renderInlines(children) ++ "**" - | Code(c) => "`" ++ c ++ "`" - | Link({content, url}) => "[" ++ renderInlines(content) ++ "](" ++ url ++ ")" - | InlineRef(refId) => "@ref(" ++ refId ++ ")" - } -} - -/// Render a list of inline elements to text. -and renderInlines = (inlines: array): string => { - inlines->Array.map(renderInline)->Array.join("") -} - -// --------------------------------------------------------------------------- -// Directive rendering -// --------------------------------------------------------------------------- - -/// Render a directive to A2ML surface syntax. -/// Single-line directives use `@name: value` format. -/// Multi-line directives use `@name:\n...\n@end` format. -let renderDirective = (dir: directive): string => { - let attrStr = if dir.attributes->Array.length > 0 { - let pairs = - dir.attributes - ->Array.map(((k, v)) => k ++ "=" ++ v) - ->Array.join(", ") - "(" ++ pairs ++ ")" - } else { - "" - } - - let hasNewlines = dir.value->String.includes("\n") - if hasNewlines { - "@" ++ dir.name ++ attrStr ++ ":\n" ++ dir.value ++ "\n@end" - } else { - "@" ++ dir.name ++ attrStr ++ ": " ++ dir.value - } -} - -// --------------------------------------------------------------------------- -// Attestation rendering -// --------------------------------------------------------------------------- - -/// Render an attestation block to A2ML surface syntax. -let renderAttestation = (att: attestation): string => { - let lines = [ - "!attest", - "identity: " ++ att.identity, - "role: " ++ att.role, - "trust-level: " ++ trustLevelToString(att.trustLevel), - ] - - switch att.timestamp { - | Some(ts) => lines->Array.push("timestamp: " ++ ts)->ignore - | None => () - } - - switch att.note { - | Some(n) => lines->Array.push("note: " ++ n)->ignore - | None => () - } - - lines->Array.push("!end")->ignore - lines->Array.join("\n") -} - -// --------------------------------------------------------------------------- -// Block rendering -// --------------------------------------------------------------------------- - -/// Render a single block to A2ML text. -let rec renderBlock = (blk: block): string => { - switch blk { - | Heading({level, content}) => - let hashes = Array.make(~length=level, "#")->Array.join("") - hashes ++ " " ++ renderInlines(content) - | Paragraph(inlines) => renderInlines(inlines) - | CodeBlock({language, content}) => - let langTag = switch language { - | Some(l) => l - | None => "" - } - "```" ++ langTag ++ "\n" ++ content ++ "\n```" - | DirectiveBlock(dir) => renderDirective(dir) - | AttestationBlock(att) => renderAttestation(att) - | ThematicBreak => "---" - | BlockQuote(blocks) => - blocks->Array.map(b => "> " ++ renderBlock(b))->Array.join("\n") - | BulletList(items) => - items->Array.map(inlines => "- " ++ renderInlines(inlines))->Array.join("\n") - | BlankLine => "" - } -} - -// --------------------------------------------------------------------------- -// Document rendering -// --------------------------------------------------------------------------- - -/// Render a complete A2ML document to text. -/// -/// ### Example -/// ``` -/// let doc = { title: Some("Hello"), directives: [], blocks: [...], attestations: [] } -/// let text = renderA2ML(doc) -/// ``` -let renderA2ML = (doc: document): string => { - doc.blocks->Array.map(renderBlock)->Array.join("\n") ++ "\n" -} diff --git a/deno/src/A2ML_Types.affine b/deno/src/A2ML_Types.affine new file mode 100644 index 0000000..0ea0073 --- /dev/null +++ b/deno/src/A2ML_Types.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module A2ML_Types; + +// TODO: Complete semantic implementation diff --git a/deno/src/A2ML_Types.res b/deno/src/A2ML_Types.res deleted file mode 100644 index a538ba9..0000000 --- a/deno/src/A2ML_Types.res +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// A2ML_Types — Core data types for A2ML (Attested Markup Language) documents. -// -// Defines the abstract syntax tree for A2ML documents including document -// structure, block-level elements, inline formatting, directives, and -// attestation provenance records with trust levels. - -// --------------------------------------------------------------------------- -// Trust levels -// --------------------------------------------------------------------------- - -/// The degree of trust associated with an attestation. -/// Forms an ordered scale from unverified content through to formally verified. -type trustLevel = - | Unverified - | Automated - | Reviewed - | Verified - -/// Parse a trust level from its canonical string representation. -/// Recognised values (case-insensitive): "unverified", "automated", -/// "reviewed", "verified". -let trustLevelFromString = (s: string): option => { - switch s->String.toLowerCase { - | "unverified" => Some(Unverified) - | "automated" => Some(Automated) - | "reviewed" => Some(Reviewed) - | "verified" => Some(Verified) - | _ => None - } -} - -/// Return the canonical string representation of a trust level. -let trustLevelToString = (level: trustLevel): string => { - switch level { - | Unverified => "unverified" - | Automated => "automated" - | Reviewed => "reviewed" - | Verified => "verified" - } -} - -// --------------------------------------------------------------------------- -// Inline-level elements -// --------------------------------------------------------------------------- - -/// An inline-level element within a block. -type rec inline = - | Text(string) - | Emphasis(array) - | Strong(array) - | Code(string) - | Link({content: array, url: string}) - | InlineRef(string) - -// --------------------------------------------------------------------------- -// Directives -// --------------------------------------------------------------------------- - -/// A machine-readable directive that provides metadata or instructions. -/// Directives begin with `@` in the source text, e.g. -/// `@version 1.0` or `@require trust-level:high`. -type directive = { - name: string, - value: string, - attributes: array<(string, string)>, -} - -/// Create a simple directive with a name and value, and no attributes. -let makeDirective = (name: string, value: string): directive => { - name, - value, - attributes: [], -} - -// --------------------------------------------------------------------------- -// Attestations -// --------------------------------------------------------------------------- - -/// An attestation record capturing who produced or reviewed content. -/// Attestation blocks start with `!attest` and record identity, -/// role, trust level, and optional timestamp of an author or reviewer. -type attestation = { - identity: string, - role: string, - trustLevel: trustLevel, - timestamp: option, - note: option, -} - -/// Create a new attestation with the minimum required fields. -let makeAttestation = ( - ~identity: string, - ~role: string, - ~trustLevel: trustLevel, -): attestation => { - identity, - role, - trustLevel, - timestamp: None, - note: None, -} - -// --------------------------------------------------------------------------- -// Block-level elements -// --------------------------------------------------------------------------- - -/// A block-level element in an A2ML document. -/// Blocks are separated by blank lines in the source text. -type rec block = - | Heading({level: int, content: array}) - | Paragraph(array) - | CodeBlock({language: option, content: string}) - | DirectiveBlock(directive) - | AttestationBlock(attestation) - | ThematicBreak - | BlockQuote(array) - | BulletList(array>) - | BlankLine - -// --------------------------------------------------------------------------- -// Top-level document -// --------------------------------------------------------------------------- - -/// A complete A2ML document, containing metadata and a sequence of blocks. -type document = { - title: option, - directives: array, - blocks: array, - attestations: array, -} - -/// Create a new, empty document with no title or content. -let emptyDocument = (): document => { - title: None, - directives: [], - blocks: [], - attestations: [], -} - -// --------------------------------------------------------------------------- -// Manifest (convenience aggregate) -// --------------------------------------------------------------------------- - -/// A high-level manifest extracted from a parsed A2ML document. -/// Collects directives and attestations for convenient programmatic access. -type manifest = { - version: option, - title: option, - directives: array, - attestations: array, -} - -/// Extract a manifest from a parsed document. -let manifestFromDocument = (doc: document): manifest => { - let version = - doc.directives - ->Array.find(d => d.name == "version") - ->Option.map(d => d.value) - - { - version, - title: doc.title, - directives: doc.directives, - attestations: doc.attestations, - } -} - -// --------------------------------------------------------------------------- -// Parse errors -// --------------------------------------------------------------------------- - -/// Errors that can occur during A2ML parsing. -type parseError = - | UnterminatedDirective({line: int, name: string}) - | InvalidHeadingLevel({line: int, level: int}) - | UnexpectedToken({line: int, token: string}) - | EmptyDocument - -/// Format a parse error as a diagnostic string. -let parseErrorToString = (err: parseError): string => { - switch err { - | UnterminatedDirective({line, name}) => - `error[A2ML]: line ${line->Int.toString}: unterminated directive @${name}` - | InvalidHeadingLevel({line, level}) => - `error[A2ML]: line ${line->Int.toString}: invalid heading level ${level->Int.toString} (must be 1-5)` - | UnexpectedToken({line, token}) => - `error[A2ML]: line ${line->Int.toString}: unexpected token "${token}"` - | EmptyDocument => "error[A2ML]: document is empty" - } -} diff --git a/haskell/examples/SafeDOMExample.affine b/haskell/examples/SafeDOMExample.affine new file mode 100644 index 0000000..9e0fd94 --- /dev/null +++ b/haskell/examples/SafeDOMExample.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module SafeDOMExample; + +// TODO: Complete semantic implementation diff --git a/haskell/examples/SafeDOMExample.res b/haskell/examples/SafeDOMExample.res deleted file mode 100644 index e5c9046..0000000 --- a/haskell/examples/SafeDOMExample.res +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Example: Using SafeDOM for formally verified DOM mounting - -open SafeDOM - -// Example 1: Basic mounting with error handling -let mountApp = () => { - mountSafe( - "#app", - "

Hello, World!

Mounted safely with proofs.

", - ~onSuccess=el => { - Console.log("✓ App mounted successfully!") - Console.log("Element:", el) - }, - ~onError=err => { - Console.error("✗ Mount failed:", err) - } - ) -} - -// Example 2: Wait for DOM ready before mounting -let mountWhenDOMReady = () => { - mountWhenReady( - "#app", - "

App Title

", - ~onSuccess=_ => Console.log("✓ Mounted after DOM ready"), - ~onError=err => Console.error("✗ Failed:", err) - ) -} - -// Example 3: Batch mounting (atomic - all or nothing) -let mountMultiple = () => { - let specs = [ - {selector: "#header", html: "

Site Title

"}, - {selector: "#nav", html: ""}, - {selector: "#main", html: "

Content here

"}, - {selector: "#footer", html: "
© 2026
"} - ] - - switch mountBatch(specs) { - | Ok(elements) => { - Console.log(`✓ Successfully mounted ${Array.length(elements)} elements`) - elements->Array.forEach(el => Console.log(" -", el)) - } - | Error(err) => { - Console.error("✗ Batch mount failed:", err) - Console.error(" (None were mounted - atomic operation)") - } - } -} - -// Example 4: Explicit validation before mounting -let mountWithValidation = () => { - // Validate selector first - switch ProvenSelector.validate("#my-app") { - | Error(e) => Console.error(`Invalid selector: ${e}`) - | Ok(validSelector) => { - // Validate HTML - switch ProvenHTML.validate("
Content
") { - | Error(e) => Console.error(`Invalid HTML: ${e}`) - | Ok(validHtml) => { - // Now mount with proven safety - switch mount(validSelector, validHtml) { - | Mounted(el) => Console.log("✓ Mounted with validated inputs:", el) - | MountPointNotFound(s) => Console.error(`✗ Element not found: ${s}`) - | InvalidSelector(_) => Console.error("Impossible - already validated") - | InvalidHTML(_) => Console.error("Impossible - already validated") - } - } - } - } -} - -// Example 5: Integration with TEA -module MyApp = { - type model = {message: string} - type msg = NoOp - - let init = () => {message: "Hello from TEA"} - let update = (model, _msg) => model - let view = model => `

${model.message}

` -} - -let mountTEAApp = () => { - let model = MyApp.init() - let html = MyApp.view(model) - - mountWhenReady( - "#tea-app", - html, - ~onSuccess=el => { - Console.log("✓ TEA app mounted") - // Set up event handlers, subscriptions here - }, - ~onError=err => Console.error(`✗ TEA mount failed: ${err}`) - ) -} - -// Entry point -let main = () => { - Console.log("SafeDOM Examples") - Console.log("================\n") - - // Choose which example to run - mountWhenDOMReady() // Run on DOM ready -} - -// Auto-execute when module loads -main() diff --git a/rs/examples/SafeDOMExample.affine b/rs/examples/SafeDOMExample.affine new file mode 100644 index 0000000..9e0fd94 --- /dev/null +++ b/rs/examples/SafeDOMExample.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine + +module SafeDOMExample; + +// TODO: Complete semantic implementation diff --git a/rs/examples/SafeDOMExample.res b/rs/examples/SafeDOMExample.res deleted file mode 100644 index e5c9046..0000000 --- a/rs/examples/SafeDOMExample.res +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Example: Using SafeDOM for formally verified DOM mounting - -open SafeDOM - -// Example 1: Basic mounting with error handling -let mountApp = () => { - mountSafe( - "#app", - "

Hello, World!

Mounted safely with proofs.

", - ~onSuccess=el => { - Console.log("✓ App mounted successfully!") - Console.log("Element:", el) - }, - ~onError=err => { - Console.error("✗ Mount failed:", err) - } - ) -} - -// Example 2: Wait for DOM ready before mounting -let mountWhenDOMReady = () => { - mountWhenReady( - "#app", - "

App Title

", - ~onSuccess=_ => Console.log("✓ Mounted after DOM ready"), - ~onError=err => Console.error("✗ Failed:", err) - ) -} - -// Example 3: Batch mounting (atomic - all or nothing) -let mountMultiple = () => { - let specs = [ - {selector: "#header", html: "

Site Title

"}, - {selector: "#nav", html: ""}, - {selector: "#main", html: "

Content here

"}, - {selector: "#footer", html: "
© 2026
"} - ] - - switch mountBatch(specs) { - | Ok(elements) => { - Console.log(`✓ Successfully mounted ${Array.length(elements)} elements`) - elements->Array.forEach(el => Console.log(" -", el)) - } - | Error(err) => { - Console.error("✗ Batch mount failed:", err) - Console.error(" (None were mounted - atomic operation)") - } - } -} - -// Example 4: Explicit validation before mounting -let mountWithValidation = () => { - // Validate selector first - switch ProvenSelector.validate("#my-app") { - | Error(e) => Console.error(`Invalid selector: ${e}`) - | Ok(validSelector) => { - // Validate HTML - switch ProvenHTML.validate("
Content
") { - | Error(e) => Console.error(`Invalid HTML: ${e}`) - | Ok(validHtml) => { - // Now mount with proven safety - switch mount(validSelector, validHtml) { - | Mounted(el) => Console.log("✓ Mounted with validated inputs:", el) - | MountPointNotFound(s) => Console.error(`✗ Element not found: ${s}`) - | InvalidSelector(_) => Console.error("Impossible - already validated") - | InvalidHTML(_) => Console.error("Impossible - already validated") - } - } - } - } -} - -// Example 5: Integration with TEA -module MyApp = { - type model = {message: string} - type msg = NoOp - - let init = () => {message: "Hello from TEA"} - let update = (model, _msg) => model - let view = model => `

${model.message}

` -} - -let mountTEAApp = () => { - let model = MyApp.init() - let html = MyApp.view(model) - - mountWhenReady( - "#tea-app", - html, - ~onSuccess=el => { - Console.log("✓ TEA app mounted") - // Set up event handlers, subscriptions here - }, - ~onError=err => Console.error(`✗ TEA mount failed: ${err}`) - ) -} - -// Entry point -let main = () => { - Console.log("SafeDOM Examples") - Console.log("================\n") - - // Choose which example to run - mountWhenDOMReady() // Run on DOM ready -} - -// Auto-execute when module loads -main()