|
|
|
|
|
|
|
|
|
|
|
|
#pragma strict_types |
|
#if !constant (RequestID) |
class RequestID {} |
#endif |
|
|
class Tag |
|
{ |
|
|
|
|
|
int flags; |
|
|
|
mapping(string:Type) req_arg_types; |
mapping(string:Type) opt_arg_types; |
|
|
|
|
|
Type content_type = t_text (PHtml); |
|
|
|
|
|
|
|
|
array(Type) result_types = ({t_text}); |
|
|
string scope_name; |
|
|
TagSet additional_tags, local_tags; |
|
|
|
|
|
|
|
|
|
inline Frame `() (mapping(string:mixed) args, void|mixed|PCode content) |
|
|
|
|
|
{ |
Tag this = [object(Tag)] this_object(); |
Frame frame = [object(Frame)] this->frame(); |
frame->tag = this; |
frame->flags = flags; |
if (scope_name) frame->scope_name = scope_name; |
if (additional_tags) frame->additional_tags = additional_tags; |
if (local_tags) frame->local_tags = local_tags; |
frame->args = args; |
if (!zero_type (content)) frame->content = content; |
return frame; |
} |
|
|
|
array handle_tag (TagSetParser parser, mapping(string:string) args, void|string content) |
|
|
{ |
|
Frame frame = `() (args, Void); |
frame->_eval (parser, args, content); |
return frame->result == Void ? ({}) : ({frame->result}); |
} |
|
string _sprintf() |
{ |
return "Tag(" + [string] this_object()->name + ")"; |
} |
} |
|
|
class TagSet |
|
|
|
|
|
|
{ |
string prefix; |
|
|
|
int prefix_required; |
|
|
array(TagSet) imported = ({}); |
|
|
|
|
int generation = 1; |
|
|
|
mapping(string:mixed) low_tags, low_containers, low_entities; |
|
|
|
void create (void|array(Tag) _tags) |
|
{ |
if (_tags) tags = mkmapping ([array(string)] _tags->name, _tags); |
} |
|
void add_tag (Tag tag) |
|
{ |
tags[tag->name] = tag; |
changed(); |
} |
|
void add_tags (array(Tag) _tags) |
|
{ |
tags += mkmapping ( _tags->name, _tags); |
changed(); |
} |
|
void remove_tag (string|Tag tag) |
|
{ |
if (stringp (tag)) |
m_delete (tags, tag); |
else for (string n; !zero_type (n = search (tags, tag));) |
m_delete (tags, n); |
changed(); |
} |
|
Tag get_tag (string name) |
|
{ |
Tag tag; |
if ((tag = tags[name])) return tag; |
foreach (imported, TagSet tag_set) |
if ((tag = [object(Tag)] tag_set->get_tag (name))) return tag; |
return 0; |
} |
|
Tag get_local_tag (string name) |
|
{ |
return tags[name]; |
} |
|
array(Tag) get_local_tags() |
|
{ |
return values (tags); |
} |
|
mixed `->= (string var, mixed val) |
{ |
switch (var) { |
case "imported": |
(imported - ({0}))->dont_notify (changed); |
imported = [array(TagSet)] val; |
imported->do_notify (changed); |
break; |
default: |
::`->= (var, val); |
} |
changed(); |
return val; |
} |
|
mixed `[]= (string var, mixed val) {return `->= (var, val);} |
|
Parser `() (Type top_level_type, void|RequestID id) |
|
|
|
{ |
return Context (this_object(), id)->new_parser (top_level_type); |
} |
|
void changed() |
|
|
{ |
generation++; |
(notify_funcs -= ({0}))(); |
set_weak_flag (notify_funcs, 1); |
} |
|
|
|
void do_notify (function(:void) func) |
{ |
notify_funcs |= ({func}); |
set_weak_flag (notify_funcs, 1); |
} |
|
void dont_notify (function(:void) func) |
{ |
notify_funcs -= ({func}); |
set_weak_flag (notify_funcs, 1); |
} |
|
void destroy() |
{ |
catch (changed()); |
} |
|
private mapping(string:Tag) tags = ([]); |
|
|
private array(function(:void)) notify_funcs = ({}); |
|
} |
|
TagSet empty_tag_set; |
|
|
|
class Context |
|
|
|
|
|
|
|
{ |
Frame frame; |
|
|
RequestID id; |
|
|
int type_check; |
|
|
TagSet tag_set; |
|
|
int tag_set_is_local; |
|
|
|
|
mixed get_var (string var, void|string scope_name) |
|
|
|
{ |
if (mapping(string:mixed) vars = scopes[scope_name || ""]) { |
mixed val; |
if (zero_type (val = vars[var])) return ([])[0]; |
else if (objectp (val) && val->eval) |
return val->eval (this_object(), var, scope_name); |
else return val; |
} |
else if (scope_name) error ("Unknown scope %O.\n", scope_name); |
else error ("No current scope.\n"); |
} |
|
mixed set_var (string var, mixed val, void|string scope_name) |
|
|
{ |
if (mapping(string:mixed) vars = scopes[scope_name || ""]) |
return vars[var] = val; |
else if (scope_name) error ("Unknown scope %O.\n", scope_name); |
else error ("No current scope.\n"); |
} |
|
void delete_var (string var, void|string scope_name) |
|
|
{ |
if (mapping(string:mixed) vars = scopes[scope_name || ""]) |
m_delete (vars, var); |
else if (scope_name) error ("Unknown scope %O.\n", scope_name); |
else error ("No current scope.\n"); |
} |
|
array(string) list_var (void|string scope_name) |
|
|
{ |
if (mapping(string:mixed) vars = scopes[scope_name || ""]) |
return indices (vars); |
else if (scope_name) error ("Unknown scope %O.\n", scope_name); |
else error ("No current scope.\n"); |
} |
|
void add_runtime_tag (Tag tag) |
|
|
{ |
if (tag_set_is_local) make_tag_set_local(); |
tag_set->add_tag (tag); |
} |
|
void remove_runtime_tag (string|Tag tag) |
|
{ |
if (tag_set_is_local) make_tag_set_local(); |
tag_set->remove_tag (tag); |
} |
|
array(string) list_scopes() |
|
{ |
return indices (scopes) - ({""}); |
} |
|
void add_scope (string scope_name, mapping(string:mixed) vars) |
|
{ |
if (scopes[scope_name]) |
if (scope_name == "") { |
mapping(string:mixed) inner = scopes[""]; |
while (mapping(string:mixed) outer = hidden[inner]) inner = outer; |
hidden[inner] = vars; |
} |
else { |
Frame outermost; |
for (Frame f = frame; f; f = f->up) |
if (f->scope_name == scope_name) outermost = f; |
if (outermost) hidden[outermost] = vars; |
else scopes[scope_name] = vars; |
} |
else scopes[scope_name] = vars; |
} |
|
void remove_scope (string scope_name) |
|
{ |
#ifdef MODULE_DEBUG |
if (scope_name == "") error ("Cannot remove current scope.\n"); |
#endif |
Frame outermost; |
for (Frame f = frame; f; f = f->up) |
if (f->scope_name == scope_name) outermost = f; |
if (outermost) m_delete (hidden, outermost); |
else m_delete (scopes, scope_name); |
} |
|
string current_scope() |
|
{ |
if (mapping(string:mixed) vars = scopes[""]) { |
string scope_name; |
while (scope_name = search (scopes, vars, scope_name)) |
if (scope_name != "") return scope_name; |
} |
return 0; |
} |
|
void error (string msg, mixed... args) |
|
{ |
if (sizeof (args)) msg = sprintf (msg, @args); |
msg = "RXML parser error: " + msg; |
for (Frame f = frame; f; f = f->up) { |
if (f->tag) msg += "<" + f->tag->name; |
else if (!f->up) break; |
else msg += "<(unknown tag)"; |
if (f->args) |
foreach (sort (indices (f->args)), string arg) { |
mixed val = f->args[arg]; |
msg += " " + arg + "="; |
if (arrayp (val)) msg += map (val, error_print_val) * ","; |
else msg += error_print_val (val); |
} |
else msg += " (no argmap)"; |
msg += ">\n"; |
} |
array b = backtrace(); |
throw (({msg, b[..sizeof (b) - 2]})); |
} |
|
|
|
private string error_print_val (mixed val) |
{ |
if (arrayp (val)) return "array"; |
else if (mappingp (val)) return "mapping"; |
else if (multisetp (val)) return "multiset"; |
else return sprintf ("%O", val); |
} |
|
mapping(string:mapping(string:mixed)) scopes = ([]); |
|
|
|
mapping(mapping(string:mixed)|Frame:mapping(string:mixed)) hidden = ([]); |
|
|
|
|
|
void enter_scope (Frame frame) |
{ |
mapping(string:mixed) vars; |
#ifdef DEBUG |
if (!frame->vars) error ("Internal error: Frame has no variables.\n"); |
#endif |
if ((vars = [mapping(string:mixed)] frame->vars) != scopes[""]) { |
hidden[vars] = scopes[""]; |
scopes[""] = vars; |
if (string scope_name = [string] frame->scope_name) { |
hidden[frame] = scopes[scope_name]; |
scopes[scope_name] = vars; |
} |
} |
} |
|
void leave_scope (Frame frame) |
{ |
if (string scope_name = [string] frame->scope_name) |
if (hidden[frame]) { |
scopes[scope_name] = hidden[frame]; |
m_delete (hidden, frame); |
} |
mapping(string:mixed) vars; |
if (hidden[vars = [mapping(string:mixed)] frame->vars]) { |
scopes[""] = hidden[vars]; |
m_delete (hidden, vars); |
} |
} |
|
#define ENTER_SCOPE(ctx, frame) (frame->vars && ctx->enter_scope (frame)) |
#define LEAVE_SCOPE(ctx, frame) (frame->vars && ctx->leave_scope (frame)) |
|
void make_tag_set_local() |
{ |
if (!tag_set_is_local) { |
TagSet new_tag_set = TagSet(); |
new_tag_set->imported = ({tag_set}); |
tag_set = new_tag_set; |
tag_set_is_local = 1; |
} |
} |
|
Parser new_parser (Type top_level_type) |
|
|
{ |
#ifdef MODULE_DEBUG |
if (in_use || frame) error ("Context already in use.\n"); |
#endif |
return top_level_type->get_parser (this_object()); |
} |
|
void create (TagSet _tag_set, void|RequestID _id) |
|
{ |
tag_set = _tag_set; |
id = _id; |
} |
|
mapping(string:mixed)|mapping(Frame:array) unwind_state; |
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef MODULE_DEBUG |
int in_use; |
#endif |
} |
|
|
|
|
|
|
#if constant (thread_create) |
private object _context = thread_local(); |
inline void set_context (Context ctx) {_context->set (ctx);} |
inline Context get_context() {return [object(Context)] _context->get();} |
#else |
private Context _context; |
inline void set_context (Context ctx) {_context = ctx;} |
inline Context get_context() {return _context;} |
#endif |
|
#ifdef MODULE_DEBUG |
|
|
|
#define ENTER_CONTEXT(ctx) \ |
Context __old_ctx = get_context(); \ |
set_context (ctx); \ |
if (ctx) { \ |
if (ctx->in_use && __old_ctx != ctx) \ |
parse_error ("Attempt to use context asynchronously.\n"); \ |
ctx->in_use = 1; \ |
} |
|
#define LEAVE_CONTEXT() \ |
if (Context ctx = get_context()) \ |
if (__old_ctx != ctx) ctx->in_use = 0; \ |
set_context (__old_ctx); |
|
#else |
|
#define ENTER_CONTEXT(ctx) \ |
Context __old_ctx = get_context(); \ |
set_context (ctx); |
|
#define LEAVE_CONTEXT() \ |
set_context (__old_ctx); |
|
#endif |
|
void parse_error (string msg, mixed... args) |
|
|
{ |
Context ctx = get_context(); |
if (ctx && ctx->error) |
ctx->error (msg, @args); |
else { |
if (sizeof (args)) msg = sprintf (msg, @args); |
msg = "RXML parser error (no context): " + msg; |
array b = backtrace(); |
throw (({msg, b[..sizeof (b) - 2]})); |
} |
} |
|
|
|
|
|
|
constant FLAG_CONTAINER = 0x00000001; |
|
|
|
|
|
constant FLAG_PARENT_SCOPE = 0x00000100; |
|
|
|
|
constant FLAG_NO_IMPLICIT_ARGS = 0x00000200; |
|
|
|
constant FLAG_STREAM_RESULT = 0x00000400; |
|
|
|
constant FLAG_STREAM_CONTENT = 0x00000800; |
|
|
|
|
|
|
|
|
constant FLAG_STREAM = FLAG_STREAM_RESULT | FLAG_STREAM_CONTENT; |
|
|
|
|
|
|
|
constant FLAG_CACHE_DIFF_ARGS = 0x00010000; |
|
|
|
constant FLAG_CACHE_DIFF_CONTENT = 0x00020000; |
|
|
constant FLAG_CACHE_DIFF_RESULT_TYPE = 0x00040000; |
|
|
|
constant FLAG_CACHE_DIFF_VARS = 0x00080000; |
|
|
|
|
constant FLAG_CACHE_SAME_STACK = 0x00100000; |
|
|
constant FLAG_CACHE_EXECUTE_RESULT = 0x00200000; |
|
|
|
|
|
class Frame |
|
{ |
constant is_RXML_Frame = 1; |
|
|
|
Frame up; |
|
|
|
|
Tag tag; |
|
|
int flags; |
|
|
mapping(string:mixed) args; |
|
|
|
Type content_type; |
|
|
mixed content = Void; |
|
|
|
Type result_type; |
|
|
|
mixed result = Void; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void error (string msg, mixed... args) |
|
{ |
parse_error (msg, @args); |
} |
|
void terminate() |
|
|
{ |
|
} |
|
void suspend() |
|
|
|
|
|
{ |
|
} |
|
void resume() |
|
|
{ |
|
} |
|
|
|
mixed _exec_array (Context ctx, array exec) |
{ |
Frame this = [object(Frame)] this_object(); |
int i = 0; |
mixed res = Void; |
Parser subparser = 0; |
|
mixed err = catch { |
if (flags & FLAG_PARENT_SCOPE) LEAVE_SCOPE (ctx, this); |
|
for (; i < sizeof (exec); i++) { |
mixed elem = exec[i], piece = Void; |
|
switch (sprintf ("%t", elem)) { |
case "string": |
if (result_type->_parser_prog == PNone) |
piece = elem; |
else { |
subparser = result_type->get_parser (ctx); |
subparser->finish (elem); |
piece = subparser->eval(); |
subparser = 0; |
} |
break; |
case "object": |
if (elem->is_RXML_Frame) { |
elem->_eval (0); |
piece = elem->result; |
} |
else if (elem->is_RXML_Parser) { |
elem->finish(); |
piece = elem->eval(); |
} |
else |
error ("File objects not yet implemented.\n"); |
break; |
case "mapping": |
error ("Header mappings not yet implemented.\n"); |
break; |
case "multiset": |
if (sizeof (elem) == 1) piece = ((array) elem)[0]; |
else if (sizeof (elem) > 1) |
error (sizeof (elem) + " values in multiset in exec array.\n"); |
else error ("No value in multiset in exec array.\n"); |
break; |
default: |
error ("Invalid type %t in exec array.\n", elem); |
} |
|
if (result_type->sequential) res += piece; |
else if (piece != Void) result = res = piece; |
} |
|
if (result_type->sequential) result += res; |
if (flags & FLAG_PARENT_SCOPE) ENTER_SCOPE (ctx, this); |
return res; |
}; |
|
if (result_type->sequential) result += res; |
|
if (objectp (err) && err->is_RXML_Frame) { |
mapping(string:mixed)|mapping(Frame:array) ustate; |
if ((ustate = ctx->unwind_state) && !zero_type (ustate->stream_piece)) |
|
if (result_type->sequential) |
ustate->stream_piece = res + ustate->stream_piece; |
else if (ustate->stream_piece == Void) |
ustate->stream_piece = res; |
ustate->exec_left = exec[i..]; |
if (subparser) |
|
|
|
ustate->exec_left[0] = subparser; |
} |
throw (err); |
} |
|
void _eval (TagSetParser parser, |
void|mapping(string:string) raw_args, |
void|string raw_content) |
|
{ |
Frame this = [object(Frame)] this_object(); |
Context ctx = parser->context; |
#ifdef DEBUG |
if (ctx != get_context()) error ("Internal error: Context not current.\n"); |
if (!parser->tag_set_eval) |
error ("Internal error: Calling _eval() with non-tag set parser.\n"); |
#endif |
|
|
int|function(RequestID:int|function) fn, iter; |
|
Parser subparser; |
mixed piece; |
Frame subframe; |
array exec; |
int tags_added; |
|
#define PRE_INIT_ERROR(X) (ctx->frame = this, error (X)) |
if (array state = ctx->unwind_state && ctx->unwind_state[this]) { |
#ifdef DEBUG |
if (!up) |
PRE_INIT_ERROR ("Internal error: Resuming frame without up pointer.\n"); |
if (raw_args || raw_content) |
PRE_INIT_ERROR ("Internal error: Can't feed new arguments or content " |
"when resuming parse.\n"); |
#endif |
[subframe, fn, iter, raw_content, subparser, piece, exec, tags_added] = state; |
m_delete (ctx->unwind_state, this); |
if (!sizeof (ctx->unwind_state)) ctx->unwind_state = 0; |
#ifdef DEBUG |
if (piece && subframe && exec && (!sizeof (exec) || exec[0] != subframe)) |
PRE_INIT_ERROR ("Internal error: Subframe ambiguity " |
"when handling a stream piece.\n"); |
#endif |
#ifdef MODULE_DEBUG |
if (piece && !(flags & FLAG_STREAM_CONTENT)) |
PRE_INIT_ERROR ("The subframe failed to notice that this frame doesn't support " |
"streaming - flags did probably change.\n"); |
#endif |
} |
else { |
#ifdef MODULE_DEBUG |
if (up && up != ctx->frame) |
PRE_INIT_ERROR ("Reuse of frame in different context.\n"); |
#endif |
up = ctx->frame; |
piece = Void; |
} |
#undef PRE_INIT_ERROR |
ctx->frame = this; |
|
int tag_set_gen = [int] parser->tag_set->generation; |
|
if (raw_args) { |
args = ([]); |
mapping(string:Type) atypes; |
if (tag->req_arg_types) { |
atypes = [mapping(string:Type)] (raw_args & tag->req_arg_types); |
if (sizeof (atypes) < sizeof (tag->req_arg_types)) { |
array(string) missing = sort (indices (tag->req_arg_types - atypes)); |
parse_error ("Required " + |
(sizeof (missing) > 1 ? |
"arguments " + String.implode_nicely (missing) + " are" : |
"argument " + missing[0] + " is") + " missing.\n"); |
} |
} |
if (tag->opt_arg_types) |
if (atypes) |
atypes += (raw_args & tag->opt_arg_types); |
else |
atypes = [mapping(string:Type)] (raw_args & tag->opt_arg_types); |
if (atypes) |
if (mixed err = catch { |
foreach (indices (atypes), string arg) |
args[arg] = |
atypes[arg]->eval (raw_args[arg], ctx, 0, 1); |
}) { |
if (objectp (err) && err->is_RXML_Frame) |
error ("Can't save parser state when evaluating arguments.\n"); |
throw (err); |
} |
} |
#ifdef DEBUG |
if (!args) error ("Internal error: args not set.\n"); |
#endif |
|
if (TagSet add_tags = raw_content && [object(TagSet)] this->additional_tags) { |
if (!ctx->tag_set_is_local) ctx->make_tag_set_local(); |
if (search (ctx->tag_set->imported, add_tags) < 0) { |
ctx->tag_set->imported = ({add_tags}) + ctx->tag_set->imported; |
tags_added = 1; |
} |
} |
|
if (!result_type) { |
Type ptype = [object(Type)] parser->type; |
foreach (tag->result_types, Type rtype) |
if (rtype->subtype_of (ptype)) {result_type = rtype; break;} |
if (!result_type) |
error ("Tag returns " + String.implode_nicely (tag->result_types->name, "or") + |
" but " + parser->type->name + " is expected.\n"); |
} |
if (!content_type) content_type = tag->content_type || result_type; |
|
mixed err = catch { |
if (!fn) |
fn = this->do_enter ? |
[int|function(RequestID:int|function)] this->do_enter (ctx->id) : |
1; |
|
do { |
if (!iter) { |
iter = fn; |
while (functionp (iter)) { |
int|function(RequestID:int|function) newiter = |
[int|function(mixed:int|function)] iter (ctx->id); |
fn = iter, iter = newiter; |
} |
} |
ENTER_SCOPE (ctx, this); |
for (; iter > 0; iter--) { |
|
if (raw_content) { |
int finished = 0; |
if (!subparser) { |
subparser = content_type->get_parser (ctx, this->local_tags); |
subparser->finish (raw_content); |
finished = 1; |
} |
|
do { |
if (flags & FLAG_STREAM_CONTENT && subparser->read) { |
|
|
mixed res = subparser->read(); |
if (content_type->sequential) piece = res + piece; |
else if (piece == Void) piece = res; |
if (piece != Void) { |
array|function(RequestID,mixed:array) do_return; |
if ((do_return = |
[array|function(RequestID,mixed:array)] this->do_return) && |
!arrayp (do_return)) { |
if (!exec) exec = do_return (ctx->id, piece); |
if (exec) { |
mixed res = _exec_array (ctx, exec); |
if (flags & FLAG_STREAM_RESULT) { |
#ifdef DEBUG |
if (!zero_type (ctx->unwind_state->stream_piece)) |
error ("Internal error: " |
"Clobbering unwind_state->stream_piece.\n"); |
#endif |
ctx->unwind_state->stream_piece = res; |
throw (this); |
} |
exec = 0; |
} |
else if (flags & FLAG_STREAM_RESULT) { |
|
ctx->unwind_state = 0; |
piece = Void; |
break; |
} |
} |
piece = Void; |
} |
if (finished) break; |
} |
else { |
piece = Void; |
if (finished) { |
mixed res = subparser->eval(); |
if (content_type->sequential) content += res; |
else if (res != Void) content = res; |
break; |
} |
} |
|
|
|
if (subframe && subparser->tag_set_eval) { |
|
|
subframe->_eval (subparser); |
if (subframe->result != Void) |
subparser->write_out (subframe->result); |
subframe = 0; |
} |
subparser->finish(); |
finished = 1; |
} while (1); |
subparser = 0; |
} |
|
if (array|function(RequestID,mixed:array) do_return = |
[array|function(RequestID,mixed:array)] this->do_return) { |
if (!exec) |
exec = arrayp (do_return) ? |
[array] do_return : do_return (ctx->id); |
if (exec) { |
mixed res = _exec_array (ctx, exec); |
if (flags & FLAG_STREAM_RESULT) { |
#ifdef DEBUG |
if (ctx->unwind_state) |
error ("Internal error: Clobbering unwind_state to do streaming.\n"); |
if (piece != Void) |
error ("Internal error: Thanks, we think about how nice it must " |
"be to play the harmonica...\n"); |
#endif |
ctx->unwind_state = (["stream_piece": res]); |
throw (this); |
} |
} |
} |
else if (result == Void && content_type->subtype_of (result_type)) |
result = content; |
|
} |
} while (fn); |
}; |
|
LEAVE_SCOPE (ctx, this); |
if (tag_set_gen != parser->tag_set->generation && |
ctx->tag_set == parser->tag_set) |
parser->recheck_tags(); |
|
if (err) { |
string action; |
if (objectp (err) && err->is_RXML_Frame) { |
mapping(string:mixed)|mapping(Frame:array) ustate = ctx->unwind_state; |
if (!ustate) ustate = ctx->unwind_state = ([]); |
#ifdef DEBUG |
if (ustate[this]) |
error ("Internal error: Frame already has an unwind state.\n"); |
#endif |
|
if (ustate->exec_left) { |
exec = [array] ustate->exec_left; |
m_delete (ustate, "exec_left"); |
} |
|
if (err == this || exec && sizeof (exec) && err == exec[0]) { |
|
|
|
|
if (err == this) err = 0; |
if (tags_added) { |
ctx->tag_set->imported -= ({ this->additional_tags}); |
tags_added = 0; |
} |
action = "break"; |
} |
else if (!zero_type (ustate->stream_piece)) { |
|
|
piece = ustate->stream_piece; |
m_delete (ustate, "stream_piece"); |
action = "continue"; |
} |
else action = "break"; |
|
ustate[this] = ({err, fn, iter, raw_content, subparser, piece, exec, tags_added}); |
} |
else action = "throw"; |
|
switch (action) { |
case "break": |
throw (this); |
case "continue": |
_eval (parser); |
return; |
case "throw": |
throw (err); |
default: |
error ("Internal error: Don't you come here and %O on me!\n", action); |
} |
} |
|
else { |
if (tags_added) |
ctx->tag_set->imported -= ({ this->additional_tags}); |
ctx->frame = up; |
} |
} |
|
string _sprintf() |
{ |
return "Frame(" + (tag && [string] tag->name) + ")"; |
} |
} |
|
|
|
|
|
class Parser |
|
|
|
{ |
constant is_RXML_Parser = 1; |
|
|
|
function(Parser:void) data_callback; |
|
|
|
|
|
|
int write (string in) |
|
|
{ |
int res; |
ENTER_CONTEXT (context); |
mixed err = catch { |
if (context && context->unwind_state) _handle_rewind(); |
if (feed (in)) res = 1; |
if (res && data_callback) data_callback (this_object()); |
}; |
LEAVE_CONTEXT(); |
if (err) _handle_unwind (err); |
return res; |
} |
|
void write_end (void|string in) |
|
|
{ |
int res; |
ENTER_CONTEXT (context); |
mixed err = catch { |
if (context && context->unwind_state) _handle_rewind(); |
finish (in); |
if (data_callback) data_callback (this_object()); |
}; |
LEAVE_CONTEXT(); |
if (err) _handle_unwind (err); |
} |
|
|
|
Context context; |
|
|
|
|
Type type; |
|
|
|
int compile; |
|
|
|
mixed feed (string in); |
|
|
|
|
|
|
|
|
|
void finish (void|string in); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void create (Context ctx, Type _type ) |
{ |
context = ctx; |
type = _type; |
} |
|
|
|
Parser _next_free; |
|
|
void _handle_rewind() |
{ |
Parser this = [object(Parser)] this_object(); |
mapping(string:mixed)|mapping(Frame:array) ustate; |
if ((ustate = context->unwind_state) && ustate->top && this->write_out) { |
#ifdef MODULE_DEBUG |
if (ustate->top[1] != this) |
context->error ("Resuming parse state with different parser.\n"); |
#endif |
Frame top = [object(Frame)] ustate->top[0]; |
m_delete (ustate, "top"); |
if (!sizeof (ustate)) context->unwind_state = ustate = 0; |
top->_eval (this); |
if (top->result != Void) this->write_out (top->result); |
} |
} |
|
void _handle_unwind (mixed err) |
{ |
if (context && objectp (err) && err->is_RXML_Frame) { |
mapping(string:mixed)|mapping(Frame:array) ustate = context->unwind_state; |
if (!ustate) ustate = context->unwind_state = ([]); |
#ifdef DEBUG |
if (ustate->exec_left || ustate->stream_piece || ustate->top) |
error ("Internal error: Unexpected unwind_state at top level: %O\n", ustate); |
#endif |
ustate->top = ({err, this_object()}); |
} |
else throw (err); |
} |
} |
|
|
class TagSetParser |
|
|
|
|
|
|
|
{ |
inherit Parser; |
|
constant tag_set_eval = 1; |
|
|
|
TagSet tag_set; |
|
|
void write_out (mixed data); |
mixed read(); |
|
|
|
|
void create (Context ctx, Type type, TagSet _tag_set ) |
{ |
::create (ctx, type); |
tag_set = _tag_set; |
} |
|
|
|
void recheck_tags(); |
|
|
|
|
|
|
mixed eval() |
{ |
return read(); |
} |
} |
|
|
class PNone |
|
{ |
inherit Parser; |
|
string data = ""; |
int evalpos = 0; |
|
int feed (string in) |
{ |
data += in; |
return 1; |
} |
|
void finish (void|string in) |
{ |
if (in) data += in; |
} |
|
string eval() |
{ |
string res = data[evalpos..]; |
evalpos = sizeof (data); |
return res; |
} |
|
string byte_compile() |
{ |
return data; |
} |
|
string byte_interpret (string byte_code, Context ctx) |
{ |
return byte_code; |
} |
|
void reset (Context ctx) |
{ |
context = ctx; |
data = ""; |
evalpos = 0; |
} |
} |
|
|
mixed simple_parse (string in, void|program parser) |
|
|
{ |
|
return t_any (parser || PExpr)->eval (in, Context (empty_tag_set)); |
} |
|
|
|
|
|
class Type |
|
|
|
|
{ |
constant is_RXML_Type = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void type_check (mixed val); |
|
|
|
Type clone() |
|
{ |
Type newtype = |
[object(Type)] object_program ([object(Type)] this_object())(); |
newtype->_parser_prog = _parser_prog; |
newtype->_parser_args = _parser_args; |
newtype->_t_obj_cache = _t_obj_cache; |
return newtype; |
} |
|
|
|
int `== (mixed other) |
|
{ |
return objectp (other) && other->is_RXML_Type && |
other->name == this_object()->name; |
} |
|
int subtype_of (Type other) |
|
{ |
return glob ([string] other->name, |
[string] ([object(Type)] this_object())->name); |
} |
|
Type `() (program newparser, mixed... parser_args) |
|
|
|
{ |
Type newtype; |
if (sizeof (parser_args)) { |
newtype = clone(); |
newtype->_parser_args = parser_args; |
if (newparser->tag_set_eval) newtype->_p_cache = ([]); |
} |
else { |
if (!_t_obj_cache) _t_obj_cache = ([]); |
if (!(newtype = _t_obj_cache[newparser])) |
if (newparser == _parser_prog) |
_t_obj_cache[newparser] = newtype = [object(Type)] this_object(); |
else { |
_t_obj_cache[newparser] = newtype = clone(); |
newtype->_parser_prog = newparser; |
if (newparser->tag_set_eval) newtype->_p_cache = ([]); |
} |
} |
return newtype; |
} |
|
inline Parser get_parser (Context ctx, void|TagSet tag_set) |
|
{ |
Parser p; |
if (_p_cache) { |
TagSet tset; |
|
PCacheObj pco = _p_cache[tset = tag_set || ctx->tag_set]; |
if (pco && pco->tag_set_gen == tset->generation) { |
if ((p = pco->free_parser)) { |
pco->free_parser = p->_next_free; |
|
p->data_callback = p->compile = 0; |
p->reset (ctx, this_object(), @_parser_args); |
} |
else |
|
if (pco->clone_parser) |
p = [object(Parser)] pco->clone_parser->clone ( |
ctx, this_object(), @_parser_args); |
else if ((p = [object(Parser)] _parser_prog ( |
ctx, this_object(), @_parser_args))->clone) |
|
|
p = [object(Parser)] (pco->clone_parser = p)->clone ( |
ctx, this_object(), @_parser_args); |
} |
else { |
|
pco = PCacheObj(); |
pco->tag_set_gen = [int] tset->generation; |
_p_cache[tset] = pco; |
if ((p = [object(Parser)] _parser_prog ( |
ctx, this_object(), @_parser_args))->clone) |
|
|
p = [object(Parser)] (pco->clone_parser = p)->clone ( |
ctx, this_object(), @_parser_args); |
} |
} |
else { |
if ((p = free_parser)) { |
|
free_parser = p->_next_free; |
p->data_callback = p->compile = 0; |
p->reset (ctx, this_object(), @_parser_args); |
} |
else if (clone_parser) |
|
p = [object(Parser)] clone_parser->clone ( |
ctx, this_object(), @_parser_args); |
else if ((p = [object(Parser)] _parser_prog ( |
ctx, this_object(), @_parser_args))->clone) |
|
|
p = [object(Parser)] (clone_parser = p)->clone ( |
ctx, this_object(), @_parser_args); |
} |
return p; |
} |
|
mixed eval (string in, void|Context ctx, void|TagSet tag_set, void|int dont_switch_ctx) |
|
|
|
|
{ |
mixed res; |
if (!ctx) ctx = get_context(); |
if (_parser_prog == PNone) res = in; |
else { |
Parser p = get_parser (ctx, tag_set); |
if (dont_switch_ctx) p->finish (in); |
else p->write_end (in); |
res = p->eval(); |
if (p->reset) |
if (_p_cache) { |
|
PCacheObj pco = _p_cache[tag_set || ctx->tag_set]; |
p->_next_free = pco->free_parser; |
pco->free_parser = p; |
} |
else { |
|
p->_next_free = free_parser; |
free_parser = p; |
} |
} |
if (ctx->type_check) type_check (res); |
return res; |
} |
|
|
|
program _parser_prog = PNone; |
|
|
private array(mixed) _parser_args = ({}); |
|
mapping(program:Type) _t_obj_cache; |
|
|
|
private Parser clone_parser; |
private Parser free_parser; |
|
|
private class PCacheObj |
{ |
int tag_set_gen; |
Parser clone_parser; |
Parser free_parser; |
} |
mapping(TagSet:PCacheObj) _p_cache; |
} |
|
|
Type t_text = class |
|
{ |
inherit Type; |
constant name = "text/*"; |
constant sequential = 1; |
constant empty_value = ""; |
constant free_text = 1; |
}(); |
|
|
Type t_any = class |
|
{ |
inherit Type; |
constant name = "*"; |
}(); |
|
|
|
|
class VarRef |
|
{ |
constant is_RXML_VarRef = 1; |
string scope, var; |
void create (string _scope, string _var) {scope = _scope, var = _var;} |
int valid (Context ctx) {return !!ctx->scopes[scope];} |
mixed get (Context ctx) {return ctx->scopes[scope][var];} |
mixed set (Context ctx, mixed val) {return ctx->scopes[scope][var] = val;} |
void remove (Context ctx) {m_delete (ctx->scopes[scope], var);} |
string name() {return scope + "." + var;} |
} |
|
class PCode |
|
|
{ |
constant is_RXML_PCode = 1; |
|
array p_code = ({}); |
|
mixed eval (Context ctx) |
|
{ |
|
} |
|
|
|
|
} |
|
|
|
|
static class VoidType |
{ |
mixed `+ (mixed... vals) {return sizeof (vals) ? predef::`+ (@vals) : this_object();} |
mixed ``+ (mixed val) {return val;} |
int `!() {return 1;} |
string _sprintf (string flag) {return flag == "O" && "Void";} |
}; |
VoidType Void = VoidType(); |
|
|
|
class ScanStream |
|
|
|
{ |
private class Link |
{ |
array data; |
Link next; |
} |
private Link head = Link(); |
private Link tail = head; |
private int next_token = 0; |
private string end = ""; |
private int fin = 0; |
|
array scan (string in, int finished); |
|
|
|
|
|
|
|
void feed (string in) |
|
{ |
#ifdef MODULE_DEBUG |
if (fin) error ("Cannot feed data to a finished stream.\n"); |
#endif |
array tokens = scan (end + in, 0); |
end = [string] tokens[-1]; |
if (sizeof (tokens) > 1) { |
tail->data = tokens[..sizeof (tokens) - 2]; |
tail = tail->next = Link(); |
} |
} |
|
void finish (void|string in) |
|
{ |
if (in || !fin && sizeof (end)) { |
#ifdef MODULE_DEBUG |
if (in && fin) error ("Cannot feed data to a finished stream.\n"); |
#endif |
fin = 1; |
if (in) end += in; |
tail->data = scan (end, 1); |
tail = tail->next = Link(); |
} |
} |
|
void reset() |
|
{ |
head = Link(); |
tail = head; |
next_token = 0; |
end = ""; |
fin = 0; |
} |
|
mixed read() |
|
{ |
while (head->next) |
if (next_token >= sizeof (head->data)) { |
next_token = 0; |
head = head->next; |
} |
else return head->data[next_token++]; |
return Void; |
} |
|
void unread (mixed... put_back) |
|
|
{ |
int i = sizeof (put_back); |
while (i) head->data[--next_token] = put_back[--i]; |
if (i) { |
Link l = Link(); |
l->next = head, head = l; |
l->data = allocate (next_token = [int] max (i - 32, 0)) + put_back[..--i]; |
} |
} |
|
array read_all() |
|
{ |
array data; |
if (next_token) { |
data = head->data[next_token..]; |
head = head->next; |
next_token = 0; |
} |
else data = ({}); |
while (head->next) { |
data += head->data; |
head = head->next; |
} |
return data; |
} |
|
int finished() |
|
{ |
return fin; |
} |
} |
|
|
|
|
|
static program PHtml; |
static program PExpr; |
void _fix_module_ref (string name, mixed val) |
{ |
mixed err = catch { |
switch (name) { |
case "PHtml": PHtml = [program] val; break; |
case "PExpr": PExpr = [program] val; break; |
case "empty_tag_set": empty_tag_set = [object(TagSet)] val; break; |
default: error ("Herk\n"); |
} |
}; |
if (err) werror (describe_backtrace (err)); |
} |
|
|