Class Source Parser - Source Excerpt 01
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;
}