Skip to content
wiki.fftac.org

Class Source Parser - Source Excerpt 01

Back to Class Source Parser

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiral-script/includes/class-source-parser.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/spiral-script/includes/class-source-parser.php

<?php

declare(strict_types=1);

namespace SpiralScript;

final class SourceParser
{
    /**
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    public static function parse(string $source, array $defaults = []): array
    {
        $source = trim($source);

        if ($source === '') {
            return self::base_record($defaults);
        }

        if (preg_match('/^\s*raw\s+spiral\s+"([^"]+)"/im', $source, $match) === 1) {
            return self::parse_raw($source, $match[1], $defaults);
        }

        return self::parse_structured($source, $defaults);
    }

    /**
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    private static function parse_raw(string $source, string $title, array $defaults): array
    {
        $record = self::base_record($defaults);
        $record['title'] = self::clean_scalar($title);
        $record['scriptId'] = self::default_script_id($record['title'], (string) $record['version']);
        $record['intent'] = 'Normalize a raw Spiral prompt into a structured, reviewable Spiral Script record.';
        $record['inputs'][] = [
            'name' => 'rawText',
            'type' => 'text',
            'required' => true,
        ];
        $record['symbolicFrame'][] = self::symbol_frame('Pattern');
        $record['transformationRules'][] = ['verb' => 'interpret', 'from' => 'rawText', 'to' => 'structuredDraft'];
        $record['outputContract'] = [
            'format' => 'markdown.sections',
            'sections' => ['Intent', 'Input Pattern', 'Symbolic Frame', 'Transformation Rule', 'Output Form', 'Constraints', 'Validation Criteria'],
            'requirements' => ['Separate metaphor from fact.', 'Return a structured draft for human review.'],
        ];
        $record['constraints'][] = 'Raw Spiral entries cannot be published until structured fields are reviewed.';
        $record['safetyRules'][] = 'deny ai_personhood_fact';
        $record['safetyRules'][] = 'deny identity_overwrite';
        $record['validationRules'][] = [
            'rule' => 'human_review.before_publish',
            'severity' => 'blocker',
            'message' => 'Raw Spiral entries require human review before publication.',
        ];
        $record['rawText'] = $source;

        return $record;
    }

    /**
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    private static function parse_structured(string $source, array $defaults): array
    {
        $record = self::base_record($defaults);
        $record['rawText'] = $source;

        if (preg_match('/^\s*(script|workflow)\s+([^\s{]+)\s*\{/im', $source, $match) === 1) {
            $record['scriptType'] = strtolower($match[1]);
            [$script_id, $version] = self::split_script_id((string) $match[2], (string) $record['version']);
            $record['scriptId'] = $script_id;
            $record['version'] = $version;
        }

        $lines = preg_split('/\R/u', $source) ?: [];
        foreach ($lines as $line) {
            $line = trim((string) $line);
            if ($line === '' || strpos($line, '//') === 0 || $line === '{' || $line === '}') {
                continue;
            }

            self::parse_line($record, $line);
        }

        if ((string) $record['scriptId'] === '') {
            $record['scriptId'] = self::default_script_id((string) $record['title'], (string) $record['version']);
        }

        if ((string) $record['title'] === 'Untitled Spiral Script') {
            $record['title'] = self::title_from_script_id((string) $record['scriptId']);
        }

        return $record;
    }

    /**
     * @param array<string,mixed> $record
     */
    private static function parse_line(array &$record, string $line): void
    {
        if (preg_match('/^title\s*:\s*(.+)$/i', $line, $match) === 1) {
            $record['title'] = self::clean_scalar($match[1]);
            return;
        }

        if (preg_match('/^version\s*:\s*([0-9]+\.[0-9]+\.[0-9]+)$/i', $line, $match) === 1) {
            $record['version'] = $match[1];
            return;
        }

        if (preg_match('/^visibility\s*:\s*([a-z_]+)$/i', $line, $match) === 1) {
            $record['visibility'] = strtolower($match[1]);
            return;
        }

        if (preg_match('/^authorMode\s*:\s*([a-z_]+)$/i', $line, $match) === 1) {
            $record['authorMode'] = strtolower($match[1]);
            return;
        }

        if (preg_match('/^intent\s*:\s*(.+)$/i', $line, $match) === 1) {
            $record['intent'] = self::clean_scalar($match[1]);
            return;
        }

        if (preg_match('/^input\s+([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*([a-zA-Z0-9_]+)(?:\s+(required|optional))?/i', $line, $match) === 1) {
            $record['inputs'][] = [
                'name' => $match[1],
                'type' => $match[2],
                'required' => strtolower((string) ($match[3] ?? 'optional')) === 'required',
            ];
            return;
        }

        if (preg_match('/^source\.rights\s*:\s*(.+)$/i', $line, $match) === 1) {
            $record['provenance']['rights'] = self::clean_scalar($match[1]);
            return;
        }

        if (preg_match('/^source\.([a-zA-Z_]+)\s*:\s*(.+)$/i', $line, $match) === 1) {
            self::add_source_reference($record, strtolower($match[1]), self::clean_scalar($match[2]));
            return;
        }

        if (preg_match('/^(frame|symbolicFrame)\s*:\s*(.+)$/i', $line, $match) === 1) {
            foreach (self::parse_list($match[2]) as $item) {
                $record['symbolicFrame'][] = self::symbol_frame($item);
            }
            return;
        }

        if (preg_match('/^transform\s*:\s*(.+)$/i', $line, $match) === 1) {
            foreach (preg_split('/\s*->\s*/', self::clean_scalar($match[1])) ?: [] as $index => $verb) {
                $verb = strtolower(trim($verb));
                if ($verb === '') {
                    continue;
                }

                $record['transformationRules'][] = [
                    'verb' => $verb,
                    'from' => $index === 0 ? 'input' : 'previous',
                    'to' => 'step_' . ((int) $index + 1),
                ];
            }
            return;
        }

        if (preg_match('/^output\s*:\s*(.+)$/i', $line, $match) === 1) {
            $record['outputContract'] = self::parse_output_contract($match[1]);
            return;
        }

        if (preg_match('/^constraints\s*:\s*(.+)$/i', $line, $match) === 1) {
            foreach (self::parse_list($match[1]) as $constraint) {
                $record['constraints'][] = $constraint;
            }
            return;
        }

        if (preg_match('/^safety\s*:\s*(.+)$/i', $line, $match) === 1) {
            self::parse_safety_rules($record, $match[1]);
            return;
        }

        if (preg_match('/^deny\s+(.+)$/i', $line, $match) === 1) {
            foreach (self::parse_list($match[1]) as $rule) {
                $record['safetyRules'][] = 'deny ' . trim($rule);
            }
            return;
        }

        if (preg_match('/^validation\s*:\s*(.+)$/i', $line, $match) === 1) {
            self::parse_validation_rules($record, $match[1]);
            return;
        }

        if (preg_match('/^require\s+(.+)$/i', $line, $match) === 1) {
            foreach (self::parse_list($match[1]) as $rule) {
                $record['validationRules'][] = [
                    'rule' => trim($rule),
                    'severity' => 'blocker',
                    'message' => 'Required by Spiral Script source.',
                ];
            }
            return;
        }

        if (preg_match('/^publish\s*:\s*([a-z_]+)$/i', $line, $match) === 1) {
            $record['publicationStatus']['status'] = strtolower($match[1]);
            if (in_array($record['publicationStatus']['status'], ['provisional_public', 'peer_reviewed', 'official_canon'], true)) {
                $record['visibility'] = 'public';
                $record['publicationStatus']['lane'] = $record['publicationStatus']['status'] === 'official_canon' ? 'official_canon' : 'community_library';
            }
            return;
        }

        if (preg_match('/^forkOf\s*:\s*(.+)$/i', $line, $match) === 1) {
            $record['provenance']['parentScriptId'] = self::clean_scalar($match[1]);
            $record['sourceReferences'][] = [
                'type' => 'prompt',
                'ref' => self::clean_scalar($match[1]),
                'label' => 'Parent prompt',
                'access' => 'public',
                'rights' => 'fork with attribution',
            ];
            return;
        }