metagen/parser.inc.php

1010 lines
27 KiB
PHP
Raw Permalink Normal View History

2022-12-08 19:14:25 +03:00
<?php
class mtgMetaInfoParser
{
2023-11-12 18:17:32 +03:00
const T_NonOrdMark = 1000; //marks start of non 'ord(..)' values
2022-12-08 19:14:25 +03:00
const T_EOF = 1001;
const T_StringConstant = 1002;
const T_IntegerConstant = 1003;
const T_FloatConstant = 1004;
const T_Enum = 1005;
const T_RPC = 1006;
const T_End = 1007;
const T_UserSymbol = 1008;
2022-12-08 19:14:25 +03:00
const T_Struct = 1009;
const T_Prop = 1010;
const T_Extends = 1011;
const T_Func = 1012;
const T_RawStringConstant = 1013;
const T_Interface = 1014;
const T_Implements = 1015;
2023-11-12 18:17:32 +03:00
const T_MinType = 1019; //built-in types begin mark
2022-12-08 19:14:25 +03:00
const T_string = 1020;
const T_uint32 = 1021;
const T_int32 = 1022;
const T_uint16 = 1023;
const T_int16 = 1024;
const T_uint8 = 1025;
const T_int8 = 1026;
const T_float = 1027;
const T_uint64 = 1028;
const T_int64 = 1029;
const T_bool = 1030;
const T_blob = 1031;
2023-11-12 18:17:32 +03:00
const T_MaxType = 1032; //built-in types end mark
private array $config = array();
private mtgMetaInfo $current_meta;
private array $parsed_files = array();
private array $file_stack = array();
private ?mtgMetaParsedModule $module = null;
private string $file = "";
private string $source = "";
private int $cursor_pos = 0;
private string $cursor_char = '';
2023-11-12 18:17:32 +03:00
private int $line = 0;
//TODO: setting it an 'int' type makes PHPStan produce many
// false positives
//token numeric identifier
2023-11-12 18:17:32 +03:00
private $T = 0;
//token extra string value which depends on concrete T
2023-11-12 18:17:32 +03:00
private string $T_value = "";
/** @var array<string,int>*/
private $symbol2T = array();
2023-11-12 18:17:32 +03:00
/** @var array<int,string>*/
private $T2descr = array();
private array $shared_tokens = array();
2022-12-08 19:14:25 +03:00
function __construct($config = array())
{
2023-11-12 18:17:32 +03:00
$this->_initTables();
self::_addDefaultTokens($config);
2022-12-08 19:14:25 +03:00
$this->config = $config;
if(!isset($this->config['include_path']))
$this->config['include_path'] = array('.');
2023-11-12 18:17:32 +03:00
}
2022-12-08 19:14:25 +03:00
2023-11-12 18:17:32 +03:00
private function _initTables()
{
$this->symbol2T = [
2022-12-08 19:14:25 +03:00
"string" => self::T_string,
"uint32" => self::T_uint32,
"int32" => self::T_int32,
"uint16" => self::T_uint16,
"int16" => self::T_int16,
"uint8" => self::T_uint8,
"int8" => self::T_int8,
"float" => self::T_float,
"double" => self::T_float,
"uint64" => self::T_uint64,
"int64" => self::T_int64,
"bool" => self::T_bool,
"blob" => self::T_blob,
2023-11-12 18:17:32 +03:00
"true" => self::T_IntegerConstant,
"false" => self::T_IntegerConstant,
"struct" => self::T_Struct,
"interface" => self::T_Interface,
"enum" => self::T_Enum,
"RPC" => self::T_RPC,
"end" => self::T_End,
"extends" => self::T_Extends,
"implements" => self::T_Implements,
"func" => self::T_Func,
];
$this->T2descr = array_flip($this->symbol2T);
2023-11-12 18:17:32 +03:00
$this->T2descr[self::T_EOF] = '<EOF>';
$this->T2descr[self::T_StringConstant] = '<StringConstant>';
$this->T2descr[self::T_RawStringConstant] = '<RawStringConstant>';
$this->T2descr[self::T_IntegerConstant] = '<IntegerConstant>';
$this->T2descr[self::T_FloatConstant] = '<FloatConstant>';
$this->T2descr[self::T_Enum] = '<enum>';
$this->T2descr[self::T_RPC] = '<RPC>';
$this->T2descr[self::T_End] = '<end>';
$this->T2descr[self::T_UserSymbol] = '<Identifier>';
2023-11-12 18:17:32 +03:00
$this->T2descr[self::T_Struct] = '<struct>';
$this->T2descr[self::T_Interface] = '<interface>';
$this->T2descr[self::T_Prop] = '<@prop>';
$this->T2descr[self::T_Extends] = '<extends>';
$this->T2descr[self::T_Implements] = '<implements>';
$this->T2descr[self::T_Func] = '<func>';
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
private static function _addDefaultTokens(array &$config)
{
if(!isset($config['valid_tokens']))
$config['valid_tokens'] = array();
$config['valid_tokens'][] = 'class_id';
$config['valid_tokens'][] = 'shared_tokens';
$config['valid_tokens'][] = 'enum_override';
2023-11-09 17:47:36 +03:00
$config['valid_tokens'][] = 'enum_replace';
}
function parse(mtgMetaInfo $meta, string $raw_file)
2022-12-08 19:14:25 +03:00
{
$this->current_meta = $meta;
$file = realpath($raw_file);
if($file === false)
throw new Exception("No such file '$raw_file'");
$this->_parse($file);
}
private function _parse(string $file)
2022-12-08 19:14:25 +03:00
{
if(isset($this->parsed_files[$file]))
return;
$module = new mtgMetaParsedModule($file);
$this->parsed_files[$file] = $module;
2022-12-08 19:14:25 +03:00
$this->file_stack[] = $file;
$source = file_get_contents($file);
try
{
if($source === false)
throw new Exception("Could not read file '$file'");
2023-11-12 18:17:32 +03:00
$this->_resolveIncludes($module, $source);
2022-12-08 19:14:25 +03:00
}
catch(Exception $e)
{
throw new Exception(end($this->file_stack) . " : " . $e->getMessage());
}
array_pop($this->file_stack);
$this->module = $module;
2022-12-08 19:14:25 +03:00
$this->file = $file;
$this->source = $source;
$this->line = 1;
$this->cursor_pos = -1;
$this->_cursorNext();
$this->shared_tokens = array();
2022-12-08 19:14:25 +03:00
try
{
$this->_nextT();
2023-11-12 18:17:32 +03:00
while($this->T != self::T_EOF)
2022-12-08 19:14:25 +03:00
{
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Prop)
$this->_parseSharedTokens($this->_parsePropTokens());
2023-11-12 18:17:32 +03:00
else if($this->T == self::T_Enum)
2022-12-08 19:14:25 +03:00
$this->_parseEnum();
2023-11-12 18:17:32 +03:00
else if($this->T == self::T_Struct)
2022-12-08 19:14:25 +03:00
$this->_parseStruct();
2023-11-12 18:17:32 +03:00
else if($this->T == self::T_Interface)
2022-12-08 19:14:25 +03:00
$this->_parseInterface();
2023-11-12 18:17:32 +03:00
else if($this->T == self::T_Func)
2022-12-08 19:14:25 +03:00
$this->_parseFreeFunc();
2023-11-12 18:17:32 +03:00
else if($this->T == self::T_RPC)
2022-12-08 19:14:25 +03:00
$this->_parseRPC();
else
$this->_error("Unexpected symbol ('" . $this->_toStr($this->T) . "' " . $this->T_value . ")");
2022-12-08 19:14:25 +03:00
}
}
catch(Exception $e)
{
throw new Exception("$file@{$this->line} : " . $e->getMessage() . " " . $e->getTraceAsString());
}
}
private function _parseInclude(mtgMetaParsedModule $module, string $file)
{
$this->_parse($file);
$module->addInclude($this->parsed_files[$file]);
}
private function _parseSharedTokens(array $tokens)
{
if(!isset($tokens['shared_tokens']))
return;
$this->shared_tokens = json_decode($tokens['shared_tokens'], true);
if(!is_array($this->shared_tokens))
$this->_error("Invalid 'shared_tokens' formant, invalid JSON");
}
private function _parseType(bool $can_be_multi = false)
2022-12-08 19:14:25 +03:00
{
$types = array();
while(true)
{
$type = null;
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Func)
2022-12-08 19:14:25 +03:00
{
$origin = new mtgOrigin($this->file, $this->line);
2022-12-08 19:14:25 +03:00
$func_type = $this->_parseFuncType();
$type = new mtgTypeRef($func_type, $this->module, $origin);
2022-12-08 19:14:25 +03:00
}
else if($this->T == self::T_UserSymbol)
2022-12-08 19:14:25 +03:00
{
$origin = new mtgOrigin($this->file, $this->line);
2022-12-08 19:14:25 +03:00
$type_name = $this->_parseDotName();
$type = new mtgTypeRef($type_name, $this->module, $origin);
2022-12-08 19:14:25 +03:00
}
else
{
$origin = new mtgOrigin($this->file, $this->line);
2023-11-12 18:17:32 +03:00
$type_name = $this->T_value;
$type = new mtgTypeRef(new mtgBuiltinType($type_name), $this->module, $origin);
$this->_nextT();
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
if($this->T == ord('['))
2022-12-08 19:14:25 +03:00
{
$origin = new mtgOrigin($this->file, $this->line);
$this->_nextT();
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord(']'));
$type = new mtgTypeRef(new mtgArrType($type), $this->module, $origin);
2022-12-08 19:14:25 +03:00
}
$types[] = $type;
if(!$can_be_multi)
break;
2023-11-12 18:17:32 +03:00
if($this->T != ord(','))
2022-12-08 19:14:25 +03:00
break;
$this->_nextT();
2022-12-08 19:14:25 +03:00
}
if(sizeof($types) > 1)
return new mtgTypeRef(new mtgMultiType($types), $this->module, new mtgOrigin($this->file, $this->line));
2022-12-08 19:14:25 +03:00
else
return $types[0];
}
private function _parseFuncType()
{
$ftype = new mtgMetaFunc('');
$this->_nextT();
2022-12-08 19:14:25 +03:00
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord('('));
2022-12-08 19:14:25 +03:00
$c = 0;
while(true)
{
2023-11-12 18:17:32 +03:00
if($this->T == ord(')'))
2022-12-08 19:14:25 +03:00
{
$this->_nextT();
2022-12-08 19:14:25 +03:00
break;
}
else if($c > 0)
{
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord(','));
2022-12-08 19:14:25 +03:00
}
$arg_type = $this->_parseType();
$c++;
$arg = new mtgMetaField("_$c", $arg_type);
$ftype->addArg($arg);
}
2023-11-12 18:17:32 +03:00
if($this->T == ord(':'))
2022-12-08 19:14:25 +03:00
{
$this->_nextT();
2022-12-08 19:14:25 +03:00
$ret_type = $this->_parseType(true/*can be multi-type*/);
$ftype->setReturnType($ret_type);
}
return $ftype;
}
private function _resolveIncludes(mtgMetaParsedModule $module, string &$text)
{
$include_paths = $this->config['include_path'];
2022-12-08 19:14:25 +03:00
$result = array();
$lines = explode("\n", $text);
foreach($lines as $line)
{
if(preg_match('~^#include\s+(\S+)~', $line, $m))
{
$this->_processInclude($module, $m[1], $include_paths);
2022-12-08 19:14:25 +03:00
$result[] = "";
}
else
$result[] = $line;
}
$text = implode("\n", $result);
}
private function _processInclude(mtgMetaParsedModule $module, string $include, array $include_paths)
2022-12-08 19:14:25 +03:00
{
$file = false;
foreach($include_paths as $include_path)
{
$file = realpath($include_path . "/" . $include);
if($file !== false)
break;
}
if($file === false)
throw new Exception("#include {$include} can't be resolved(include path is '". implode(':', $include_paths) . "')");
$this->_parseInclude($module, $file);
2022-12-08 19:14:25 +03:00
}
private function _parseEnumOrValues()
{
$values = array();
while(true)
{
if($this->T == self::T_UserSymbol)
2022-12-08 19:14:25 +03:00
{
2023-11-12 18:17:32 +03:00
$values[] = $this->T_value;
$this->_nextT();
2023-11-12 18:17:32 +03:00
if(!$this->_nextIf(ord('|')))
2022-12-08 19:14:25 +03:00
break;
}
else
break;
}
return $values;
}
private function _parseEnum()
{
$this->_nextT();
2022-12-08 19:14:25 +03:00
$name = $this->_parseDotName();
$enum = new mtgMetaEnum($name);
$tokens = $this->shared_tokens;
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Prop)
$tokens = array_merge($tokens, $this->_parsePropTokens());
$enum->setTokens($tokens);
2022-12-08 19:14:25 +03:00
$or_values = array();
while(true)
{
if($this->_nextIf(self::T_End))
break;
$key = $this->T_value;
$this->_checkThenNext(self::T_UserSymbol);
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord('='));
if($this->T == self::T_UserSymbol)
2022-12-08 19:14:25 +03:00
{
$or_values[$key] = $this->_parseEnumOrValues();
}
else
{
$value = $this->T_value;
$this->_checkThenNext(self::T_IntegerConstant);
2022-12-08 19:14:25 +03:00
$enum->addValue($key, $value);
}
}
$enum->addOrValues($or_values);
//NOTE: special case for enums when we allow to 'override' the original one,
// with additional values
if($enum->hasToken('enum_override'))
{
$existing = $this->current_meta->findUnit($enum->getMetaId());
if(!$existing)
2023-11-09 17:47:36 +03:00
throw new Exception("Not found '{$name}' enum to override values");
if(!($existing->object instanceof mtgMetaEnum))
throw new Exception("Not an enum struct '{$name}'");
$existing->object->override($enum);
}
2023-11-09 17:47:36 +03:00
//NOTE: special case for enums when we allow to 'replace' the original one,
// with additional values
else if($enum->hasToken('enum_replace'))
{
$existing = $this->current_meta->findUnit($enum->getMetaId());
if(!$existing)
throw new Exception("Not found '{$name}' enum to replace values");
if(!($existing->object instanceof mtgMetaEnum))
throw new Exception("Not an enum struct '{$name}'");
$existing->object->replace($enum);
}
else
$this->_addUnit(new mtgMetaInfoUnit($this->module, $enum));
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
static private function _isBuiltinType(int $t) : bool
{
return $t > self::T_MinType && $t < self::T_MaxType;
}
private function _parseFields(callable $next_doer)
2022-12-08 19:14:25 +03:00
{
$flds = array();
while(true)
{
if($next_doer())
break;
if($this->T == self::T_UserSymbol)
2022-12-08 19:14:25 +03:00
{
2023-11-12 18:17:32 +03:00
$name = $this->T_value;
$this->_nextT();
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord(':'));
2022-12-08 19:14:25 +03:00
if($this->T == self::T_UserSymbol ||
2023-11-12 18:17:32 +03:00
$this->T == self::T_Func ||
self::_isBuiltinType($this->T))
2022-12-08 19:14:25 +03:00
{
$type = $this->_parseType();
$fld = new mtgMetaField($name, $type);
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Prop)
2022-12-08 19:14:25 +03:00
$fld->setTokens($this->_parsePropTokens());
$flds[] = $fld;
}
else
2023-11-12 18:17:32 +03:00
$this->_error("Type expected");
2022-12-08 19:14:25 +03:00
}
else
$this->_error("Unexpected fields symbol");
2022-12-08 19:14:25 +03:00
}
return $flds;
}
private function _parseFuncs()
{
$end_token = self::T_End;
$funcs = array();
while(true)
{
$fn = $this->_parseFunc();
$funcs[] = $fn;
2023-11-12 18:17:32 +03:00
if($this->T == $end_token)
2022-12-08 19:14:25 +03:00
{
$this->_nextT();
2022-12-08 19:14:25 +03:00
break;
}
$this->_nextT();
2022-12-08 19:14:25 +03:00
}
return $funcs;
}
private function _parseDotName() : string
2022-12-08 19:14:25 +03:00
{
$dot_name = '';
while(true)
{
if($this->T != self::T_UserSymbol)
$this->_error("Unexpected name symbol");
2022-12-08 19:14:25 +03:00
2023-11-12 18:17:32 +03:00
$dot_name .= $this->T_value;
$this->_nextT();
2023-11-12 18:17:32 +03:00
if($this->T != ord('.'))
2022-12-08 19:14:25 +03:00
break;
$dot_name .= '.';
$this->_nextT();
2022-12-08 19:14:25 +03:00
}
return $dot_name;
}
private function _parseFunc() : mtgMetaFunc
2022-12-08 19:14:25 +03:00
{
$name = $this->_parseDotName();
$fn = new mtgMetaFunc($name);
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord('('));
if($this->T == self::T_Prop)
2022-12-08 19:14:25 +03:00
$fn->setTokens($this->_parsePropTokens());
$args = $this->_parseFields(function()
2023-11-12 18:17:32 +03:00
{ return $this->_nextIf(ord(')')); }
2022-12-08 19:14:25 +03:00
);
$fn->setArgs($args);
$ret_type = null;
2023-11-12 18:17:32 +03:00
if($this->T == ord(':'))
2022-12-08 19:14:25 +03:00
{
$this->_nextT();
if($this->T == self::T_UserSymbol ||
2023-11-12 18:17:32 +03:00
$this->T == self::T_Func ||
self::_isBuiltinType($this->T))
2022-12-08 19:14:25 +03:00
{
$ret_type = $this->_parseType(true/*can be multi-type*/);
$fn->setReturnType($ret_type);
}
else
2023-11-12 18:17:32 +03:00
$this->_error("Unexpected func type");
2022-12-08 19:14:25 +03:00
}
return $fn;
}
private function _addUnit(mtgMetaInfoUnit $unit)
{
$this->current_meta->addUnit($unit);
$this->module->addUnit($unit);
}
2022-12-08 19:14:25 +03:00
private function _parseFreeFunc()
{
$this->_nextT();
2022-12-08 19:14:25 +03:00
$fn = $this->_parseFunc();
$fn->setTokens(array_merge($this->shared_tokens, $fn->getTokens()));
$this->_addUnit(new mtgMetaInfoUnit($this->module, $fn));
2022-12-08 19:14:25 +03:00
}
private function _parseStruct()
{
$this->_nextT();
$struct_origin = new mtgOrigin($this->file, $this->line);
2022-12-08 19:14:25 +03:00
$name = $this->_parseDotName();
$parent = null;
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Extends)
2022-12-08 19:14:25 +03:00
{
$this->_nextT();
$origin = new mtgOrigin($this->file, $this->line);
$parent_name = $this->_parseDotName();
$parent = new mtgTypeRef($parent_name, $this->module, $origin);
2022-12-08 19:14:25 +03:00
}
$implements = array();
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Implements)
{
do
{
$this->_nextT();
$origin = new mtgOrigin($this->file, $this->line);
$if_name = $this->_parseDotName();
$implements[] = new mtgTypeRef($if_name, $this->module, $origin);
2023-11-12 18:17:32 +03:00
} while($this->T == ord(','));
}
$s = new mtgMetaStruct($name, array(), $parent, array(), $implements);
$s->setOrigin($struct_origin);
$this->_addUnit(new mtgMetaInfoUnit($this->module, $s));
2022-12-08 19:14:25 +03:00
$tokens = $this->shared_tokens;
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Prop)
$tokens = array_merge($tokens, $this->_parsePropTokens());
$s->setTokens($tokens);
2022-12-08 19:14:25 +03:00
$seen_funcs = false;
$flds = $this->_parseFields(
function() use(&$seen_funcs)
{
if($this->_nextIf(self::T_End))
return true;
if($this->_nextIf(self::T_Func))
{
$seen_funcs = true;
return true;
}
}
);
foreach($flds as $fld)
$s->addField($fld);
if($seen_funcs)
{
$funcs = $this->_parseFuncs();
foreach($funcs as $fn)
$s->addFunc($fn);
}
}
private function _parseInterface()
{
$this->_nextT();
2022-12-08 19:14:25 +03:00
$name = $this->_parseDotName();
$s = new mtgMetaInterface($name);
$this->_addUnit(new mtgMetaInfoUnit($this->module, $s));
2022-12-08 19:14:25 +03:00
$tokens = $this->shared_tokens;
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Prop)
$tokens = array_merge($tokens, $this->_parsePropTokens());
$s->setTokens($tokens);
2022-12-08 19:14:25 +03:00
2023-11-12 18:17:32 +03:00
if($this->T !== self::T_End)
{
$this->_nextT();
$funcs = $this->_parseFuncs();
foreach($funcs as $fn)
$s->addFunc($fn);
}
else
$this->_nextT();
2022-12-08 19:14:25 +03:00
}
private function _parseRPC()
{
$this->_nextT();
$code = $this->T_value;
$this->_checkThenNext(self::T_IntegerConstant);
$name = $this->_parseDotName();
2023-11-12 18:17:32 +03:00
$this->_checkThenNext(ord('('));
2022-12-08 19:14:25 +03:00
$tokens = $this->shared_tokens;
2023-11-12 18:17:32 +03:00
if($this->T == self::T_Prop)
$tokens = array_merge($tokens, $this->_parsePropTokens());
2022-12-08 19:14:25 +03:00
$req_fields = $this->_parseFields(function()
2023-11-12 18:17:32 +03:00
{ return $this->_nextIf(ord(')')); }
2022-12-08 19:14:25 +03:00
);
$rsp_fields = $this->_parseFields(function()
{ return $this->_nextIf(self::T_End); }
);
$req = new mtgMetaPacket($code, "RPC_REQ_$name");
$req->setFields($req_fields);
$rsp = new mtgMetaPacket($code, "RPC_RSP_$name");
$rsp->setFields($rsp_fields);
$rpc = new mtgMetaRPC("RPC_$name", $code, $req, $rsp, $tokens);
$this->_addUnit(new mtgMetaInfoUnit($this->module, $rpc));
2022-12-08 19:14:25 +03:00
}
private function _parsePropTokens() : array
2022-12-08 19:14:25 +03:00
{
$prop_tokens = array();
while(true)
{
2023-11-12 18:17:32 +03:00
if($this->T != self::T_Prop)
2022-12-08 19:14:25 +03:00
break;
2023-11-12 18:17:32 +03:00
$name = ltrim($this->T_value, '@');
$this->_validatePropToken($name);
$this->_nextT();
2022-12-08 19:14:25 +03:00
$value = null;
2023-11-12 18:17:32 +03:00
if($this->T == ord(':'))
2022-12-08 19:14:25 +03:00
{
//let's read the value
2022-12-08 19:14:25 +03:00
while(true)
{
//TODO: The code below is ugly and must be heavily refactored,
// it just tries to be convenient and keep BC: any token property
// value can have almost any kind of symbols excluding new line.
// In the future we should restrict property values to certain types only
$this->_nextT(true/*stop on new line*/);
if($this->T == ord("\n"))
{
$this->_nextT();
break;
}
else if($this->T == self::T_Prop)
2022-12-08 19:14:25 +03:00
{
break;
}
else
{
2023-11-12 18:17:32 +03:00
$tmp = $this->T_value;
if($this->T == self::T_StringConstant)
2022-12-08 19:14:25 +03:00
$tmp = "\"$tmp\"";
if($value === null)
$value = '';
$value .= $tmp;
}
}
}
2023-11-12 18:17:32 +03:00
2022-12-08 19:14:25 +03:00
$prop_tokens[$name] = $value;
}
return $prop_tokens;
}
2023-11-12 18:17:32 +03:00
private function _validatePropToken(string $name)
2022-12-08 19:14:25 +03:00
{
if(!isset($this->config['valid_tokens']) ||
!is_array($this->config['valid_tokens']))
2022-12-08 19:14:25 +03:00
return;
if(!in_array($name, $this->config['valid_tokens']))
throw new Exception("Unknown property token '@$name'");
2022-12-08 19:14:25 +03:00
}
private function _nextT(bool $stop_on_new_line = false)
2022-12-08 19:14:25 +03:00
{
while(true)
{
$c = $this->cursor_char;
2023-11-12 18:17:32 +03:00
//setting default values
2023-11-12 18:17:32 +03:00
$this->T = ord($c);
$this->T_value = $c;
2022-12-08 19:14:25 +03:00
//NOTE: current 'cursor_pos' is ahead of 'c' by one character
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
switch($c)
{
case '': $this->T = self::T_EOF; return;
case "\n": if($stop_on_new_line) return; else break;
2022-12-08 19:14:25 +03:00
case ' ': case "\r": case "\t": break;
case '{': case '}': case '(': case ')': case '[': case ']': case '|': return;
case ',': case ':': case ';': case '=': return;
case '.':
if(!ctype_digit($this->cursor_char))
return;
2023-11-12 18:17:32 +03:00
$this->_error("Floating point constant can't start with .");
2022-12-08 19:14:25 +03:00
break;
case '"':
$this->T_value = '';
while($this->cursor_char != '"')
2022-12-08 19:14:25 +03:00
{
if(ord($this->cursor_char) < ord(' '))
2023-11-12 18:17:32 +03:00
$this->_error("Illegal character in string constant");
if($this->cursor_char == '\\')
2022-12-08 19:14:25 +03:00
{
$this->_cursorNext();
switch($this->cursor_char)
2022-12-08 19:14:25 +03:00
{
case 'n': $this->T_value .= "\n"; $this->_cursorNext(); break;
case 't': $this->T_value .= "\t"; $this->_cursorNext(); break;
case 'r': $this->T_value .= "\r"; $this->_cursorNext(); break;
case '"': $this->T_value .= '"'; $this->_cursorNext(); break;
case '\\': $this->T_value .= '\\'; $this->_cursorNext(); break;
2023-11-12 18:17:32 +03:00
default: $this->_error("Unknown escape code in string constant"); break;
2022-12-08 19:14:25 +03:00
}
}
else // printable chars + UTF-8 bytes
{
$this->T_value .= $this->cursor_char;
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
}
}
2023-11-12 18:17:32 +03:00
$this->T = self::T_StringConstant;
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
return;
case '`':
$this->T_value = '';
//TODO: code below is not robust enough
while($this->cursor_char != '`')
2022-12-08 19:14:25 +03:00
{
$this->T_value .= $this->cursor_char;
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
$this->T = self::T_RawStringConstant;
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
return;
case '/':
if($this->cursor_char == '/')
2022-12-08 19:14:25 +03:00
{
$this->_cursorNext();
//@phpstan-ignore-next-line
while($this->cursor_char != '' && $this->cursor_char != "\n")
$this->_cursorNext();
//@phpstan-ignore-next-line
2022-12-08 19:14:25 +03:00
break;
}
case '#':
while($this->cursor_char != '' && $this->cursor_char != "\n")
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
break;
case '@':
$start = $this->cursor_pos - 1;
while(ctype_alnum($this->cursor_char) || $this->cursor_char == '_')
$this->_cursorNext();
2023-11-12 18:17:32 +03:00
$this->T = self::T_Prop;
$this->T_value = substr($this->source, $start, $this->cursor_pos - $start);
2022-12-08 19:14:25 +03:00
return;
default:
//symbols
2022-12-08 19:14:25 +03:00
if(ctype_alpha($c))
{
//collect all chars of an identifier
$start = $this->cursor_pos - 1;
while(ctype_alnum($this->cursor_char) || $this->cursor_char == '_')
$this->_cursorNext();
$this->T_value = substr($this->source, $start, $this->cursor_pos - $start);
2022-12-08 19:14:25 +03:00
if(isset($this->symbol2T[$this->T_value]))
$this->T = $this->symbol2T[$this->T_value];
else //otherwise it's assumed to be a user defined symbol
$this->T = self::T_UserSymbol;
2022-12-08 19:14:25 +03:00
return;
}
//digits
2022-12-08 19:14:25 +03:00
else if(ctype_digit($c) || $c == '-')
{
$start = $this->cursor_pos - 1;
while(ctype_digit($this->cursor_char))
$this->_cursorNext();
if($this->cursor_char == '.')
2022-12-08 19:14:25 +03:00
{
$this->_cursorNext();
while(ctype_digit($this->cursor_char))
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
// see if this float has a scientific notation suffix. Both JSON
// and C++ (through strtod() we use) have the same format:
//@phpstan-ignore-next-line
if($this->cursor_char == 'e' || $this->cursor_char == 'E')
2022-12-08 19:14:25 +03:00
{
$this->_cursorNext();
if($this->cursor_char == '+' || $this->cursor_char == '-')
$this->_cursorNext();
while(ctype_digit($this->cursor_char))
$this->_cursorNext();
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
$this->T = self::T_FloatConstant;
2022-12-08 19:14:25 +03:00
}
else
2023-11-12 18:17:32 +03:00
$this->T = self::T_IntegerConstant;
$this->T_value = substr($this->source, $start, $this->cursor_pos - $start);
2022-12-08 19:14:25 +03:00
return;
}
2023-11-12 18:17:32 +03:00
$this->_error("Illegal character '$c'");
2022-12-08 19:14:25 +03:00
}
}
}
private function _cursorNext()
{
++$this->cursor_pos;
$this->cursor_char = substr($this->source, $this->cursor_pos, 1);
if($this->cursor_char === "\n")
$this->line++;
//EOF
if($this->cursor_char === '' ||
//keeping BC with substr(..) before php 8.0
//@phpstan-ignore-next-line
is_bool($this->cursor_char))
{
$this->cursor_char = '';
}
}
2023-11-12 18:17:32 +03:00
private function _nextIf(int $t) : bool
2022-12-08 19:14:25 +03:00
{
2023-11-12 18:17:32 +03:00
$yes = $t === $this->T;
2022-12-08 19:14:25 +03:00
if($yes)
$this->_nextT();
2022-12-08 19:14:25 +03:00
return $yes;
}
private function _checkThenNext(int $t)
2022-12-08 19:14:25 +03:00
{
2023-11-12 18:17:32 +03:00
if($t !== $this->T)
$this->_error("Expecting '" . $this->_toStr($t) . "' instead got '" . $this->_toStr($this->T) . "'");
$this->_nextT();
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
private function _toStr(int $t) : string
2022-12-08 19:14:25 +03:00
{
2023-11-12 18:17:32 +03:00
if($t < self::T_NonOrdMark)
2022-12-08 19:14:25 +03:00
return chr($t);
2023-11-12 18:17:32 +03:00
return $this->T2descr[$t];
2022-12-08 19:14:25 +03:00
}
2023-11-12 18:17:32 +03:00
private function _error(string $msg)
2022-12-08 19:14:25 +03:00
{
throw new Exception($msg . " ('{$this->T_value}', {$this->T})");
2022-12-08 19:14:25 +03:00
}
}
class mtgMetaParsedModule
{
public string $file;
public $units = array();
public $includes = array();
function __construct(string $file)
{
$this->file = $file;
}
function addUnit(mtgMetaInfoUnit $unit)
{
$this->units[$unit->object->getMetaId()] = $unit;
}
function findUnit($id)
{
if(isset($this->units[$id]))
return $this->units[$id];
foreach($this->includes as $include)
{
if(isset($include->units[$id]))
return $include->units[$id];
}
return null;
}
function addInclude(mtgMetaParsedModule $include)
{
$this->includes[] = $include;
}
}
function mtg_parse_meta(array $meta_srcs, $valid_tokens = null, $inc_path = null)
2022-12-08 19:14:25 +03:00
{
if($inc_path === null)
2022-12-08 19:14:25 +03:00
{
//let's autodetect include path
$inc_path = array();
foreach($meta_srcs as $src)
{
if(is_dir($src))
$inc_path[] = $src;
else if(is_file($src))
$inc_path[] = dirname($src);
}
2022-12-08 19:14:25 +03:00
}
$meta_parser = new mtgMetaInfoParser(
array(
'include_path' => $inc_path,
2022-12-08 19:14:25 +03:00
'valid_tokens' => $valid_tokens
)
);
$meta = new mtgMetaInfo();
foreach($meta_srcs as $src)
mtg_load_meta($meta, $meta_parser, $src);
$meta->validate();
mtgTypeRef::checkAllResolved();
2022-12-08 19:14:25 +03:00
return $meta;
}
function mtg_load_meta(mtgMetaInfo $meta, mtgMetaInfoParser $meta_parser, $dir_or_file)
{
$files = array();
if(is_dir($dir_or_file))
$files = mtg_find_meta_files($dir_or_file);
else if(is_file($dir_or_file))
$files[] = $dir_or_file;
else
throw new Exception("Bad meta source '$dir_or_file'");
foreach($files as $file)
$meta_parser->parse($meta, $file);
}
function mtg_find_meta_files($dir)
{
$items = scandir($dir);
if($items === false)
throw new Exception("Directory '$dir' is invalid");
$files = array();
foreach($items as $item)
{
if($item[0] == '.')
continue;
if(strpos($item, ".meta") !== (strlen($item)-5))
continue;
$file = $dir . '/' . $item;
if(is_file($file) && !is_dir($file))
$files[] = $file;
}
return $files;
}