Skip to content

Latest commit

 

History

History
885 lines (698 loc) · 28.9 KB

File metadata and controls

885 lines (698 loc) · 28.9 KB

FROG logo

🐸 Reference Validator — validate_source()

MVP validation pseudo-code for the non-normative FROG reference implementation
FROG — Free Open Graphical Language


Contents


1. Overview

This document defines the first detailed pseudo-code sketch for validate_source() in the FROG reference implementation. Its purpose is to make the first validation stage directly implementable for the MVP slice already selected by the repository work.

The validator must decide whether loaded canonical source is acceptable as the basis for later derivation. If validation fails, the pipeline stops. If validation succeeds, the validator returns a compact validated basis for derivation.


2. Status and Boundary

This document is non-normative. It does not own the language rules. It is an implementation sketch that consumes the published repository rules and turns them into a staged validation procedure.

If this pseudo-code reveals ambiguity in the language rules, the specification should be clarified upstream instead of silently inventing private validator behavior.


3. MVP Scope

The first validator slice covers:

  • required top-level sections,
  • basic metadata presence,
  • basic interface declarations,
  • diagram nodes and edges,
  • core primitive use for the first examples,
  • interface_input and interface_output,
  • widget_value,
  • widget_reference,
  • frog.ui.property_read,
  • frog.ui.property_write,
  • frog.ui.method_invoke,
  • frog.core.delay with required initial,
  • basic cycle legality.

The first slice does not attempt to validate the entire future language surface. It validates enough to process the first example and conformance corpus coherently.


4. Input Contract

The validator consumes a loaded-source artifact conceptually equivalent to:

{
  "artifact_kind": "frog_loaded_source",
  "artifact_version": "0.1-dev",
  "source": {
    "path": "...",
    "content_hash": "...",
    "spec_version": "0.1"
  },
  "document": { ... canonical source object ... },
  "diagnostics": []
}

The validator assumes the source has already been decoded as structured data. It does not assume that the source is valid.


5. Output Contract

On success, the validator returns an artifact conceptually equivalent to:

{
  "artifact_kind": "frog_validation_result",
  "artifact_version": "0.1-dev",
  "source_ref": {
    "path": "...",
    "content_hash": "..."
  },
  "status": "ok",
  "validated_subset": { ... },
  "validated_program": {
    "program_id": "...",
    "entry_kind": "single_frog_program",
    "type_facts": [],
    "resolved_entities": { ... }
  },
  "diagnostics": []
}

On failure, the validator returns:

{
  "artifact_kind": "frog_validation_result",
  "artifact_version": "0.1-dev",
  "source_ref": {
    "path": "...",
    "content_hash": "..."
  },
  "status": "error",
  "error_code": "...",
  "diagnostics": [
    {
      "severity": "error",
      "message": "...",
      "source_anchor": { ... }
    }
  ]
}

No later stage may claim success if validation returned status = error.


6. Validation Strategy

The first validator should use a strict phased strategy:

  1. check top-level document shape,
  2. build local indexes,
  3. validate interface declarations,
  4. validate front-panel declarations if present,
  5. validate diagram node declarations,
  6. validate diagram edges,
  7. resolve types and participation roles,
  8. validate UI primitive usage,
  9. validate cycle legality,
  10. emit success or failure.

This phased shape keeps the code easy to debug and makes rejection reasons easier to explain.


7. Phase Breakdown

7.1 Top-level shape

Check that the source contains the required sections for canonical FROG source in the first slice.

7.2 Index build

Build fast lookup maps for:

  • interface inputs,
  • interface outputs,
  • widgets,
  • diagram nodes,
  • diagram edges.

7.3 Interface validation

Validate uniqueness, identifiers, and supported MVP types for interface declarations.

7.4 Front-panel validation

If a front_panel exists, validate widgets, widget IDs, widget classes, roles, and value-carrying constraints.

7.5 Diagram-node validation

Validate every node by kind and collect resolved facts.

7.6 Edge validation

Validate edge endpoints, port existence, directionality, and provisional type compatibility.

7.7 UI interaction validation

Check that:

  • widget_value is used only for value participation,
  • widget_reference is used only as object-style widget participation,
  • standardized UI primitives receive the right kinds of inputs,
  • no confusion exists between natural valueflow and object-style access.

7.8 Cycle validation

Detect directed cycles and confirm that each cycle contains at least one explicit local-memory primitive in the MVP slice.


8. Helper Conventions

The pseudo-code below uses the following conventions:

  • ctx is a mutable validation context.
  • ctx.errors stores accumulated hard failures.
  • ctx.warnings stores optional non-fatal notes.
  • fail(...) appends an error and marks the validation as failed.
  • stop_if_errors(ctx) returns early if hard failures already exist.
  • resolved_* helpers compute implementation-side facts for later derivation.

9. Pseudo-Code — Main Function

function validate_source(loaded_source):
    ctx = new ValidationContext()

    ctx.source_ref = {
        "path": loaded_source.source.path,
        "content_hash": loaded_source.source.content_hash
    }

    document = loaded_source.document

    validate_top_level_shape(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "invalid_top_level_shape")

    build_indexes(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "index_build_failed")

    validate_metadata(document, ctx)
    validate_interface(document, ctx)
    validate_front_panel(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "invalid_declarations")

    validate_diagram_nodes(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "invalid_diagram_nodes")

    validate_diagram_edges(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "invalid_diagram_edges")

    validate_ui_interaction_rules(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "invalid_ui_interaction")

    validate_cycle_legality(document, ctx)
    if ctx.has_errors():
        return build_validation_error(ctx, "invalid_cycles")

    validated_program = build_validated_program(document, ctx)

    return {
        "artifact_kind": "frog_validation_result",
        "artifact_version": "0.1-dev",
        "source_ref": ctx.source_ref,
        "status": "ok",
        "validated_subset": build_validated_subset(ctx),
        "validated_program": validated_program,
        "diagnostics": ctx.warnings
    }

10. Pseudo-Code — Phase Functions

10.1 Top-level shape

function validate_top_level_shape(document, ctx):
    required_sections = ["spec_version", "metadata", "interface", "diagram"]

    for key in required_sections:
        if key not in document:
            fail(ctx,
                 code = "missing_required_section",
                 message = "Missing required top-level section: " + key,
                 anchor = {"section": key})

    if "spec_version" in document and document["spec_version"] != "0.1":
        fail(ctx,
             code = "unsupported_spec_version",
             message = "Only spec_version 0.1 is supported in the first reference slice.",
             anchor = {"section": "spec_version"})

    if "interface" in document:
        if type(document["interface"]) is not object:
            fail(ctx,
                 code = "invalid_interface_section",
                 message = "interface must be an object.",
                 anchor = {"section": "interface"})

    if "diagram" in document:
        if type(document["diagram"]) is not object:
            fail(ctx,
                 code = "invalid_diagram_section",
                 message = "diagram must be an object.",
                 anchor = {"section": "diagram"})

10.2 Index build

function build_indexes(document, ctx):
    ctx.interface_inputs = {}
    ctx.interface_outputs = {}
    ctx.widgets = {}
    ctx.nodes = {}
    ctx.edges = {}

    if "interface" in document:
        for item in document["interface"].get("inputs", []):
            register_unique(ctx.interface_inputs, item, "id", ctx, "duplicate_interface_input_id")

        for item in document["interface"].get("outputs", []):
            register_unique(ctx.interface_outputs, item, "id", ctx, "duplicate_interface_output_id")

    if "front_panel" in document:
        for widget in document["front_panel"].get("widgets", []):
            register_unique(ctx.widgets, widget, "id", ctx, "duplicate_widget_id")

    if "diagram" in document:
        for node in document["diagram"].get("nodes", []):
            register_unique(ctx.nodes, node, "id", ctx, "duplicate_node_id")

        for edge in document["diagram"].get("edges", []):
            register_unique(ctx.edges, edge, "id", ctx, "duplicate_edge_id")

10.3 Metadata validation

function validate_metadata(document, ctx):
    metadata = document.get("metadata", {})

    if "name" not in metadata:
        fail(ctx,
             code = "missing_metadata_name",
             message = "metadata.name is required in the first reference slice.",
             anchor = {"section": "metadata"})

    if "program_version" not in metadata:
        ctx.warnings.append({
            "severity": "warning",
            "message": "metadata.program_version is recommended.",
            "source_anchor": {"section": "metadata"}
        })

10.4 Interface validation

function validate_interface(document, ctx):
    interface = document.get("interface", {})
    inputs = interface.get("inputs", [])
    outputs = interface.get("outputs", [])

    for port in inputs:
        validate_port_decl(port, "input", ctx)

    for port in outputs:
        validate_port_decl(port, "output", ctx)

function validate_port_decl(port, role, ctx):
    if "id" not in port:
        fail(ctx,
             code = "missing_interface_port_id",
             message = "Interface " + role + " is missing id.",
             anchor = {"role": role})
        return

    if "type" not in port:
        fail(ctx,
             code = "missing_interface_port_type",
             message = "Interface " + role + " '" + port["id"] + "' is missing type.",
             anchor = {"interface_port": port["id"]})
        return

    if not is_supported_mvp_type(port["type"]):
        fail(ctx,
             code = "unsupported_interface_type",
             message = "Unsupported MVP interface type: " + to_string(port["type"]),
             anchor = {"interface_port": port["id"]})

10.5 Front-panel validation

function validate_front_panel(document, ctx):
    if "front_panel" not in document:
        return

    fp = document["front_panel"]

    if "widgets" not in fp:
        fail(ctx,
             code = "missing_front_panel_widgets",
             message = "front_panel.widgets is required when front_panel is present.",
             anchor = {"section": "front_panel"})
        return

    for widget in fp["widgets"]:
        validate_widget_decl(widget, ctx)

function validate_widget_decl(widget, ctx):
    widget_id = widget.get("id")

    if "widget" not in widget:
        fail(ctx,
             code = "missing_widget_class",
             message = "Widget is missing widget class.",
             anchor = {"widget_id": widget_id})
        return

    if not is_supported_mvp_widget_class(widget["widget"]):
        fail(ctx,
             code = "unsupported_widget_class",
             message = "Unsupported MVP widget class: " + widget["widget"],
             anchor = {"widget_id": widget_id})

    role = widget.get("role")
    if role not in ["control", "indicator"]:
        fail(ctx,
             code = "invalid_widget_role",
             message = "Widget role must be 'control' or 'indicator' in the MVP slice.",
             anchor = {"widget_id": widget_id})

    if widget_requires_value_type(widget["widget"]):
        if "value_type" not in widget:
            fail(ctx,
                 code = "missing_widget_value_type",
                 message = "Value-carrying widget is missing value_type.",
                 anchor = {"widget_id": widget_id})
        elif not is_supported_mvp_type(widget["value_type"]):
            fail(ctx,
                 code = "unsupported_widget_value_type",
                 message = "Unsupported MVP widget value_type.",
                 anchor = {"widget_id": widget_id})

10.6 Diagram node validation

function validate_diagram_nodes(document, ctx):
    for node in document["diagram"].get("nodes", []):
        kind = node.get("kind")

        if kind == "interface_input":
            validate_interface_input_node(node, ctx)

        elif kind == "interface_output":
            validate_interface_output_node(node, ctx)

        elif kind == "primitive":
            validate_primitive_node(node, ctx)

        elif kind == "widget_value":
            validate_widget_value_node(node, ctx)

        elif kind == "widget_reference":
            validate_widget_reference_node(node, ctx)

        else:
            fail(ctx,
                 code = "unsupported_node_kind",
                 message = "Unsupported MVP node kind: " + to_string(kind),
                 anchor = {"node_id": node.get("id")})

10.7 Interface nodes

function validate_interface_input_node(node, ctx):
    port_id = node.get("interface_port")
    if port_id not in ctx.interface_inputs:
        fail(ctx,
             code = "unknown_interface_input",
             message = "interface_input references unknown input port.",
             anchor = {"node_id": node.get("id")})
        return

    ctx.resolved_node_facts[node["id"]] = {
        "kind": "interface_input",
        "value_type": ctx.interface_inputs[port_id]["type"],
        "interface_port": port_id
    }

function validate_interface_output_node(node, ctx):
    port_id = node.get("interface_port")
    if port_id not in ctx.interface_outputs:
        fail(ctx,
             code = "unknown_interface_output",
             message = "interface_output references unknown output port.",
             anchor = {"node_id": node.get("id")})
        return

    ctx.resolved_node_facts[node["id"]] = {
        "kind": "interface_output",
        "value_type": ctx.interface_outputs[port_id]["type"],
        "interface_port": port_id
    }

10.8 Primitive nodes

function validate_primitive_node(node, ctx):
    primitive_ref = node.get("type")

    if primitive_ref not in supported_mvp_primitives():
        fail(ctx,
             code = "unsupported_primitive_ref",
             message = "Unsupported MVP primitive: " + to_string(primitive_ref),
             anchor = {"node_id": node.get("id")})
        return

    if primitive_ref == "frog.core.delay":
        if "initial" not in node:
            fail(ctx,
                 code = "missing_delay_initial",
                 message = "frog.core.delay requires explicit initial in the MVP slice.",
                 anchor = {"node_id": node.get("id")})
            return

        if not is_numeric_literal(node["initial"]):
            fail(ctx,
                 code = "invalid_delay_initial",
                 message = "frog.core.delay initial must be a numeric literal in the MVP slice.",
                 anchor = {"node_id": node.get("id")})

    if primitive_ref in ["frog.ui.property_read", "frog.ui.property_write", "frog.ui.method_invoke"]:
        validate_ui_primitive_metadata(node, ctx)

    ctx.resolved_node_facts[node["id"]] = {
        "kind": "primitive",
        "primitive_ref": primitive_ref
    }

function validate_ui_primitive_metadata(node, ctx):
    primitive_ref = node["type"]

    if primitive_ref in ["frog.ui.property_read", "frog.ui.property_write"]:
        if "widget_member" not in node:
            fail(ctx,
                 code = "missing_widget_member",
                 message = primitive_ref + " requires widget_member.",
                 anchor = {"node_id": node.get("id")})
            return

        member = node["widget_member"]
        if "member" not in member:
            fail(ctx,
                 code = "missing_widget_member_name",
                 message = "widget_member.member is required.",
                 anchor = {"node_id": node.get("id")})

    if primitive_ref == "frog.ui.method_invoke":
        if "widget_method" not in node:
            fail(ctx,
                 code = "missing_widget_method",
                 message = "frog.ui.method_invoke requires widget_method.",
                 anchor = {"node_id": node.get("id")})

10.9 Widget participation nodes

function validate_widget_value_node(node, ctx):
    widget_id = node.get("widget")

    if widget_id not in ctx.widgets:
        fail(ctx,
             code = "unknown_widget_for_widget_value",
             message = "widget_value references unknown widget.",
             anchor = {"node_id": node.get("id")})
        return

    widget = ctx.widgets[widget_id]

    if not widget_has_primary_value(widget):
        fail(ctx,
             code = "widget_value_on_non_value_widget",
             message = "widget_value requires a value-carrying widget.",
             anchor = {"node_id": node.get("id")})
        return

    ctx.resolved_node_facts[node["id"]] = {
        "kind": "widget_value",
        "widget_id": widget_id,
        "widget_role": widget.get("role"),
        "value_type": widget.get("value_type")
    }

function validate_widget_reference_node(node, ctx):
    widget_id = node.get("widget")

    if widget_id not in ctx.widgets:
        fail(ctx,
             code = "unknown_widget_for_widget_reference",
             message = "widget_reference references unknown widget.",
             anchor = {"node_id": node.get("id")})
        return

    ctx.resolved_node_facts[node["id"]] = {
        "kind": "widget_reference",
        "widget_id": widget_id
    }

10.10 Edge validation

function validate_diagram_edges(document, ctx):
    for edge in document["diagram"].get("edges", []):
        validate_single_edge(edge, ctx)

function validate_single_edge(edge, ctx):
    from_node_id = edge.get("from", {}).get("node")
    from_port = edge.get("from", {}).get("port")
    to_node_id = edge.get("to", {}).get("node")
    to_port = edge.get("to", {}).get("port")

    if from_node_id not in ctx.nodes:
        fail(ctx,
             code = "unknown_edge_source_node",
             message = "Edge source node does not exist.",
             anchor = {"edge_id": edge.get("id")})
        return

    if to_node_id not in ctx.nodes:
        fail(ctx,
             code = "unknown_edge_target_node",
             message = "Edge target node does not exist.",
             anchor = {"edge_id": edge.get("id")})
        return

    source_fact = ctx.resolved_node_facts.get(from_node_id)
    target_fact = ctx.resolved_node_facts.get(to_node_id)

    if source_fact is null or target_fact is null:
        fail(ctx,
             code = "edge_before_node_resolution",
             message = "Cannot validate edge because node facts were not resolved.",
             anchor = {"edge_id": edge.get("id")})
        return

    if not is_valid_output_port(source_fact, from_port):
        fail(ctx,
             code = "invalid_source_port",
             message = "Invalid edge source port.",
             anchor = {"edge_id": edge.get("id")})
        return

    if not is_valid_input_port(target_fact, to_port):
        fail(ctx,
             code = "invalid_target_port",
             message = "Invalid edge target port.",
             anchor = {"edge_id": edge.get("id")})
        return

    source_type = resolve_output_port_type(source_fact, from_port)
    target_type = resolve_input_port_type(target_fact, to_port)

    if not types_compatible(source_type, target_type):
        fail(ctx,
             code = "edge_type_mismatch",
             message = "Edge type mismatch: " + to_string(source_type) + " -> " + to_string(target_type),
             anchor = {"edge_id": edge.get("id")})

10.11 UI interaction rules

function validate_ui_interaction_rules(document, ctx):
    for node in document["diagram"].get("nodes", []):
        if node.get("kind") != "primitive":
            continue

        primitive_ref = node.get("type")

        if primitive_ref in ["frog.ui.property_read", "frog.ui.property_write", "frog.ui.method_invoke"]:
            validate_ui_primitive_wiring(node, document, ctx)

    validate_no_widget_reference_confusion(document, ctx)

function validate_ui_primitive_wiring(node, document, ctx):
    node_id = node["id"]
    incoming = find_incoming_edges(document, node_id)

    has_ref = any(edge.to.port == "ref" for edge in incoming)
    if not has_ref:
        fail(ctx,
             code = "missing_ui_primitive_ref_input",
             message = node["type"] + " requires a ref input.",
             anchor = {"node_id": node_id})
        return

    ref_edge = first(edge for edge in incoming if edge.to.port == "ref")
    ref_source_fact = ctx.resolved_node_facts[ref_edge["from"]["node"]]

    if ref_source_fact["kind"] != "widget_reference":
        fail(ctx,
             code = "ui_primitive_ref_must_come_from_widget_reference",
             message = "UI primitive ref input must be driven by widget_reference.",
             anchor = {"node_id": node_id})

function validate_no_widget_reference_confusion(document, ctx):
    for edge in document["diagram"].get("edges", []):
        from_node_id = edge["from"]["node"]
        source_fact = ctx.resolved_node_facts[from_node_id]

        if source_fact["kind"] != "widget_reference":
            continue

        target_node_id = edge["to"]["node"]
        target_node = ctx.nodes[target_node_id]

        if target_node.get("kind") != "primitive":
            fail(ctx,
                 code = "widget_reference_without_ui_primitive",
                 message = "widget_reference must be used through a valid standardized UI primitive in the MVP slice.",
                 anchor = {"edge_id": edge.get("id")})
            continue

        if target_node.get("type") not in ["frog.ui.property_read", "frog.ui.property_write", "frog.ui.method_invoke"]:
            fail(ctx,
                 code = "widget_reference_without_ui_primitive",
                 message = "widget_reference cannot feed ordinary primitives in the MVP slice.",
                 anchor = {"edge_id": edge.get("id")})

10.12 Cycle legality

function validate_cycle_legality(document, ctx):
    graph = build_directed_graph_from_edges(document["diagram"].get("edges", []))
    cycles = find_directed_cycles(graph)

    for cycle in cycles:
        if not cycle_contains_explicit_local_memory(cycle, ctx):
            fail(ctx,
                 code = "illegal_feedback_without_explicit_memory",
                 message = "Directed cycle without explicit local-memory primitive.",
                 anchor = {
                     "node_ids": cycle.node_ids,
                     "edge_ids": cycle.edge_ids
                 })

function cycle_contains_explicit_local_memory(cycle, ctx):
    for node_id in cycle.node_ids:
        fact = ctx.resolved_node_facts[node_id]
        if fact["kind"] == "primitive" and fact["primitive_ref"] == "frog.core.delay":
            return true
    return false

10.13 Build validated program

function build_validated_program(document, ctx):
    return {
        "program_id": make_program_id(ctx.source_ref["path"], document),
        "entry_kind": "single_frog_program",
        "type_facts": collect_type_facts(ctx),
        "resolved_entities": {
            "interface_inputs": list(keys(ctx.interface_inputs)),
            "interface_outputs": list(keys(ctx.interface_outputs)),
            "widgets": list(keys(ctx.widgets)),
            "primitive_refs": collect_primitive_refs(ctx)
        }
    }

function build_validated_subset(ctx):
    return {
        "core_primitives": ctx.used_core_primitives,
        "public_interface": ctx.used_public_interface,
        "widget_value": ctx.used_widget_value,
        "widget_reference": ctx.used_widget_reference,
        "ui_object_primitives": ctx.used_ui_object_primitives,
        "explicit_local_memory": ctx.used_explicit_local_memory
    }

11. MVP Rules Enforced

The first validator should enforce at least the following MVP rules:

  • required top-level sections must exist,
  • interface ports must be declared explicitly and typed,
  • front-panel widgets must be declared explicitly if referenced,
  • widget_value requires a value-carrying widget,
  • widget_reference requires an existing widget,
  • UI primitives must carry the required metadata,
  • UI primitive ref must come from widget_reference,
  • widget_reference must not be treated as ordinary valueflow,
  • frog.core.delay requires explicit initial,
  • a cycle is valid only if it contains explicit local memory.

12. Expected Invalid Rejections

The first validator should explicitly reject the three initial invalid conformance cases:

  • widget reference without UI primitive
  • illegal feedback without explicit memory
  • interface / widget role confusion

The validator must reject those cases rather than:

  • inventing a missing primitive,
  • inserting hidden memory,
  • auto-converting widget participation into public interface participation,
  • normalizing widget_value into property access or the reverse.

13. Summary

This document provides a directly codable MVP pseudo-code sketch for validate_source() in the FROG reference implementation. It keeps the validator narrow, explicit, and aligned with the published repository boundaries: reject invalid source, establish a validated basis, and stop the pipeline unless derivation is semantically justified.