|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef OBJ_COUNT_DEBUG |
|
|
# define DECLARE_CNT(count) static int count = ++all_constants()->_obj_count |
# define PAREN_CNT(count) ("(" + (count) + ")") |
# define COMMA_CNT(count) ("," + (count)) |
#else |
# define DECLARE_CNT(count) |
# define PAREN_CNT(count) "" |
# define COMMA_CNT(count) "" |
#endif |
|
|
class Tag |
|
{ |
constant is_RXML_Tag = 1; |
|
|
|
|
|
|
int flags; |
|
|
|
mapping(string:Type) req_arg_types = ([]); |
mapping(string:Type) opt_arg_types = ([]); |
|
|
|
|
Type def_arg_type = t_text (PEnt); |
|
|
|
|
Type content_type = t_same (PHtml); |
|
|
|
|
|
|
|
|
array(Type) result_types = ({t_xml, t_html, t_text}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline object `() (mapping(string:mixed) args, void|mixed content) |
|
|
|
|
|
{ |
Tag this = this_object(); |
object frame = ([function(:object)] this->Frame)(); |
frame->tag = this; |
frame->flags = flags; |
frame->args = args; |
if (!zero_type (content)) frame->content = content; |
return frame; |
} |
|
int eval_args (mapping(string:mixed) args, void|int dont_throw, void|Context ctx) |
|
|
|
|
|
|
|
|
{ |
|
mapping(string:Type) atypes = args & req_arg_types; |
if (sizeof (atypes) < sizeof (req_arg_types)) |
if (dont_throw) return 0; |
else { |
array(string) missing = sort (indices (req_arg_types - atypes)); |
rxml_fatal ("Required " + |
(sizeof (missing) > 1 ? |
"arguments " + String.implode_nicely (missing) + " are" : |
"argument " + missing[0] + " is") + " missing.\n"); |
} |
atypes += args & opt_arg_types; |
#ifdef MODULE_DEBUG |
if (mixed err = catch { |
#endif |
foreach (indices (atypes), string arg) |
args[arg] = atypes[arg]->eval (args[arg], ctx); |
#ifdef MODULE_DEBUG |
}) { |
if (objectp (err) && ([object] err)->thrown_at_unwind) |
error ("Can't save parser state when evaluating arguments.\n"); |
throw (err); |
} |
#endif |
return 1; |
} |
|
|
|
array _handle_tag (TagSetParser parser, mapping(string:string) args, |
void|string content) |
|
|
|
{ |
Context ctx = parser->context; |
|
object frame; |
if (mapping(string:mixed)|mapping(object:array) ustate = ctx->unwind_state) |
if (ustate[parser]) { |
frame = [object] ustate[parser][0]; |
m_delete (ustate, parser); |
if (!sizeof (ustate)) ctx->unwind_state = 0; |
} |
else frame = `() (args, Void); |
else frame = `() (args, Void); |
|
mixed err = catch { |
frame->_eval (parser, args, content); |
mixed res; |
if ((res = frame->result) == Void) return ({}); |
if (frame->result_type->quoting_scheme != parser->type->quoting_scheme) |
res = parser->type->quote (res); |
return ({res}); |
}; |
|
if (objectp (err) && ([object] err)->thrown_at_unwind) { |
mapping(string:mixed)|mapping(object:array) ustate = ctx->unwind_state; |
if (!ustate) ustate = ctx->unwind_state = ([]); |
#ifdef DEBUG |
if (err != frame) |
error ("Internal error: Unexpected unwind object catched.\n"); |
if (ustate[parser]) |
error ("Internal error: Clobbering unwind state for parser.\n"); |
#endif |
ustate[parser] = ({err}); |
err = parser; |
} |
|
throw (err); |
} |
|
DECLARE_CNT (__count); |
|
string _sprintf() |
{ |
return "RXML.Tag(" + [string] this_object()->name + COMMA_CNT (__count) + ")"; |
} |
} |
|
|
class TagSet |
|
|
|
|
|
|
{ |
string name; |
|
|
string prefix; |
|
|
|
|
int prefix_req; |
|
|
array(TagSet) imported = ({}); |
|
|
|
|
function(Context:void) prepare_context; |
|
|
|
|
|
int generation = 1; |
|
|
|
#define LOW_TAG_TYPE \ |
string|array| \ |
function(:int(1..1)|string|array)| \ |
function(object,mapping(string:string):int(1..1)|string|array) |
|
#define LOW_CONTAINER_TYPE \ |
string|array| \ |
function(:int(1..1)|string|array)| \ |
function(object,mapping(string:string),string:int(1..1)|string|array) |
|
#define LOW_ENTITY_TYPE \ |
string|array| \ |
function(:int(1..1)|string|array)| \ |
function(object:int(1..1)|string|array) |
|
mapping(string:LOW_TAG_TYPE) low_tags; |
mapping(string:LOW_CONTAINER_TYPE) low_containers; |
mapping(string:LOW_ENTITY_TYPE) low_entities; |
|
|
|
|
static void create (string _name, void|array(Tag) _tags) |
|
{ |
name = _name; |
if (_tags) |
foreach (_tags, Tag tag) |
if (tag->plugin_name) tags[tag->name + "#" + tag->plugin_name] = tag; |
else tags[tag->name] = tag; |
} |
|
void add_tag (Tag tag) |
|
{ |
if (tag->plugin_name) tags[tag->name + "#" + tag->plugin_name] = tag; |
else tags[tag->name] = tag; |
changed(); |
} |
|
void add_tags (array(Tag) _tags) |
|
{ |
foreach (_tags, Tag tag) |
if (tag->plugin_name) tags[tag->name + "#" + tag->plugin_name] = tag; |
else tags[tag->name] = tag; |
changed(); |
} |
|
void remove_tag (string|Tag tag) |
|
{ |
if (stringp (tag)) |
m_delete (tags, tag); |
else |
for (string n; !zero_type (n = search (tags, [object(Tag)] tag));) |
m_delete (tags, n); |
changed(); |
} |
|
local Tag|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) get_local_tag (string name) |
|
|
|
{ |
if (Tag tag = tags[name]) return tag; |
else if (LOW_CONTAINER_TYPE cdef = low_containers && low_containers[name]) |
return ({0, cdef}); |
else if (LOW_TAG_TYPE tdef = low_tags && low_tags[name]) |
return ({tdef, 0}); |
else return 0; |
} |
|
array(Tag) get_local_tags() |
|
{ |
return values (tags); |
} |
|
Tag|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) get_tag (string name) |
|
|
{ |
if (object(Tag)|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) def = get_local_tag (name)) |
return def; |
foreach (imported, TagSet tag_set) |
if (object(Tag) tag = [object(Tag)] tag_set->get_tag (name)) return tag; |
return 0; |
} |
|
Tag|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) get_overridden_tag ( |
Tag|LOW_TAG_TYPE|LOW_CONTAINER_TYPE tagdef, void|string name) |
|
|
|
|
{ |
if (objectp (tagdef) && ([object] tagdef)->is_RXML_Tag) |
name = [string] ([object] tagdef)->name; |
#ifdef MODULE_DEBUG |
if (!name) error ("Need tag name.\n"); |
#endif |
if (tags[name] == tagdef || |
(low_containers && low_containers[name] == tagdef) || |
(low_tags && low_tags[name] == tagdef)) { |
foreach (imported, TagSet tag_set) |
if (object(Tag)|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) tagdef = |
tag_set->get_tag (name)) return tagdef; |
} |
else { |
int found = 0; |
foreach (imported, TagSet tag_set) |
if (object(Tag)|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) subtag = |
tag_set->get_tag (name)) |
if (found) return subtag; |
else if (arrayp (subtag) ? |
subtag[0] == tagdef || subtag[1] == tagdef : |
subtag == tagdef) |
if ((subtag = tag_set->get_overridden_tag (tagdef, name))) |
return subtag; |
else found = 1; |
} |
return 0; |
} |
|
array(Tag|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE)) get_overridden_tags (string name) |
|
|
|
|
{ |
if (object(Tag)|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) def = get_local_tag (name)) |
return ({def}) + imported->get_overridden_tags (name) * ({}); |
else return imported->get_overridden_tags (name) * ({}); |
} |
|
multiset(string) get_tag_names() |
|
{ |
multiset(string) res = (multiset) indices (tags); |
if (low_tags) res |= (multiset) indices (low_tags); |
if (low_containers) res |= (multiset) indices (low_containers); |
return `| (res, @imported->get_tag_names()); |
} |
|
mapping(string:Tag) get_plugins (string name) |
|
|
{ |
mapping(string:Tag) res; |
if ((res = plugins[name])) return res; |
low_get_plugins (name + "#", res = ([])); |
return plugins[name] = res; |
} |
|
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) |
|
|
|
{ |
Context ctx = Context (this_object(), id); |
if (!prepare_funs) prepare_funs = get_prepare_funs(); |
prepare_funs -= ({0}); |
prepare_funs (ctx); |
return ctx->new_parser (top_level_type); |
} |
|
void changed() |
|
|
{ |
generation++; |
prepare_funs = 0; |
plugins = ([]); |
(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()); |
} |
|
static mapping(string:Tag) tags = ([]); |
|
|
static array(function(:void)) notify_funcs = ({}); |
|
|
static array(function(Context:void)) prepare_funs; |
|
array(function(Context:void)) get_prepare_funs() |
{ |
if (prepare_funs) return prepare_funs; |
array(function(Context:void)) funs = ({}); |
for (int i = sizeof (imported) - 1; i >= 0; i--) |
funs += imported[i]->get_prepare_funs(); |
if (prepare_context) funs += ({prepare_context}); |
|
return funs; |
} |
|
static mapping(string:mapping(string:Tag)) plugins = ([]); |
|
void low_get_plugins (string prefix, mapping(string:Tag) res) |
{ |
for (int i = sizeof (imported) - 1; i >= 0; i--) |
imported[i]->low_get_plugins (prefix, res); |
foreach (indices (tags), string name) |
if (name[..sizeof (prefix) - 1] == prefix) { |
Tag tag = tags[name]; |
if (tag->plugin_name) res[[string] tag->plugin_name] = tag; |
} |
|
} |
|
DECLARE_CNT (__count); |
|
string _sprintf() |
{ |
return name ? "RXML.TagSet(" + name + COMMA_CNT (__count) + ")" : |
"RXML.TagSet" + PAREN_CNT (__count); |
} |
} |
|
TagSet empty_tag_set; |
|
|
|
class Value |
|
|
{ |
mixed rxml_var_eval (Context ctx, string var, string scope_name, void|Type type) |
|
|
|
|
|
|
|
|
{ |
mixed val = rxml_const_eval (ctx, var, scope_name, type); |
ctx->set_var(var, val, scope_name); |
return val; |
} |
|
mixed rxml_const_eval (Context ctx, string var, string scope_name, void|Type type) |
|
|
{ |
return 0; |
} |
|
string _sprintf() {return "RXML.Value";} |
} |
|
class Scope |
|
{ |
mixed `[] (string var, void|Context ctx, void|string scope_name) |
{rxml_fatal ("Cannot query variable" + _in_the_scope (scope_name) + ".\n");} |
|
mixed `[]= (string var, mixed val, void|Context ctx, void|string scope_name) |
{rxml_fatal ("Cannot set variable" + _in_the_scope (scope_name) + ".\n");} |
|
array(string) _indices (void|Context ctx, void|string scope_name) |
{rxml_fatal ("Cannot list variables" + _in_the_scope (scope_name) + ".\n");} |
|
void m_delete (string var, void|Context ctx, void|string scope_name) |
{rxml_fatal ("Cannot delete variable" + _in_the_scope (scope_name) + ".\n");} |
|
private string _in_the_scope (string scope_name) |
{ |
if (scope_name) |
if (scope_name != "_") return " in the scope " + scope_name; |
else return " in the current scope"; |
else return ""; |
} |
|
string _sprintf() {return "RXML.Scope";} |
} |
|
#define SCOPE_TYPE mapping(string:mixed)|object(Scope) |
|
class Context |
|
|
|
|
|
|
|
{ |
Frame frame; |
|
|
RequestID id; |
|
|
int type_check; |
|
|
int error_count; |
|
|
TagSet tag_set; |
|
|
int tag_set_is_local; |
|
|
|
array parse_user_var (string var, void|string scope_name) |
|
|
{ |
if(!var || !sizeof(var)) return ([])[0]; |
array(string) splitted=var/"."; |
if(sizeof(splitted)>2) return ([])[0]; |
if(sizeof(splitted)==2) |
scope_name=splitted[0]; |
else |
scope_name = scope_name || "_"; |
return ({ scope_name, splitted[-1] }); |
} |
|
local mixed get_var (string var, void|string scope_name, void|Type want_type) |
|
|
|
|
|
|
|
{ |
if (SCOPE_TYPE vars = scopes[scope_name || "_"]) { |
mixed val; |
if (objectp (vars)) { |
if (zero_type (val = ([object(Scope)] vars)->`[] ( |
var, this_object(), scope_name || "_")) || |
val == Void) |
return ([])[0]; |
} |
else |
if (zero_type (val = vars[var])) |
return ([])[0]; |
if (objectp (val) && ([object] val)->rxml_var_eval) { |
return |
zero_type (val = ([object(Value)] val)->rxml_var_eval ( |
this_object(), var, scope_name || "_", want_type)) || |
val == Void ? ([])[0] : val; |
} |
else |
if (want_type) |
return |
|
zero_type (val = want_type->convert (val)) || |
val == Void ? ([])[0] : val; |
else |
return val; |
} |
else if (scope_name) rxml_fatal ("Unknown scope %O.\n", scope_name); |
else rxml_fatal ("No current scope.\n"); |
} |
|
mixed user_get_var (string var, void|string scope_name, void|Type want_type) |
|
|
{ |
if(!var || !sizeof(var)) return ([])[0]; |
array(string) splitted=var/"."; |
if(sizeof(splitted)>2) return ([])[0]; |
if(sizeof(splitted)==2) |
scope_name=splitted[0]; |
else |
scope_name = scope_name || "_"; |
return get_var(splitted[-1], scope_name, want_type); |
} |
|
local mixed set_var (string var, mixed val, void|string scope_name) |
|
|
{ |
if (SCOPE_TYPE vars = scopes[scope_name || "_"]) |
if (objectp (vars)) |
return ([object(Scope)] vars)->`[]= (var, val, this_object(), scope_name || "_"); |
else |
return vars[var] = val; |
else if (scope_name) rxml_fatal ("Unknown scope %O.\n", scope_name); |
else rxml_fatal ("No current scope.\n"); |
} |
|
mixed user_set_var (string var, mixed val, void|string scope_name) |
|
|
{ |
if(!var || !sizeof(var)) return val; |
array(string) splitted=var/"."; |
if(sizeof(splitted)>2) return val; |
if(sizeof(splitted)==2) |
scope_name=splitted[0]; |
else |
scope_name = scope_name || "_"; |
return set_var(splitted[-1], val, scope_name); |
} |
|
local void delete_var (string var, void|string scope_name) |
|
|
{ |
if (SCOPE_TYPE vars = scopes[scope_name || "_"]) |
if (objectp (vars)) |
([object(Scope)] vars)->m_delete (var, this_object(), scope_name || "_"); |
else |
m_delete ([mapping(string:mixed)] vars, var); |
else if (scope_name) rxml_fatal ("Unknown scope %O.\n", scope_name); |
else rxml_fatal ("No current scope.\n"); |
} |
|
void user_delete_var (string var, void|string scope_name) |
|
|
{ |
if(!var || !sizeof(var)) return; |
array(string) splitted=var/"."; |
if(sizeof(splitted)>2) return; |
if(sizeof(splitted)==2) |
scope_name=splitted[0]; |
else |
scope_name = scope_name || "_"; |
delete_var(splitted[-1], scope_name); |
} |
|
array(string) list_var (void|string scope_name) |
|
|
{ |
if (SCOPE_TYPE vars = scopes[scope_name || "_"]) |
if (objectp (vars)) |
return ([object(Scope)] vars)->_indices (this_object(), scope_name || "_"); |
else |
return indices ([mapping(string:mixed)] vars); |
else if (scope_name) rxml_fatal ("Unknown scope %O.\n", scope_name); |
else rxml_fatal ("No current scope.\n"); |
} |
|
array(string) list_scopes() |
|
{ |
return indices (scopes) - ({"_"}); |
} |
|
int exist_scope (void|string scope_name) |
|
{ |
return !!scopes[scope_name || "_"]; |
} |
|
void add_scope (string scope_name, SCOPE_TYPE vars) |
|
|
|
{ |
if (scopes[scope_name]) |
if (scope_name == "_") { |
array(SCOPE_TYPE) hid; |
for (Frame f = frame; f; f = f->up) |
if (array(SCOPE_TYPE) h = hidden[f]) hid = h; |
if (hid) hid[0] = vars; |
else scopes["_"] = vars; |
} |
else { |
Frame outermost; |
for (Frame f = frame; f; f = f->up) |
if (f->scope_name == scope_name) outermost = f; |
if (outermost) hidden[outermost][1] = vars; |
else scopes[scope_name] = vars; |
} |
else scopes[scope_name] = vars; |
} |
|
int extend_scope (string scope_name, SCOPE_TYPE vars) |
|
|
{ |
if (scopes[scope_name]) { |
SCOPE_TYPE oldvars; |
if (scope_name == "_") { |
array(SCOPE_TYPE) hid; |
for (Frame f = frame; f; f = f->up) |
if (array(SCOPE_TYPE) h = hidden[f]) hid = h; |
if (hid) oldvars = hid[0]; |
else oldvars = scopes["_"]; |
} |
else { |
Frame outermost; |
for (Frame f = frame; f; f = f->up) |
if (f->scope_name == scope_name) outermost = f; |
if (outermost) oldvars = hidden[outermost][1]; |
else oldvars = scopes[scope_name]; |
} |
#ifdef DEBUG |
if (!oldvars) error ("Internal error: I before e except after c.\n"); |
#endif |
if (!mappingp(vars)) { |
return 0; |
} |
foreach (indices(vars), string var) |
set_var(var, vars[var], scope_name); |
} |
else scopes[scope_name] = vars; |
return 1; |
} |
|
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 (SCOPE_TYPE vars = scopes["_"]) { |
string scope_name; |
while (scope_name = search (scopes, vars, scope_name)) |
if (scope_name != "_") return scope_name; |
} |
return 0; |
} |
|
void add_runtime_tag (Tag tag) |
|
|
|
{ |
if (!new_runtime_tags) new_runtime_tags = RuntimeTags(); |
new_runtime_tags->add_tags[tag] = 1; |
|
new_runtime_tags->remove_tags[tag] = 0; |
new_runtime_tags->remove_tags[tag->name] = 0; |
} |
|
void remove_runtime_tag (string|Tag tag) |
|
|
|
{ |
if (!new_runtime_tags) new_runtime_tags = RuntimeTags(); |
new_runtime_tags->remove_tags[tag] = 1; |
} |
|
string describe_rxml_backtrace (Frame f, void|string current_var) |
|
|
|
{ |
string msg = current_var ? " | &" + current_var + ";\n" : ""; |
for (; 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"; |
} |
return msg; |
} |
|
void rxml_error (string msg, mixed... args) |
|
|
|
|
{ |
if (sizeof (args)) msg = sprintf (msg, @args); |
msg = rxml_error_prefix + ": " + msg + |
describe_rxml_backtrace (frame, current_var); |
array b = backtrace(); |
throw (({msg, b[..sizeof (b) - 2]})); |
} |
|
void rxml_fatal (string msg, mixed... args) |
|
|
|
|
{ |
if (sizeof (args)) msg = sprintf (msg, @args); |
msg = rxml_fatal_prefix + ": " + msg + |
describe_rxml_backtrace (frame, current_var); |
array b = backtrace(); |
throw (({msg, b[..sizeof (b) - 2]})); |
} |
|
void handle_exception (mixed err, PCode|Parser evaluator) |
|
|
{ |
error_count++; |
string msg = describe_error (err); |
int error = msg[..sizeof (rxml_error_prefix) - 1] == rxml_error_prefix; |
if (error || msg[..sizeof (rxml_fatal_prefix) - 1] == rxml_fatal_prefix) { |
|
while (evaluator->_parent) { |
evaluator->error_count++; |
evaluator = evaluator->_parent; |
} |
if (id && id->conf) |
msg = (error ? |
([function(mixed,Type:string)] |
([object] id->conf)->handle_rxml_error) : |
([function(mixed,Type:string)] |
([object] id->conf)->handle_rxml_fatal) |
) (err, evaluator->type); |
else { |
#ifdef MODULE_DEBUG |
report_notice (describe_backtrace (err)); |
#else |
report_notice (msg); |
#endif |
} |
if (msg && evaluator->type->free_text && evaluator->report_error) |
evaluator->report_error (msg); |
} |
else throw (err); |
} |
|
|
|
constant rxml_error_prefix = "RXML error"; |
constant rxml_fatal_prefix = "RXML fatal"; |
|
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); |
} |
|
string current_var; |
|
|
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()); |
} |
|
mapping(string:SCOPE_TYPE) scopes = ([]); |
|
|
|
mapping(Frame:array(SCOPE_TYPE)) hidden = ([]); |
|
|
|
|
void enter_scope (Frame frame) |
{ |
#ifdef DEBUG |
if (!frame->vars) error ("Internal error: Frame has no variables.\n"); |
#endif |
if (!hidden[frame]) |
if (string scope_name = [string] frame->scope_name) { |
hidden[frame] = ({scopes["_"], scopes[scope_name]}); |
scopes["_"] = scopes[scope_name] = [SCOPE_TYPE] frame->vars; |
} |
else { |
hidden[frame] = ({scopes["_"], 0}); |
scopes["_"] = [SCOPE_TYPE] frame->vars; |
} |
} |
|
void leave_scope (Frame frame) |
{ |
if (array(SCOPE_TYPE) back = hidden[frame]) { |
if (SCOPE_TYPE cur = back[0]) scopes["_"] = cur; |
else m_delete (scopes, "_"); |
if (SCOPE_TYPE named = back[1]) { |
#ifdef MODULE_DEBUG |
if (!stringp (frame->scope_name)) |
error ("Scope named changed to %O during parsing.\n", frame->scope_name); |
#endif |
scopes[[string] frame->scope_name] = named; |
} |
else m_delete (scopes, [string] frame->scope_name); |
m_delete (hidden, frame); |
} |
} |
|
#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 (tag_set->name + " (local)"); |
new_tag_set->imported = ({tag_set}); |
tag_set = new_tag_set; |
tag_set_is_local = 1; |
} |
} |
|
class RuntimeTags |
{ |
multiset(Tag) add_tags = (<>); |
multiset(Tag|string) remove_tags = (<>); |
} |
RuntimeTags new_runtime_tags; |
|
|
|
void create (TagSet _tag_set, void|RequestID _id) |
|
{ |
tag_set = _tag_set; |
id = _id; |
} |
|
mapping(string:mixed)|mapping(object:array) unwind_state; |
|
|
|
|
|
|
|
|
|
|
|
|
DECLARE_CNT (__count); |
|
string _sprintf() {return "RXML.Context" + PAREN_CNT (__count);} |
|
#ifdef MODULE_DEBUG |
int in_use; |
#endif |
} |
|
|
|
|
|
|
#if constant (thread_create) |
private Thread.Local _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) \ |
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 rxml_error (string msg, mixed... args) |
|
{ |
Context ctx = get_context(); |
if (ctx && ctx->rxml_error) |
ctx->rxml_error (msg, @args); |
else { |
if (sizeof (args)) msg = sprintf (msg, @args); |
msg = Context.rxml_error_prefix + " (no context): " + msg; |
array b = backtrace(); |
throw (({msg, b[..sizeof (b) - 2]})); |
} |
} |
|
void rxml_fatal (string msg, mixed... args) |
|
|
{ |
Context ctx = get_context(); |
if (ctx && ctx->rxml_fatal) |
ctx->rxml_fatal (msg, @args); |
else { |
if (sizeof (args)) msg = sprintf (msg, @args); |
msg = Context.rxml_fatal_prefix + " (no context): " + msg; |
array b = backtrace(); |
throw (({msg, b[..sizeof (b) - 2]})); |
} |
} |
|
Frame make_tag (string name, mapping(string:mixed) args, void|mixed content) |
|
|
|
{ |
TagSet tag_set = get_context()->tag_set; |
object(Tag)|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) tag = tag_set->get_tag (name); |
if (arrayp (tag)) |
error ("Getting frames for low level tags are currently not implemented.\n"); |
return tag (args, content); |
} |
|
Frame make_unparsed_tag (string name, mapping(string:string) args, void|string content) |
|
|
|
|
{ |
TagSet tag_set = get_context()->tag_set; |
object(Tag)|array(LOW_TAG_TYPE|LOW_CONTAINER_TYPE) tag = tag_set->get_tag (name); |
if (arrayp (tag)) |
error ("Getting frames for low level tags are currently not implemented.\n"); |
Frame frame = tag (args, content); |
frame->flags |= FLAG_UNPARSED; |
return frame; |
} |
|
|
|
|
constant FLAG_NONE = 0x00000000; |
|
|
|
|
constant FLAG_CONTAINER = 0x00000001; |
|
|
|
constant FLAG_NO_PREFIX = 0x00000002; |
|
|
constant FLAG_SOCKET_TAG = 0x0000004; |
|
|
|
constant FLAG_DONT_PREPARSE = 0x00000040; |
|
|
|
constant FLAG_POSTPARSE = 0x00000080; |
|
|
|
|
|
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; |
|
|
|
|
constant FLAG_UNPARSED = 0x80000000; |
|
|
|
class Frame |
|
{ |
constant is_RXML_Frame = 1; |
constant thrown_at_unwind = 1; |
|
|
|
Frame up; |
|
|
|
|
Tag tag; |
|
|
int flags; |
|
|
mapping(string:mixed) args; |
|
|
|
Type content_type; |
|
|
mixed content = Void; |
|
|
|
Type result_type; |
|
|
|
mixed result = Void; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optional array cached_return (Context ctx, void|mixed piece); |
|
|
|
|
|
|
|
|
void rxml_error (string msg, mixed... args) |
|
|
|
{ |
get_context()->rxml_error (msg, @args); |
} |
|
void rxml_fatal (string msg, mixed... args) |
|
|
|
|
{ |
get_context()->rxml_fatal (msg, @args); |
} |
|
void terminate() |
|
|
{ |
error ("FIXME\n"); |
} |
|
void suspend() |
|
|
|
|
|
{ |
error ("FIXME\n"); |
} |
|
void resume() |
|
|
{ |
error ("FIXME\n"); |
} |
|
mapping(string:Tag) get_plugins() |
|
|
|
|
|
{ |
#ifdef MODULE_DEBUG |
if (!(flags & FLAG_SOCKET_TAG)) |
error ("This tag is not a socket tag.\n"); |
#endif |
return get_context()->tag_set->get_plugins (tag->name); |
} |
|
|
|
mixed _exec_array (TagSetParser parser, array exec) |
{ |
Frame this = this_object(); |
Context ctx = parser->context; |
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->_parent = parser; |
subparser->finish ([string] elem); |
piece = subparser->eval(); |
subparser = 0; |
} |
break; |
case "object": |
if (([object] elem)->is_RXML_Frame) { |
([object(Frame)] elem)->_eval (0); |
piece = ([object(Frame)] elem)->result; |
} |
else if (([object] elem)->is_RXML_Parser) { |
|
([object(Parser)] elem)->finish(); |
piece = ([object(Parser)] 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 ([multiset] elem) == 1) piece = ((array) elem)[0]; |
else if (sizeof ([multiset] elem) > 1) |
error (sizeof ([multiset] 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) && ([object] err)->thrown_at_unwind) { |
mapping(string:mixed)|mapping(object:array) ustate; |
if ((ustate = ctx->unwind_state) && !zero_type (ustate->stream_piece)) |
|
if (result_type->quoting_scheme != parser->type->quoting_scheme) |
res = parser->type->quote (res); |
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) |
|
|
|
([array] ustate->exec_left)[0] = subparser; |
} |
throw (err); |
} |
|
private void _handle_runtime_tags (TagSetParser parser, |
Context.RuntimeTags runtime_tags) |
{ |
|
multiset(string|Tag) rem_tags = runtime_tags->remove_tags; |
multiset(Tag) add_tags = runtime_tags->add_tags - rem_tags; |
if (sizeof (rem_tags)) |
foreach (indices (add_tags), Tag tag) |
if (rem_tags[tag->name]) add_tags[tag] = 0; |
array(string|Tag) arr_rem_tags = (array) rem_tags; |
array(Tag) arr_add_tags = (array) add_tags; |
for (Parser p = parser; p; p = p->_parent) |
if (p->tag_set_eval) { |
foreach (arr_add_tags, Tag tag) |
([object(TagSetParser)] p)->add_runtime_tag (tag); |
foreach (arr_rem_tags, string|object(Tag) tag) |
([object(TagSetParser)] p)->remove_runtime_tag (tag); |
} |
} |
|
void _eval (TagSetParser parser, |
void|mapping(string:string) raw_args, |
void|string raw_content) |
|
{ |
Frame this = this_object(); |
Context ctx = parser->context; |
|
|
|
#define ESTATE_BEGIN 0 |
#define ESTATE_ENTERED 1 |
#define ESTATE_LAST_ITER 2 |
int eval_state = ESTATE_BEGIN; |
int iter; |
Parser subparser; |
mixed piece; |
array exec; |
int tags_added; |
|
|
#define PRE_INIT_ERROR(X) (ctx->frame = this, error (X)) |
#ifdef DEBUG |
|
if (ctx != get_context()) |
PRE_INIT_ERROR ("Internal error: Context not current.\n"); |
if (!parser->tag_set_eval) |
PRE_INIT_ERROR ("Internal error: Calling _eval() with non-tag set parser.\n"); |
#endif |
#ifdef MODULE_DEBUG |
if (ctx->new_runtime_tags) |
PRE_INIT_ERROR ("Looks like Context.add_runtime_tag() or " |
"Context.remove_runtime_tag() was used outside any parser.\n"); |
#endif |
|
if (flags & FLAG_UNPARSED) { |
#ifdef DEBUG |
if (raw_args || raw_content) |
PRE_INIT_ERROR ("Internal error: raw_args or raw_content given for " |
"unparsed frame.\n"); |
#endif |
raw_args = args, args = 0; |
raw_content = content, content = Void; |
#ifdef MODULE_DEBUG |
if (!stringp (raw_content)) |
PRE_INIT_ERROR ("Content is not a string in unparsed tag frame.\n"); |
#endif |
} |
|
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 |
object ignored; |
[ignored, eval_state, iter, raw_content, subparser, piece, exec, tags_added, |
ctx->new_runtime_tags] = state; |
m_delete (ctx->unwind_state, this); |
if (!sizeof (ctx->unwind_state)) ctx->unwind_state = 0; |
} |
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; |
|
if (raw_args) { |
args = raw_args; |
|
mapping(string:Type) atypes = raw_args & tag->req_arg_types; |
if (sizeof (atypes) < sizeof (tag->req_arg_types)) { |
array(string) missing = sort (indices (tag->req_arg_types - atypes)); |
rxml_fatal ("Required " + |
(sizeof (missing) > 1 ? |
"arguments " + String.implode_nicely (missing) + " are" : |
"argument " + missing[0] + " is") + " missing.\n"); |
} |
atypes += raw_args & tag->opt_arg_types; |
#ifdef MODULE_DEBUG |
if (mixed err = catch { |
#endif |
foreach (indices (args), string arg) |
args[arg] = (atypes[arg] || tag->def_arg_type)-> |
eval (raw_args[arg], ctx, 0, parser, 1); |
#ifdef MODULE_DEBUG |
}) { |
if (objectp (err) && ([object] err)->thrown_at_unwind) |
error ("Can't save parser state when evaluating arguments.\n"); |
throw (err); |
} |
#endif |
} |
#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 = parser->type; |
foreach (tag->result_types, Type rtype) |
if (ptype->subtype_of (rtype)) {result_type = rtype; break;} |
if (!result_type) |
rxml_fatal ( |
"Tag returns " + |
String.implode_nicely ([array(string)] tag->result_types->name, "or") + |
" but " + [string] parser->type->name + " is expected.\n"); |
} |
if (!content_type) { |
content_type = tag->content_type; |
if (content_type == t_same) |
content_type = result_type (content_type->_parser_prog); |
} |
|
mixed err = catch { |
if (eval_state == ESTATE_BEGIN) |
if (array|function(RequestID,void|mixed:array) do_enter = |
[array|function(RequestID,void|mixed:array)] this->do_enter) { |
if (!exec) exec = do_enter (ctx->id); |
if (exec) { |
mixed res = _exec_array (parser, 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 |
if (result_type->quoting_scheme != parser->type->quoting_scheme) |
res = parser->type->quote (res); |
ctx->unwind_state = (["stream_piece": res]); |
throw (this); |
} |
} |
} |
eval_state = ESTATE_ENTERED; |
|
do { |
if (eval_state != ESTATE_LAST_ITER) { |
int|function(RequestID:int) do_iterate = |
[int|function(RequestID:int)] this->do_iterate; |
if (intp (do_iterate)) { |
iter = [int] do_iterate || 1; |
eval_state = ESTATE_LAST_ITER; |
} |
else |
if (!(iter = ( do_iterate |
) (ctx->id))) |
eval_state = ESTATE_LAST_ITER; |
} |
ENTER_SCOPE (ctx, this); |
for (; iter > 0; iter--) { |
|
if (raw_content) { |
if (ctx->new_runtime_tags) { |
|
_handle_runtime_tags (parser, ctx->new_runtime_tags); |
ctx->new_runtime_tags = 0; |
} |
|
int finished = 0; |
if (!subparser) { |
subparser = content_type->get_parser ( |
ctx, [object(TagSet)] this->local_tags); |
subparser->_parent = parser; |
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,void|mixed:array) do_return; |
if ((do_return = |
[array|function(RequestID,void|mixed:array)] |
this->do_return) && |
functionp (do_return)) { |
if (!exec) exec = do_return (ctx->id, piece); |
if (exec) { |
mixed res = _exec_array (parser, 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 |
if (result_type->quoting_scheme != parser->type->quoting_scheme) |
res = parser->type->quote (res); |
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; |
} |
} |
|
subparser->finish(); |
finished = 1; |
} while (1); |
subparser = 0; |
} |
|
if (array|function(RequestID,void|mixed:array) do_return = |
[array|function(RequestID,void|mixed:array)] this->do_return) { |
if (!exec) |
exec = functionp (do_return) ? |
([function(RequestID,void|mixed:array)] do_return) ( |
ctx->id) : |
[array] do_return; |
if (exec) { |
mixed res = _exec_array (parser, 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 |
if (result_type->quoting_scheme != parser->type->quoting_scheme) |
res = parser->type->quote (res); |
ctx->unwind_state = (["stream_piece": res]); |
throw (this); |
} |
} |
} |
|
} |
LEAVE_SCOPE (ctx, this); |
} while (eval_state != ESTATE_LAST_ITER); |
|
if (!this->do_return && result == Void && content_type->subtype_of (result_type)) |
result = content; |
|
if (ctx->new_runtime_tags) { |
_handle_runtime_tags (parser, ctx->new_runtime_tags); |
ctx->new_runtime_tags = 0; |
} |
}; |
|
if (err) { |
LEAVE_SCOPE (ctx, this); |
string action; |
if (objectp (err) && ([object] err)->thrown_at_unwind) { |
mapping(string:mixed)|mapping(object: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 (parser->unwind_safe) { |
|
|
|
if (err == this) err = 0; |
if (tags_added) { |
ctx->tag_set->imported -= ({ this->additional_tags}); |
tags_added = 0; |
} |
action = "break"; |
} |
else { |
|
|
m_delete (ustate, "stream_piece"); |
action = "continue"; |
} |
else if (!zero_type (ustate->stream_piece)) { |
|
|
piece = ustate->stream_piece; |
m_delete (ustate, "stream_piece"); |
action = "continue"; |
} |
else action = "break"; |
|
ustate[this] = ({err, eval_state, iter, raw_content, subparser, piece, |
exec, tags_added, ctx->new_runtime_tags}); |
} |
else { |
ctx->handle_exception (err, parser); |
action = "return"; |
} |
|
switch (action) { |
case "break": |
#ifdef MODULE_DEBUG |
if (!parser->unwind_state) |
error ("Trying to unwind inside a parser that isn't unwind safe.\n"); |
#endif |
throw (this); |
case "continue": |
_eval (parser); |
return; |
case "return": |
break; |
default: |
error ("Internal error: Don't you come here and %O on me!\n", action); |
} |
} |
|
if (tags_added) |
ctx->tag_set->imported -= ({ this->additional_tags}); |
ctx->frame = up; |
} |
|
DECLARE_CNT (__count); |
|
string _sprintf() |
{ |
return "RXML.Frame(" + (tag && [string] tag->name) + COMMA_CNT (__count) + ")"; |
} |
} |
|
|
|
|
|
class Parser |
|
|
|
{ |
constant is_RXML_Parser = 1; |
constant thrown_at_unwind = 1; |
|
|
|
int error_count; |
|
|
|
function(Parser:void) data_callback; |
|
|
|
|
|
|
int write (string in) |
|
|
{ |
int res; |
ENTER_CONTEXT (context); |
mixed err = catch { |
if (context && context->unwind_state && context->unwind_state->top) { |
#ifdef MODULE_DEBUG |
if (context->unwind_state->top != this_object()) |
error ("The context got an unwound state from another parser. Can't rewind.\n"); |
#endif |
m_delete (context->unwind_state, "top"); |
if (!sizeof (context->unwind_state)) context->unwind_state = 0; |
} |
if (feed (in)) res = 1; |
if (res && data_callback) data_callback (this_object()); |
}; |
LEAVE_CONTEXT(); |
if (err) |
if (objectp (err) && ([object] err)->thrown_at_unwind) { |
#ifdef DEBUG |
if (err != this_object()) |
error ("Internal error: Unexpected unwind object catched.\n"); |
#endif |
if (!context->unwind_state) context->unwind_state = ([]); |
context->unwind_state->top = err; |
} |
else if (context) |
context->handle_exception (err, this_object()); |
else throw (err); |
return res; |
} |
|
void write_end (void|string in) |
|
|
{ |
int res; |
ENTER_CONTEXT (context); |
mixed err = catch { |
if (context && context->unwind_state && context->unwind_state->top) { |
#ifdef MODULE_DEBUG |
if (context->unwind_state->top != this_object()) |
error ("The context got an unwound state from another parser. Can't rewind.\n"); |
#endif |
m_delete (context->unwind_state, "top"); |
if (!sizeof (context->unwind_state)) context->unwind_state = 0; |
} |
finish (in); |
if (data_callback) data_callback (this_object()); |
}; |
LEAVE_CONTEXT(); |
if (err) |
if (objectp (err) && ([object] err)->thrown_at_unwind) { |
#ifdef DEBUG |
if (err != this_object()) |
error ("Internal error: Unexpected unwind object catched.\n"); |
#endif |
if (!context->unwind_state) context->unwind_state = ([]); |
context->unwind_state->top = err; |
} |
else if (context) |
context->handle_exception (err, this_object()); |
else throw (err); |
} |
|
array handle_var (string varref) |
|
|
{ |
|
array(string) split = varref / "."; |
if (sizeof (split) == 2) |
if (mixed err = catch { |
sscanf (split[1], "%[^:]:%s", split[1], string encoding); |
context->current_var = varref; |
mixed val; |
if (zero_type (val = context->get_var ( |
split[1], split[0], encoding ? t_text : type))) { |
context->current_var = 0; |
return ({}); |
} |
context->current_var = 0; |
return encoding ? ({roxen->roxen_encode (val, encoding)}) : ({val}); |
}) { |
context->current_var = 0; |
context->handle_exception (err, this_object()); |
return ({}); |
} |
return type->free_text ? 0 : ({}); |
} |
|
|
|
Context context; |
|
|
|
|
Type type; |
|
|
|
int compile; |
|
|
|
|
|
|
|
|
|
mixed feed (string in); |
|
|
|
|
|
void finish (void|string in); |
|
|
|
|
optional void report_error (string msg); |
|
|
|
|
optional mixed read(); |
|
|
|
|
|
mixed eval(); |
|
|
|
|
|
optional PCode p_compile(); |
|
|
|
optional void reset (Context ctx, Type type, mixed... args); |
|
|
|
|
|
|
optional Parser clone (Context ctx, Type type, mixed... args); |
|
|
|
|
|
static void create (Context ctx, Type _type, mixed... args) |
{ |
context = ctx; |
type = _type; |
} |
|
|
|
Parser _next_free; |
|
|
Parser _parent; |
|
|
Stdio.File _source_file; |
mapping _defines; |
|
|
DECLARE_CNT (__count); |
|
string _sprintf() {return "RXML.Parser" + PAREN_CNT (__count);} |
} |
|
|
class TagSetParser |
|
|
|
|
|
|
{ |
inherit Parser; |
|
constant tag_set_eval = 1; |
|
|
|
mixed eval() {return read();} |
|
|
|
TagSet tag_set; |
|
|
optional void reset (Context ctx, Type type, TagSet tag_set, mixed... args); |
optional Parser clone (Context ctx, Type type, TagSet tag_set, mixed... args); |
static void create (Context ctx, Type type, TagSet _tag_set, mixed... args) |
{ |
::create (ctx, type); |
tag_set = _tag_set; |
} |
|
|
|
mixed read(); |
|
|
|
|
void add_runtime_tag (Tag tag); |
|
|
|
void remove_runtime_tag (string|Tag tag); |
|
|
string _sprintf() {return "RXML.TagSetParser" + PAREN_CNT (__count);} |
} |
|
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; |
} |
|
string _sprintf() {return "RXML.PNone" + PAREN_CNT (__count);} |
} |
|
|
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); |
|
|
|
|
|
|
|
|
|
mixed quote (mixed val) |
|
|
{ |
return val; |
} |
|
mixed convert (mixed val, void|Type from); |
|
|
|
|
|
|
|
|
|
Type clone() |
|
{ |
Type newtype = object_program (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) && ([object] other)->is_RXML_Type && |
([object(Type)] other)->name == this_object()->name; |
} |
|
int subtype_of (Type other) |
|
{ |
return glob ([string] other->name, [string] this_object()->name); |
} |
|
Type `() (program newparser, mixed... parser_args) |
|
|
|
{ |
Type newtype; |
if (sizeof (parser_args)) { |
newtype = clone(); |
newtype->_parser_prog = newparser; |
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 = 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(), tset, @_parser_args); |
} |
else |
|
if (pco->clone_parser) |
p = pco->clone_parser->clone (ctx, this_object(), tset, @_parser_args); |
else if ((p = _parser_prog (0, this_object(), tset, @_parser_args))->clone) |
|
|
p = (pco->clone_parser = p)->clone (ctx, this_object(), tset, @_parser_args); |
} |
else { |
|
pco = PCacheObj(); |
pco->tag_set_gen = tset->generation; |
_p_cache[tset] = pco; |
if ((p = _parser_prog (0, this_object(), tset, @_parser_args))->clone) |
|
|
p = (pco->clone_parser = p)->clone (ctx, this_object(), tset, @_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 = clone_parser->clone (ctx, this_object(), @_parser_args); |
else if ((p = _parser_prog (0, this_object(), @_parser_args))->clone) |
|
|
p = (clone_parser = p)->clone (ctx, this_object(), @_parser_args); |
} |
return p; |
} |
|
mixed eval (string in, void|Context ctx, void|TagSet tag_set, |
void|Parser|PCode parent, 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); |
p->_parent = parent; |
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; |
|
|
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; |
|
DECLARE_CNT (__count); |
|
string _sprintf() {return "RXML.Type" + PAREN_CNT (__count);} |
} |
|
|
static class TAny |
|
{ |
inherit Type; |
constant name = "*"; |
constant quoting_scheme = "none"; |
|
mixed convert (mixed val) {return val;} |
|
string _sprintf() {return "RXML.t_any" + PAREN_CNT (__count);} |
} |
TAny t_any = TAny(); |
|
static class TSame |
|
{ |
inherit Type; |
constant name = "same"; |
string _sprintf() {return "RXML.t_same" + PAREN_CNT (__count);} |
} |
TSame t_same = TSame(); |
|
static class TText |
|
{ |
inherit Type; |
constant name = "text/*"; |
constant sequential = 1; |
constant empty_value = ""; |
constant free_text = 1; |
constant quoting_scheme = "none"; |
|
string convert (mixed val) |
{ |
if (mixed err = catch {return (string) val;}) |
rxml_fatal ("Couldn't convert value to text: " + describe_error (err)); |
} |
|
string _sprintf() {return "RXML.t_text" + PAREN_CNT (__count);} |
} |
TText t_text = TText(); |
|
static class TXml |
|
{ |
inherit TText; |
constant name = "text/xml"; |
constant quoting_scheme = "xml"; |
|
string quote (string val) |
{ |
return replace ( |
val, |
|
({"&", "<", ">", "\"", "\'", |
"\000", "\001", "\002", "\003", "\004", "\005", "\006", "\007", |
"\010", "\013", "\014", "\016", "\017", |
"\020", "\021", "\022", "\023", "\024", "\025", "\026", "\027", |
"\030", "\031", "\032", "\033", "\034", "\035", "\036", "\037", |
}), |
({"&", "<", ">", """, "'", |
"�", "", "", "", "", "", "", "", |
"", "", "", "", "", |
"", "", "", "", "", "", "", "", |
"", "", "", "", "", "", "", "", |
})); |
} |
|
string convert (mixed val, void|Type from) |
{ |
if (mixed err = catch {val = (string) val;}) |
rxml_fatal ("Couldn't convert value to text: " + describe_error (err)); |
if (!from || from->quoting_scheme != quoting_scheme) |
val = quote ([string] val); |
return val; |
} |
|
string _sprintf() {return "RXML.t_xml" + PAREN_CNT (__count);} |
} |
THtml t_xml = TXml(); |
|
static class THtml |
|
{ |
inherit TXml; |
constant name = "text/html"; |
string _sprintf() {return "RXML.t_html" + PAREN_CNT (__count);} |
} |
THtml t_html = THtml(); |
|
|
|
|
class VarRef |
|
{ |
constant is_RXML_VarRef = 1; |
string scope, var; |
static void create (string _scope, string _var) {scope = _scope, var = _var;} |
int valid (Context ctx) {return ctx->exist_scope (scope);} |
mixed get (Context ctx) {return ctx->get_var (var, scope);} |
mixed set (Context ctx, mixed val) {return ctx->set_var (var, val, scope);} |
void delete (Context ctx) {ctx->delete_var (var, scope);} |
string name() {return scope + "." + var;} |
DECLARE_CNT (__count); |
string _sprintf() |
{return "RXML.VarRef(" + scope + "." + var + COMMA_CNT (__count) + ")";} |
} |
|
class PCode |
|
|
{ |
constant is_RXML_PCode = 1; |
constant thrown_at_unwind = 1; |
|
array p_code = ({}); |
|
int error_count; |
|
|
|
mixed eval (Context ctx) |
|
{ |
|
} |
|
function(Context:mixed) compile(); |
|
|
|
|
|
|
void report_error (string msg) |
{ |
|
} |
|
PCode|Parser _parent; |
|
|
DECLARE_CNT (__count); |
|
string _sprintf() {return "RXML.PCode" + PAREN_CNT (__count);} |
} |
|
|
|
|
static class VoidType |
{ |
mixed `+ (mixed... vals) {return sizeof (vals) ? predef::`+ (@vals) : this_object();} |
mixed ``+ (mixed val) {return val;} |
int `!() {return 1;} |
string _sprintf() {return "RXML.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 = 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; |
} |
|
DECLARE_CNT (__count); |
|
string _sprintf() {return "RXML.ScanStream" + PAREN_CNT (__count);} |
} |
|
|
|
|
|
static program PHtml; |
static program PEnt; |
static program PExpr; |
void _fix_module_ref (string name, mixed val) |
{ |
mixed err = catch { |
switch (name) { |
case "PHtml": PHtml = [program] val; break; |
case "PEnt": PEnt = [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)); |
} |
|
|