Class Source Parser - Source Excerpt 02
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
if (preg_match('/^step\s+([a-zA-Z][a-zA-Z0-9_]*)\s+uses\s+([^\s]+)(?:\s+after\s+([a-zA-Z][a-zA-Z0-9_]*))?/i', $line, $match) === 1) {
if (!isset($record['workflow']) || !is_array($record['workflow'])) {
$record['workflow'] = ['steps' => [], 'gates' => []];
}
$record['workflow']['steps'][] = [
'name' => $match[1],
'uses' => $match[2],
'after' => (string) ($match[3] ?? ''),
];
return;
}
if (preg_match('/^gate\s+([a-zA-Z][a-zA-Z0-9_]*)\s+requires\s+(.+)$/i', $line, $match) === 1) {
if (!isset($record['workflow']) || !is_array($record['workflow'])) {
$record['workflow'] = ['steps' => [], 'gates' => []];
}
$record['workflow']['gates'][] = [
'name' => $match[1],
'requires' => self::clean_scalar($match[2]),
];
}
}
/**
* @param array<string,mixed> $defaults
* @return array<string,mixed>
*/
private static function base_record(array $defaults): array
{
$now = (string) ($defaults['createdAt'] ?? gmdate('c'));
$created_by = (string) ($defaults['createdBy'] ?? 'user:local');
return [
'scriptId' => (string) ($defaults['scriptId'] ?? ''),
'version' => (string) ($defaults['version'] ?? '0.1.0'),
'title' => (string) ($defaults['title'] ?? 'Untitled Spiral Script'),
'visibility' => (string) ($defaults['visibility'] ?? 'private'),
'authorMode' => (string) ($defaults['authorMode'] ?? 'human'),
'sourceReferences' => [],
'intent' => (string) ($defaults['intent'] ?? ''),
'inputs' => [],
'symbolicFrame' => [],
'transformationRules' => [],
'outputContract' => [
'format' => 'markdown.sections',
'sections' => [],
'requirements' => [],
],
'constraints' => [],
'safetyRules' => [],
'validationRules' => [],
'provenance' => [
'createdBy' => $created_by,
'createdAt' => $now,
'rights' => (string) ($defaults['rights'] ?? 'unspecified'),
'reviewState' => (string) ($defaults['reviewState'] ?? 'draft'),
],
'runSettings' => [
'modelPolicy' => (string) ($defaults['modelPolicy'] ?? 'site-default'),
'temperature' => (float) ($defaults['temperature'] ?? 0.4),
'maxOutputTokens' => (int) ($defaults['maxOutputTokens'] ?? 1200),
'storeRunLog' => true,
],
'revisionHistory' => [],
'forkPolicy' => [
'allowForks' => (bool) ($defaults['allowForks'] ?? false),
'requiresAttribution' => true,
'allowedVisibility' => ['private'],
],
'publicationStatus' => [
'status' => 'draft',
'lane' => 'private_workspace',
],
];
}
/**
* @return array{0:string,1:string}
*/
private static function split_script_id(string $value, string $default_version): array
{
$value = trim($value);
if (preg_match('/^(ss:[a-z0-9.-]+)@([0-9]+\.[0-9]+\.[0-9]+(?:\+[a-z0-9.-]+)?)$/i', $value, $match) === 1) {
$version = preg_replace('/\+.*/', '', $match[2]) ?: $default_version;
return [$match[1] . '@' . $match[2], $version];
}
if (strpos($value, 'ss:') === 0) {
return [$value . '@' . $default_version, $default_version];
}
return [self::default_script_id($value, $default_version), $default_version];
}
private static function default_script_id(string $title, string $version): string
{
$slug = strtolower(trim((string) preg_replace('/[^a-zA-Z0-9]+/', '-', $title), '-'));
if ($slug === '') {
$slug = 'untitled';
}
return 'ss:draft.' . $slug . '@' . $version;
}
private static function title_from_script_id(string $script_id): string
{
$slug = preg_replace('/^ss:[a-z0-9.-]*?([a-z0-9-]+)@.*$/', '$1', $script_id) ?: 'Spiral Script';
$title = ucwords(str_replace('-', ' ', (string) $slug));
return $title !== '' ? $title : 'Spiral Script';
}
/**
* @param array<string,mixed> $record
*/
private static function add_source_reference(array &$record, string $type, string $raw_ref): void
{
if ($type === 'rights') {
$record['provenance']['rights'] = $raw_ref;
return;
}
$ref = $raw_ref;
if (preg_match('/^(folio|url|user|prompt)\("([^"]+)"\)$/i', $raw_ref, $match) === 1) {
$ref = strtolower($type) === 'manuscript' ? 'folio:' . $match[2] : $match[2];
}
$access = strtolower($type) === 'user_note' || strtolower($type) === 'upload' ? 'private' : 'public';
$record['sourceReferences'][] = [
'type' => $type,
'ref' => $ref,
'label' => $raw_ref,
'access' => $access,
'rights' => '',
];
}
/**
* @return array<string,mixed>
*/
private static function symbol_frame(string $item): array
{
$resolved = LanguageSpec::resolve_symbol($item);
if (is_array($resolved)) {
return [
'symbol' => (string) ($resolved['symbol'] ?? ''),
'name' => (string) ($resolved['name'] ?? $item),
'machineMeaning' => (string) ($resolved['machineMeaning'] ?? ''),
'note' => self::clean_scalar($item),
];
}
return [
'symbol' => '',
'name' => self::clean_scalar($item),
'machineMeaning' => '',
'note' => self::clean_scalar($item),
];
}
/**
* @return array<int,string>
*/
private static function parse_list(string $value): array
{
$value = trim($value);
if ($value === '') {
return [];
}
if (preg_match('/^[a-z_]+\((.*)\)$/i', $value, $match) === 1) {
$value = $match[1];
}
$value = trim($value);
if (strlen($value) >= 2 && $value[0] === '[' && substr($value, -1) === ']') {
$value = substr($value, 1, -1);
}
if (preg_match_all('/"([^"]+)"/', $value, $matches) && !empty($matches[1])) {
return array_values(array_filter(array_map([self::class, 'clean_scalar'], $matches[1])));
}
return array_values(array_filter(array_map(
static fn ($item): string => self::clean_scalar((string) $item),
explode(',', $value)
)));
}
/**
* @return array<string,mixed>
*/
private static function parse_output_contract(string $value): array
{
$value = trim($value);
$contract = [
'format' => self::clean_scalar($value),
'sections' => [],
'requirements' => [],
];
if (preg_match('/^([a-zA-Z0-9_.:-]+)\s*\((.*)\)$/', $value, $match) === 1) {
$contract['format'] = $match[1];
if (stripos($match[1], 'sections') !== false) {
$contract['sections'] = self::parse_list($match[2]);
}
}
return $contract;
}
/**
* @param array<string,mixed> $record
*/
private static function parse_safety_rules(array &$record, string $value): void
{
$value = trim($value);
if (preg_match('/^deny\((.*)\)$/i', $value, $match) === 1) {
foreach (self::parse_list($match[1]) as $rule) {
$record['safetyRules'][] = 'deny ' . $rule;
}
return;
}
foreach (self::parse_list($value) as $rule) {
$record['safetyRules'][] = $rule;
}
}
/**
* @param array<string,mixed> $record
*/
private static function parse_validation_rules(array &$record, string $value): void
{
$value = trim($value);
if (preg_match('/^require\((.*)\)$/i', $value, $match) === 1) {
foreach (self::parse_list($match[1]) as $rule) {
$record['validationRules'][] = [
'rule' => $rule,
'severity' => 'blocker',
'message' => 'Required by Spiral Script source.',
];
}
return;
}
foreach (self::parse_list($value) as $rule) {
$record['validationRules'][] = [