4293072001-05-08Martin Stjernholm // $Id: module.pmod,v 1.152 2001/05/08 00:57:30 mast Exp $
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström // Kludge: Must use "RXML.refs" somewhere for the whole module to be // loaded correctly.
6c902e2000-03-17Martin Stjernholm static object Roxen;
4becf22000-03-19Martin Nilsson class RequestID { };
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström //(!) FIXME: This was not legal autodoc; do we need some form of markup //(!) to do what it tried to do? I'm not sure I fully understand what the //(!) (now) (!) marked lines were meant to do; if it's just a comment //(!) worth reading when browsing the source, please remove this and turn //(!) these comments into normal "//" comments. / jhs //! RXML parser and compiler framework. //! //! Created 1999-07-30 by Martin Stjernholm. //!
689dc62000-08-31Martin Stjernholm //! API stability notes: //! //! The API in this file regarding the global functions and the Tag, //! TagSet, Context, Frame and Type classes and their descendants is //! intended to not change in incompatible ways. There are however //! some areas where incompatible changes still must be expected: //!
151fa92001-04-19Johan Sundström //! @list ul //! @item //! The namespace handling will likely change to conform to XML //! namespaces. The currently implemented system is inadequate then //! and will probably be removed. //! @item //! The semantics for caching and reuse of Frame objects is //! deliberatily documented vaguely (see the class doc for the //! Frame class). The currently implemented behavior will change //! when the cache system becomes reality. So never assume that //! you'll always get fresh Frame instances every time a tag is //! evaluated. //! @item //! The parser currently doesn't stream data according to the //! interface for streaming tags (but the implementation still //! follows the documented API for it). Therefore there's a risk //! that incompatible changes must be made in it due to design bugs //! when it's tested out. That is considered very unlikely, though. //! @item //! The type system will be developed further, and the API in the //! Type class might change as advanced types gets implemented. //! Don't make assumptions about undocumented behavior. Declare //! data properly with the types RXML.t_xml, RXML.t_html and //! RXML.t_text to let the parser handle the necessary conversions //! instead of doing it yourself. Try to avoid implementing types. //! @item //! Various utilities have FIXME's in their documentation. Needless //! to say they don't work as documented yet, and the doc should be //! considered as ideas only; it might work differently when it's //! actually implemented. //! @endlist
689dc62000-08-31Martin Stjernholm //!
151fa92001-04-19Johan Sundström //! @note //! The API for parsers, p-code evaluators etc is not part of the //! "official" API. (The syntax _parsed_ by the currently implemented //! parsers is well defined, of course.)
ed81751999-12-11Martin Stjernholm 
d8769c2000-02-08Martin Stjernholm //#pragma strict_types // Disabled for now since it doesn't work well enough.
ed81751999-12-11Martin Stjernholm 
cd7d5f2000-02-16Martin Stjernholm #include <config.h>
b483382000-06-30Martin Stjernholm #include <request_trace.h>
ed81751999-12-11Martin Stjernholm 
4293072001-05-08Martin Stjernholm // #define PROFILE_PARSER
ec027c2000-03-16Martin Stjernholm #ifdef RXML_OBJ_DEBUG
f02d002000-03-18Martin Stjernholm # define MARK_OBJECT \ Debug.ObjectMarker __object_marker = Debug.ObjectMarker (this_object()) # define MARK_OBJECT_ONLY \ Debug.ObjectMarker __object_marker = Debug.ObjectMarker (0)
ec027c2000-03-16Martin Stjernholm #else
f02d002000-03-18Martin Stjernholm # define MARK_OBJECT # define MARK_OBJECT_ONLY
ec027c2000-03-16Martin Stjernholm #endif
26ff092000-01-21Martin Stjernholm #ifdef OBJ_COUNT_DEBUG // This debug mode gives every object a unique number in the // _sprintf() string.
f02d002000-03-18Martin Stjernholm # ifndef RXML_OBJ_DEBUG # undef MARK_OBJECT # undef MARK_OBJECT_ONLY # define MARK_OBJECT \ mapping __object_marker = (["count": ++all_constants()->_obj_count]) # define MARK_OBJECT_ONLY \ mapping __object_marker = (["count": ++all_constants()->_obj_count]) # endif # define OBJ_COUNT (__object_marker ? "[" + __object_marker->count + "]" : "")
26ff092000-01-21Martin Stjernholm #else
f02d002000-03-18Martin Stjernholm # define OBJ_COUNT ""
26ff092000-01-21Martin Stjernholm #endif
4293072001-05-08Martin Stjernholm #ifdef PROFILE_PARSER #define PROFILE_ENTER(ctx, what) do { \ ctx->profile[what] -= gethrtime(); \ /* if (what == "rxml internal") trace (1); */ \ } while (0) #define PROFILE_LEAVE(ctx, what) do { \ /* if (what == "rxml internal") trace (0); */ \ ctx->profile[what] += gethrtime(); \ } while (0) #define PROFILE_SWITCH(ctx, from, to) do { \ /* if (from == "rxml internal") trace (0); */ \ int now = gethrtime(); \ ctx->profile[from] += now; \ ctx->profile[to] -= now; \ /* if (to == "rxml internal") trace (1); */ \ } while (0) #else # define PROFILE_ENTER(ctx, what) do ; while (0) # define PROFILE_LEAVE(ctx, what) do ; while (0) # define PROFILE_SWITCH(ctx, from, to) do ; while (0) #endif
ac69772000-09-08Martin Stjernholm #ifdef DEBUG # define TAG_DEBUG(frame, msg) (frame)->tag_debug ("%O: %s", (frame), (msg))
7dd3f82001-04-18Martin Stjernholm # define DO_IF_DEBUG(code...) code
ac69772000-09-08Martin Stjernholm #else # define TAG_DEBUG(frame, msg) 0
7dd3f82001-04-18Martin Stjernholm # define DO_IF_DEBUG(code...)
ac69772000-09-08Martin Stjernholm #endif
f02d002000-03-18Martin Stjernholm #define HASH_INT2(m, n) (n < 65536 ? (m << 16) + n : sprintf ("%x,%x", m, n))
7dd3f82001-04-18Martin Stjernholm // Use defines since typedefs doesn't work in soft casts yet. #define SCOPE_TYPE mapping(string:mixed)|object(Scope) #define UNWIND_STATE mapping(string:mixed)|mapping(object:array)
23a0882000-08-12Martin Stjernholm 
ed81751999-12-11Martin Stjernholm class Tag //! Interface class for the static information about a tag. {
cece9e2000-01-11Martin Stjernholm  constant is_RXML_Tag = 1;
151fa92001-04-19Johan Sundström  //(!) Interface:
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl string name;
4293072001-05-08Martin Stjernholm  //! //! The name of the tag. Required and considered constant.
ed81751999-12-11Martin Stjernholm 
f585722000-01-18Martin Stjernholm  /*extern*/ int flags;
ed81751999-12-11Martin Stjernholm  //! Various bit flags that affect parsing; see the FLAG_* constants.
4293072001-05-08Martin Stjernholm  //! @[RXML.Frame.flags] is initialized from this.
ed81751999-12-11Martin Stjernholm 
710b482000-02-07Martin Stjernholm  mapping(string:Type) req_arg_types = ([]); mapping(string:Type) opt_arg_types = ([]); //! The names and types of the required and optional arguments. If a //! type specifies a parser, it'll be used on the argument value. //! Note that the order in which arguments are parsed is arbitrary.
ed81751999-12-11Martin Stjernholm 
f55a012001-03-14Martin Stjernholm  Type def_arg_type = t_text (PEnt);
3187d82000-01-28Martin Stjernholm  //! The type used for arguments that isn't present in neither
4293072001-05-08Martin Stjernholm  //! @[req_arg_types] nor @[opt_arg_types]. This default is a parser //! that only parses XML-style entities.
3187d82000-01-28Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm  Type content_type = t_same (PXml);
590b3c2000-02-05Martin Stjernholm  //! The handled type of the content, if the tag gets any.
ed81751999-12-11Martin Stjernholm  //!
4293072001-05-08Martin Stjernholm  //! The default is the special type @[RXML.t_same], which means the //! type is taken from the effective type of the result. The //! argument to the type is @[RXML.PXml], which causes that parser, //! i.e. the standard XML parser, to be used to read it. The effect //! is that the content is preparsed with XML syntax. Use no parser, //! or @[RXML.PNone], to get the raw text.
24f9e82000-08-05Martin Stjernholm  //!
f55a012001-03-14Martin Stjernholm  //! Note: You probably want to change this to @[RXML.t_text]
0c9e5b2001-03-13Martin Stjernholm  //! (without parser) if the tag is a processing instruction (see //! @[FLAG_PROC_INSTR]).
ed81751999-12-11Martin Stjernholm 
f55a012001-03-14Martin Stjernholm  array(Type) result_types = ({t_xml, t_html, t_text});
c6245b1999-12-31Martin Stjernholm  //! The possible types of the result, in order of precedence. If a
91c8982000-03-11Martin Stjernholm  //! result type has a parser, it'll be used to parse any strings in
0c9e5b2001-03-13Martin Stjernholm  //! the exec array returned from @[Frame.do_enter] and similar
91c8982000-03-11Martin Stjernholm  //! callbacks.
46c68f2000-08-12Martin Stjernholm  //!
0c9e5b2001-03-13Martin Stjernholm  //! When the tag is used in content of some type, the content type //! may be a supertype of any type in @[result_types], but it may //! also be a subtype of any of them. The tag must therefore be //! prepared to produce result of more specific types than those //! declared here. I.e. the extreme case, @[RXML.t_any], means that //! this tag takes the responsibility to produce result of any type //! that's asked for, not that it has the liberty to produce results //! of any type it chooses.
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl program Frame; //! @decl object(Frame) Frame();
4293072001-05-08Martin Stjernholm  //!
c159ec2000-02-04Martin Stjernholm  //! This program/function is used to clone the objects used as
4293072001-05-08Martin Stjernholm  //! frames. A frame object must (in practice) inherit @[RXML.Frame].
c159ec2000-02-04Martin Stjernholm  //! (It can, of course, be any function that requires no arguments //! and returns a new frame object.) This is not used for plugin //! tags.
151fa92001-04-19Johan Sundström  //! @decl string plugin_name;
4293072001-05-08Martin Stjernholm  //!
c159ec2000-02-04Martin Stjernholm  //! If this is defined, this is a so-called plugin tag. That means
4293072001-05-08Martin Stjernholm  //! it plugs in some sort of functionality in another @[RXML.Tag] //! object instead of handling the actual tags of its own. It works //! as follows:
c159ec2000-02-04Martin Stjernholm  //!
151fa92001-04-19Johan Sundström  //! @list ul //! @item //! Instead of installing the callbacks for this tag, the parser
4293072001-05-08Martin Stjernholm  //! uses another registered "socket" @[Tag] object that got the //! same name as this one. Socket tags have the @[FLAG_SOCKET_TAG] //! flag set to signify that they accept plugins.
151fa92001-04-19Johan Sundström  //! @item //! When the socket tag is parsed or evaluated, it can get the
4293072001-05-08Martin Stjernholm  //! @[Tag] objects for the registered plugins with the function //! @[Frame.get_plugins]. It's then up to the socket tag to use
151fa92001-04-19Johan Sundström  //! the plugins according to some API it defines. //! @item
4293072001-05-08Martin Stjernholm  //! @[plugin_name] is the name of the plugin. It's used as index //! in the mapping that the @[Frame.get_plugins] returns.
151fa92001-04-19Johan Sundström  //! @item //! The plugin tag is registered in the tag set with the
4293072001-05-08Martin Stjernholm  //! identifier @code{@[name] + "#" + @[plugin_name]@}.
c159ec2000-02-04Martin Stjernholm  //!
151fa92001-04-19Johan Sundström  //! It overrides other plugin tags with that name according to //! the normal tag set rules, but, as said above, is never //! registered for actual parsing at all.
c159ec2000-02-04Martin Stjernholm  //!
151fa92001-04-19Johan Sundström  //! It's undefined whether plugin tags override normal tags --
4293072001-05-08Martin Stjernholm  //! @tt{#@} should never be used in normal tag names.
151fa92001-04-19Johan Sundström  //! @item //! It's not an error to register a plugin for which there is no //! socket. Such plugins are simply ignored. //! @endlist
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //(!) Services:
ed81751999-12-11Martin Stjernholm 
938bfd2000-11-04Martin Stjernholm  inline final object/*(Frame)HMM*/ `() (mapping(string:mixed) args, void|mixed content)
ed81751999-12-11Martin Stjernholm  //! Make an initialized frame for the tag. Typically useful when
4293072001-05-08Martin Stjernholm  //! returning generated tags from e.g. @[RXML.Frame.do_process]. The
cf3c902000-02-04Martin Stjernholm  //! argument values and the content are normally not parsed.
ed81751999-12-11Martin Stjernholm  {
56532d1999-12-19Martin Stjernholm  Tag this = this_object();
cf3c902000-02-04Martin Stjernholm  object/*(Frame)HMM*/ frame = ([function(:object/*(Frame)HMM*/)] this->Frame)();
ed81751999-12-11Martin Stjernholm  frame->tag = this; frame->flags = flags; frame->args = args;
23a0882000-08-12Martin Stjernholm  frame->content = zero_type (content) ? nil : content; frame->result = nil;
ed81751999-12-11Martin Stjernholm  return frame; }
c159ec2000-02-04Martin Stjernholm  int eval_args (mapping(string:mixed) args, void|int dont_throw, void|Context ctx) //! Parses and evaluates the tag arguments according to
4293072001-05-08Martin Stjernholm  //! @[req_arg_types] and @[opt_arg_types]. The @[args] mapping //! contains the unparsed arguments on entry, and they get replaced //! by the parsed results. Arguments not mentioned in //! @[req_arg_types] or @[opt_arg_types] are not touched. RXML //! errors, such as missing argument, are thrown if @[dont_throw] is //! zero or left out, otherwise zero is returned when any such error //! occurs. @[ctx] specifies the context to use; it defaults to the //! current context.
c159ec2000-02-04Martin Stjernholm  { // Note: Code duplication in Frame._eval().
710b482000-02-07Martin Stjernholm  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));
5ed0132000-02-13Martin Stjernholm  parse_error ("Required " + (sizeof (missing) > 1 ? "arguments " + String.implode_nicely (missing) + " are" : "argument " + missing[0] + " is") + " missing.\n");
710b482000-02-07Martin Stjernholm  } atypes += args & opt_arg_types;
c159ec2000-02-04Martin Stjernholm #ifdef MODULE_DEBUG
710b482000-02-07Martin Stjernholm  if (mixed err = catch {
c159ec2000-02-04Martin Stjernholm #endif
710b482000-02-07Martin Stjernholm  foreach (indices (atypes), string arg) args[arg] = atypes[arg]->eval (args[arg], ctx); // Should not unwind.
c159ec2000-02-04Martin Stjernholm #ifdef MODULE_DEBUG
710b482000-02-07Martin Stjernholm  }) { if (objectp (err) && ([object] err)->thrown_at_unwind)
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Can't save parser state when evaluating arguments.\n"); throw_fatal (err);
c159ec2000-02-04Martin Stjernholm  }
710b482000-02-07Martin Stjernholm #endif
c159ec2000-02-04Martin Stjernholm  return 1; }
151fa92001-04-19Johan Sundström  //(!) Internals:
ed81751999-12-11Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm #define MAKE_FRAME(frame, ctx, parser, args) \ make_new_frame: do { \ if (UNWIND_STATE ustate = ctx->unwind_state) \ if (ustate[parser]) { \ frame = [object/*(Frame)HMM*/] ustate[parser][0]; \ m_delete (ustate, parser); \ if (!sizeof (ustate)) ctx->unwind_state = 0; \ break make_new_frame; \ } \ frame = `() (args, nil); \ TAG_DEBUG (frame, "New frame\n"); \ } while (0) #define EVAL_FRAME(frame, ctx, parser, type, args, content, result) \ eval_frame: do { \ mixed err = catch { \ frame->_eval (parser, args, content); \ if ((result = frame->result) != nil) \ if (frame->result_type->name != type->name) { \ TAG_DEBUG ( \ frame, sprintf ( \ "Converting result from %s to %s of surrounding content\n", \ frame->result_type->name, type->name)); \ result = type->encode (result, frame->result_type); \ } \ break eval_frame; \ }; \ \ if (objectp (err) && ([object] err)->thrown_at_unwind) { \ UNWIND_STATE ustate = ctx->unwind_state; \ if (!ustate) ustate = ctx->unwind_state = ([]); \ DO_IF_DEBUG ( \ if (err != frame) \ fatal_error ("Internal error: " \ "Unexpected unwind object catched.\n"); \ if (ustate[parser]) \ fatal_error ("Internal error: " \ "Clobbering unwind state for parser.\n"); \ ); \ ustate[parser] = ({err}); \ throw (err = parser); \ } \ else { \ /* Will rethrow unknown errors. */ \ ctx->handle_exception (err, parser); \ result = nil; \ } \ } while (0) final mixed handle_tag (TagSetParser parser, mapping(string:string) args, void|string content) // Callback for tag set parsers to handle tags. Note that this // function handles an unwind frame for the parser. { // Note: args may be zero when this is called for PI tags. Context ctx = parser->context; // FIXME: P-code generation. object/*(Frame)HMM*/ frame; MAKE_FRAME (frame, ctx, parser, args); if (!zero_type (frame->raw_tag_text)) frame->raw_tag_text = parser->raw_tag_text(); mixed result; EVAL_FRAME (frame, ctx, parser, parser->type, args, content, result); return result; } final array _p_xml_handle_tag (object/*(PXml)*/ parser, mapping(string:string) args, void|string content)
ed81751999-12-11Martin Stjernholm  {
7dd3f82001-04-18Martin Stjernholm  Type type = parser->type; if (type->handle_literals) parser->handle_literal();
24f9e82000-08-05Martin Stjernholm 
56532d1999-12-19Martin Stjernholm  Context ctx = parser->context;
ed81751999-12-11Martin Stjernholm  // FIXME: P-code generation.
9d80e82000-02-11Martin Stjernholm 
51f88b2000-06-23Martin Stjernholm  string splice_args;
24f9e82000-08-05Martin Stjernholm  if (args && (splice_args = args["::"])) {
51f88b2000-06-23Martin Stjernholm #ifdef MODULE_DEBUG if (mixed err = catch { #endif
b4f4242001-03-15Martin Stjernholm  splice_args = t_string (PEnt)->eval (splice_args, ctx, 0, parser, 1);
51f88b2000-06-23Martin Stjernholm #ifdef MODULE_DEBUG }) { if (objectp (err) && ([object] err)->thrown_at_unwind) fatal_error ("Can't save parser state when evaluating splice argument.\n"); throw_fatal (err); } #endif
9d80e82000-02-11Martin Stjernholm  m_delete (args, "::"); args += parser->parse_tag_args (splice_args); }
f997202000-01-18Martin Stjernholm  object/*(Frame)HMM*/ frame;
7dd3f82001-04-18Martin Stjernholm  MAKE_FRAME (frame, ctx, parser, args);
56532d1999-12-19Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (!zero_type (frame->raw_tag_text))
51f88b2000-06-23Martin Stjernholm  if (splice_args) frame->raw_tag_text =
9a38062000-08-04Martin Stjernholm  t_xml->format_tag (parser->tag_name(), args, content, flags | FLAG_RAW_ARGS);
51f88b2000-06-23Martin Stjernholm  else frame->raw_tag_text = parser->current_input();
7dd3f82001-04-18Martin Stjernholm  mixed result; EVAL_FRAME (frame, ctx, parser, type, args, content, result);
56532d1999-12-19Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (result != nil) { if (type->free_text) return ({result}); parser->add_value (result);
f0ce412000-03-06Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  return ({});
ed81751999-12-11Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  final array _p_xml_handle_pi_tag (object/*(PXml)*/ parser, string content)
24f9e82000-08-05Martin Stjernholm  {
7dd3f82001-04-18Martin Stjernholm  Type type = parser->type; if (type->handle_literals) parser->handle_literal();
46c68f2000-08-12Martin Stjernholm  sscanf (content, "%[ \t\n\r]%s", string ws, string rest);
7dd3f82001-04-18Martin Stjernholm  if (ws == "" && rest != "") {
24f9e82000-08-05Martin Stjernholm  // The parser didn't match a complete name, so this is a false // alarm for an unknown PI tag.
7dd3f82001-04-18Martin Stjernholm  if (!type->free_text) return utils->unknown_pi_tag_error (parser, content);
24f9e82000-08-05Martin Stjernholm  return 0;
7dd3f82001-04-18Martin Stjernholm  }
24f9e82000-08-05Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  Context ctx = parser->context; // FIXME: P-code generation.
87fb7e2000-03-25Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  object/*(Frame)HMM*/ frame; MAKE_FRAME (frame, ctx, parser, 0); if (!zero_type (frame->raw_tag_text)) frame->raw_tag_text = parser->current_input(); mixed result; EVAL_FRAME (frame, ctx, parser, type, 0, content, result); if (result != nil) { if (type->free_text) return ({result}); parser->add_value (result); } return ({});
87fb7e2000-03-25Martin Stjernholm  }
f02d002000-03-18Martin Stjernholm  MARK_OBJECT;
26ff092000-01-21Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  string _sprintf() {
24f9e82000-08-05Martin Stjernholm  return "RXML.Tag(" + [string] this_object()->name + (this_object()->plugin_name ? "#" + [string] this_object()->plugin_name : "") + ([int] this_object()->flags & FLAG_PROC_INSTR ? " [PI]" : "") + ")" + OBJ_COUNT;
ed81751999-12-11Martin Stjernholm  } } class TagSet //! Contains a set of tags. Tag sets can import other tag sets, and
4293072001-05-08Martin Stjernholm //! later changes in them are propagated. Parser instances (contexts) //! to parse data are created from this. @[TagSet] objects may //! somewhat safely be destructed explicitly; the tags in a destructed //! tag set will not be active in parsers that are instantiated later, //! but will work in current instances. Element (i.e. non-PI) tags and //! PI tags have separate namespaces.
24f9e82000-08-05Martin Stjernholm //!
151fa92001-04-19Johan Sundström //! @note
4293072001-05-08Martin Stjernholm //! An @[RXML.Tag] object may not be registered in more than one tag //! set at the same time.
ed81751999-12-11Martin Stjernholm {
2bd21a2000-01-05Martin Stjernholm  string name; //! Used for identification only.
ed81751999-12-11Martin Stjernholm  string prefix;
f997202000-01-18Martin Stjernholm  //! A namespace prefix that may precede the tags. If it's zero, it's
4293072001-05-08Martin Stjernholm  //! up to the importing tag set(s). A @tt{:@} is always inserted //! between the prefix and the tag name. //! //! @note //! This namespace scheme is not compliant with the XML namespaces //! standard. Since the intention is to implement XML namespaces at //! some point, this way of specifying tag prefixes will probably //! change.
ed81751999-12-11Martin Stjernholm 
f997202000-01-18Martin Stjernholm  int prefix_req;
ed81751999-12-11Martin Stjernholm  //! The prefix must precede the tags. array(TagSet) imported = ({}); //! Other tag sets that will be used. The precedence is local tags //! first, then imported from left to right. It's not safe to //! destructively change entries in this array.
660cb52000-01-12Martin Stjernholm  function(Context:void) prepare_context; //! If set, this is a function that will be called before a new
4293072001-05-08Martin Stjernholm  //! @[RXML.Context] object is taken into use. It'll typically //! prepare predefined scopes and variables. The functions will be //! called in order of precedence; highest last.
660cb52000-01-12Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  int generation = 1; //! A number that is increased every time something changes in this //! object or in some tag set it imports.
f02d002000-03-18Martin Stjernholm  int id_number; //! Unique number identifying this tag set.
2bd21a2000-01-05Martin Stjernholm  static void create (string _name, void|array(Tag) _tags)
ed81751999-12-11Martin Stjernholm  //! {
f02d002000-03-18Martin Stjernholm  id_number = ++tag_set_count;
2bd21a2000-01-05Martin Stjernholm  name = _name;
24f9e82000-08-05Martin Stjernholm  if (_tags) add_tags (_tags);
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG __object_marker->create (this_object()); #endif
ed81751999-12-11Martin Stjernholm  } void add_tag (Tag tag) //! {
e821042000-08-17Martin Stjernholm #ifdef MODULE_DEBUG if (!stringp (tag->name)) error ("Trying to register a tag %O without a name.\n", tag);
055e932001-04-09Per Hedbor  if (!callablep (tag->Frame) && !tag->plugin_name)
e821042000-08-17Martin Stjernholm  error ("Trying to register a tag %O without a Frame class or function.\n", tag);
1a65d92000-08-30Martin Stjernholm  if (tag->name[..3] != "!--#" && // Ugly special case for SSI tags. replace (tag->name, "#<>& \t\n\r" / "", ({""}) * 8) != tag->name) error ("Invalid character(s) in name for tag %O.\n", tag);
e821042000-08-17Martin Stjernholm #endif
24f9e82000-08-05Martin Stjernholm  if (tag->flags & FLAG_PROC_INSTR) { if (!proc_instrs) proc_instrs = ([]); if (tag->plugin_name) proc_instrs[tag->name + "#" + tag->plugin_name] = tag; else proc_instrs[tag->name] = tag; } else if (tag->plugin_name) tags[tag->name + "#" + tag->plugin_name] = tag; else tags[tag->name] = tag;
ed81751999-12-11Martin Stjernholm  changed(); } void add_tags (array(Tag) _tags) //! {
24f9e82000-08-05Martin Stjernholm  foreach (_tags, Tag tag) {
e821042000-08-17Martin Stjernholm #ifdef MODULE_DEBUG if (!stringp (tag->name)) error ("Trying to register a tag %O without a name.\n", tag);
055e932001-04-09Per Hedbor  if (!callablep (tag->Frame)&& !tag->plugin_name)
e821042000-08-17Martin Stjernholm  error ("Trying to register a tag %O without a Frame class or function.\n", tag);
1a65d92000-08-30Martin Stjernholm  if (tag->name[..3] != "!--#" && // Ugly special case for SSI tags. replace (tag->name, "#<>& \t\n\r" / "", ({""}) * 8) != tag->name) error ("Invalid character(s) in name for tag %O.\n", tag);
e821042000-08-17Martin Stjernholm #endif
24f9e82000-08-05Martin Stjernholm  if (tag->flags & FLAG_PROC_INSTR) { if (!proc_instrs) proc_instrs = ([]); if (tag->plugin_name) proc_instrs[tag->name + "#" + tag->plugin_name] = tag; else proc_instrs[tag->name] = tag; } else if (tag->plugin_name) tags[tag->name + "#" + tag->plugin_name] = tag; else tags[tag->name] = tag; }
ed81751999-12-11Martin Stjernholm  changed(); }
24f9e82000-08-05Martin Stjernholm  void remove_tag (string|Tag tag, void|int proc_instr)
4293072001-05-08Martin Stjernholm  //! If tag is an @[RXML.Tag] object, it's removed if this tag set //! contains it. If tag is a string, the tag with that name is //! removed. In the latter case, if @[proc_instr] is nonzero the set //! of PI tags is searched, else the set of normal element tags.
ed81751999-12-11Martin Stjernholm  { if (stringp (tag))
24f9e82000-08-05Martin Stjernholm  if (proc_instr) { if (proc_instrs) m_delete (proc_instrs, tag); } else m_delete (tags, tag); else { string n; if (tag->flags & FLAG_PROC_INSTR) { if (proc_instrs && !zero_type (n = search (tags, [object(Tag)] tag))) m_delete (proc_instrs, n); } else if (!zero_type (n = search (tags, [object(Tag)] tag))) m_delete (tags, n); }
ed81751999-12-11Martin Stjernholm  changed(); }
24f9e82000-08-05Martin Stjernholm  local Tag get_local_tag (string name, void|int proc_instr)
4293072001-05-08Martin Stjernholm  //! Returns the @[RXML.Tag] object for the given name in this tag //! set, if any. If @[proc_instr] is nonzero the set of PI tags is //! searched, else the set of normal element tags.
ed81751999-12-11Martin Stjernholm  {
24f9e82000-08-05Martin Stjernholm  return proc_instr ? proc_instrs && proc_instrs[name] : tags[name];
ed81751999-12-11Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm  local array(Tag) get_local_tags()
4293072001-05-08Martin Stjernholm  //! Returns all the @[RXML.Tag] objects in this tag set.
ed81751999-12-11Martin Stjernholm  {
24f9e82000-08-05Martin Stjernholm  array(Tag) res = values (tags); if (proc_instrs) res += values (proc_instrs); return res;
ed81751999-12-11Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm  local Tag get_tag (string name, void|int proc_instr)
4293072001-05-08Martin Stjernholm  //! Returns the @[RXML.Tag] object for the given name, if any, //! that's defined by this tag set (including its imported tag //! sets). If @[proc_instr] is nonzero the set of PI tags is //! searched, else the set of normal element tags.
2bd21a2000-01-05Martin Stjernholm  {
24f9e82000-08-05Martin Stjernholm  if (object(Tag) def = get_local_tag (name, proc_instr))
56ffee2000-01-12Martin Stjernholm  return def; foreach (imported, TagSet tag_set)
24f9e82000-08-05Martin Stjernholm  if (object(Tag) tag = [object(Tag)] tag_set->get_tag (name, proc_instr)) return tag;
56ffee2000-01-12Martin Stjernholm  return 0;
da54bd2000-01-08Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm  local int has_tag (Tag tag) //! Returns nonzero if the given tag is contained in this tag set //! (including its imported tag sets). { return !!get_tag (tag->name, tag->flags & FLAG_PROC_INSTR); } local multiset(string) get_tag_names()
24f9e82000-08-05Martin Stjernholm  //! Returns the names of all non-PI tags that this tag set defines. { return `| ((multiset) indices (tags), @imported->get_tag_names()); }
689dc62000-08-31Martin Stjernholm  local multiset(string) get_proc_instr_names()
24f9e82000-08-05Martin Stjernholm  //! Returns the names of all PI tags that this tag set defines. { return `| (proc_instrs ? (multiset) indices (proc_instrs) : (<>), @imported->get_proc_instr_names()); }
689dc62000-08-31Martin Stjernholm  local Tag get_overridden_tag (Tag overrider)
56ffee2000-01-12Martin Stjernholm  //! Returns the tag definition that the given one overrides, or zero
2e0a6d2000-07-06Martin Stjernholm  //! if none.
da54bd2000-01-08Martin Stjernholm  {
51f88b2000-06-23Martin Stjernholm  if (!mappingp (overridden_tag_lookup)) overridden_tag_lookup = set_weak_flag (([]), 1);
2e0a6d2000-07-06Martin Stjernholm  Tag tag;
09b6c52001-01-24Martin Stjernholm  if (zero_type (tag = overridden_tag_lookup[overrider])) { string overrider_name = overrider->plugin_name ? overrider->plugin_name + "#" + overrider->name : overrider->name;
2e0a6d2000-07-06Martin Stjernholm  tag = overridden_tag_lookup[overrider] =
24f9e82000-08-05Martin Stjernholm  overrider->flags & FLAG_PROC_INSTR ?
09b6c52001-01-24Martin Stjernholm  find_overridden_proc_instr (overrider, overrider_name) : find_overridden_tag (overrider, overrider_name); }
51f88b2000-06-23Martin Stjernholm  return tag;
2bd21a2000-01-05Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm  local array(Tag) get_overridden_tags (string name, void|int proc_instr)
56ffee2000-01-12Martin Stjernholm  //! Returns all tag definitions for the given name, i.e. including //! the overridden ones. A tag to the left overrides one to the
4293072001-05-08Martin Stjernholm  //! right. If @[proc_instr] is nonzero the set of PI tags is //! searched, else the set of normal element tags.
56ffee2000-01-12Martin Stjernholm  {
24f9e82000-08-05Martin Stjernholm  if (object(Tag) def = get_local_tag (name, proc_instr)) return ({def}) + imported->get_overridden_tags (name, proc_instr) * ({}); else return imported->get_overridden_tags (name, proc_instr) * ({});
2e0a6d2000-07-06Martin Stjernholm  } void add_string_entities (mapping(string:string) entities)
fe628d2001-03-01Martin Stjernholm  //! Adds a set of entity replacements that are used foremost by the
4293072001-05-08Martin Stjernholm  //! @[RXML.PXml] parser to decode simple entities like @tt{&amp;@}. //! The indices are the entity names without @tt{&@} and @tt{;@}.
2e0a6d2000-07-06Martin Stjernholm  { if (string_entities) string_entities |= entities; else string_entities = entities + ([]); changed(); } void clear_string_entities() //! { string_entities = 0; changed(); }
689dc62000-08-31Martin Stjernholm  local mapping(string:string) get_string_entities()
2e0a6d2000-07-06Martin Stjernholm  //! Returns the set of entity replacements, including those from //! imported tag sets. { if (string_entities) return `+(@imported->get_string_entities(), string_entities); else return `+(@imported->get_string_entities(), ([]));
56ffee2000-01-12Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm  local mapping(string:Tag) get_plugins (string name, void|int proc_instr)
4293072001-05-08Martin Stjernholm  //! Returns the registered plugins for the given tag name. If //! @[proc_instr] is nonzero, the function searches for processing //! instruction plugins, otherwise it searches for plugins to normal //! element tags. Don't be destructive on the returned mapping.
c159ec2000-02-04Martin Stjernholm  { mapping(string:Tag) res;
24f9e82000-08-05Martin Stjernholm  if (proc_instr) { if (!pi_plugins) pi_plugins = ([]); if ((res = pi_plugins[name])) return res; low_get_pi_plugins (name + "#", res = ([])); return pi_plugins[name] = res; } else { if (!plugins) plugins = ([]); if ((res = plugins[name])) return res; low_get_plugins (name + "#", res = ([])); return plugins[name] = res; }
c159ec2000-02-04Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm  local int has_effective_tags (TagSet tset)
87fb7e2000-03-25Martin Stjernholm  //! This one deserves some explanation. { return tset == top_tag_set && !got_local_tags; }
689dc62000-08-31Martin Stjernholm  local mixed `->= (string var, mixed val)
ed81751999-12-11Martin Stjernholm  { switch (var) { case "imported":
49897a2000-02-13Martin Stjernholm  if (!val) return val; // Pike can call us with 0 as part of an optimization. filter (imported, "dont_notify", changed);
ed81751999-12-11Martin Stjernholm  imported = [array(TagSet)] val; imported->do_notify (changed);
87fb7e2000-03-25Martin Stjernholm  top_tag_set = sizeof (imported) && imported[0];
ed81751999-12-11Martin Stjernholm  break; default: ::`->= (var, val); } changed(); return val; }
689dc62000-08-31Martin Stjernholm  local mixed `[]= (string var, mixed val) {return `->= (var, val);}
ed81751999-12-11Martin Stjernholm  Parser `() (Type top_level_type, void|RequestID id) //! Creates a new context for parsing content of the specified type, //! and returns the parser object for it. id is put into the //! context. {
660cb52000-01-12Martin Stjernholm  Context ctx = Context (this_object(), id); if (!prepare_funs) prepare_funs = get_prepare_funs();
777ff22000-02-15Martin Stjernholm  (prepare_funs -= ({0})) (ctx);
660cb52000-01-12Martin Stjernholm  return ctx->new_parser (top_level_type);
ed81751999-12-11Martin Stjernholm  } void changed() //! Should be called whenever something is changed. Done //! automatically most of the time, however. { generation++;
660cb52000-01-12Martin Stjernholm  prepare_funs = 0;
51f88b2000-06-23Martin Stjernholm  overridden_tag_lookup = 0;
24f9e82000-08-05Martin Stjernholm  plugins = pi_plugins = 0;
ed81751999-12-11Martin Stjernholm  (notify_funcs -= ({0}))(); set_weak_flag (notify_funcs, 1);
24f9e82000-08-05Martin Stjernholm  got_local_tags = sizeof (tags) || (proc_instrs && sizeof (proc_instrs));
ed81751999-12-11Martin Stjernholm  }
3083572001-04-24Martin Nilsson  function(Backtrace,Type:string) handle_run_error = lambda (Backtrace err, Type type) //! Formats the run error backtrace.
f5bd9e2001-04-25Martin Nilsson  // This wrapper function however only search for a // "real" handle_run_error function.
3083572001-04-24Martin Nilsson  {
f5bd9e2001-04-25Martin Nilsson  string result; foreach(imported, TagSet tag_set) { result = tag_set->handle_run_error(err, type); if(result) return result; } return 0;
3083572001-04-24Martin Nilsson  }; function(Backtrace,Type:string) handle_parse_error = lambda (Backtrace err, Type type) //! Formats the parse error backtrace.
f5bd9e2001-04-25Martin Nilsson  // This wrapper function however only search for a // "real" handle_parse_error function.
3083572001-04-24Martin Nilsson  {
f5bd9e2001-04-25Martin Nilsson  string result; foreach(imported, TagSet tag_set) { result = tag_set->handle_parse_error(err, type); if(result) return result; } return 0;
3083572001-04-24Martin Nilsson  };
151fa92001-04-19Johan Sundström  //(!) Internals:
ed81751999-12-11Martin Stjernholm  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()); }
24f9e82000-08-05Martin Stjernholm  static mapping(string:Tag) tags = ([]), proc_instrs; // Static since we want to track changes in these.
2e0a6d2000-07-06Martin Stjernholm  static mapping(string:string) string_entities; // Used by e.g. PXml to hold normal entities that should be replaced // during parsing.
ed81751999-12-11Martin Stjernholm 
87fb7e2000-03-25Martin Stjernholm  static TagSet top_tag_set; // The imported tag set with the highest priority. static int got_local_tags;
24f9e82000-08-05Martin Stjernholm  // Nonzero if there are local element tags or PI tags.
87fb7e2000-03-25Martin Stjernholm 
c159ec2000-02-04Martin Stjernholm  static array(function(:void)) notify_funcs = ({});
ed81751999-12-11Martin Stjernholm  // Weak (when nonempty).
2bd21a2000-01-05Martin Stjernholm 
c159ec2000-02-04Martin Stjernholm  static array(function(Context:void)) prepare_funs;
660cb52000-01-12Martin Stjernholm 
c159ec2000-02-04Martin Stjernholm  /*static*/ array(function(Context:void)) get_prepare_funs()
660cb52000-01-12Martin Stjernholm  { 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}); // We don't cache in prepare_funs; do that only at the top level. return funs; }
2e0a6d2000-07-06Martin Stjernholm  static mapping(Tag:Tag) overridden_tag_lookup;
51f88b2000-06-23Martin Stjernholm 
09b6c52001-01-24Martin Stjernholm  /*static*/ Tag find_overridden_tag (Tag overrider, string overrider_name)
51f88b2000-06-23Martin Stjernholm  {
09b6c52001-01-24Martin Stjernholm  if (tags[overrider_name] == overrider) {
51f88b2000-06-23Martin Stjernholm  foreach (imported, TagSet tag_set)
09b6c52001-01-24Martin Stjernholm  if (object(Tag) overrider = tag_set->get_tag (overrider_name))
2e0a6d2000-07-06Martin Stjernholm  return overrider;
51f88b2000-06-23Martin Stjernholm  } else { int found = 0; foreach (imported, TagSet tag_set)
09b6c52001-01-24Martin Stjernholm  if (object(Tag) subtag = tag_set->get_tag (overrider_name))
51f88b2000-06-23Martin Stjernholm  if (found) return subtag;
2e0a6d2000-07-06Martin Stjernholm  else if (subtag == overrider)
09b6c52001-01-24Martin Stjernholm  if ((subtag = tag_set->find_overridden_tag ( overrider, overrider_name)))
51f88b2000-06-23Martin Stjernholm  return subtag; else found = 1; } return 0; }
09b6c52001-01-24Martin Stjernholm  /*static*/ Tag find_overridden_proc_instr (Tag overrider, string overrider_name)
24f9e82000-08-05Martin Stjernholm  {
09b6c52001-01-24Martin Stjernholm  if (proc_instrs && proc_instrs[overrider_name] == overrider) {
24f9e82000-08-05Martin Stjernholm  foreach (imported, TagSet tag_set)
4293072001-05-08Martin Stjernholm  if (object(Tag) overrider = tag_set->get_tag (overrider_name, 1))
24f9e82000-08-05Martin Stjernholm  return overrider; } else { int found = 0; foreach (imported, TagSet tag_set)
4293072001-05-08Martin Stjernholm  if (object(Tag) subtag = tag_set->get_tag (overrider_name, 1))
24f9e82000-08-05Martin Stjernholm  if (found) return subtag; else if (subtag == overrider)
09b6c52001-01-24Martin Stjernholm  if ((subtag = tag_set->find_overridden_proc_instr ( overrider, overrider_name)))
24f9e82000-08-05Martin Stjernholm  return subtag; else found = 1; } return 0; }
777ff22000-02-15Martin Stjernholm  void call_prepare_funs (Context ctx) // Kludge function used from rxml.pike. { if (!prepare_funs) prepare_funs = get_prepare_funs(); (prepare_funs -= ({0})) (ctx); }
24f9e82000-08-05Martin Stjernholm  static mapping(string:mapping(string:Tag)) plugins, pi_plugins;
c159ec2000-02-04Martin Stjernholm  /*static*/ 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; } // We don't cache in plugins; do that only at the top level. }
24f9e82000-08-05Martin Stjernholm  /*static*/ void low_get_pi_plugins (string prefix, mapping(string:Tag) res) { for (int i = sizeof (imported) - 1; i >= 0; i--) imported[i]->low_get_pi_plugins (prefix, res); if (proc_instrs) foreach (indices (proc_instrs), string name) if (name[..sizeof (prefix) - 1] == prefix) { Tag tag = proc_instrs[name]; if (tag->plugin_name) res[[string] tag->plugin_name] = tag; } // We don't cache in pi_plugins; do that only at the top level. }
2bd21a2000-01-05Martin Stjernholm  string _sprintf() {
f02d002000-03-18Martin Stjernholm  return sprintf ("RXML.TagSet(%O,%d)%s", name, id_number, OBJ_COUNT);
2bd21a2000-01-05Martin Stjernholm  }
ec027c2000-03-16Martin Stjernholm 
f02d002000-03-18Martin Stjernholm  MARK_OBJECT_ONLY;
ed81751999-12-11Martin Stjernholm }
1b2b752000-01-07Martin Stjernholm TagSet empty_tag_set;
ed81751999-12-11Martin Stjernholm //! The empty tag set.
af06d52000-01-12Martin Stjernholm class Value //! Interface for objects used as variable values that are evaluated //! when referenced. {
a21fcb2000-02-08Martin Nilsson  mixed rxml_var_eval (Context ctx, string var, string scope_name, void|Type type)
2b5f472001-02-11Martin Stjernholm  //! This is called to get the value of the variable. @[ctx], @[var] //! and @[scope_name] are set to where this @[Value] object was //! found. Note that @[scope_name] can be on the form
4293072001-05-08Martin Stjernholm  //! @tt{"scope.index1.index2..."@} when this object was encountered
0c9e5b2001-03-13Martin Stjernholm  //! through subindexing. Either @[RXML.nil] or the undefined value //! may be returned if the variable doesn't have a value.
710b482000-02-07Martin Stjernholm  //!
2b5f472001-02-11Martin Stjernholm  //! If the @[type] argument is given, it's the type the returned //! value should have. If the value can't be converted to that type, //! an RXML error should be thrown. If you don't want to do any //! special handling of this, it's enough to call
151fa92001-04-19Johan Sundström  //! @code{@[type]->encode(value)@}, since the encode functions does
2b5f472001-02-11Martin Stjernholm  //! just that. //! //! Some design discussion follows to justify the last paragraph; //! there are no more interface rules below. //! //! It may seem like forcing a lot of overhead upon the //! implementations having to call encode functions, but that's //! really not the case. In the case when a type check and //! conversion is wanted, i.e. when @[type] isn't zero, that work //! have to be done somewhere anyway, so letting the producer of the //! value do it instead of the caller both improves the chances for //! doing optimizations and gives more power to the producer. //! //! By using knowledge about the actual value, the producer can in //! many cases avoid the call to the encode function. A typical case
ec860d2001-03-13Martin Stjernholm  //! is when the value is known to be an arbitrary literal string //! (not zero), which is preferably optimized like this:
2b5f472001-02-11Martin Stjernholm  //! //! @example
f55a012001-03-14Martin Stjernholm  //! return type && type != RXML.t_text ? //! type->encode (my_string, RXML.t_text) : my_string;
2b5f472001-02-11Martin Stjernholm  //! @endexample //! //! Also, by letting the producer know the type context of the value //! and handle the type conversion, it's possible for the producer //! to adapt the value according to the context it'll be used in, //! e.g. to return a powerful object if no type conversion is //! wanted, a simple text representation of it when the type is
f55a012001-03-14Martin Stjernholm  //! @[RXML.t_text], and a more nicely formatted representation when
fe628d2001-03-01Martin Stjernholm  //! it's @[RXML.t_html].
2b5f472001-02-11Martin Stjernholm  //!
151fa92001-04-19Johan Sundström  //! @note //! The @[type] argument being @tt{void|Type@} means that the caller //! is free to leave out that argument, not that the function
2b5f472001-02-11Martin Stjernholm  //! implementor is free to ignore it.
a21fcb2000-02-08Martin Nilsson  { mixed val = rxml_const_eval (ctx, var, scope_name, type); ctx->set_var(var, val, scope_name); return val; }
9d80e82000-02-11Martin Stjernholm  mixed rxml_const_eval (Context ctx, string var, string scope_name, void|Type type);
2b5f472001-02-11Martin Stjernholm  //! If the variable value is the same throughout the life of the //! context, this method should be used instead of @[rxml_var_eval].
26ff092000-01-21Martin Stjernholm  string _sprintf() {return "RXML.Value";}
af06d52000-01-12Martin Stjernholm } class Scope
2b5f472001-02-11Martin Stjernholm //! Interface for objects that emulate a scope mapping.
1cfbeb2000-11-06Martin Stjernholm //!
151fa92001-04-19Johan Sundström //! @note //! The @tt{scope_name@} argument to the functions can be on the form
4293072001-05-08Martin Stjernholm //! @tt{"scope.index1.index2..."@} when this object was encountered
151fa92001-04-19Johan Sundström //! through subindexing.
af06d52000-01-12Martin Stjernholm {
2b5f472001-02-11Martin Stjernholm  mixed `[] (string var, void|Context ctx, void|string scope_name, void|Type type) //! Called to get the value of a variable in the scope. @[var] is //! the name of it, @[ctx] and @[scope_name] are set to where this
0c9e5b2001-03-13Martin Stjernholm  //! @[Scope] object was found. Either @[RXML.nil] or the undefined //! value may be returned if the variable doesn't exist in the //! scope.
2b5f472001-02-11Martin Stjernholm  //! //! If the @[type] argument is given, it's the type the returned //! value should have, unless it's an object which implements //! @[Value.rxml_var_eval]. If the value can't be converted to that //! type, an RXML error should be thrown. If you don't want to do //! any special handling of this, it's enough to call //! @tt{@[type]->encode(value)@}, since the encode functions does //! just that. See @[Value.rxml_var_eval] for more discussion about //! this. //!
151fa92001-04-19Johan Sundström  //! @note //! The @[type] argument being @tt{void|Type@} means that the caller //! is free to leave out that argument, not that the function
2b5f472001-02-11Martin Stjernholm  //! implementor is free to ignore it.
5ed0132000-02-13Martin Stjernholm  {parse_error ("Cannot query variable" + _in_the_scope (scope_name) + ".\n");}
01e43b2000-01-14Martin Stjernholm  mixed `[]= (string var, mixed val, void|Context ctx, void|string scope_name)
2b5f472001-02-11Martin Stjernholm  //! Called to set the value of a variable in the scope. @[var] is //! the name of it, @[ctx] and @[scope_name] are set to where this //! @[Scope] object was found. //! //! An RXML error may be thrown if the value is not acceptable for //! the variable. It's undefined what happens if a variable is set
0c9e5b2001-03-13Martin Stjernholm  //! to @[RXML.nil]; it should be avoided.
5ed0132000-02-13Martin Stjernholm  {parse_error ("Cannot set variable" + _in_the_scope (scope_name) + ".\n");}
01e43b2000-01-14Martin Stjernholm  array(string) _indices (void|Context ctx, void|string scope_name)
2b5f472001-02-11Martin Stjernholm  //! Called to get a list of all defined variables in the scope. //! @[ctx] and @[scope_name] are set to where this @[Scope] object //! was found. //! //! There's no guarantee that the returned variable names produce a
0c9e5b2001-03-13Martin Stjernholm  //! value (i.e. neither @[RXML.nil] nor the undefined value) when
2b5f472001-02-11Martin Stjernholm  //! indexed.
5ed0132000-02-13Martin Stjernholm  {parse_error ("Cannot list variables" + _in_the_scope (scope_name) + ".\n");}
01e43b2000-01-14Martin Stjernholm 
2b5f472001-02-11Martin Stjernholm  void _m_delete (string var, void|Context ctx, void|string scope_name) //! Called to delete a variable in the scope. @[var] is the name of //! it, @[ctx] and @[scope_name] are set to where this @[Scope] //! object was found. { if (m_delete != local::m_delete) m_delete (var, ctx, scope_name); // For compatibility with 2.1. else parse_error ("Cannot delete variable" + _in_the_scope (scope_name) + ".\n"); }
01e43b2000-01-14Martin Stjernholm  void m_delete (string var, void|Context ctx, void|string scope_name)
2b5f472001-02-11Martin Stjernholm  // For compatibility with 2.1. {_m_delete (var, ctx, scope_name);}
01e43b2000-01-14Martin Stjernholm  private string _in_the_scope (string scope_name) { if (scope_name)
3e59342000-01-18Martin Stjernholm  if (scope_name != "_") return " in the scope " + scope_name;
01e43b2000-01-14Martin Stjernholm  else return " in the current scope"; else return ""; }
26ff092000-01-21Martin Stjernholm  string _sprintf() {return "RXML.Scope";}
af06d52000-01-12Martin Stjernholm }
ed81751999-12-11Martin Stjernholm class Context //! A parser context. This contains the current variable bindings and //! so on. The current context can always be retrieved with
4293072001-05-08Martin Stjernholm //! @[RXML.get_context].
ed81751999-12-11Martin Stjernholm //!
151fa92001-04-19Johan Sundström //! @note //! Don't store pointers to this object since that will likely
ed81751999-12-11Martin Stjernholm //! introduce circular references. It can be retrieved easily through
4293072001-05-08Martin Stjernholm //! @[RXML.get_context].
ed81751999-12-11Martin Stjernholm { Frame frame; //! The currently evaluating frame.
ffa6932000-02-20Martin Stjernholm  int frame_depth; //! Number of frames currently on the frame stack. int max_frame_depth = 100; //! Maximum number of frames allowed on the frame stack.
ed81751999-12-11Martin Stjernholm  RequestID id; //! int type_check; //! Whether to do type checking.
db04172000-01-14Martin Stjernholm  int error_count; //! Number of RXML errors that has occurred.
01e43b2000-01-14Martin Stjernholm 
1b2b752000-01-07Martin Stjernholm  TagSet tag_set;
ed81751999-12-11Martin Stjernholm  //! The current tag set that will be inherited by subparsers.
c8021c2000-02-15Martin Nilsson #ifdef OLD_RXML_COMPAT int compatible_scope = 0;
4293072001-05-08Martin Stjernholm  //! If set, the @tt{user_*_var@} functions access the variables in //! the scope "form" by default, and there's no subindex splitting //! or ".." decoding is done (see @[parse_user_var]). //!
151fa92001-04-19Johan Sundström  //! @note
4293072001-05-08Martin Stjernholm  //! This is only present when the @tt{OLD_RXML_COMPAT@} define is //! set. #endif #ifdef PROFILE_PARSER mapping(string:int) profile = ([]);
1cfbeb2000-11-06Martin Stjernholm #endif
8abef52001-02-11Martin Stjernholm  array(string|int) parse_user_var (string var, void|string|int scope_name)
1cfbeb2000-11-06Martin Stjernholm  //! Parses the var string for scope and/or subindexes according to
4293072001-05-08Martin Stjernholm  //! the RXML rules, e.g. @tt{"scope.var.1.foo"@}. Returns an array //! where the first entry is the scope, and the remaining entries //! are the list of indexes. If @[scope_name] is a string, it's used //! as the scope and the var string is only used for subindexes. A //! default scope is chosen as appropriate if var cannot be split, //! unless @[scope_name] is a nonzero integer in which case it's //! returned in the scope position in the array (useful to detect //! whether @[var] actually was splitted or not).
1cfbeb2000-11-06Martin Stjernholm  //!
4293072001-05-08Martin Stjernholm  //! @tt{".."@} in the var string quotes a literal @tt{"."@}, e.g. //! @tt{"yow...cons..yet"@} is separated into @tt{"yow."@} and //! @tt{"cons.yet"@}. Any subindex that can be parsed as a signed //! integer is converted to it. Note that it doesn't happen for the //! first index, since a variable in a scope always is a string.
9390fe2000-01-23Martin Nilsson  {
c8021c2000-02-15Martin Nilsson #ifdef OLD_RXML_COMPAT
2cab1b2001-03-05Martin Nilsson  if (compatible_scope && !intp(scope_name))
1cfbeb2000-11-06Martin Stjernholm  return ({scope_name || "form", var});
c8021c2000-02-15Martin Nilsson #endif
1cfbeb2000-11-06Martin Stjernholm 
1766fe2001-02-11Martin Nilsson  array(string|int) splitted;
0aa54b2001-02-11Martin Stjernholm  if(has_value(var, "..")) {
6ec8cc2001-02-11Martin Stjernholm  // The \0 stuff is really here for a reason: The _only_ special // character is '.'.
0aa54b2001-02-11Martin Stjernholm  string coded = replace (var, "\0", "\0\0"); if (coded != var) splitted = map (replace (coded, "..", "\0p") / ".", replace, ({"\0p", "\0\0"}), ({".", "\0"})); else splitted = map (replace (var, "..", "\0") / ".", replace, "\0", "."); }
1766fe2001-02-11Martin Nilsson  else splitted = var / ".";
8abef52001-02-11Martin Stjernholm  if (stringp (scope_name))
1cfbeb2000-11-06Martin Stjernholm  splitted = ({scope_name}) + splitted;
8abef52001-02-11Martin Stjernholm  else if (sizeof (splitted) == 1) splitted = ({scope_name || "_"}) + splitted;
1cfbeb2000-11-06Martin Stjernholm  for (int i = 2; i < sizeof (splitted); i++) if (sscanf (splitted[i], "%d%*c", int d) == 1) splitted[i] = d; return splitted;
9390fe2000-01-23Martin Nilsson  }
1cfbeb2000-11-06Martin Stjernholm  local mixed get_var (string|array(string|int) var, void|string scope_name, void|Type want_type)
4293072001-05-08Martin Stjernholm  //! Returns the value of the given variable in the specified scope, //! or the current scope if none is given. Returns undefined (zero //! with zero type 1) if there's no such variable (or it's //! @[RXML.nil]).
8778dd2000-02-05Martin Stjernholm  //!
4293072001-05-08Martin Stjernholm  //! If @[var] is an array, it's used to successively index the value //! to get subvalues (see @[rxml_index] for details).
1cfbeb2000-11-06Martin Stjernholm  //!
4293072001-05-08Martin Stjernholm  //! If the @[want_type] argument is set, the result value is //! converted to that type with @[Type.encode]. If the value can't //! be converted, an RXML error is thrown.
1cfbeb2000-11-06Martin Stjernholm  { #ifdef MODULE_DEBUG if (arrayp (var) ? !sizeof (var) : !stringp (var)) error ("Invalid variable specifier.\n");
0ddf4e2000-09-15Martin Nilsson #endif
1cfbeb2000-11-06Martin Stjernholm  if (!scope_name) scope_name = "_"; if (SCOPE_TYPE vars = scopes[scope_name]) return rxml_index (vars, var, scope_name, this_object(), want_type); else if (scope_name == "_") parse_error ("No current scope.\n"); else parse_error ("Unknown scope %O.\n", scope_name);
ed81751999-12-11Martin Stjernholm  }
8778dd2000-02-05Martin Stjernholm  mixed user_get_var (string var, void|string scope_name, void|Type want_type)
4293072001-05-08Martin Stjernholm  //! As @[get_var], but parses the var string for scope and/or //! subindexes, e.g. @tt{"scope.var.1.foo"@} (see @[parse_user_var] //! for details).
9390fe2000-01-23Martin Nilsson  { if(!var || !sizeof(var)) return ([])[0];
1cfbeb2000-11-06Martin Stjernholm  array(string|int) splitted = parse_user_var (var, scope_name); return get_var (splitted[1..], splitted[0], want_type);
9390fe2000-01-23Martin Nilsson  }
1cfbeb2000-11-06Martin Stjernholm  local mixed set_var (string|array(string|int) var, mixed val, void|string scope_name)
ed81751999-12-11Martin Stjernholm  //! Sets the value of a variable in the specified scope, or the
4293072001-05-08Martin Stjernholm  //! current scope if none is given. Returns @[val].
1cfbeb2000-11-06Martin Stjernholm  //!
4293072001-05-08Martin Stjernholm  //! If @[var] is an array, it's used to successively index the value //! to get subvalues (see @[rxml_index] for details).
ed81751999-12-11Martin Stjernholm  {
1cfbeb2000-11-06Martin Stjernholm #ifdef MODULE_DEBUG if (arrayp (var) ? !sizeof (var) : !stringp (var)) error ("Invalid variable specifier.\n"); #endif if (!scope_name) scope_name = "_"; if (SCOPE_TYPE vars = scopes[scope_name]) { string|int index; if (arrayp (var)) if (sizeof (var) > 1) { index = var[-1]; var = var[..sizeof (var) - 1]; vars = rxml_index (vars, var, scope_name, this_object()); scope_name += "." + (array(string)) var * "."; } else index = var[0]; else index = var; if (objectp (vars) && vars->`[]=) return ([object(Scope)] vars)->`[]= (index, val, this_object(), scope_name); else if (mappingp (vars) || multisetp (vars)) return vars[index] = val; else if (arrayp (vars)) if (intp (index) && index) if ((index < 0 ? -index : index) > sizeof (vars)) parse_error( "Index %d out of range for array of size %d in %s.\n", index, sizeof (val), scope_name ); else if (index < 0) return vars[index] = val; else return vars[index - 1] = val; else parse_error( "Cannot index the array in %s with %O.\n", scope_name, index );
01e43b2000-01-14Martin Stjernholm  else
1cfbeb2000-11-06Martin Stjernholm  parse_error ("%s is %O which cannot be indexed with %O.\n", scope_name, vars, index); } else if (scope_name == "_") parse_error ("No current scope.\n"); else parse_error ("Unknown scope %O.\n", scope_name);
ed81751999-12-11Martin Stjernholm  }
9390fe2000-01-23Martin Nilsson  mixed user_set_var (string var, mixed val, void|string scope_name)
4293072001-05-08Martin Stjernholm  //! As @[set_var], but parses the var string for scope and/or //! subindexes, e.g. @tt{"scope.var.1.foo"@} (see @[parse_user_var] //! for details).
9390fe2000-01-23Martin Nilsson  {
c8021c2000-02-15Martin Nilsson  if(!var || !sizeof(var)) parse_error ("No variable specified.\n");
1cfbeb2000-11-06Martin Stjernholm  array(string|int) splitted = parse_user_var (var, scope_name); return set_var(splitted[1..], val, splitted[0]);
9390fe2000-01-23Martin Nilsson  }
1cfbeb2000-11-06Martin Stjernholm  local void delete_var (string|array(string|int) var, void|string scope_name)
ed81751999-12-11Martin Stjernholm  //! Removes a variable in the specified scope, or the current scope //! if none is given.
1cfbeb2000-11-06Martin Stjernholm  //!
4293072001-05-08Martin Stjernholm  //! If @[var] is an array, it's used to successively index the value //! to get subvalues (see @[rxml_index] for details).
ed81751999-12-11Martin Stjernholm  {
1cfbeb2000-11-06Martin Stjernholm #ifdef MODULE_DEBUG if (arrayp (var) ? !sizeof (var) : !stringp (var)) error ("Invalid variable specifier.\n"); #endif if (!scope_name) scope_name = "_"; if (SCOPE_TYPE vars = scopes[scope_name]) { if (arrayp (var)) if (sizeof (var) > 1) { string|int last = var[-1]; var = var[..sizeof (var) - 1]; vars = rxml_index (vars, var, scope_name, this_object()); scope_name += "." + (array(string)) var * "."; var = last; } else var = var[0];
2b5f472001-02-11Martin Stjernholm  if (objectp (vars) && vars->_m_delete) ([object(Scope)] vars)->_m_delete (var, this_object(), scope_name);
1cfbeb2000-11-06Martin Stjernholm  else if (mappingp (vars))
01e43b2000-01-14Martin Stjernholm  m_delete ([mapping(string:mixed)] vars, var);
1cfbeb2000-11-06Martin Stjernholm  else if (multisetp (vars)) vars[var] = 0; else parse_error ("Cannot remove the index %O from the %t in %s.\n", var, vars, scope_name); } else if (scope_name == "_") parse_error ("No current scope.\n");
9290ad2000-02-20Martin Stjernholm  else parse_error ("Unknown scope %O.\n", scope_name);
ed81751999-12-11Martin Stjernholm  }
291ade2000-01-25Martin Stjernholm  void user_delete_var (string var, void|string scope_name)
4293072001-05-08Martin Stjernholm  //! As @[delete_var], but parses the var string for scope and/or //! subindexes, e.g. @tt{"scope.var.1.foo"@} (see @[parse_user_var] //! for details).
9390fe2000-01-23Martin Nilsson  {
291ade2000-01-25Martin Stjernholm  if(!var || !sizeof(var)) return;
1cfbeb2000-11-06Martin Stjernholm  array(string|int) splitted = parse_user_var (var, scope_name); delete_var(splitted[1..], splitted[0]);
9390fe2000-01-23Martin Nilsson  }
ed81751999-12-11Martin Stjernholm  array(string) list_var (void|string scope_name) //! Returns the names of all variables in the specified scope, or //! the current scope if none is given. {
3e59342000-01-18Martin Stjernholm  if (SCOPE_TYPE vars = scopes[scope_name || "_"])
01e43b2000-01-14Martin Stjernholm  if (objectp (vars))
3e59342000-01-18Martin Stjernholm  return ([object(Scope)] vars)->_indices (this_object(), scope_name || "_");
01e43b2000-01-14Martin Stjernholm  else return indices ([mapping(string:mixed)] vars);
9290ad2000-02-20Martin Stjernholm  else if ((<0, "_">)[scope_name]) parse_error ("No current scope.\n"); else parse_error ("Unknown scope %O.\n", scope_name);
ed81751999-12-11Martin Stjernholm  } array(string) list_scopes() //! Returns the names of all defined scopes. {
3e59342000-01-18Martin Stjernholm  return indices (scopes) - ({"_"});
ed81751999-12-11Martin Stjernholm  }
af06d52000-01-12Martin Stjernholm  int exist_scope (void|string scope_name) //! {
3e59342000-01-18Martin Stjernholm  return !!scopes[scope_name || "_"];
af06d52000-01-12Martin Stjernholm  } void add_scope (string scope_name, SCOPE_TYPE vars) //! Adds or replaces the specified scope at the global level. A
4293072001-05-08Martin Stjernholm  //! scope can be a mapping or an @[RXML.Scope] object. A global //! @tt{"_"@} scope may also be defined this way.
ed81751999-12-11Martin Stjernholm  { if (scopes[scope_name])
3e59342000-01-18Martin Stjernholm  if (scope_name == "_") {
291ade2000-01-25Martin Stjernholm  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;
ed81751999-12-11Martin Stjernholm  } else { Frame outermost; for (Frame f = frame; f; f = f->up) if (f->scope_name == scope_name) outermost = f;
291ade2000-01-25Martin Stjernholm  if (outermost) hidden[outermost][1] = vars;
ed81751999-12-11Martin Stjernholm  else scopes[scope_name] = vars; } else scopes[scope_name] = vars; }
290c802000-01-25Martin Nilsson  int extend_scope (string scope_name, SCOPE_TYPE vars)
291ade2000-01-25Martin Stjernholm  //! Adds or extends the specified scope at the global level.
290c802000-01-25Martin Nilsson  //! Returns 1 on success and 0 on failure.
0273b22000-01-21Martin Nilsson  {
291ade2000-01-25Martin Stjernholm  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
8cb24d2000-02-13Martin Stjernholm  if (!oldvars) fatal_error ("Internal error: I before e except after c.\n");
291ade2000-01-25Martin Stjernholm #endif if (!mappingp(vars)) { return 0; } foreach (indices(vars), string var) set_var(var, vars[var], scope_name);
f6020c2000-01-23Martin Nilsson  }
291ade2000-01-25Martin Stjernholm  else scopes[scope_name] = vars;
290c802000-01-25Martin Nilsson  return 1;
0273b22000-01-21Martin Nilsson  }
ed81751999-12-11Martin Stjernholm  void remove_scope (string scope_name) //! Removes the named scope from the global level, if it exists. { #ifdef MODULE_DEBUG
8cb24d2000-02-13Martin Stjernholm  if (scope_name == "_") fatal_error ("Cannot remove current scope.\n");
ed81751999-12-11Martin Stjernholm #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() //! Returns the name of the current scope, if it has any. {
3e59342000-01-18Martin Stjernholm  if (SCOPE_TYPE vars = scopes["_"]) {
ed81751999-12-11Martin Stjernholm  string scope_name; while (scope_name = search (scopes, vars, scope_name))
3e59342000-01-18Martin Stjernholm  if (scope_name != "_") return scope_name;
ed81751999-12-11Martin Stjernholm  } return 0; }
af06d52000-01-12Martin Stjernholm  void add_runtime_tag (Tag tag) //! Adds a tag that will exist from this point forward in the
0ebc9d2000-02-15Martin Stjernholm  //! current context only.
af06d52000-01-12Martin Stjernholm  {
24f9e82000-08-05Martin Stjernholm #ifdef MODULE_DEBUG if (tag->plugin_name) fatal_error ("Can't currently handle adding of plugin tags at runtime.\n"); #endif
0ebc9d2000-02-15Martin Stjernholm  if (!new_runtime_tags) new_runtime_tags = NewRuntimeTags();
24f9e82000-08-05Martin Stjernholm  new_runtime_tags->add_tag (tag);
af06d52000-01-12Martin Stjernholm  }
24f9e82000-08-05Martin Stjernholm  void remove_runtime_tag (string|Tag tag, void|int proc_instr)
4293072001-05-08Martin Stjernholm  //! If @[tag] is an @[RXML.Tag] object, it's removed from the set of //! runtime tags. If @[tag] is a string, the tag with that name is //! removed. In the latter case, if @[proc_instr] is nonzero the set //! of runtime PI tags is searched, else the set of normal element //! runtime tags.
af06d52000-01-12Martin Stjernholm  {
0ebc9d2000-02-15Martin Stjernholm  if (!new_runtime_tags) new_runtime_tags = NewRuntimeTags();
87fb7e2000-03-25Martin Stjernholm  if (objectp (tag)) tag = tag->name;
24f9e82000-08-05Martin Stjernholm  new_runtime_tags->remove_tag (tag);
af06d52000-01-12Martin Stjernholm  }
18abb32000-02-15Martin Stjernholm  multiset(Tag) get_runtime_tags()
24f9e82000-08-05Martin Stjernholm  //! Returns all currently active runtime tags.
0ebc9d2000-02-15Martin Stjernholm  {
87fb7e2000-03-25Martin Stjernholm  mapping(string:Tag) tags = runtime_tags;
24f9e82000-08-05Martin Stjernholm  if (new_runtime_tags) tags = new_runtime_tags->filter_tags (tags);
87fb7e2000-03-25Martin Stjernholm  return mkmultiset (values (tags));
0ebc9d2000-02-15Martin Stjernholm  }
01e43b2000-01-14Martin Stjernholm  void handle_exception (mixed err, PCode|Parser evaluator) //! This function gets any exception that is catched during //! evaluation. evaluator is the object that catched the error.
c757c42000-01-08Martin Stjernholm  {
db04172000-01-14Martin Stjernholm  error_count++;
c64acf2000-02-17Martin Stjernholm  if (objectp (err) && err->is_RXML_Backtrace) {
140f362000-10-19Martin Stjernholm  evaluator->error_count++; if (evaluator->report_error && evaluator->recover_errors && evaluator->type->free_text) { string msg;
f5bd9e2001-04-25Martin Nilsson  if (id && id->conf) {
140f362000-10-19Martin Stjernholm  msg = err->type == "help" ? err->msg : (err->type == "run" ? ([function(Backtrace,Type:string)]
3083572001-04-24Martin Nilsson  tag_set->handle_run_error) :
140f362000-10-19Martin Stjernholm  ([function(Backtrace,Type:string)]
3083572001-04-24Martin Nilsson  tag_set->handle_parse_error)
140f362000-10-19Martin Stjernholm  ) ([object(Backtrace)] err, evaluator->type);
f5bd9e2001-04-25Martin Nilsson  if(!msg) msg = describe_error(err); }
140f362000-10-19Martin Stjernholm  else msg = err->msg; if (evaluator->report_error (msg)) return;
01e43b2000-01-14Martin Stjernholm  }
140f362000-10-19Martin Stjernholm  throw (err);
c757c42000-01-08Martin Stjernholm  }
8cb24d2000-02-13Martin Stjernholm  else throw_fatal (err);
c757c42000-01-08Martin Stjernholm  }
151fa92001-04-19Johan Sundström  //(!) Internals:
ed81751999-12-11Martin Stjernholm 
db04172000-01-14Martin Stjernholm  string current_var; // Used to get the parsed variable into the RXML error backtrace.
c6245b1999-12-31Martin Stjernholm  Parser new_parser (Type top_level_type) // Returns a new parser object to start parsing with this context. // Normally TagSet.`() should be used instead of this. { #ifdef MODULE_DEBUG
8cb24d2000-02-13Martin Stjernholm  if (in_use || frame) fatal_error ("Context already in use.\n");
c6245b1999-12-31Martin Stjernholm #endif return top_level_type->get_parser (this_object()); }
af06d52000-01-12Martin Stjernholm  mapping(string:SCOPE_TYPE) scopes = ([]);
ed81751999-12-11Martin Stjernholm  // The variable mappings for every currently visible scope. A
3e59342000-01-18Martin Stjernholm  // special entry "_" points to the current local scope.
ed81751999-12-11Martin Stjernholm 
291ade2000-01-25Martin Stjernholm  mapping(Frame:array(SCOPE_TYPE)) hidden = ([]); // The currently hidden scopes. The indices are frame objects which // introduce scopes. The values are tuples of the current scope and // the named scope they hide.
ed81751999-12-11Martin Stjernholm  void enter_scope (Frame frame) { #ifdef DEBUG
8cb24d2000-02-13Martin Stjernholm  if (!frame->vars) fatal_error ("Internal error: Frame has no variables.\n");
ed81751999-12-11Martin Stjernholm #endif
b2d01b2000-02-15Martin Stjernholm  if (string scope_name = [string] frame->scope_name) { if (!hidden[frame])
291ade2000-01-25Martin Stjernholm  hidden[frame] = ({scopes["_"], scopes[scope_name]});
b2d01b2000-02-15Martin Stjernholm  scopes["_"] = scopes[scope_name] = [SCOPE_TYPE] frame->vars; } else { if (!hidden[frame])
291ade2000-01-25Martin Stjernholm  hidden[frame] = ({scopes["_"], 0});
b2d01b2000-02-15Martin Stjernholm  scopes["_"] = [SCOPE_TYPE] frame->vars; }
ed81751999-12-11Martin Stjernholm  } void leave_scope (Frame frame) {
291ade2000-01-25Martin Stjernholm  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))
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Scope named changed to %O during parsing.\n", frame->scope_name);
291ade2000-01-25Martin Stjernholm #endif scopes[[string] frame->scope_name] = named;
ed81751999-12-11Martin Stjernholm  }
291ade2000-01-25Martin Stjernholm  else m_delete (scopes, [string] frame->scope_name); m_delete (hidden, frame);
ed81751999-12-11Martin Stjernholm  } } #define ENTER_SCOPE(ctx, frame) (frame->vars && ctx->enter_scope (frame)) #define LEAVE_SCOPE(ctx, frame) (frame->vars && ctx->leave_scope (frame))
87fb7e2000-03-25Martin Stjernholm  mapping(string:Tag) runtime_tags = ([]);
24f9e82000-08-05Martin Stjernholm  // The active runtime tags. PI tags are stored in the same mapping // with their names prefixed by '?'.
0ebc9d2000-02-15Martin Stjernholm  NewRuntimeTags new_runtime_tags;
c6245b1999-12-31Martin Stjernholm  // Used to record the result of any add_runtime_tag() and // remove_runtime_tag() calls since the last time the parsers ran.
ed81751999-12-11Martin Stjernholm 
26ff092000-01-21Martin Stjernholm  void create (TagSet _tag_set, void|RequestID _id)
ed81751999-12-11Martin Stjernholm  // Normally TagSet.`() should be used instead of this. {
1b2b752000-01-07Martin Stjernholm  tag_set = _tag_set;
ed81751999-12-11Martin Stjernholm  id = _id;
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG __object_marker->create (this_object()); #endif
ed81751999-12-11Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  UNWIND_STATE unwind_state;
ed81751999-12-11Martin Stjernholm  // If this is a mapping, we have an unwound stack state. It contains
56532d1999-12-19Martin Stjernholm  // strings with arbitrary exception info, and the objects being // unwound with arrays containing the extra state info they need. // The first entry in these arrays are always the subobject. The // special entries are:
ed81751999-12-11Martin Stjernholm  //
56532d1999-12-19Martin Stjernholm  // "top": ({Frame|Parser|PCode (top object)})
ed81751999-12-11Martin Stjernholm  // "stream_piece": mixed (When continuing, do a streaming
91c8982000-03-11Martin Stjernholm  // do_process() with this stream piece.)
ed81751999-12-11Martin Stjernholm  // "exec_left": array (Exec array left to evaluate. Only used // between Frame._exec_array() and Frame._eval().)
f02d002000-03-18Martin Stjernholm  MARK_OBJECT_ONLY;
2bd21a2000-01-05Martin Stjernholm 
f02d002000-03-18Martin Stjernholm  string _sprintf() {return "RXML.Context" + OBJ_COUNT;}
ec027c2000-03-16Martin Stjernholm 
ed81751999-12-11Martin Stjernholm #ifdef MODULE_DEBUG
2d20342000-02-12Martin Stjernholm #if constant (thread_create) Thread.Thread in_use; #else
ed81751999-12-11Martin Stjernholm  int in_use; #endif
2d20342000-02-12Martin Stjernholm #endif
ed81751999-12-11Martin Stjernholm }
ec027c2000-03-16Martin Stjernholm static class NewRuntimeTags
24f9e82000-08-05Martin Stjernholm // Tool class used to track runtime tags in Context.
ec027c2000-03-16Martin Stjernholm {
24f9e82000-08-05Martin Stjernholm  static mapping(string:Tag) add_tags; static mapping(string:int|string) remove_tags; void add_tag (Tag tag) { if (!add_tags) add_tags = ([]); if (tag->flags & FLAG_PROC_INSTR) { add_tags["?" + tag->name] = tag; // By doing the following, we can let remove_proc_instrs take precedence. if (remove_tags) m_delete (remove_tags, "?" + tag->name); } else { add_tags[tag->name] = tag; if (remove_tags) m_delete (remove_tags, tag->name); } } void remove_tag (string name, int proc_instr) { if (!remove_tags) remove_tags = ([]); if (proc_instr) remove_tags["?" + name] = name; else remove_tags[name] = 1; } array(Tag) added_tags() { if (!add_tags) return ({}); if (remove_tags) return values (add_tags - remove_tags); return values (add_tags); } array(string) removed_tags() { return remove_tags ? indices (filter (remove_tags, intp)) : ({}); } array(string) removed_pi_tags() { return remove_tags ? values (remove_tags) - ({1}) : ({}); } mapping(string:Tag) filter_tags (mapping(string:Tag) tags) { if (add_tags) tags |= add_tags; if (remove_tags) tags -= remove_tags; return tags; }
ec027c2000-03-16Martin Stjernholm }
8cb24d2000-02-13Martin Stjernholm class Backtrace //! The object used to throw RXML errors. { constant is_generic_error = 1; constant is_RXML_Backtrace = 1;
0ebc9d2000-02-15Martin Stjernholm  string type; // Currently "run" or "parse".
8cb24d2000-02-13Martin Stjernholm  string msg; Context context; Frame frame; string current_var; array backtrace;
51f88b2000-06-23Martin Stjernholm  void create (void|string _type, void|string _msg, void|Context _context, void|array _backtrace)
8cb24d2000-02-13Martin Stjernholm  { type = _type; msg = _msg; if (context = _context || get_context()) { frame = context->frame; current_var = context->current_var; }
51f88b2000-06-23Martin Stjernholm  if (_backtrace) backtrace = _backtrace; else { backtrace = predef::backtrace(); backtrace = backtrace[..sizeof (backtrace) - 2]; }
8cb24d2000-02-13Martin Stjernholm  } string describe_rxml_backtrace (void|int no_msg) //! Returns a formatted RXML frame backtrace. {
1c73f62000-03-04Martin Stjernholm  string txt = no_msg ? "" : "RXML" + (type ? " " + type : "") + " error";
8cb24d2000-02-13Martin Stjernholm  if (context) { if (!no_msg) txt += ": " + (msg || "(no error message)\n"); txt += current_var ? " | &" + current_var + ";\n" : ""; for (Frame f = frame; f; f = f->up) {
24f9e82000-08-05Martin Stjernholm  string name; if (f->tag) name = f->tag->name;
8cb24d2000-02-13Martin Stjernholm  else if (!f->up) break;
24f9e82000-08-05Martin Stjernholm  else name = "(unknown)"; if (f->flags & FLAG_PROC_INSTR) txt += " | <?" + name + "?>\n"; else { txt += " | <" + name; if (f->args) foreach (sort (indices (f->args)), string arg) { mixed val = f->args[arg]; txt += " " + arg + "="; if (arrayp (val)) txt += map (val, error_print_val) * ","; else txt += error_print_val (val); } else txt += " (no argmap)"; txt += ">\n"; }
8cb24d2000-02-13Martin Stjernholm  } } else if (!no_msg) txt += " (no context): " + (msg || "(no error message)\n"); return txt; } 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|array `[] (int i) { switch (i) { case 0: return describe_rxml_backtrace(); case 1: return backtrace; } }
1c73f62000-03-04Martin Stjernholm  string _sprintf() {return "RXML.Backtrace(" + (type || "") + ")";}
8cb24d2000-02-13Martin Stjernholm }
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström //(!) Current context:
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström //(!) It's set before any function in RXML.Tag or RXML.Frame is called.
ed81751999-12-11Martin Stjernholm  #if constant (thread_create)
1cfbeb2000-11-06Martin Stjernholm private Thread.Local _context = Thread.Local();
5ad5292000-03-09Martin Stjernholm local void set_context (Context ctx) {_context->set (ctx);} local Context get_context() {return [object(Context)] _context->get();}
ed81751999-12-11Martin Stjernholm #else private Context _context;
5ad5292000-03-09Martin Stjernholm local void set_context (Context ctx) {_context = ctx;} local Context get_context() {return _context;}
ed81751999-12-11Martin Stjernholm #endif
2d20342000-02-12Martin Stjernholm #if defined (MODULE_DEBUG) && constant (thread_create)
ed81751999-12-11Martin Stjernholm  // Got races in this debug check, but looks like we have to live with that. :\ #define ENTER_CONTEXT(ctx) \ Context __old_ctx = get_context(); \ set_context (ctx); \ if (ctx) { \
2d20342000-02-12Martin Stjernholm  if (ctx->in_use && ctx->in_use != this_thread()) \
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Attempt to use context asynchronously.\n"); \
2d20342000-02-12Martin Stjernholm  ctx->in_use = this_thread(); \
4293072001-05-08Martin Stjernholm  } \ PROFILE_ENTER (ctx, "rxml internal");
ed81751999-12-11Martin Stjernholm  #define LEAVE_CONTEXT() \
4293072001-05-08Martin Stjernholm  PROFILE_LEAVE (get_context(), "rxml internal"); \
ed81751999-12-11Martin Stjernholm  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(); \
4293072001-05-08Martin Stjernholm  set_context (ctx); \ PROFILE_ENTER (ctx, "rxml internal");
ed81751999-12-11Martin Stjernholm  #define LEAVE_CONTEXT() \
4293072001-05-08Martin Stjernholm  PROFILE_LEAVE (get_context(), "rxml internal"); \
ed81751999-12-11Martin Stjernholm  set_context (__old_ctx); #endif
76cbfb2000-02-04Martin Stjernholm 
151fa92001-04-19Johan Sundström //(!) Constants for the bit field RXML.Frame.flags.
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_NONE = 0x00000000;
c159ec2000-02-04Martin Stjernholm //! The no-flags flag. In case you think 0 is too ugly. ;)
ac69772000-09-08Martin Stjernholm constant FLAG_DEBUG = 0x40000000; //! Write a lot of debug during the execution of the tag, showing what //! type conversions are done, what callbacks are being called etc.
4293072001-05-08Martin Stjernholm //! Note that @tt{DEBUG@} must be defined for the debug printouts to //! be compiled in (normally enabled with the @tt{--debug@} flag to //! Roxen).
ac69772000-09-08Martin Stjernholm 
151fa92001-04-19Johan Sundström //(!) Static flags (i.e. tested in the Tag object):
ed81751999-12-11Martin Stjernholm 
24f9e82000-08-05Martin Stjernholm constant FLAG_PROC_INSTR = 0x00000010; //! Flags this as a processing instruction tag (i.e. one parsed with
4293072001-05-08Martin Stjernholm //! the @tt{<?name ... ?>@} syntax in XML). The string after the tag //! name to the ending separator constitutes the content of the tag. //! Arguments are not used.
ed81751999-12-11Martin Stjernholm 
51f88b2000-06-23Martin Stjernholm constant FLAG_COMPAT_PARSE = 0x00000002;
4293072001-05-08Martin Stjernholm //! Makes the @[RXML.PXml] parser parse the tag in an HTML compatible //! way: If @[FLAG_EMPTY_ELEMENT] is set and the tag doesn't end with //! @tt{"/>"@}, it will be parsed as an empty element. The effect of //! this flag in other parsers is currently undefined.
51f88b2000-06-23Martin Stjernholm  constant FLAG_NO_PREFIX = 0x00000004;
cece9e2000-01-11Martin Stjernholm //! Never apply any prefix to this tag.
51f88b2000-06-23Martin Stjernholm constant FLAG_SOCKET_TAG = 0x00000008;
c159ec2000-02-04Martin Stjernholm //! Declare the tag to be a socket tag, which accepts plugin tags (see
4293072001-05-08Martin Stjernholm //! @[RXML.Tag.plugin_name] for details).
c159ec2000-02-04Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_DONT_PREPARSE = 0x00000040;
4293072001-05-08Martin Stjernholm //! Don't preparse the content with the @[RXML.PXml] parser. This is //! always the case for PI tags, so this flag doesn't have any effect //! for those. This is only used in the simple tag wrapper. Defined //! here as placeholder.
c159ec2000-02-04Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_POSTPARSE = 0x00000080;
4293072001-05-08Martin Stjernholm //! Postparse the result with the @[RXML.PXml] parser. This is only //! used in the simple tag wrapper. Defined here as placeholder.
c159ec2000-02-04Martin Stjernholm 
151fa92001-04-19Johan Sundström //(!) The rest of the flags are dynamic (i.e. tested in the Frame object):
ed81751999-12-11Martin Stjernholm 
4293072001-05-08Martin Stjernholm constant FLAG_EMPTY_ELEMENT = 0x00000001; //! If set, the tag does not use any content. E.g. with an HTML parser //! this defines whether the tag is a container or not, and in XML //! parsing the parser will signal an error if the tag have anything //! but "" as content. Should not be changed after //! @[RXML.Frame.do_enter] has returned.
8cb24d2000-02-13Martin Stjernholm constant FLAG_PARENT_SCOPE = 0x00000100;
91c8982000-03-11Martin Stjernholm //! If set, exec arrays will be interpreted in the scope of the parent //! tag, rather than in the current one.
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_NO_IMPLICIT_ARGS = 0x00000200;
ed81751999-12-11Martin Stjernholm //! If set, the parser won't apply any implicit arguments. FIXME: Not //! yet implemented.
8cb24d2000-02-13Martin Stjernholm constant FLAG_STREAM_RESULT = 0x00000400;
4293072001-05-08Martin Stjernholm //! If set, the @[do_process] function will be called repeatedly until
ed81751999-12-11Martin Stjernholm //! it returns 0 or no more content is wanted.
8cb24d2000-02-13Martin Stjernholm constant FLAG_STREAM_CONTENT = 0x00000800;
ed81751999-12-11Martin Stjernholm //! If set, the tag supports getting its content in streaming mode:
4293072001-05-08Martin Stjernholm //! @[do_process] will be called repeatedly with successive parts of //! the content then. Can't be changed from @[do_process]. //!
151fa92001-04-19Johan Sundström //! @note //! It might be obvious, but using streaming is significantly less //! effective than nonstreaming, so it should only be done when big //! delays are expected.
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_STREAM = FLAG_STREAM_RESULT | FLAG_STREAM_CONTENT;
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_UNPARSED = 0x00001000;
4293072001-05-08Martin Stjernholm //! If set, @[RXML.Frame.args] and @[RXML.Frame.content] contain //! unparsed strings. The frame will be parsed before it's evaluated. //! This flag should never be set in @[RXML.Tag.flags], but it's //! useful when creating frames directly (see @[make_unparsed_tag]).
49897a2000-02-13Martin Stjernholm 
140f362000-10-19Martin Stjernholm constant FLAG_DONT_RECOVER = 0x00002000; //! If set, RXML errors are never recovered when parsing the content //! in the tag. If any occurs, it will instead abort the execution of //! this tag too to propagate the error to the parent tag.
4d62ba2000-08-15Martin Stjernholm //!
140f362000-10-19Martin Stjernholm //! When an error occurs, the parser aborts tags upward in the frame
4d62ba2000-08-15Martin Stjernholm //! stack until it comes to one which looks like it can accept an
140f362000-10-19Martin Stjernholm //! error report in its content. The parser then reports the error //! there and continues.
4d62ba2000-08-15Martin Stjernholm //!
140f362000-10-19Martin Stjernholm //! The criteria for the frame which will handle the error recovery is
4293072001-05-08Martin Stjernholm //! that its content type has the @[RXML.Type.free_text] property, and //! that the parser that parses it has an @[RXML.Parser.report_error] //! function (which e.g. @[RXML.PXml] has). With this flag, a frame //! can declare that it isn't suitable to receive error reports even //! if it satisfies this.
140f362000-10-19Martin Stjernholm  constant FLAG_DONT_REPORT_ERRORS = FLAG_DONT_RECOVER; // For compatibility.
4d62ba2000-08-15Martin Stjernholm  constant FLAG_RAW_ARGS = 0x00004000;
0c9e5b2001-03-13Martin Stjernholm //! Special flag to @[RXML.t_xml.format_tag]; only defined here as a //! placeholder. When this is given to @[RXML.t_xml.format_tag], it //! only encodes the argument quote character with the "Roxen //! encoding" when writing argument values, instead of encoding with //! entity references. It's intended for reformatting a tag which has
4293072001-05-08Martin Stjernholm //! been parsed by @[Parser.HTML] (or @[parse_html]) but hasn't been
9a38062000-08-04Martin Stjernholm //! processed further.
151fa92001-04-19Johan Sundström //(!) The following flags specifies whether certain conditions must be //(!) met for a cached frame to be considered (if RXML.Frame.is_valid() //(!) is defined). They may be read directly after do_return() returns. //(!) The tag name is always the same. FIXME: These are ideas only; //(!) nothing is currently implemented and they might change //(!) arbitrarily.
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_CACHE_DIFF_ARGS = 0x00010000;
ed81751999-12-11Martin Stjernholm //! If set, the arguments to the tag need not be the same (using
4293072001-05-08Martin Stjernholm //! @[equal]) as the cached args.
ed81751999-12-11Martin Stjernholm  constant FLAG_CACHE_DIFF_CONTENT = 0x00020000; //! If set, the content need not be the same. constant FLAG_CACHE_DIFF_RESULT_TYPE = 0x00040000; //! If set, the result type need not be the same. (Typically //! not useful unless cached_return() is used.)
8cb24d2000-02-13Martin Stjernholm constant FLAG_CACHE_DIFF_VARS = 0x00080000;
ed81751999-12-11Martin Stjernholm //! If set, the variables with external scope in vars (i.e. normally //! those that has been accessed with get_var()) need not have the //! same values (using equal()) as the actual variables.
46c68f2000-08-12Martin Stjernholm constant FLAG_CACHE_DIFF_TAG_INSTANCE = 0x00100000; //! If set, the tag in the source document needs to be the same, so //! the same frame may be used when the tag occurs in another context.
ed81751999-12-11Martin Stjernholm  constant FLAG_CACHE_EXECUTE_RESULT = 0x00200000;
91c8982000-03-11Martin Stjernholm //! If set, an exec array will be stored in the frame instead of the //! final result. On a cache hit it'll be executed to produce the //! result.
ed81751999-12-11Martin Stjernholm  class Frame
46c68f2000-08-12Martin Stjernholm //! A tag instance. A new frame is normally created for every parsed //! tag in the source document. It might be reused both when the //! document is requested again and when the tag is reevaluated in a //! loop, but it's not certain in either case. Therefore, be careful //! about using variable initializers.
ed81751999-12-11Martin Stjernholm { constant is_RXML_Frame = 1;
56532d1999-12-19Martin Stjernholm  constant thrown_at_unwind = 1;
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //(!) Interface:
ed81751999-12-11Martin Stjernholm  Frame up; //! The parent frame. This frame is either created from the content
91c8982000-03-11Martin Stjernholm  //! inside the up frame, or it's in an exec array produced by the up //! frame.
ed81751999-12-11Martin Stjernholm  Tag tag; //! The RXML.Tag object this frame was created from. int flags; //! Various bit flags that affect parsing. See the FLAG_* constants.
46c68f2000-08-12Martin Stjernholm  //! It's copied from Tag.flag when the frame is created.
ed81751999-12-11Martin Stjernholm  mapping(string:mixed) args;
46c68f2000-08-12Martin Stjernholm  //! The (parsed and evaluated) arguments passed to the tag. Set //! every time the frame is executed, before any frame callbacks are //! called. Not set for processing instruction (FLAG_PROC_INSTR)
24f9e82000-08-05Martin Stjernholm  //! tags.
ed81751999-12-11Martin Stjernholm  Type content_type; //! The type of the content.
0f998b2000-08-15Martin Stjernholm  mixed content = nil;
91c8982000-03-11Martin Stjernholm  //! The content, if any. Set before do_process() and do_return() are
46c68f2000-08-12Martin Stjernholm  //! called. Initialized to RXML.nil every time the frame executed.
ed81751999-12-11Martin Stjernholm  Type result_type;
46c68f2000-08-12Martin Stjernholm  //! The required result type. If it has a parser, it will affect how //! execution arrays are handled; see the return value for //! do_return() for details. //! //! This is set by the type inference from Tag.result_types before //! any frame callbacks are called. The frame may change this type, //! but it must produce a result value which matches it. The value //! is converted before being inserted into the parent content if //! necessary. An exception (which this frame can't catch) is thrown //! if conversion is impossible.
0f998b2000-08-15Martin Stjernholm  mixed result = nil;
46c68f2000-08-12Martin Stjernholm  //! The result, which is assumed to be either RXML.nil or a valid //! value according to result_type. The exec arrays returned by e.g.
91c8982000-03-11Martin Stjernholm  //! do_return() changes this. It may also be set directly.
46c68f2000-08-12Martin Stjernholm  //! Initialized to RXML.nil every time the frame executed. //! //! If result_type has a parser set, it will be used by do_return() //! etc before assigning to this variable. Thus it contains the //! value after any parsing and will not be parsed again.
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl mapping(string:mixed) vars;
ed81751999-12-11Martin Stjernholm  //! Set this to introduce a new variable scope that will be active //! during parsing of the content and return values (but see also
146e342000-02-15Martin Stjernholm  //! FLAG_PARENT_SCOPE).
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl string scope_name;
291ade2000-01-25Martin Stjernholm  //! The scope name for the variables. Must be set before the scope //! is used for the first time, and can't be changed after that.
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl TagSet additional_tags;
ed81751999-12-11Martin Stjernholm  //! If set, the tags in this tag set will be used in addition to the //! tags inherited from the surrounding parser. The additional tags //! will in turn be inherited by subparsers.
151fa92001-04-19Johan Sundström  //! @decl TagSet local_tags;
ed81751999-12-11Martin Stjernholm  //! If set, the tags in this tag set will be used in the parser for //! the content, instead of the one inherited from the surrounding //! parser. The tags are not inherited by subparsers.
151fa92001-04-19Johan Sundström  //! @decl Frame parent_frame;
689dc62000-08-31Martin Stjernholm  //! If this variable exists, it gets set to the frame object of the //! closest surrounding tag that defined this tag in its //! additional_tags or local_tags. Useful to access the "mother tag" //! from the subtags it defines.
151fa92001-04-19Johan Sundström  //! @decl string raw_tag_text;
51f88b2000-06-23Martin Stjernholm  //! If this variable exists, it gets the raw text representation of //! the tag, if there is any. Note that it's after parsing of any //! splice argument.
151fa92001-04-19Johan Sundström  //! @decl array do_enter (RequestID id); //! @decl array do_process (RequestID id, void|mixed piece); //! @decl array do_return (RequestID id);
291ade2000-01-25Martin Stjernholm  //! do_enter() is called first thing when processing the tag.
91c8982000-03-11Martin Stjernholm  //! do_process() is called after (some of) the content has been //! processed. do_return() is called lastly before leaving the tag.
291ade2000-01-25Martin Stjernholm  //! //! For tags that loops more than one time (see do_iterate): //! do_enter() is only called initially before the first call to
91c8982000-03-11Martin Stjernholm  //! do_iterate(). do_process() is called after each iteration. //! do_return() is called after the last call to do_process().
ed81751999-12-11Martin Stjernholm  //! //! The result_type variable is set to the type of result the parser
46c68f2000-08-12Martin Stjernholm  //! wants. The tag may change it; the value will then be converted //! to the type that the parser wants. If the result type is //! sequential, it's spliced into the surrounding content, otherwise //! it replaces the previous value of the content, if any. If the //! result is RXML.nil, it does not affect the surrounding content //! at all.
ed81751999-12-11Martin Stjernholm  //! //! Return values:
151fa92001-04-19Johan Sundström  //! @list dl //! @item array //! A so-called execution array to be handled by the parser. The //! elements are processed in order, and have the following usage: //! @list dl //! @item string //! Added or put into the result. If the result type has a //! parser, the string will be parsed with it before it's //! assigned to the result variable and passed on. //! @item RXML.Frame //! Already initialized frame to process. Neither arguments nor //! content will be parsed. It's result is added or put into the //! result of this tag. //! @item mapping(string:mixed) //! Fields to merge into the headers. FIXME: Not yet //! implemented. //! @item object //! Treated as a file object to read in blocking or nonblocking //! mode. FIXME: Not yet implemented, details not decided. //! @item multiset(mixed) //! Should only contain one element that'll be added or put into //! the result. Normally not necessary; assign it directly to //! the result variable instead. //! @item propagate_tag() //! Use a call to this function to propagate the tag to be //! handled by an overridden tag definition, if any exists. If //! this is used, it's probably necessary to define the //! raw_tag_text variable. For further details see the doc for //! propagate_tag() in this class. //! @endlist //! @item 0 //! Do nothing special. Exits the tag when used from //! do_process() and FLAG_STREAM_RESULT is set. //! @endlist
291ade2000-01-25Martin Stjernholm  //! //! Note that the intended use is not to postparse by setting a //! parser on the result type, but instead to return an array with //! literal strings and RXML.Frame objects where parsing (or, more //! accurately, evaluation) needs to be done. //! //! If an array instead of a function is given, the array is handled
46c68f2000-08-12Martin Stjernholm  //! as above. If the result variable is RXML.nil (which it defaults //! to), content is used as result if it's of a compatible type.
291ade2000-01-25Martin Stjernholm  //!
c28dd22000-03-11Martin Nilsson  //! If there is no do_return() and the result from parsing the
fe628d2001-03-01Martin Stjernholm  //! content is not RXML.nil, it's assigned to or added to the result //! variable. Assignment is used if the content type is
c28dd22000-03-11Martin Nilsson  //! nonsequential, addition otherwise. Thus earlier values are //! simply overridden for nonsequential types.
ed81751999-12-11Martin Stjernholm  //!
9477392000-03-20Martin Stjernholm  //! Regarding do_process only: //! //! Normally the content variable is set to the parsed content of
46c68f2000-08-12Martin Stjernholm  //! the tag before do_process() is called. This may be RXML.nil if //! the content parsing didn't produce any result.
9477392000-03-20Martin Stjernholm  //!
ed81751999-12-11Martin Stjernholm  //! piece is used when the tag is operating in streaming mode (i.e. //! FLAG_STREAM_CONTENT is set). It's then set to each successive //! part of the content in the stream, and the content variable is
91c8982000-03-11Martin Stjernholm  //! never touched. do_process() is also called "normally" with no
ed81751999-12-11Martin Stjernholm  //! piece argument afterwards. Note that tags that support streaming //! mode might still be used nonstreaming (it might also vary //! between iterations). //!
91c8982000-03-11Martin Stjernholm  //! As long as FLAG_STREAM_RESULT is set, do_process() will be //! called repeatedly until it returns 0. It's only the result piece //! from the execution array that is propagated after each turn; the
ed81751999-12-11Martin Stjernholm  //! result variable only accumulates all these pieces.
291ade2000-01-25Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl int do_iterate (RequestID id);
291ade2000-01-25Martin Stjernholm  //! Controls the number of passes in the tag done by the parser. In //! every pass, the content of the tag (if any) is processed, then
91c8982000-03-11Martin Stjernholm  //! do_process() is called.
291ade2000-01-25Martin Stjernholm  //! //! Before doing any pass, do_iterate() is called. If the return //! value is nonzero, that many passes is done, then do_iterate() is //! called again and the process repeats. If the return value is //! zero, the tag exits and the value in result is used in the //! surrounding content as described above.
ed81751999-12-11Martin Stjernholm  //!
291ade2000-01-25Martin Stjernholm  //! The most common way to iterate is to do the setup before every //! pass (e.g. setup the variable scope) and return 1 to do one pass //! through the content. This will repeat until 0 is returned. //! //! If do_iterate is a positive integer, that many passes is done
ac69772000-09-08Martin Stjernholm  //! and then the tag exits. If do_iterate is zero or missing, one //! pass is done. If do_iterate is negative, no pass is done.
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl int|function(RequestID:int) is_valid;
ed81751999-12-11Martin Stjernholm  //! When defined, the frame may be cached. First the name of the tag //! must be the same. Then the conditions specified by the cache //! bits in flag are checked. Then, if this is a function, it's //! called. If it returns 1, the frame is reused. FIXME: Not yet //! implemented.
c6245b1999-12-31Martin Stjernholm  optional array cached_return (Context ctx, void|mixed piece);
ed81751999-12-11Martin Stjernholm  //! If defined, this will be called to get the value from a cached //! frame (that's still valid) instead of using the cached result. //! It's otherwise handled like do_return(). Note that the cached //! frame may be used from several threads. FIXME: Not yet //! implemented.
151fa92001-04-19Johan Sundström  //(!) Services:
ed81751999-12-11Martin Stjernholm 
938bfd2000-11-04Martin Stjernholm  final mixed get_var (string var, void|string scope_name, void|Type want_type)
ffa6932000-02-20Martin Stjernholm  //! A wrapper for easy access to RXML.Context.get_var(). { return get_context()->get_var (var, scope_name, want_type); }
938bfd2000-11-04Martin Stjernholm  final mixed set_var (string var, mixed val, void|string scope_name)
ffa6932000-02-20Martin Stjernholm  //! A wrapper for easy access to RXML.Context.set_var(). { return get_context()->set_var (var, val, scope_name); }
938bfd2000-11-04Martin Stjernholm  final void delete_var (string var, void|string scope_name)
ffa6932000-02-20Martin Stjernholm  //! A wrapper for easy access to RXML.Context.delete_var(). { get_context()->delete_var (var, scope_name); }
bca67e2000-10-18Martin Stjernholm  void run_error (string msg, mixed... args)
ffa6932000-02-20Martin Stjernholm  //! A wrapper for easy access to RXML.run_error().
ed81751999-12-11Martin Stjernholm  {
5ed0132000-02-13Martin Stjernholm  _run_error (msg, @args);
ed81751999-12-11Martin Stjernholm  }
bca67e2000-10-18Martin Stjernholm  void parse_error (string msg, mixed... args)
ffa6932000-02-20Martin Stjernholm  //! A wrapper for easy access to RXML.parse_error().
291ade2000-01-25Martin Stjernholm  {
5ed0132000-02-13Martin Stjernholm  _parse_error (msg, @args);
291ade2000-01-25Martin Stjernholm  }
bca67e2000-10-18Martin Stjernholm  void tag_debug (string msg, mixed... args)
ac69772000-09-08Martin Stjernholm  //! Writes the message to the debug log if this tag has FLAG_DEBUG //! set. {
2cab1b2001-03-05Martin Nilsson  if (flags & FLAG_DEBUG) report_debug (msg, @args);
ac69772000-09-08Martin Stjernholm  }
bca67e2000-10-18Martin Stjernholm  void terminate()
ed81751999-12-11Martin Stjernholm  //! Makes the parser abort. The data parsed so far will be returned. //! Does not return; throws a special exception instead. {
8cb24d2000-02-13Martin Stjernholm  fatal_error ("FIXME\n");
ed81751999-12-11Martin Stjernholm  }
bca67e2000-10-18Martin Stjernholm  void suspend()
ed81751999-12-11Martin Stjernholm  //! Used together with resume() for nonblocking mode. May be called
91c8982000-03-11Martin Stjernholm  //! from any frame callback to suspend the parser: The parser will //! just stop, leaving the context intact. If it returns, the parser //! is used in a place that doesn't support nonblocking, so just go //! ahead and block.
ed81751999-12-11Martin Stjernholm  {
8cb24d2000-02-13Martin Stjernholm  fatal_error ("FIXME\n");
ed81751999-12-11Martin Stjernholm  }
bca67e2000-10-18Martin Stjernholm  void resume()
ed81751999-12-11Martin Stjernholm  //! Makes the parser continue where it left off. The function that //! called suspend() will be called again. {
8cb24d2000-02-13Martin Stjernholm  fatal_error ("FIXME\n");
ed81751999-12-11Martin Stjernholm  }
bca67e2000-10-18Martin Stjernholm  mapping(string:Tag) get_plugins()
cf3c902000-02-04Martin Stjernholm  //! Returns the plugins registered for this tag, which is assumed to //! be a socket tag, i.e. to have FLAG_SOCKET_TAG set (see //! Tag.plugin_name for details). Indices are the plugin_name values //! for the plugin Tag objects, values are the plugin objects //! themselves. Don't be destructive on the returned mapping. { #ifdef MODULE_DEBUG
bca67e2000-10-18Martin Stjernholm  if (!(tag->flags & FLAG_SOCKET_TAG))
8cb24d2000-02-13Martin Stjernholm  fatal_error ("This tag is not a socket tag.\n");
cf3c902000-02-04Martin Stjernholm #endif
24f9e82000-08-05Martin Stjernholm  return get_context()->tag_set->get_plugins (tag->name, tag->flags & FLAG_PROC_INSTR);
cf3c902000-02-04Martin Stjernholm  }
938bfd2000-11-04Martin Stjernholm  final Tag get_overridden_tag()
641d3c2000-10-12Martin Stjernholm  //! Returns the Tag object the tag for this frame overrides, if any. { return get_context()->tag_set->get_overridden_tag (tag); }
bca67e2000-10-18Martin Stjernholm  Frame|string propagate_tag (void|mapping(string:string) args, void|string content)
51f88b2000-06-23Martin Stjernholm  //! This function is intended to be used in the execution array from //! do_return() etc to propagate the tag to the next overridden tag //! definition, if any exists. It either returns a frame from the //! overridden tag or, if no overridden tag exists, a string //! containing a formatted tag (which requires that the result type //! supports formatted tags, i.e. has a working format_tag() //! function). If args and content are given, they will be used in //! the tag after parsing, otherwise the raw_tag_text variable is //! used, which must have a string value. {
4293072001-05-08Martin Stjernholm #ifdef MODULE_DEBUG #define CHECK_RAW_TEXT \ if (zero_type (this_object()->raw_tag_text)) \ fatal_error ("The variable raw_tag_text must be defined.\n"); \ if (!stringp (this_object()->raw_tag_text)) \ fatal_error ("raw_tag_text must have a string value.\n"); #else #define CHECK_RAW_TEXT #endif // FIXME: This assumes an xml-like parser.
641d3c2000-10-12Martin Stjernholm  if (object(Tag) overridden = get_overridden_tag()) {
51f88b2000-06-23Martin Stjernholm  Frame frame;
4293072001-05-08Martin Stjernholm  if (flags & FLAG_PROC_INSTR) { if (!content) { CHECK_RAW_TEXT; if (mixed err = catch { Parser_HTML()->add_quote_tag ( "?", lambda (object p, string c) { sscanf (c, "%*[^ \t\n\r]%s", content); throw (0); }, "?")->finish (this_object()->raw_tag_text); }) throw (err); #ifdef DEBUG if (!stringp (content)) fatal_error ("Failed to parse PI tag content for <?%s?> from %O.\n", tag->name, this_object()->raw_tag_text);
51f88b2000-06-23Martin Stjernholm #endif
4293072001-05-08Martin Stjernholm  } } else if (!args || !content && !(flags & FLAG_EMPTY_ELEMENT)) { CHECK_RAW_TEXT;
b56de02001-03-26Martin Stjernholm  if (mixed err = catch {
4293072001-05-08Martin Stjernholm  Parser_HTML()->_set_tag_callback ( lambda (object p, string s) { if (!args) args = p->tag_args(); if (content || flags & FLAG_EMPTY_ELEMENT) throw (0); else { p->_set_tag_callback (0); p->add_container (p->tag_name(), lambda (object p, mapping a, string c) { content = c; throw (0); }); return 1; } })->finish (this_object()->raw_tag_text);
b56de02001-03-26Martin Stjernholm  }) throw (err);
4c0ed62001-01-18Martin Stjernholm #ifdef DEBUG
4293072001-05-08Martin Stjernholm  if (!mappingp (args)) fatal_error ("Failed to parse tag args for <%s> from %O.\n", tag->name, this_object()->raw_tag_text); if (!stringp (content)) fatal_error ("Failed to parse tag content for <%s> from %O.\n", tag->name, this_object()->raw_tag_text);
4c0ed62001-01-18Martin Stjernholm #endif
51f88b2000-06-23Martin Stjernholm  }
4293072001-05-08Martin Stjernholm 
51f88b2000-06-23Martin Stjernholm  frame = overridden (args, content || ""); frame->flags |= FLAG_UNPARSED; return frame; }
4293072001-05-08Martin Stjernholm  else { // Format a new tag, as like the original as possible. FIXME: // When it's reformatted, the prefix (if any) won't stick, but // it will when it isn't. if (flags & FLAG_PROC_INSTR) { if (content) return result_type->format_tag (tag, 0, content); else { CHECK_RAW_TEXT;
24f9e82000-08-05Martin Stjernholm  return this_object()->raw_tag_text;
4293072001-05-08Martin Stjernholm  } } else { if (args && (content || flags & FLAG_EMPTY_ELEMENT)) return result_type->format_tag (tag, args, content);
24f9e82000-08-05Martin Stjernholm  else {
4293072001-05-08Martin Stjernholm  CHECK_RAW_TEXT; string s;
51f88b2000-06-23Martin Stjernholm #ifdef MODULE_DEBUG
24f9e82000-08-05Martin Stjernholm  if (mixed err = catch {
51f88b2000-06-23Martin Stjernholm #endif
4293072001-05-08Martin Stjernholm  s = t_xml (PEnt)->eval (this_object()->raw_tag_text, get_context(), empty_tag_set);
24f9e82000-08-05Martin Stjernholm #ifdef MODULE_DEBUG }) { if (objectp (err) && ([object] err)->thrown_at_unwind) fatal_error ("Can't save parser state when evaluating arguments.\n"); throw_fatal (err); } #endif
4293072001-05-08Martin Stjernholm  if (!args && !content) return s; if (mixed err = catch { Parser_HTML()->_set_tag_callback ( lambda (object p, string s) { if (!args) args = p->tag_args(); if (content || flags & FLAG_EMPTY_ELEMENT) throw (0); else { p->_set_tag_callback (0); p->add_container (p->tag_name(), lambda (object p, mapping a, string c) { content = c; throw (0); }); return 1; } })->finish (this_object()->raw_tag_text); }) throw (err); #ifdef DEBUG if (!mappingp (args)) fatal_error ("Failed to parse tag args for <%s> from %O.\n", tag->name, this_object()->raw_tag_text); if (!stringp (content)) fatal_error ("Failed to parse tag content for <%s> from %O.\n", tag->name, this_object()->raw_tag_text); #endif return result_type->format_tag (tag, args, content);
24f9e82000-08-05Martin Stjernholm  }
51f88b2000-06-23Martin Stjernholm  }
4293072001-05-08Martin Stjernholm  } #undef CHECK_RAW_TEXT
51f88b2000-06-23Martin Stjernholm  }
151fa92001-04-19Johan Sundström  //(!) Internals:
ed81751999-12-11Martin Stjernholm 
ac69772000-09-08Martin Stjernholm #ifdef DEBUG # define THIS_TAG_TOP_DEBUG(msg) tag_debug ("%O: %s", this_object(), (msg)) # define THIS_TAG_DEBUG(msg) tag_debug ("%O: %s", this_object(), (msg)) # define THIS_TAG_DEBUG_ENTER_SCOPE(ctx, this, msg) \ if (this->vars && ctx->scopes["_"] != this->vars) THIS_TAG_DEBUG (msg) # define THIS_TAG_DEBUG_LEAVE_SCOPE(ctx, this, msg) \ if (this->vars && ctx->scopes["_"] == this->vars) THIS_TAG_DEBUG (msg) #else # define THIS_TAG_TOP_DEBUG(msg) 0 # define THIS_TAG_DEBUG(msg) 0 # define THIS_TAG_DEBUG_ENTER_SCOPE(ctx, this, msg) 0 # define THIS_TAG_DEBUG_LEAVE_SCOPE(ctx, this, msg) 0 #endif
e86f2d2000-02-15Martin Stjernholm  mixed _exec_array (TagSetParser parser, array exec, int parent_scope)
ed81751999-12-11Martin Stjernholm  {
56532d1999-12-19Martin Stjernholm  Frame this = this_object();
c6245b1999-12-31Martin Stjernholm  Context ctx = parser->context;
ed81751999-12-11Martin Stjernholm  int i = 0;
46c68f2000-08-12Martin Stjernholm  mixed res = nil;
ed81751999-12-11Martin Stjernholm  Parser subparser = 0; mixed err = catch {
ac69772000-09-08Martin Stjernholm  if (parent_scope) { THIS_TAG_DEBUG_LEAVE_SCOPE (ctx, this, "Exec: Temporary leaving scope\n"); LEAVE_SCOPE (ctx, this); }
ed81751999-12-11Martin Stjernholm  for (; i < sizeof (exec); i++) {
46c68f2000-08-12Martin Stjernholm  mixed elem = exec[i], piece = nil;
ed81751999-12-11Martin Stjernholm  switch (sprintf ("%t", elem)) { case "string":
7dd3f82001-04-18Martin Stjernholm  if (result_type->parser_prog == PNone) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Exec[%d]: String\n", i));
ed81751999-12-11Martin Stjernholm  piece = elem;
ac69772000-09-08Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  else {
0ebc9d2000-02-15Martin Stjernholm  subparser = result_type->get_parser (ctx, 0, parser);
140f362000-10-19Martin Stjernholm  if (parser->recover_errors && !(flags & FLAG_DONT_RECOVER)) subparser->recover_errors = 1;
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Exec[%d]: Parsing string with %O\n", i, subparser));
56532d1999-12-19Martin Stjernholm  subparser->finish ([string] elem); // Might unwind. piece = subparser->eval(); // Might unwind.
ed81751999-12-11Martin Stjernholm  subparser = 0; } break;
ac69772000-09-08Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  case "mapping":
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Exec[%d]: Header mapping\n", i));
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Header mappings not yet implemented.\n");
ed81751999-12-11Martin Stjernholm  break;
ac69772000-09-08Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  case "multiset":
ac69772000-09-08Martin Stjernholm  if (sizeof ([multiset] elem) == 1) { piece = ((array) elem)[0]; THIS_TAG_DEBUG (sprintf ("Exec[%d]: Verbatim %t value\n", i, piece)); }
56532d1999-12-19Martin Stjernholm  else if (sizeof ([multiset] elem) > 1)
8cb24d2000-02-13Martin Stjernholm  fatal_error (sizeof ([multiset] elem) + " values in multiset in exec array.\n"); else fatal_error ("No value in multiset in exec array.\n");
ed81751999-12-11Martin Stjernholm  break;
ac69772000-09-08Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  default:
5a455b2000-02-13Martin Stjernholm  if (objectp (elem)) // Can't count on that sprintf ("%t", ...) on an object // returns "object". if (([object] elem)->is_RXML_Frame) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Exec[%d]: Evaluating frame %O\n", i, ([object] elem)));
a85a952000-02-13Martin Stjernholm  ([object(Frame)] elem)->_eval (parser); // Might unwind.
5a455b2000-02-13Martin Stjernholm  piece = ([object(Frame)] elem)->result; } else if (([object] elem)->is_RXML_Parser) { // The subparser above unwound.
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Exec[%d]: Continuing eval of frame %O\n", i, ([object] elem)));
5a455b2000-02-13Martin Stjernholm  ([object(Parser)] elem)->finish(); // Might unwind. piece = ([object(Parser)] elem)->eval(); // Might unwind. } else
8cb24d2000-02-13Martin Stjernholm  fatal_error ("File objects not yet implemented.\n");
5a455b2000-02-13Martin Stjernholm  else
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Invalid type %t in exec array.\n", elem);
ed81751999-12-11Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm  if (result_type->sequential) { THIS_TAG_DEBUG (sprintf ("Exec[%d]: Adding %t to result\n", i, piece)); res += piece; } else if (piece != nil) { THIS_TAG_DEBUG (sprintf ("Exec[%d]: Setting result to %t\n", i, piece)); result = res = piece; }
ed81751999-12-11Martin Stjernholm  } if (result_type->sequential) result += res;
ac69772000-09-08Martin Stjernholm  if (parent_scope) { THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this, "Exec: Reentering scope\n"); ENTER_SCOPE (ctx, this); }
ed81751999-12-11Martin Stjernholm  return res; };
ac69772000-09-08Martin Stjernholm  if (parent_scope) { THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this, "Exec: Reentering scope\n"); ENTER_SCOPE (ctx, this); }
ed81751999-12-11Martin Stjernholm  if (result_type->sequential) result += res;
56532d1999-12-19Martin Stjernholm  if (objectp (err) && ([object] err)->thrown_at_unwind) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Exec: Interrupted at position %d\n", i));
56532d1999-12-19Martin Stjernholm  mapping(string:mixed)|mapping(object:array) ustate;
33712a2000-03-13Martin Stjernholm  if ((ustate = ctx->unwind_state) && !zero_type (ustate->stream_piece)) {
ed81751999-12-11Martin Stjernholm  // Subframe wants to stream. Update stream_piece and send it on.
fe628d2001-03-01Martin Stjernholm  if (result_type->name != parser->type->name)
23a0882000-08-12Martin Stjernholm  res = parser->type->encode (res, result_type);
ed81751999-12-11Martin Stjernholm  if (result_type->sequential) ustate->stream_piece = res + ustate->stream_piece;
46c68f2000-08-12Martin Stjernholm  else if (ustate->stream_piece == nil)
ed81751999-12-11Martin Stjernholm  ustate->stream_piece = res;
33712a2000-03-13Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  ustate->exec_left = exec[i..]; // Left to execute. if (subparser) // Replace the string with the subparser object so that we'll
56532d1999-12-19Martin Stjernholm  // continue in it later. It's done here to keep the original // exec array untouched. ([array] ustate->exec_left)[0] = subparser;
8cb24d2000-02-13Martin Stjernholm  throw (err);
ed81751999-12-11Martin Stjernholm  }
8cb24d2000-02-13Martin Stjernholm  throw_fatal (err);
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm  private void _handle_runtime_tags (Context ctx, TagSetParser parser)
2bd21a2000-01-05Martin Stjernholm  { // FIXME: PCode handling.
24f9e82000-08-05Martin Stjernholm  array(Tag) arr_add_tags = ctx->new_runtime_tags->added_tags(); array(string) arr_rem_tags = ctx->new_runtime_tags->removed_tags(); array(string) arr_rem_pi_tags = ctx->new_runtime_tags->removed_pi_tags();
2bd21a2000-01-05Martin Stjernholm  for (Parser p = parser; p; p = p->_parent)
9502852000-02-15Martin Stjernholm  if (p->tag_set_eval && !p->_local_tag_set && p->add_runtime_tag) {
ac69772000-09-08Martin Stjernholm  foreach (arr_add_tags, Tag tag) { THIS_TAG_DEBUG (sprintf ("Adding runtime tag %O\n", tag));
2bd21a2000-01-05Martin Stjernholm  ([object(TagSetParser)] p)->add_runtime_tag (tag);
ac69772000-09-08Martin Stjernholm  } foreach (arr_rem_tags, string tag) { THIS_TAG_DEBUG (sprintf ("Removing runtime tag %s\n", tag));
2bd21a2000-01-05Martin Stjernholm  ([object(TagSetParser)] p)->remove_runtime_tag (tag);
ac69772000-09-08Martin Stjernholm  } foreach (arr_rem_pi_tags, string tag) { THIS_TAG_DEBUG (sprintf ("Removing runtime tag %s\n", tag));
24f9e82000-08-05Martin Stjernholm  ([object(TagSetParser)] p)->remove_runtime_tag (tag, 1);
ac69772000-09-08Martin Stjernholm  }
2bd21a2000-01-05Martin Stjernholm  }
24f9e82000-08-05Martin Stjernholm  ctx->runtime_tags = ctx->new_runtime_tags->filter_tags (ctx->runtime_tags);
0ebc9d2000-02-15Martin Stjernholm  ctx->new_runtime_tags = 0;
2bd21a2000-01-05Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  void _eval (TagSetParser parser, void|mapping(string:string) raw_args, void|string raw_content) // Note: It might be somewhat tricky to override this function.
51f88b2000-06-23Martin Stjernholm  // Note: Might be destructive on raw_args.
ed81751999-12-11Martin Stjernholm  {
56532d1999-12-19Martin Stjernholm  Frame this = this_object();
ed81751999-12-11Martin Stjernholm  Context ctx = parser->context;
b483382000-06-30Martin Stjernholm  RequestID id = ctx->id;
ed81751999-12-11Martin Stjernholm 
c6245b1999-12-31Martin Stjernholm  // Unwind state data: //raw_content
49897a2000-02-13Martin Stjernholm #define EVSTAT_BEGIN 0 #define EVSTAT_ENTERED 1 #define EVSTAT_LAST_ITER 2 #define EVSTAT_ITER_DONE 3 int eval_state = EVSTAT_BEGIN;
291ade2000-01-25Martin Stjernholm  int iter;
ac69772000-09-08Martin Stjernholm #ifdef DEBUG int debug_iter = 1; #endif
ed81751999-12-11Martin Stjernholm  Parser subparser; mixed piece; array exec;
f02d002000-03-18Martin Stjernholm  TagSet orig_tag_set; // Flags that we added additional_tags to ctx->tag_set.
c6245b1999-12-31Martin Stjernholm  //ctx->new_runtime_tags
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm #define PRE_INIT_ERROR(X) (ctx->frame = this, fatal_error (X))
2bd21a2000-01-05Martin Stjernholm #ifdef DEBUG // Internal sanity checks. if (ctx != get_context()) PRE_INIT_ERROR ("Internal error: Context not current.\n");
1b2b752000-01-07Martin Stjernholm  if (!parser->tag_set_eval)
2bd21a2000-01-05Martin Stjernholm  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
ed81751999-12-11Martin Stjernholm  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
56532d1999-12-19Martin Stjernholm  object ignored;
f02d002000-03-18Martin Stjernholm  [ignored, eval_state, iter, raw_content, subparser, piece, exec, orig_tag_set,
ac69772000-09-08Martin Stjernholm  ctx->new_runtime_tags #ifdef DEBUG , debug_iter #endif ] = state;
ed81751999-12-11Martin Stjernholm  m_delete (ctx->unwind_state, this); if (!sizeof (ctx->unwind_state)) ctx->unwind_state = 0;
ac69772000-09-08Martin Stjernholm  THIS_TAG_TOP_DEBUG ("Continuing evaluation" + (piece ? " with stream piece\n" : "\n"));
ed81751999-12-11Martin Stjernholm  } else {
ac69772000-09-08Martin Stjernholm  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 = nil; #ifdef MODULE_DEBUG if (!stringp (raw_content)) PRE_INIT_ERROR ("Content is not a string in unparsed tag frame.\n"); #endif THIS_TAG_TOP_DEBUG ("Evaluating unparsed\n"); } else THIS_TAG_TOP_DEBUG ("Evaluating\n");
ed81751999-12-11Martin Stjernholm #ifdef MODULE_DEBUG if (up && up != ctx->frame) PRE_INIT_ERROR ("Reuse of frame in different context.\n"); #endif up = ctx->frame;
23a0882000-08-12Martin Stjernholm  piece = nil;
ffa6932000-02-20Martin Stjernholm  if (++ctx->frame_depth >= ctx->max_frame_depth) { ctx->frame = this; ctx->frame_depth--; _run_error ("Too deep recursion -- exceeding %d nested tags.\n", ctx->max_frame_depth); }
ed81751999-12-11Martin Stjernholm  }
2bd21a2000-01-05Martin Stjernholm 
ed81751999-12-11Martin Stjernholm #undef PRE_INIT_ERROR ctx->frame = this;
7dd3f82001-04-18Martin Stjernholm  mixed err1 = 0;
2531fa2001-03-23Martin Stjernholm  process_tag: {
7dd3f82001-04-18Martin Stjernholm  if ((err1 = catch { if (tag) { if ((raw_args || args || ([]))->help) { TRACE_ENTER ("tag &lt;" + tag->name + " help&gt;", tag); string help = id->conf->find_tag_doc (tag->name, id); TRACE_LEAVE (""); THIS_TAG_TOP_DEBUG ("Reporting help - frame done\n"); ctx->handle_exception ( // Will throw if necessary. Backtrace ("help", help, ctx), parser); break process_tag; }
b483382000-06-30Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  TRACE_ENTER("tag &lt;" + tag->name + "&gt;", tag);
b483382000-06-30Martin Stjernholm  #ifdef MODULE_LEVEL_SECURITY
7dd3f82001-04-18Martin Stjernholm  if (id->conf->check_security (tag, id, id->misc->seclevel)) { THIS_TAG_TOP_DEBUG ("Access denied - exiting\n"); TRACE_LEAVE("access denied"); break process_tag; }
b483382000-06-30Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  }
b483382000-06-30Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (raw_args) {
24f9e82000-08-05Martin Stjernholm #ifdef MODULE_DEBUG
7dd3f82001-04-18Martin Stjernholm  if (flags & FLAG_PROC_INSTR) fatal_error ("Can't pass arguments to a processing instruction tag.\n");
24f9e82000-08-05Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  if (sizeof (raw_args)) { // Note: Code duplication in Tag.eval_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)); parse_error ("Required " + (sizeof (missing) > 1 ? "arguments " + String.implode_nicely (missing) + " are" : "argument " + missing[0] + " is") + " missing.\n"); } atypes += raw_args & tag->opt_arg_types;
c159ec2000-02-04Martin Stjernholm #ifdef MODULE_DEBUG
7dd3f82001-04-18Martin Stjernholm  if (mixed err = catch {
c159ec2000-02-04Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  foreach (indices (raw_args), string arg) {
ac69772000-09-08Martin Stjernholm #ifdef DEBUG
7dd3f82001-04-18Martin Stjernholm  Type t = atypes[arg] || tag->def_arg_type; if (t->parser_prog != PNone) { Parser p = t->get_parser (ctx, 0, parser); THIS_TAG_DEBUG (sprintf ("Evaluating argument %O with %O\n", arg, p)); p->finish (raw_args[arg]); // Should not unwind. raw_args[arg] = p->eval(); // Should not unwind. }
ac69772000-09-08Martin Stjernholm #else
7dd3f82001-04-18Martin Stjernholm  raw_args[arg] = (atypes[arg] || tag->def_arg_type)-> eval (raw_args[arg], ctx, 0, parser, 1); // Should not unwind.
ac69772000-09-08Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  }
c159ec2000-02-04Martin Stjernholm #ifdef MODULE_DEBUG
7dd3f82001-04-18Martin Stjernholm  }) { if (objectp (err) && ([object] err)->thrown_at_unwind) fatal_error ("Can't save parser state when evaluating arguments.\n"); throw_fatal (err); }
710b482000-02-07Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  } args = raw_args;
b483382000-06-30Martin Stjernholm  }
49897a2000-02-13Martin Stjernholm #ifdef MODULE_DEBUG
7dd3f82001-04-18Martin Stjernholm  else if (!args && !(flags & FLAG_PROC_INSTR)) fatal_error ("args not set.\n");
ed81751999-12-11Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  if (!zero_type (this->parent_frame)) if (up->local_tags && up->local_tags->has_tag (tag)) { THIS_TAG_DEBUG (sprintf ("Setting parent_frame to %O from local_tags\n", up)); this->parent_frame = up; } else for (Frame f = up; f; f = f->up) if (f->additional_tags && f->additional_tags->has_tag (tag)) { THIS_TAG_DEBUG (sprintf ("Setting parent_frame to %O " "from additional_tags\n", f)); this->parent_frame = f; break; }
689dc62000-08-31Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (TagSet add_tags = raw_content && [object(TagSet)] this->additional_tags) { TagSet tset = ctx->tag_set; if (!tset->has_effective_tags (add_tags)) { THIS_TAG_DEBUG (sprintf ("Installing additional_tags %O\n", add_tags)); int hash = HASH_INT2 (tset->id_number, add_tags->id_number); orig_tag_set = tset; TagSet local_ts; if (!(local_ts = local_tag_set_cache[hash])) { local_ts = TagSet (add_tags->name + "+" + orig_tag_set->name); local_ts->imported = ({add_tags, orig_tag_set}); local_tag_set_cache[hash] = local_ts; // Race, but it doesn't matter. } ctx->tag_set = local_ts;
b483382000-06-30Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  else THIS_TAG_DEBUG (sprintf ("Not installing additional_tags %O " "since they're already in the tag set\n", add_tags));
87fb7e2000-03-25Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (!result_type) {
a85a952000-02-13Martin Stjernholm #ifdef MODULE_DEBUG
7dd3f82001-04-18Martin Stjernholm  if (!tag) fatal_error ("result_type not set in Frame object %O, " "and it has no Tag object to use for inferring it.\n", this_object());
a85a952000-02-13Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  Type ptype = parser->type; find_result_type: { // First check if any of the types is a subtype of the // wanted type. If so, we can use it directly. foreach (tag->result_types, Type rtype) if (rtype->subtype_of (ptype)) { result_type = rtype; break find_result_type; } // Then check if any of the types is a supertype of the // wanted type. If so, set the result type to the wanted // type, since the tag has the responsibility to produce a // value of that type. foreach (tag->result_types, Type rtype) if (ptype->subtype_of (rtype)) { result_type = ptype (rtype->parser_prog, @rtype->parser_args); break find_result_type; } parse_error ( "Tag returns " + String.implode_nicely ([array(string)] tag->result_types->name, "or") + " but " + [string] parser->type->name + " is expected.\n"); } THIS_TAG_DEBUG (sprintf ("Resolved result_type to %O from surrounding %O\n", result_type, ptype));
0c9e5b2001-03-13Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  else THIS_TAG_DEBUG (sprintf ("Keeping result_type %O\n", result_type));
ac69772000-09-08Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (!content_type) {
a85a952000-02-13Martin Stjernholm #ifdef MODULE_DEBUG
7dd3f82001-04-18Martin Stjernholm  if (!tag) fatal_error ("content_type not set in Frame object %O, " "and it has no Tag object to use for inferring it.\n", this_object());
a85a952000-02-13Martin Stjernholm #endif
7dd3f82001-04-18Martin Stjernholm  content_type = tag->content_type; if (content_type == t_same) { content_type = result_type (content_type->parser_prog, @content_type->parser_args); THIS_TAG_DEBUG (sprintf ("Resolved t_same to content_type %O\n", content_type)); } else THIS_TAG_DEBUG (sprintf ("Setting content_type to %O from tag\n", content_type));
ac69772000-09-08Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  else THIS_TAG_DEBUG (sprintf ("Keeping content_type %O\n", content_type));
ac69772000-09-08Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (raw_content) { THIS_TAG_DEBUG ("Initializing the content variable to nil\n"); content = nil; } })) break process_tag;
b483382000-06-30Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (mixed err2 = catch {
b483382000-06-30Martin Stjernholm  switch (eval_state) { case EVSTAT_BEGIN: if (array|function(RequestID:array) do_enter = [array|function(RequestID:array)] this->do_enter) { if (!exec) {
ac69772000-09-08Martin Stjernholm  if (arrayp (do_enter)) { THIS_TAG_DEBUG ("Getting exec array from do_enter\n"); exec = [array] do_enter; } else { THIS_TAG_DEBUG ("Calling do_enter\n");
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "rxml internal", "tag:" + tag->name);
ac69772000-09-08Martin Stjernholm  exec = ([function(RequestID:array)] do_enter) (id); // Might unwind.
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "tag:" + tag->name, "rxml internal");
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ((exec ? "Exec array" : "Zero") + " returned from do_enter\n"); }
b483382000-06-30Martin Stjernholm  if (ctx->new_runtime_tags) _handle_runtime_tags (ctx, parser); }
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  if (exec) {
ac69772000-09-08Martin Stjernholm  if (!(flags & FLAG_PARENT_SCOPE)) { THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this, "Entering scope\n"); ENTER_SCOPE (ctx, this); }
b483382000-06-30Martin Stjernholm  mixed res = _exec_array (parser, exec, 0); // Might unwind.
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  if (flags & FLAG_STREAM_RESULT) {
291ade2000-01-25Martin Stjernholm #ifdef DEBUG
b483382000-06-30Martin Stjernholm  if (ctx->unwind_state) fatal_error ("Internal error: Clobbering unwind_state " "to do streaming.\n");
46c68f2000-08-12Martin Stjernholm  if (piece != nil)
b483382000-06-30Martin Stjernholm  fatal_error ("Internal error: Thanks, we think about how nice " "it must be to play the harmonica...\n");
291ade2000-01-25Martin Stjernholm #endif
fe628d2001-03-01Martin Stjernholm  if (result_type->name != parser->type->name) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Converting result from %s to %s of " "surrounding content\n", result_type->name, parser->type->name));
23a0882000-08-12Martin Stjernholm  res = parser->type->encode (res, result_type);
ac69772000-09-08Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  ctx->unwind_state = (["stream_piece": res]);
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Streaming %t from do_enter\n", res));
b483382000-06-30Martin Stjernholm  throw (this); }
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  exec = 0;
49897a2000-02-13Martin Stjernholm  }
291ade2000-01-25Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  eval_state = EVSTAT_ENTERED; /* Fall through. */ case EVSTAT_ENTERED: case EVSTAT_LAST_ITER: do { if (eval_state != EVSTAT_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 = EVSTAT_LAST_ITER;
ac69772000-09-08Martin Stjernholm #ifdef DEBUG if (iter > 1) THIS_TAG_DEBUG (sprintf ("Getting %d iterations from do_iterate\n", iter)); else if (iter < 0) THIS_TAG_DEBUG ("Skipping to finish since do_iterate is negative\n"); #endif
b483382000-06-30Martin Stjernholm  } else {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ("Calling do_iterate\n");
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "rxml internal", "tag:" + tag->name);
b483382000-06-30Martin Stjernholm  iter = (/*[function(RequestID:int)]HMM*/ do_iterate) ( id); // Might unwind.
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "tag:" + tag->name, "rxml internal");
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("%O returned from do_iterate\n", iter));
b483382000-06-30Martin Stjernholm  if (ctx->new_runtime_tags) _handle_runtime_tags (ctx, parser); if (!iter) eval_state = EVSTAT_LAST_ITER; }
0ebc9d2000-02-15Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this, "Entering scope\n");
b483382000-06-30Martin Stjernholm  ENTER_SCOPE (ctx, this);
ac69772000-09-08Martin Stjernholm  for (; iter > 0; iter-- #ifdef DEBUG , debug_iter++ #endif ) {
0f998b2000-08-15Martin Stjernholm  if (raw_content && raw_content != "") if (flags & FLAG_EMPTY_ELEMENT) parse_error ("This tag doesn't handle content.\n");
ac69772000-09-08Martin Stjernholm 
0f998b2000-08-15Martin Stjernholm  else { // Got nested parsing to do. int finished = 0; if (!subparser) { // The nested content is not yet parsed. if (this->local_tags) { subparser = content_type->get_parser ( ctx, [object(TagSet)] this->local_tags, parser); subparser->_local_tag_set = 1;
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ( sprintf ("Iter[%d]: Evaluating content with %O " "from local_tags\n", debug_iter, subparser));
0f998b2000-08-15Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm  else {
0f998b2000-08-15Martin Stjernholm  subparser = content_type->get_parser (ctx, 0, parser);
ac69772000-09-08Martin Stjernholm #ifdef DEBUG
7dd3f82001-04-18Martin Stjernholm  if (content_type->parser_prog != PNone)
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Iter[%d]: Evaluating content " "with %O\n", debug_iter, subparser)); #endif }
140f362000-10-19Martin Stjernholm  if (parser->recover_errors && !(flags & FLAG_DONT_RECOVER)) subparser->recover_errors = 1;
0f998b2000-08-15Martin Stjernholm  subparser->finish (raw_content); // Might unwind. finished = 1;
b483382000-06-30Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm 
2531fa2001-03-23Martin Stjernholm  process_subparser:
0f998b2000-08-15Martin Stjernholm  do { if (flags & FLAG_STREAM_CONTENT && subparser->read) { // Handle a stream piece. // Squeeze out any free text from the subparser first. mixed res = subparser->read(); if (content_type->sequential) piece = res + piece; else if (piece == nil) piece = res;
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ( sprintf ("Iter[%d]: Got %s %t stream piece\n", debug_iter, finished ? "ending" : "a", piece));
0f998b2000-08-15Martin Stjernholm  if (piece != nil) { array|function(RequestID,void|mixed:array) do_process; if ((do_process = [array|function(RequestID,void|mixed:array)] this->do_process) && !arrayp (do_process)) { if (!exec) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Iter[%d]: Calling do_process in " "streaming mode\n", debug_iter));
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "rxml internal", "tag:" + tag->name);
0f998b2000-08-15Martin Stjernholm  exec = do_process (id, piece); // Might unwind.
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "tag:" + tag->name, "rxml internal");
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Iter[%d]: %s returned from " "do_process\n", debug_iter, exec ? "Exec array" : "Zero"));
0f998b2000-08-15Martin Stjernholm  if (ctx->new_runtime_tags) _handle_runtime_tags (ctx, parser); }
ac69772000-09-08Martin Stjernholm 
0f998b2000-08-15Martin Stjernholm  if (exec) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE ( ctx, this, sprintf ("Iter[%d]: Entering scope\n", debug_iter));
0f998b2000-08-15Martin Stjernholm  ENTER_SCOPE (ctx, this); mixed res = _exec_array ( parser, exec, flags & FLAG_PARENT_SCOPE); // Might unwind.
ac69772000-09-08Martin Stjernholm 
0f998b2000-08-15Martin Stjernholm  if (flags & FLAG_STREAM_RESULT) {
ed81751999-12-11Martin Stjernholm #ifdef DEBUG
0f998b2000-08-15Martin Stjernholm  if (!zero_type (ctx->unwind_state->stream_piece)) fatal_error ("Internal error: Clobbering " "unwind_state->stream_piece.\n");
ed81751999-12-11Martin Stjernholm #endif
fe628d2001-03-01Martin Stjernholm  if (result_type->name != parser->type->name) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ( sprintf ("Iter[%d]: Converting result from %s to %s" " of surrounding content\n", debug_iter, result_type->name, parser->type->name));
0f998b2000-08-15Martin Stjernholm  res = parser->type->encode (res, result_type);
ac69772000-09-08Martin Stjernholm  }
0f998b2000-08-15Martin Stjernholm  ctx->unwind_state->stream_piece = res;
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ( sprintf ("Iter[%d]: Streaming %t from " "do_process\n", debug_iter, res));
0f998b2000-08-15Martin Stjernholm  throw (this); }
ac69772000-09-08Martin Stjernholm 
0f998b2000-08-15Martin Stjernholm  exec = 0; } else if (flags & FLAG_STREAM_RESULT) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ( sprintf ("Iter[%d]: do_process finished the stream; " "ignoring remaining content\n", debug_iter));
0f998b2000-08-15Martin Stjernholm  ctx->unwind_state = 0; piece = nil;
2531fa2001-03-23Martin Stjernholm  break process_subparser;
b483382000-06-30Martin Stjernholm  }
49897a2000-02-13Martin Stjernholm  }
0f998b2000-08-15Martin Stjernholm  piece = nil;
49897a2000-02-13Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm 
2531fa2001-03-23Martin Stjernholm  if (finished) break process_subparser;
ed81751999-12-11Martin Stjernholm  }
0f998b2000-08-15Martin Stjernholm  else { // The frame doesn't handle streamed content. piece = nil; if (finished) { mixed res = subparser->eval(); // Might unwind.
ac69772000-09-08Martin Stjernholm  if (content_type->sequential) { THIS_TAG_DEBUG (sprintf ("Iter[%d]: Adding %t to content\n", debug_iter, res)); content += res; } else if (res != nil) { THIS_TAG_DEBUG (sprintf ("Iter[%d]: Setting content to %t\n", debug_iter, res)); content = res; }
2531fa2001-03-23Martin Stjernholm  break process_subparser;
0f998b2000-08-15Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm 
0f998b2000-08-15Martin Stjernholm  subparser->finish(); // Might unwind. finished = 1;
2531fa2001-03-23Martin Stjernholm  } while (1); // Only loops when an unwound // subparser has been recovered.
0f998b2000-08-15Martin Stjernholm  subparser = 0; }
b483382000-06-30Martin Stjernholm  if (array|function(RequestID,void|mixed:array) do_process = [array|function(RequestID,void|mixed:array)] this->do_process) { if (!exec) {
ac69772000-09-08Martin Stjernholm  if (arrayp (do_process)) { THIS_TAG_DEBUG (sprintf ("Iter[%d]: Getting exec array from " "do_process\n", debug_iter)); exec = [array] do_process; } else { THIS_TAG_DEBUG (sprintf ("Iter[%d]: Calling do_process\n", debug_iter));
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "rxml internal", "tag:" + tag->name);
ac69772000-09-08Martin Stjernholm  exec = ([function(RequestID,void|mixed:array)] do_process) (
b483382000-06-30Martin Stjernholm  id); // Might unwind.
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "tag:" + tag->name, "rxml internal");
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Iter[%d]: %s returned from do_process\n", debug_iter, exec ? "Exec array" : "Zero")); }
b483382000-06-30Martin Stjernholm  if (ctx->new_runtime_tags) _handle_runtime_tags (ctx, parser); }
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  if (exec) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE ( ctx, this, sprintf ("Iter[%d]: Entering scope\n", debug_iter));
b483382000-06-30Martin Stjernholm  ENTER_SCOPE (ctx, this); mixed res = _exec_array ( parser, exec, flags & FLAG_PARENT_SCOPE); // Might unwind.
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  if (flags & FLAG_STREAM_RESULT) {
ed81751999-12-11Martin Stjernholm #ifdef DEBUG
b483382000-06-30Martin Stjernholm  if (ctx->unwind_state) fatal_error ("Internal error: Clobbering unwind_state " "to do streaming.\n");
46c68f2000-08-12Martin Stjernholm  if (piece != nil)
b483382000-06-30Martin Stjernholm  fatal_error ("Internal error: Thanks, we think about how nice " "it must be to play the harmonica...\n");
ed81751999-12-11Martin Stjernholm #endif
fe628d2001-03-01Martin Stjernholm  if (result_type->name != parser->type->name) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Iter[%d]: Converting result from " "type %s to type %s of surrounding " "content\n", debug_iter, result_type->name, parser->type->name));
23a0882000-08-12Martin Stjernholm  res = parser->type->encode (res, result_type);
ac69772000-09-08Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  ctx->unwind_state = (["stream_piece": res]);
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Iter[%d]: Streaming %t from " "do_process\n", debug_iter, res));
b483382000-06-30Martin Stjernholm  throw (this); }
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  exec = 0;
49897a2000-02-13Martin Stjernholm  } }
ed81751999-12-11Martin Stjernholm 
b483382000-06-30Martin Stjernholm  } } while (eval_state != EVSTAT_LAST_ITER); /* Fall through. */ case EVSTAT_ITER_DONE: if (array|function(RequestID:array) do_return = [array|function(RequestID:array)] this->do_return) { eval_state = EVSTAT_ITER_DONE; // Only need to record this state here. if (!exec) {
ac69772000-09-08Martin Stjernholm  if (arrayp (do_return)) { THIS_TAG_DEBUG ("Getting exec array from do_return\n"); exec = [array] do_return; } else { THIS_TAG_DEBUG ("Calling do_return\n");
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "rxml internal", "tag:" + tag->name);
ac69772000-09-08Martin Stjernholm  exec = ([function(RequestID:array)] do_return) (id); // Might unwind.
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (ctx, "tag:" + tag->name, "rxml internal");
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG ((exec ? "Exec array" : "Zero") + " returned from do_return\n"); }
b483382000-06-30Martin Stjernholm  if (ctx->new_runtime_tags) _handle_runtime_tags (ctx, parser); }
ac69772000-09-08Martin Stjernholm 
b483382000-06-30Martin Stjernholm  if (exec) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this, "Entering scope\n");
b483382000-06-30Martin Stjernholm  ENTER_SCOPE (ctx, this);
e86f2d2000-02-15Martin Stjernholm  _exec_array (parser, exec, flags & FLAG_PARENT_SCOPE); // Might unwind. exec = 0;
49897a2000-02-13Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm  else if (result == nil && !(flags & FLAG_EMPTY_ELEMENT)) {
7dd3f82001-04-18Martin Stjernholm  if (result_type->parser_prog == PNone) {
fe628d2001-03-01Martin Stjernholm  if (content_type->name != result_type->name) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG (sprintf ("Assigning content to result after " "converting from %s to %s\n", content_type->name, result_type->name)); result = result_type->encode (content, content_type); } else { THIS_TAG_DEBUG ("Assigning content to result\n");
b483382000-06-30Martin Stjernholm  result = content;
ac69772000-09-08Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  } else
ac69772000-09-08Martin Stjernholm  if (stringp (content)) {
b483382000-06-30Martin Stjernholm  eval_state = EVSTAT_ITER_DONE; // Only need to record this state here.
ac69772000-09-08Martin Stjernholm  if (!exec) { THIS_TAG_DEBUG ("Parsing content with exec array " "for assignment to result\n"); exec = ({content}); }
b483382000-06-30Martin Stjernholm  _exec_array (parser, exec, flags & FLAG_PARENT_SCOPE); // Might unwind. exec = 0; }
ac69772000-09-08Martin Stjernholm  } else { #ifdef DEBUG if (!(flags & FLAG_EMPTY_ELEMENT)) THIS_TAG_DEBUG ("Skipping nil result\n"); #endif } THIS_TAG_DEBUG_LEAVE_SCOPE (ctx, this, "Leaving scope\n");
b483382000-06-30Martin Stjernholm  LEAVE_SCOPE (ctx, this); }
f997202000-01-18Martin Stjernholm 
b483382000-06-30Martin Stjernholm  if (ctx->new_runtime_tags) _handle_runtime_tags (ctx, parser);
ffa6932000-02-20Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  }) {
ac69772000-09-08Martin Stjernholm  THIS_TAG_DEBUG_LEAVE_SCOPE (ctx, this, "Leaving scope\n");
b483382000-06-30Martin Stjernholm  LEAVE_SCOPE (ctx, this); string action;
7dd3f82001-04-18Martin Stjernholm  if (objectp (err2) && ([object] err2)->thrown_at_unwind) {
b483382000-06-30Martin Stjernholm  mapping(string:mixed)|mapping(object:array) ustate = ctx->unwind_state; if (!ustate) ustate = ctx->unwind_state = ([]);
ed81751999-12-11Martin Stjernholm #ifdef DEBUG
b483382000-06-30Martin Stjernholm  if (ustate[this]) fatal_error ("Internal error: Frame already has an unwind state.\n");
ed81751999-12-11Martin Stjernholm #endif
b483382000-06-30Martin Stjernholm  if (ustate->exec_left) { exec = [array] ustate->exec_left; m_delete (ustate, "exec_left");
56532d1999-12-19Martin Stjernholm  }
b483382000-06-30Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  if (err2 == this || exec && sizeof (exec) && err2 == exec[0])
b483382000-06-30Martin Stjernholm  // This frame or a frame in the exec array wants to stream. if (parser->unwind_safe) { // Rethrow to continue in parent since we've already done // the appropriate do_process stuff in this frame in // either case.
7dd3f82001-04-18Martin Stjernholm  if (err2 == this) err2 = 0;
b483382000-06-30Martin Stjernholm  if (orig_tag_set) ctx->tag_set = orig_tag_set, orig_tag_set = 0; action = "break";
ac69772000-09-08Martin Stjernholm  THIS_TAG_TOP_DEBUG ("Interrupted for streaming - " "breaking to parent frame\n");
b483382000-06-30Martin Stjernholm  } else { // Can't stream since the parser isn't unwind safe. Just // continue. m_delete (ustate, "stream_piece"); action = "continue";
ac69772000-09-08Martin Stjernholm  THIS_TAG_TOP_DEBUG ("Interrupted for streaming - " "continuing since parser isn't unwind safe\n");
b483382000-06-30Martin Stjernholm  } else if (!zero_type (ustate->stream_piece)) { // Got a stream piece from a subframe. We handle it above; // store the state and tail recurse. piece = ustate->stream_piece;
56532d1999-12-19Martin Stjernholm  m_delete (ustate, "stream_piece"); action = "continue";
ed81751999-12-11Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm  else { action = "break"; // Some other reason - back up to the top. THIS_TAG_TOP_DEBUG ("Interrupted\n"); }
ed81751999-12-11Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  ustate[this] = ({err2, eval_state, iter, raw_content, subparser, piece,
ac69772000-09-08Martin Stjernholm  exec, orig_tag_set, ctx->new_runtime_tags, #ifdef DEBUG debug_iter, #endif });
0f998b2000-08-15Martin Stjernholm  TRACE_LEAVE (action);
b483382000-06-30Martin Stjernholm  } else {
ac69772000-09-08Martin Stjernholm  THIS_TAG_TOP_DEBUG ("Exception\n");
0f998b2000-08-15Martin Stjernholm  TRACE_LEAVE ("exception");
7dd3f82001-04-18Martin Stjernholm  ctx->handle_exception (err2, parser); // Will rethrow unknown errors.
46c68f2000-08-12Martin Stjernholm  result = nil;
b483382000-06-30Martin Stjernholm  action = "return"; }
ed81751999-12-11Martin Stjernholm 
b483382000-06-30Martin Stjernholm  switch (action) { case "break": // Throw and handle in parent frame.
56532d1999-12-19Martin Stjernholm #ifdef MODULE_DEBUG
b483382000-06-30Martin Stjernholm  if (!parser->unwind_state) fatal_error ("Trying to unwind inside a parser that isn't unwind safe.\n");
56532d1999-12-19Martin Stjernholm #endif
b483382000-06-30Martin Stjernholm  throw (this); case "continue": // Continue in this frame through tail recursion. _eval (parser); return; case "return": // A normal return.
2531fa2001-03-23Martin Stjernholm  break process_tag;
b483382000-06-30Martin Stjernholm  default: fatal_error ("Internal error: Don't you come here and %O on me!\n", action); }
ed81751999-12-11Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm 
ac69772000-09-08Martin Stjernholm  else { THIS_TAG_TOP_DEBUG ("Done\n");
0f998b2000-08-15Martin Stjernholm  TRACE_LEAVE ("");
ac69772000-09-08Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  // Normal clean up on tag return or exception.
f02d002000-03-18Martin Stjernholm  if (orig_tag_set) ctx->tag_set = orig_tag_set;
01e43b2000-01-14Martin Stjernholm  ctx->frame = up;
7dd3f82001-04-18Martin Stjernholm  ctx->frame_depth--; if (err1) throw (err1);
ed81751999-12-11Martin Stjernholm  }
f02d002000-03-18Martin Stjernholm  MARK_OBJECT;
26ff092000-01-21Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  string _sprintf() {
f02d002000-03-18Martin Stjernholm  return "RXML.Frame(" + (tag && [string] tag->name) + ")" + OBJ_COUNT;
ed81751999-12-11Martin Stjernholm  } }
151fa92001-04-19Johan Sundström //(!) Global services.
49897a2000-02-13Martin Stjernholm 
f134b92000-11-27Martin Stjernholm //! Shortcuts to some common functions in the current context (see the //! corresponding functions in the Context class for details). final mixed get_var (string var, void|string scope_name, void|Type want_type)
5ad5292000-03-09Martin Stjernholm  {return get_context()->get_var (var, scope_name, want_type);}
f134b92000-11-27Martin Stjernholm final mixed user_get_var (string var, void|string scope_name, void|Type want_type)
5ad5292000-03-09Martin Stjernholm  {return get_context()->user_get_var (var, scope_name, want_type);}
f134b92000-11-27Martin Stjernholm final mixed set_var (string var, mixed val, void|string scope_name)
5ad5292000-03-09Martin Stjernholm  {return get_context()->set_var (var, val, scope_name);}
f134b92000-11-27Martin Stjernholm final mixed user_set_var (string var, mixed val, void|string scope_name)
5ad5292000-03-09Martin Stjernholm  {return get_context()->user_set_var (var, val, scope_name);}
f134b92000-11-27Martin Stjernholm final void delete_var (string var, void|string scope_name)
5ad5292000-03-09Martin Stjernholm  {get_context()->delete_var (var, scope_name);}
f134b92000-11-27Martin Stjernholm final void user_delete_var (string var, void|string scope_name)
5ad5292000-03-09Martin Stjernholm  {get_context()->user_delete_var (var, scope_name);}
f134b92000-11-27Martin Stjernholm final void run_error (string msg, mixed... args)
8cb24d2000-02-13Martin Stjernholm //! Throws an RXML run error with a dump of the parser stack in the //! current context. This is intended to be used by tags for errors //! that can occur during normal operation, such as when the //! connection to an SQL server fails.
49897a2000-02-13Martin Stjernholm {
8cb24d2000-02-13Martin Stjernholm  if (sizeof (args)) msg = sprintf (msg, @args);
51f88b2000-06-23Martin Stjernholm  array bt = backtrace(); throw (Backtrace ("run", msg, get_context(), bt[..sizeof (bt) - 2]));
49897a2000-02-13Martin Stjernholm }
f134b92000-11-27Martin Stjernholm final void parse_error (string msg, mixed... args)
8cb24d2000-02-13Martin Stjernholm //! Throws an RXML parse error with a dump of the parser stack in the //! current context. This is intended to be used for programming //! errors in the RXML code, such as lookups in nonexisting scopes and //! invalid arguments to a tag.
49897a2000-02-13Martin Stjernholm {
8cb24d2000-02-13Martin Stjernholm  if (sizeof (args)) msg = sprintf (msg, @args);
51f88b2000-06-23Martin Stjernholm  array bt = backtrace(); throw (Backtrace ("parse", msg, get_context(), bt[..sizeof (bt) - 2]));
49897a2000-02-13Martin Stjernholm }
f134b92000-11-27Martin Stjernholm final void fatal_error (string msg, mixed... args)
8cb24d2000-02-13Martin Stjernholm //! Throws a Pike error that isn't catched and handled anywhere. It's //! just like the common error() function, but includes the RXML frame //! backtrace.
49897a2000-02-13Martin Stjernholm {
8cb24d2000-02-13Martin Stjernholm  if (sizeof (args)) msg = sprintf (msg, @args); array bt = backtrace(); throw_fatal (({msg, bt[..sizeof (bt) - 2]})); }
f134b92000-11-27Martin Stjernholm final void throw_fatal (mixed err)
8cb24d2000-02-13Martin Stjernholm //! Mainly used internally to throw an error that includes the RXML //! frame backtrace. { if (arrayp (err) && sizeof (err) == 2 || objectp (err) && !err->is_RXML_Backtrace && err->is_generic_error) { string msg; if (catch (msg = err[0])) throw (err); if (stringp (msg) && !has_value (msg, "\nRXML frame backtrace:\n")) {
c64acf2000-02-17Martin Stjernholm  string descr = Backtrace (0, 0)->describe_rxml_backtrace (1);
5ed0132000-02-13Martin Stjernholm  if (sizeof (descr)) { if (sizeof (msg) && msg[-1] != '\n') msg += "\n"; msg += "RXML frame backtrace:\n" + descr; catch (err[0] = msg); }
8cb24d2000-02-13Martin Stjernholm  } } throw (err);
49897a2000-02-13Martin Stjernholm }
1cfbeb2000-11-06Martin Stjernholm final mixed rxml_index (mixed val, string|int|array(string|int) index, string scope_name, Context ctx, void|Type want_type) //! Index the value according to RXML type rules and returns the //! result. Throws RXML exceptions on any errors. If index is an //! array, its elements are used to successively subindex the value, //! e.g. ({"a", 2, "b"}) corresponds to val["a"][2]["c"]. scope_name //! is used to identify the context for the indexing. //! //! The special RXML index rules are: //!
151fa92001-04-19Johan Sundström //! @list ul //! @item //! Arrays are indexed with 1 for the first element, or //! alternatively -1 for the last. Indexing an array of size n with //! 0, n+1 or greater, -n-1 or less, or with a non-integer is an //! error. //! @item //! Strings, along with integers and floats, are treated as simple //! scalar types which aren't really indexable. If a scalar type is //! indexed with 1 or -1, it produces itself instead of generating //! an error. (This is a convenience to avoid many special cases //! when treating both arrays and scalar types.) //! @item //! If a value is an object which has an rxml_var_eval identifier, //! it's treated as a Value object and the rxml_var_eval function is //! called to produce its value. //! @item //! If a value which is about to be indexed is an object which has a //! `[] function, it's called as a Scope object. //! @item //! Both the special value nil and the undefined value (a zero with //! zero_type 1) may be used to signify no value at all, and both //! will be returned as the undefined value. //! @endlist
1cfbeb2000-11-06Martin Stjernholm //! //! If the want_type argument is set, the result value is converted to //! that type with Type.encode(). If the value can't be converted, an //! RXML error is thrown. //! //! This function is mainly for internal use; you commonly want to use //! get_var, set_var, user_get_var or user_set_var instead. { #ifdef MODULE_DEBUG if (arrayp (index) ? !sizeof (index) : !(stringp (index) || intp (index))) error ("Invalid index specifier.\n"); #endif
2b5f472001-02-11Martin Stjernholm  int scope_got_type = 0;
1cfbeb2000-11-06Martin Stjernholm  array(string|int) idxpath;
2b5f472001-02-11Martin Stjernholm  if (arrayp (index)) idxpath = index, index = index[0]; else idxpath = ({0});
1cfbeb2000-11-06Martin Stjernholm 
2b5f472001-02-11Martin Stjernholm  for (int i = 1;; i++) {
1cfbeb2000-11-06Martin Stjernholm  // stringp was not really a good idea. if( arrayp( val ) /*|| stringp( val )*/ ) if (intp (index) && index) if( (index > sizeof( val )) || ((index < 0) && (-index > sizeof( val ) )) ) parse_error( "Index %d out of range for array of size %d in %s.\n", index, sizeof (val), scope_name ); else if( index < 0 ) val = val[index]; else val = val[index-1]; else parse_error( "Cannot index the array in %s with %O.\n", scope_name, index ); else if (val == nil) parse_error ("%s produced no value to index with %O.\n", scope_name, index); else if( objectp( val ) && val->`[] ) {
fe628d2001-03-01Martin Stjernholm #ifdef MODULE_DEBUG Scope scope = [object(Scope)] val; #endif
1cfbeb2000-11-06Martin Stjernholm  if (zero_type (
2b5f472001-02-11Martin Stjernholm  val = ([object(Scope)] val)->`[]( index, ctx, scope_name, i == sizeof (idxpath) && (scope_got_type = 1, want_type))))
1cfbeb2000-11-06Martin Stjernholm  val = nil;
fe628d2001-03-01Martin Stjernholm #ifdef MODULE_DEBUG
2531fa2001-03-23Martin Stjernholm  else if (mixed err = scope_got_type && want_type && val != nil &&
0c9e5b2001-03-13Martin Stjernholm  !(objectp (val) && ([object] val)->rxml_var_eval) &&
fe628d2001-03-01Martin Stjernholm  catch (want_type->type_check (val))) if (([object] err)->is_RXML_Backtrace) error ("%O->`[] didn't return a value of the correct type:\n%s", scope, err->msg); else throw (err); #endif
1cfbeb2000-11-06Martin Stjernholm  } else if( mappingp( val ) || objectp (val) ) { if (zero_type (val = val[ index ])) val = nil; } else if (multisetp (val)) { if (!val[index]) val = nil; } else if (!(<1, -1>)[index]) parse_error ("%s is %O which cannot be indexed with %O.\n", scope_name, val, index);
2b5f472001-02-11Martin Stjernholm  if (i == sizeof (idxpath)) break; scope_name += "." + index; index = idxpath[i]; #ifdef MODULE_DEBUG mapping(object:int) called = ([]); #endif while (objectp (val) && ([object] val)->rxml_var_eval) { #ifdef MODULE_DEBUG // Detect infinite loops. This check is slightly too strong; // it's theoretically possible that a couple of Value objects // return each other a few rounds and then something different, // but we'll live with that. Besides, that situation ought to be // solved internally in them anyway. if (called[val]) error ("Cyclic rxml_var_eval chain detected in %O.\n" "All called objects:%{ %O%}\n", val, indices (called)); called[val] = 1; #endif if (zero_type (val = ([object(Value)] val)->rxml_var_eval ( ctx, index, scope_name, 0))) { val = nil; break; } }
1cfbeb2000-11-06Martin Stjernholm  } if (val == nil) return ([])[0]; else if (!objectp (val) || !([object] val)->rxml_var_eval)
2b5f472001-02-11Martin Stjernholm  if (want_type && !scope_got_type)
1cfbeb2000-11-06Martin Stjernholm  return // FIXME: Some system to find out the source type? zero_type (val = want_type->encode (val)) || val == nil ? ([])[0] : val; else return val;
2b5f472001-02-11Martin Stjernholm #ifdef MODULE_DEBUG mapping(object:int) called = ([]); #endif
1cfbeb2000-11-06Martin Stjernholm  do {
2b5f472001-02-11Martin Stjernholm #ifdef MODULE_DEBUG if (called[val]) error ("Cyclic rxml_var_eval chain detected in %O.\n" "All called objects:%{ %O%}\n", val, indices (called)); called[val] = 1;
fe628d2001-03-01Martin Stjernholm  Value val_obj = [object(Value)] val;
2b5f472001-02-11Martin Stjernholm #endif
1cfbeb2000-11-06Martin Stjernholm  if (zero_type (val = ([object(Value)] val)->rxml_var_eval ( ctx, index, scope_name, want_type)) || val == nil) return ([])[0];
fe628d2001-03-01Martin Stjernholm #ifdef MODULE_DEBUG else if (mixed err = want_type && catch (want_type->type_check (val))) if (([object] err)->is_RXML_Backtrace) error ("%O->rxml_var_eval didn't return a value of the correct type:\n%s", val_obj, err->msg); else throw (err); #endif
1cfbeb2000-11-06Martin Stjernholm  } while (objectp (val) && ([object] val)->rxml_var_eval); return val; } final void tag_debug (string msg, mixed... args)
ac69772000-09-08Martin Stjernholm //! Writes the message to the debug log if the innermost tag being //! executed has FLAG_DEBUG set. { if (Frame f = get_context()->frame) // It's intentional that this assumes a context. if (f->flags & FLAG_DEBUG)
2cab1b2001-03-05Martin Nilsson  report_debug (msg, @args);
ac69772000-09-08Martin Stjernholm }
f134b92000-11-27Martin Stjernholm final Frame make_tag (string name, mapping(string:mixed) args, void|mixed content, void|Tag overridden_by)
51f88b2000-06-23Martin Stjernholm //! Returns a frame for the specified tag, or 0 if no such tag exists. //! The tag definition is looked up in the current context and tag //! set. args and content are not parsed or evaluated. If //! overridden_by is given, the returned frame will come from the tag
2e0a6d2000-07-06Martin Stjernholm //! that overridden_by overrides, if there is any (name is not used in //! that case).
49897a2000-02-13Martin Stjernholm { TagSet tag_set = get_context()->tag_set;
2e0a6d2000-07-06Martin Stjernholm  Tag tag = overridden_by ? tag_set->get_overridden_tag (overridden_by) :
51f88b2000-06-23Martin Stjernholm  tag_set->get_tag (name);
2e0a6d2000-07-06Martin Stjernholm  return tag && tag (args, content);
49897a2000-02-13Martin Stjernholm }
f134b92000-11-27Martin Stjernholm final Frame make_unparsed_tag (string name, mapping(string:string) args, void|string content, void|Tag overridden_by)
51f88b2000-06-23Martin Stjernholm //! Returns a frame for the specified tag, or 0 if no such tag exists. //! The tag definition is looked up in the current context and tag //! set. args and content are given unparsed in this variant; they're //! parsed when the frame is about to be evaluated. If overridden_by //! is given, the returned frame will come from the tag that
2e0a6d2000-07-06Martin Stjernholm //! overridden_by overrides, if there is any (name is not used in that //! case).
49897a2000-02-13Martin Stjernholm { TagSet tag_set = get_context()->tag_set;
2e0a6d2000-07-06Martin Stjernholm  Tag tag = overridden_by ? tag_set->get_overridden_tag (overridden_by) :
51f88b2000-06-23Martin Stjernholm  tag_set->get_tag (name); if (!tag) return 0;
49897a2000-02-13Martin Stjernholm  Frame frame = tag (args, content); frame->flags |= FLAG_UNPARSED; return frame; }
7dd3f82001-04-18Martin Stjernholm //! @decl Frame parse_frame (Type type, string to_parse); //!
49897a2000-02-13Martin Stjernholm //! Returns a frame that, when evaluated, parses the given string //! according to the type (which typically has a parser set).
7dd3f82001-04-18Martin Stjernholm class parse_frame
49897a2000-02-13Martin Stjernholm { inherit Frame; constant flags = FLAG_UNPARSED; mapping(string:mixed) args = ([]); void create (Type type, string to_parse) { content_type = type, result_type = type (PNone); content = to_parse; }
ac69772000-09-08Martin Stjernholm  string _sprintf() {return sprintf ("RXML.parse_frame(%O)", content_type);}
49897a2000-02-13Martin Stjernholm }
151fa92001-04-19Johan Sundström //(!) Parsers:
ed81751999-12-11Martin Stjernholm  class Parser //! Interface class for a syntax parser that scans, parses and //! evaluates an input stream. Access to a parser object is assumed to //! be done in a thread safe way except where noted. { constant is_RXML_Parser = 1;
56532d1999-12-19Martin Stjernholm  constant thrown_at_unwind = 1;
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //(!) Services:
ed81751999-12-11Martin Stjernholm 
db04172000-01-14Martin Stjernholm  int error_count; //! Number of RXML errors that occurred during evaluation. If this //! is nonzero, the value from eval() shouldn't be trusted.
ed81751999-12-11Martin Stjernholm  function(Parser:void) data_callback; //! A function to be called when data is likely to be available from //! eval(). It's always called when the source stream closes. //! write() and write_end() are the functions to use from outside //! the parser system, not feed() or finish(). int write (string in) //! Writes some source data to the parser. Returns nonzero if there //! might be data available in eval(). { int res; ENTER_CONTEXT (context);
80954c2000-03-03Martin Stjernholm  if (mixed err = catch {
56532d1999-12-19Martin Stjernholm  if (context && context->unwind_state && context->unwind_state->top) {
c6245b1999-12-31Martin Stjernholm #ifdef MODULE_DEBUG if (context->unwind_state->top != this_object())
8cb24d2000-02-13Martin Stjernholm  fatal_error ("The context got an unwound state from another parser. " "Can't rewind.\n");
c6245b1999-12-31Martin Stjernholm #endif
56532d1999-12-19Martin Stjernholm  m_delete (context->unwind_state, "top"); if (!sizeof (context->unwind_state)) context->unwind_state = 0; }
b436892000-01-19Martin Stjernholm  if (feed (in)) res = 1; // Might unwind.
ed81751999-12-11Martin Stjernholm  if (res && data_callback) data_callback (this_object());
80954c2000-03-03Martin Stjernholm  })
56532d1999-12-19Martin Stjernholm  if (objectp (err) && ([object] err)->thrown_at_unwind) {
c6245b1999-12-31Martin Stjernholm #ifdef DEBUG
80954c2000-03-03Martin Stjernholm  if (err != this_object()) { LEAVE_CONTEXT();
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Internal error: Unexpected unwind object catched.\n");
80954c2000-03-03Martin Stjernholm  }
c6245b1999-12-31Martin Stjernholm #endif
56532d1999-12-19Martin Stjernholm  if (!context->unwind_state) context->unwind_state = ([]); context->unwind_state->top = err; }
80954c2000-03-03Martin Stjernholm  else { LEAVE_CONTEXT(); throw_fatal (err); } LEAVE_CONTEXT();
ed81751999-12-11Martin Stjernholm  return res; } void write_end (void|string in) //! Closes the source data stream, optionally with a last bit of //! data. { int res; ENTER_CONTEXT (context);
80954c2000-03-03Martin Stjernholm  if (mixed err = catch {
56532d1999-12-19Martin Stjernholm  if (context && context->unwind_state && context->unwind_state->top) {
c6245b1999-12-31Martin Stjernholm #ifdef MODULE_DEBUG if (context->unwind_state->top != this_object())
8cb24d2000-02-13Martin Stjernholm  fatal_error ("The context got an unwound state from another parser. " "Can't rewind.\n");
c6245b1999-12-31Martin Stjernholm #endif
56532d1999-12-19Martin Stjernholm  m_delete (context->unwind_state, "top"); if (!sizeof (context->unwind_state)) context->unwind_state = 0; }
b436892000-01-19Martin Stjernholm  finish (in); // Might unwind.
ed81751999-12-11Martin Stjernholm  if (data_callback) data_callback (this_object());
80954c2000-03-03Martin Stjernholm  })
56532d1999-12-19Martin Stjernholm  if (objectp (err) && ([object] err)->thrown_at_unwind) {
c6245b1999-12-31Martin Stjernholm #ifdef DEBUG
80954c2000-03-03Martin Stjernholm  if (err != this_object()) { LEAVE_CONTEXT();
8cb24d2000-02-13Martin Stjernholm  fatal_error ("Internal error: Unexpected unwind object catched.\n");
80954c2000-03-03Martin Stjernholm  }
c6245b1999-12-31Martin Stjernholm #endif
56532d1999-12-19Martin Stjernholm  if (!context->unwind_state) context->unwind_state = ([]); context->unwind_state->top = err; }
80954c2000-03-03Martin Stjernholm  else { LEAVE_CONTEXT(); throw_fatal (err); } LEAVE_CONTEXT();
4293072001-05-08Martin Stjernholm #ifdef PROFILE_PARSER werror ("Profile for %s: %O\n", context->id->not_query, context->profile); #endif
01e43b2000-01-14Martin Stjernholm  }
7dd3f82001-04-18Martin Stjernholm  mixed handle_var (string varref, Type surrounding_type)
01e43b2000-01-14Martin Stjernholm  // Parses and evaluates a possible variable reference, with the // appropriate error handling. {
7dd3f82001-04-18Martin Stjernholm  if (mixed err = catch { // It's intentional that we split on the first ':' for now, to // allow for future enhancements of this syntax. Scope and // variable names containing ':' are thus not accessible this way. sscanf (varref, "%[^:]:%s", varref, string encoding); context->current_var = varref; array(string|int) splitted = context->parse_user_var (varref, 1); if (splitted[0] == 1) parse_error ( "No scope in variable reference.\n" "(Use ':' in front to quote a character reference containing dots.)\n"); mixed val;
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (context, "rxml internal", "var:" + varref);
7dd3f82001-04-18Martin Stjernholm  if (zero_type (val = context->get_var ( // May throw. splitted[1..], splitted[0], encoding ? t_string : surrounding_type))) val = nil;
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (context, "var:" + varref, "rxml internal");
7dd3f82001-04-18Martin Stjernholm  if (encoding && !(val = Roxen->roxen_encode (val + "", encoding))) parse_error ("Unknown encoding %O.\n", encoding); context->current_var = 0; return val; }) { context->current_var = 0; context->handle_exception (err, this_object()); // May throw. return nil; }
ed81751999-12-11Martin Stjernholm  }
151fa92001-04-19Johan Sundström  //(!) Interface:
ed81751999-12-11Martin Stjernholm  Context context; //! The context to do evaluation in. It's assumed to never be //! modified asynchronously during the time the parser is working on //! an input stream. Type type; //! The expected result type of the current stream. (The parser //! should not do any type checking on this.) int compile; //! Must be set to nonzero before a stream is fed which should be //! compiled to p-code.
151fa92001-04-19Johan Sundström  //! @decl int unwind_safe;
56532d1999-12-19Martin Stjernholm  //! If nonzero, the parser supports unwinding with throw()/catch(). //! Whenever an exception is thrown from some evaluation function, //! it should be able to call that function again with identical //! arguments the next time it continues.
140f362000-10-19Martin Stjernholm  int recover_errors; //! Set to nonzero to allow error recovery in this parser. //! report_error() will never be called if this is zero.
4d62ba2000-08-15Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  mixed feed (string in); //! Feeds some source data to the parse stream. The parser may do //! scanning and parsing before returning. If context is set, it may //! also do evaluation in that context. Returns nonzero if there //! could be new data to get from eval(). void finish (void|string in); //! Like feed(), but also finishes the parse stream. A last bit of //! data may be given. It should work to call this on an already //! finished stream if no argument is given to it.
8cb24d2000-02-13Martin Stjernholm  optional int report_error (string msg);
01e43b2000-01-14Martin Stjernholm  //! Used to report errors to the end user through the output. This
4d62ba2000-08-15Martin Stjernholm  //! is only called when type->free_text is nonzero and
140f362000-10-19Martin Stjernholm  //! recover_errors is nonzero. msg should be stored in the output
4d62ba2000-08-15Martin Stjernholm  //! queue to be returned by eval(). If the context is bad for an
140f362000-10-19Martin Stjernholm  //! error message, do nothing and return zero. The parser will then //! be aborted and the error will be propagated instead. Return //! nonzero if a message was written.
01e43b2000-01-14Martin Stjernholm 
c6245b1999-12-31Martin Stjernholm  optional mixed read();
ed81751999-12-11Martin Stjernholm  //! Define to allow streaming operation. Returns the evaluated
46c68f2000-08-12Martin Stjernholm  //! result so far, but does not do any evaluation. Returns RXML.nil //! if there's no data (for sequential types the empty value is also
ed81751999-12-11Martin Stjernholm  //! ok).
56532d1999-12-19Martin Stjernholm  mixed eval();
ed81751999-12-11Martin Stjernholm  //! Evaluates the data fed so far and returns the result. The result //! returned by previous eval() calls should not be returned again
46c68f2000-08-12Martin Stjernholm  //! as (part of) this return value. Returns RXML.nil if there's no //! data (for sequential types the empty value is also ok).
ed81751999-12-11Martin Stjernholm 
c6245b1999-12-31Martin Stjernholm  optional PCode p_compile();
ed81751999-12-11Martin Stjernholm  //! Define this to return a p-code representation of the current //! stream, which always is finished.
c6245b1999-12-31Martin Stjernholm  optional void reset (Context ctx, Type type, mixed... args);
ed81751999-12-11Martin Stjernholm  //! Define to support reuse of a parser object. It'll be called //! instead of making a new object for a new stream. It keeps the
b2d01b2000-02-15Martin Stjernholm  //! static configuration, i.e. the type (and tag set when used in
2531fa2001-03-23Martin Stjernholm  //! @[TagSetParser]). Note that this function needs to deal with //! leftovers from @[TagSetParser.add_runtime_tag] for //! @[TagSetParser] objects. It should call @[initialize] with the //! given context and type to reset this base class properly.
ed81751999-12-11Martin Stjernholm 
c6245b1999-12-31Martin Stjernholm  optional Parser clone (Context ctx, Type type, mixed... args);
ed81751999-12-11Martin Stjernholm  //! Define to create new parser objects by cloning instead of //! creating from scratch. It returns a new instance of this parser
b2d01b2000-02-15Martin Stjernholm  //! with the same static configuration, i.e. the type (and tag set //! when used in TagSetParser).
ed81751999-12-11Martin Stjernholm 
2531fa2001-03-23Martin Stjernholm  static void create (Context ctx, Type type, mixed... args) //! Should (at least) call @[initialize] with the given context and //! type.
ed81751999-12-11Martin Stjernholm  {
2531fa2001-03-23Martin Stjernholm  initialize (ctx, type);
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG __object_marker->create (this_object()); #endif
ed81751999-12-11Martin Stjernholm  }
2531fa2001-03-23Martin Stjernholm  static void initialize (Context ctx, Type _type) //! Does the required initialization for this base class. Use from //! @[create] and @[reset] (when it's defined) to initialize or //! reset the parser object properly. { context = ctx; type = _type; }
51f88b2000-06-23Martin Stjernholm  string current_input() {return 0;} //! Should return the representation in the input stream for the
2531fa2001-03-23Martin Stjernholm  //! current tag, entity or text being parsed, or 0 if it isn't //! known.
51f88b2000-06-23Martin Stjernholm 
151fa92001-04-19Johan Sundström  //(!) Internals:
ed81751999-12-11Martin Stjernholm  Parser _next_free; // Used to link together unused parser objects for reuse.
c6245b1999-12-31Martin Stjernholm  Parser _parent; // The parent parser if this one is nested.
2bd21a2000-01-05Martin Stjernholm 
01e43b2000-01-14Martin Stjernholm  Stdio.File _source_file; mapping _defines; // These two are compatibility kludges for use with parse_rxml().
f02d002000-03-18Martin Stjernholm  MARK_OBJECT_ONLY;
ec027c2000-03-16Martin Stjernholm 
f02d002000-03-18Martin Stjernholm  string _sprintf() {
ac69772000-09-08Martin Stjernholm  return sprintf ("RXML.Parser(%O)%s", type, OBJ_COUNT);
f02d002000-03-18Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm } class TagSetParser //! Interface class for parsers that evaluates using the tag set. It //! provides the evaluation and compilation functionality. The parser
7dd3f82001-04-18Martin Stjernholm //! should call @[Tag.handle_tag] or similar from @[feed] and //! @[finish] for every encountered tag. @[Parser.handle_var] should //! be called for encountered variable references. It must be able to //! continue cleanly after @[throw] from @[Tag.handle_tag].
ed81751999-12-11Martin Stjernholm { inherit Parser;
ffa6932000-02-20Martin Stjernholm  constant is_RXML_TagSetParser = 1;
1b2b752000-01-07Martin Stjernholm  constant tag_set_eval = 1;
2bd21a2000-01-05Martin Stjernholm 
151fa92001-04-19Johan Sundström  //(!) Services:
2bd21a2000-01-05Martin Stjernholm 
b436892000-01-19Martin Stjernholm  mixed eval() {return read();}
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //(!) Interface:
ed81751999-12-11Martin Stjernholm 
1b2b752000-01-07Martin Stjernholm  TagSet tag_set;
ed81751999-12-11Martin Stjernholm  //! The tag set used for parsing.
2531fa2001-03-23Martin Stjernholm  //! In addition to the type, the tag set is part of the static //! configuration. 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)
ed81751999-12-11Martin Stjernholm  {
2531fa2001-03-23Martin Stjernholm  initialize (ctx, type, tag_set);
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG __object_marker->create (this_object()); #endif
ed81751999-12-11Martin Stjernholm  }
2531fa2001-03-23Martin Stjernholm  static void initialize (Context ctx, Type type, TagSet _tag_set) { ::initialize (ctx, type); tag_set = _tag_set; }
ed81751999-12-11Martin Stjernholm 
2bd21a2000-01-05Martin Stjernholm  mixed read();
3187d82000-01-28Martin Stjernholm  //! No longer optional in this class. Since the evaluation is done
7dd3f82001-04-18Martin Stjernholm  //! in Tag.handle_tag() or similar, this always does the same as
3187d82000-01-28Martin Stjernholm  //! eval().
2bd21a2000-01-05Martin Stjernholm 
b2d01b2000-02-15Martin Stjernholm  optional void add_runtime_tag (Tag tag);
c6245b1999-12-31Martin Stjernholm  //! Adds a tag that will exist from this point forward in the
b2d01b2000-02-15Martin Stjernholm  //! current parser instance only. This may only be left undefined if //! the parser doesn't parse tags at all.
ed81751999-12-11Martin Stjernholm 
24f9e82000-08-05Martin Stjernholm  optional void remove_runtime_tag (string|Tag tag, void|int proc_instr); //! If tag is a Tag object, it's removed from the set of runtime //! tags that's been added by add_runtime_tag(). If tag is a string, //! the tag with that name is removed. In this case, if proc_instr //! is nonzero the set of runtime PI tags is searched, else the set //! of normal element runtime tags. This may only be left undefined //! if the parser doesn't parse tags at all.
2bd21a2000-01-05Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  string raw_tag_text() {return 0;} //! Used by @[Tag.handle_tag] to set @[Frame.raw_tag_text] in a //! newly created frame. This default implementation simply leaves //! it unset.
151fa92001-04-19Johan Sundström  //(!) Internals:
9502852000-02-15Martin Stjernholm  int _local_tag_set;
f02d002000-03-18Martin Stjernholm  string _sprintf() {
ac69772000-09-08Martin Stjernholm  return sprintf ("RXML.TagSetParser(%O,%O)%s", type, tag_set, OBJ_COUNT);
f02d002000-03-18Martin Stjernholm  }
c6245b1999-12-31Martin Stjernholm }
ed81751999-12-11Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm 
ed81751999-12-11Martin Stjernholm class PNone //! The identity parser. It only returns its input. { 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; }
2bd21a2000-01-05Martin Stjernholm 
f02d002000-03-18Martin Stjernholm  string _sprintf() {return "RXML.PNone" + OBJ_COUNT;}
ed81751999-12-11Martin Stjernholm } mixed simple_parse (string in, void|program parser) //! A convenience function to parse a string with no type info, no tag //! set, and no variable references. The parser defaults to PExpr. { // FIXME: Recycle contexts?
1b2b752000-01-07Martin Stjernholm  return t_any (parser || PExpr)->eval (in, Context (empty_tag_set));
ed81751999-12-11Martin Stjernholm }
151fa92001-04-19Johan Sundström //(!) Types:
ed81751999-12-11Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm static mapping(string:Type) reg_types = ([]); // Maps each type name to a type object with the PNone parser.
ed81751999-12-11Martin Stjernholm class Type //! A static type definition. It does type checking and specifies some //! properties of the type. It may also contain a Parser program that //! will be used to read text and evaluate values of this type. Note //! that the parser is not relevant for type checking.
0c9e5b2001-03-13Martin Stjernholm //!
151fa92001-04-19Johan Sundström //! @note //! The doc for this class is rather lengthy, but most of it applies //! only to type implementors. It's very much simpler to use a type //! than to implement one; typical users only need to choose one and //! use @[encode], @[subtype_of], @[`()] or @[eval] in it.
ed81751999-12-11Martin Stjernholm { constant is_RXML_Type = 1;
151fa92001-04-19Johan Sundström  //(!) Services:
ed81751999-12-11Martin Stjernholm  int `== (mixed other)
fe628d2001-03-01Martin Stjernholm  //! Returns nonzero iff this type is the same as @[other], i.e. has //! the same name. If @[other] is known to be a type, it's somewhat //! faster to compare the names directly.
ed81751999-12-11Martin Stjernholm  {
fe628d2001-03-01Martin Stjernholm  return /*::`== (this_object(), other) ||*/ objectp (other) && ([object] other)->is_RXML_Type &&
56532d1999-12-19Martin Stjernholm  ([object(Type)] other)->name == this_object()->name;
ed81751999-12-11Martin Stjernholm  } int subtype_of (Type other)
fe628d2001-03-01Martin Stjernholm  //! Returns nonzero iff this type is the same as or a subtype of
7dd3f82001-04-18Martin Stjernholm  //! @[other]. //! //! That means that any value of this type can be expressed by //! @[other] without (or with at most negligible) loss of //! information. There's however one notable exception to that: //! @[RXML.t_any] is the supertype for all types even though it can //! hardly express any value without losing its information context. { // FIXME: Add some cache here? for (Type type = this_object(); type; type = type->supertype) if (type->name == other->name) return 1; return 0;
ed81751999-12-11Martin Stjernholm  }
fe628d2001-03-01Martin Stjernholm  int `< (mixed other) //! Comparison regards a type as less than its supertypes. If //! @[other] isn't a type, it's compared with @[name]. { if (objectp (other) && ([object] other)->is_RXML_Type) { if (([object(Type)] other)->name == this_object()->name) return 0; return subtype_of (other); } return this_object()->name < other; } int convertible (Type from) //! Returns nonzero iff it's possible to convert values of the type //! @[from] to this type using some chain of conversions. { if (conversion_type->name == from->name || conversion_type->name == from->conversion_type->name || this_object()->name == from->conversion_type->name || this_object()->name == from->name) return 1; // The following is not terribly efficient, but most situations // should be handled by the special cases above. for (Type tconv = conversion_type; tconv; tconv = tconv->conversion_type) for (Type fconv = from->conversion_type; fconv; fconv = fconv->conversion_type) if (fconv->name == tconv->name) return 1; return 0; }
2bd21a2000-01-05Martin Stjernholm  Type `() (program/*(Parser)HMM*/ newparser, mixed... parser_args)
ed81751999-12-11Martin Stjernholm  //! Returns a type identical to this one, but which has the given //! parser. parser_args is passed as extra arguments to the //! create()/reset()/clone() functions. { Type newtype; if (sizeof (parser_args)) { // Can't cache this. newtype = clone();
7dd3f82001-04-18Martin Stjernholm  newtype->parser_prog = newparser; newtype->parser_args = parser_args;
f02d002000-03-18Martin Stjernholm  if (newparser->tag_set_eval) newtype->_p_cache = set_weak_flag (([]), 1);
ed81751999-12-11Martin Stjernholm  } else { if (!_t_obj_cache) _t_obj_cache = ([]); if (!(newtype = _t_obj_cache[newparser]))
7dd3f82001-04-18Martin Stjernholm  if (newparser == parser_prog)
56532d1999-12-19Martin Stjernholm  _t_obj_cache[newparser] = newtype = this_object();
ed81751999-12-11Martin Stjernholm  else { _t_obj_cache[newparser] = newtype = clone();
7dd3f82001-04-18Martin Stjernholm  newtype->parser_prog = newparser;
f02d002000-03-18Martin Stjernholm  if (newparser->tag_set_eval) newtype->_p_cache = set_weak_flag (([]), 1);
ed81751999-12-11Martin Stjernholm  } }
7dd3f82001-04-18Martin Stjernholm #ifdef DEBUG if (reg_types[this_object()->name]->parser_prog != PNone) error ("Incorrect type object registered in reg_types.\n"); #endif
ed81751999-12-11Martin Stjernholm  return newtype; }
0ebc9d2000-02-15Martin Stjernholm  inline Parser get_parser (Context ctx, void|TagSet tag_set, void|Parser|PCode parent)
ed81751999-12-11Martin Stjernholm  //! Returns a parser instance initialized with the given context. { Parser p; if (_p_cache) { // It's a tag set parser.
0ebc9d2000-02-15Martin Stjernholm  TagSet tset = tag_set || ctx->tag_set;
ffa6932000-02-20Martin Stjernholm  if (parent && parent->is_RXML_TagSetParser && tset == parent->tag_set && sizeof (ctx->runtime_tags) &&
0ebc9d2000-02-15Martin Stjernholm  parent->clone && parent->type == this_object()) { // There are runtime tags. Try to clone the parent parser if // all conditions are met.
7dd3f82001-04-18Martin Stjernholm  p = parent->clone (ctx, this_object(), tset, @parser_args);
0ebc9d2000-02-15Martin Stjernholm  p->_parent = parent; return p; }
ed81751999-12-11Martin Stjernholm  // vvv Using interpreter lock from here.
0ebc9d2000-02-15Martin Stjernholm  PCacheObj pco = _p_cache[tset];
1b2b752000-01-07Martin Stjernholm  if (pco && pco->tag_set_gen == tset->generation) {
ed81751999-12-11Martin Stjernholm  if ((p = pco->free_parser)) { pco->free_parser = p->_next_free; // ^^^ Using interpreter lock to here. p->data_callback = p->compile = 0;
7dd3f82001-04-18Martin Stjernholm  p->reset (ctx, this_object(), tset, @parser_args);
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG p->__object_marker->create (p); #endif
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  else // ^^^ Using interpreter lock to here. if (pco->clone_parser)
7dd3f82001-04-18Martin Stjernholm  p = pco->clone_parser->clone (ctx, this_object(), tset, @parser_args); else if ((p = parser_prog (ctx, this_object(), tset, @parser_args))->clone) {
ed81751999-12-11Martin Stjernholm  // pco->clone_parser might already be initialized here due // to race, but that doesn't matter.
ec027c2000-03-16Martin Stjernholm  p->context = 0; // Don't leave the context in the clone master.
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG p->__object_marker->create (p); #endif
7dd3f82001-04-18Martin Stjernholm  p = (pco->clone_parser = p)->clone (ctx, this_object(), tset, @parser_args);
ec027c2000-03-16Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  else { // ^^^ Using interpreter lock to here. pco = PCacheObj();
1b2b752000-01-07Martin Stjernholm  pco->tag_set_gen = tset->generation;
ed81751999-12-11Martin Stjernholm  _p_cache[tset] = pco; // Might replace an object due to race, but that's ok.
7dd3f82001-04-18Martin Stjernholm  if ((p = parser_prog (ctx, this_object(), tset, @parser_args))->clone) {
ed81751999-12-11Martin Stjernholm  // pco->clone_parser might already be initialized here due // to race, but that doesn't matter.
ec027c2000-03-16Martin Stjernholm  p->context = 0; // Don't leave the context in the clone master.
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG p->__object_marker->create (p); #endif
7dd3f82001-04-18Martin Stjernholm  p = (pco->clone_parser = p)->clone (ctx, this_object(), tset, @parser_args);
ec027c2000-03-16Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm 
b2d01b2000-02-15Martin Stjernholm  if (ctx->tag_set == tset && p->add_runtime_tag && sizeof (ctx->runtime_tags))
87fb7e2000-03-25Martin Stjernholm  foreach (values (ctx->runtime_tags), Tag tag)
0ebc9d2000-02-15Martin Stjernholm  p->add_runtime_tag (tag);
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  else { if ((p = free_parser)) { // Relying on interpreter lock here. free_parser = p->_next_free; p->data_callback = p->compile = 0;
7dd3f82001-04-18Martin Stjernholm  p->reset (ctx, this_object(), @parser_args);
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG p->__object_marker->create (p); #endif
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm 
ed81751999-12-11Martin Stjernholm  else if (clone_parser) // Relying on interpreter lock here.
7dd3f82001-04-18Martin Stjernholm  p = clone_parser->clone (ctx, this_object(), @parser_args);
0ebc9d2000-02-15Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  else if ((p = parser_prog (ctx, this_object(), @parser_args))->clone) {
ed81751999-12-11Martin Stjernholm  // clone_parser might already be initialized here due to race, // but that doesn't matter.
ec027c2000-03-16Martin Stjernholm  p->context = 0; // Don't leave the context in the clone master.
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG p->__object_marker->create (p); #endif
7dd3f82001-04-18Martin Stjernholm  p = (clone_parser = p)->clone (ctx, this_object(), @parser_args);
ec027c2000-03-16Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  }
0ebc9d2000-02-15Martin Stjernholm  p->_parent = parent;
ed81751999-12-11Martin Stjernholm  return p; }
01e43b2000-01-14Martin Stjernholm  mixed eval (string in, void|Context ctx, void|TagSet tag_set, void|Parser|PCode parent, void|int dont_switch_ctx)
51f88b2000-06-23Martin Stjernholm  //! Parses and evaluates the value in the given string. If a context //! isn't given, the current one is used. The current context and //! ctx are assumed to be the same if dont_switch_ctx is nonzero.
ed81751999-12-11Martin Stjernholm  { mixed res; if (!ctx) ctx = get_context();
7dd3f82001-04-18Martin Stjernholm  if (parser_prog == PNone) res = in;
ed81751999-12-11Martin Stjernholm  else {
0ebc9d2000-02-15Martin Stjernholm  Parser p = get_parser (ctx, tag_set, parent);
01e43b2000-01-14Martin Stjernholm  p->_parent = parent;
ed81751999-12-11Martin Stjernholm  if (dont_switch_ctx) p->finish (in); // Optimize the job in p->write_end(). else p->write_end (in); res = p->eval();
ec027c2000-03-16Martin Stjernholm  if (p->reset) {
140f362000-10-19Martin Stjernholm  p->context = p->recover_errors = p->_parent = 0;
f02d002000-03-18Martin Stjernholm #ifdef RXML_OBJ_DEBUG p->__object_marker->create (p); #endif
ed81751999-12-11Martin Stjernholm  if (_p_cache) {
9477392000-03-20Martin Stjernholm  if (PCacheObj pco = _p_cache[tag_set || ctx->tag_set]) { // Relying on interpreter lock here. p->_next_free = pco->free_parser; pco->free_parser = p; }
ed81751999-12-11Martin Stjernholm  } else { // Relying on interpreter lock in this block. p->_next_free = free_parser; free_parser = p; }
ec027c2000-03-16Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  } if (ctx->type_check) type_check (res); return res; }
7dd3f82001-04-18Martin Stjernholm  //! The parser for this type. Must not be changed after being //! initialized when the type is created; use @[`()] instead. program/*(Parser)HMM*/ parser_prog = lambda () { // Kludge to execute some code at object creation without // bothering with create(), which can be overridden. if (!reg_types[this_object()->name]) reg_types[this_object()->name] = this_object(); return PNone; }(); array(mixed) parser_args = ({}); //! The arguments to the parser @[parser_prog]. Must not be changed //! after being initialized when the type is created; use @[`()] //! instead.
151fa92001-04-19Johan Sundström  //(!) Interface:
0c9e5b2001-03-13Martin Stjernholm  //! @decl constant string name; //! //! Unique type identifier. Required and considered constant. //! //! If it contains a "/", it's treated as a MIME type and should //! then follow the rules for a MIME type with subtype (RFC 2045, //! section 5.1). Among other things, that means that the valid //! characters are, besides the "/", US-ASCII values 33-126 except //! "(", ")", "<", ">", "@@", ",", ";", ":", "\", """, "/", "[", //! "]", "?" and "=". //! //! If it doesn't contain a "/", it's treated as a type outside the //! MIME system, e.g. "int" for an integer. Any type that can be //! mapped to a MIME type should be so. constant sequential = 0; //! Nonzero if data of this type is sequential, defined as:
151fa92001-04-19Johan Sundström  //! @list ul //! @item //! One or more data items can be concatenated with `+. //! @item //! (Sane) parsers are homomorphic on the type, i.e. //! @code{eval("da") + eval("ta") == eval("da" + "ta")@} and //! @code{eval("data") + eval("") == eval("data")@} provided the //! data is only split between (sensibly defined) atomic elements. //! @endlist
0c9e5b2001-03-13Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  //! @decl constant mixed empty_value; //!
0c9e5b2001-03-13Martin Stjernholm  //! The empty value, i.e. what eval ("") would produce.
7dd3f82001-04-18Martin Stjernholm  Type supertype; //! The supertype for this type. //! //! The supertype should be able to express any value that this type //! can express without (or with at most negligible) loss of //! information, but not necessarily on the same form. This is //! different from the type trees described by @[conversion_type], //! although it's always preferable if a supertype also can be used //! as @[conversion_type] in its subtypes. //! //! There are however exceptions to the rule above about information //! preservation, since it's impossible to satisfy it for //! sufficiently generic types. E.g. the type @[RXML.t_any] cannot //! express hardly any value (except @[RXML.nil]) without loss of //! information, but still it should be used as the supertype as the //! last resort if no better alternative exists.
0c9e5b2001-03-13Martin Stjernholm  Type conversion_type; //! The type to use as middlestep in indirect conversions. Required //! and considered constant. It should be zero (not @[RXML.t_any]) //! if there is no sensible conversion type. The @[conversion_type] //! references must never produce a cycle between types. //! //! It's values of the conversion type that @[decode] tries to //! return, and also that @[encode] must handle without resorting to //! indirect conversions. It's used as a fallback between types //! which doesn't have explicit conversion functions for each other; //! see @[indirect_convert]. //!
151fa92001-04-19Johan Sundström  //! @note //! The trees described by the conversion types aren't proper type //! hierarchies in the sense of value set sizes, as opposed to the //! relations expressed by the glob patterns in @[name]. The
0c9e5b2001-03-13Martin Stjernholm  //! conversion type is chosen purely on pragmatic grounds for doing //! indirect conversions. It's better if the conversion type is a //! supertype (i.e. has a larger value set), but in lack of proper //! supertypes it may also be a subtype, to make it possible to use //! indirect conversion for at least a subset of the values. See the //! example in @[decode].
7dd3f82001-04-18Martin Stjernholm  //! @decl optional constant int free_text; //! //! Nonzero constant if the type keeps the free text between parsed //! tokens, e.g. the plain text between tags in XML. The type must //! be sequential and use strings. Must be zero when //! @[handle_literals] is nonzero. //! @decl optional constant int handle_literals; //! //! Nonzero constant if the type can parse string literals into //! values. This will have the effect that any free text will be //! passed to @[encode] without a specified type. @[free_text] is //! assumed to be zero when this is nonzero. //!
151fa92001-04-19Johan Sundström  //! @note //! Parsers will commonly trim leading and trailing whitespace from //! the literal before passing it to @[encode].
0c9e5b2001-03-13Martin Stjernholm  //! @decl optional constant int entity_syntax; //! //! Nonzero constant for all types with string values that use //! entity syntax, like XML or HTML. void type_check (mixed val, void|string msg, mixed... args); //! Checks whether the given value is a valid one of this type. //! Errors are thrown as RXML parse errors, and in that case @[msg],
7dd3f82001-04-18Martin Stjernholm  //! if given, is prepended to the error message with ": " in //! between. If there are any more arguments on the line, the //! prepended message is formatted with //! @tt{sprintf (@[msg], @@@[args])@}. There's a @[type_check_error] //! helper that can be used to handle the message formatting and //! error throwing.
0c9e5b2001-03-13Martin Stjernholm  mixed encode (mixed val, void|Type from); //! Converts the given value to this type. //! //! If the @[from] type isn't given, the function should try to //! convert it to the required internal form for this type, using a //! cast as a last resort if the type of @[val] isn't recognized. It //! should then encode it, if necessary, as though it were a literal //! (typically only applicable for types using strings with //! encodings, like the @[RXML.t_xml] type). Any conversion error, //! including in the cast, should be thrown as an RXML parse error. //! //! If the @[from] type is given, it's the type of the value. If //! it's @[RXML.t_any], the function should (superficially) check //! the value and return it without conversion. Otherwise, if the //! encode function doesn't have routines to explicitly handle a //! conversion from that type, then indirect conversion using //! @[conversion_type] should be done. The @[indirect_convert] //! function implements that. The encode function should at least be //! able to convert values of @[conversion_type] to this type, or //! else throw an RXML parse error if it isn't possible. //!
151fa92001-04-19Johan Sundström  //! @note //! Remember to override @[convertible] if this function can convert //! directly from any type besides the conversion type. Don't count //! on that the conversion type tree is constant so that the default //! implementation would return true anyway.
0c9e5b2001-03-13Martin Stjernholm  optional mixed decode (mixed val); //! Converts the value, which is of this type, to a value of type //! @[conversion_type]. If this function isn't defined, then any //! value of this type works directly in the conversion type. //! //! If the type can't be converted, an RXML parse error should be //! thrown. That might happen if the value contains markup or //! similar that can't be represented in the conversion type. //!
f55a012001-03-14Martin Stjernholm  //! E.g. in a type for XML markup which have @[RXML.t_text] as the
0c9e5b2001-03-13Martin Stjernholm  //! conversion type, this function should return a literal string //! only if the text doesn't contain tags, otherwise it should throw
ec860d2001-03-13Martin Stjernholm  //! an error. It should never both decode "&lt;" to "<" and just //! leave literal "<" in the string. It should also not parse the //! value with some evaluating parser (see @[get_parser]) since the //! value should only change representation. (This example shows
0c9e5b2001-03-13Martin Stjernholm  //! that a more fitting conversion type for XML markup would be a //! DOM type that can represent XML node trees, since values //! containing tags could be decoded then.) Type clone() //! Returns a copy of the type. Exists only for overriding purposes; //! it's normally not useful to call this since type objects are //! shared. { Type newtype = object_program ((object(this_program)) this_object())();
7dd3f82001-04-18Martin Stjernholm  newtype->parser_prog = parser_prog; newtype->parser_args = parser_args;
0c9e5b2001-03-13Martin Stjernholm  newtype->_t_obj_cache = _t_obj_cache; return newtype; } string format_tag (string|Tag tag, void|mapping(string:string) args, void|string content, void|int flags) //! Returns a formatted tag according to the type. tag is either a //! tag object or the name of the tag. Throws an error if this type //! cannot format tags. { parse_error ("Cannot format tags with type %s.\n", this_object()->name); } string format_entity (string entity) //! Returns a formatted entity according to the type. Throws an //! error if this type cannot format entities. { parse_error ("Cannot format entities with type %s.\n", this_object()->name); }
fe628d2001-03-01Martin Stjernholm  static final void type_check_error (string msg1, array args1, string msg2, mixed... args2) //! Helper intended to format and throw an RXML parse error in //! @[type_check]. Assuming the same argument names as in the //! @[type_check] declaration, use like this: //! //! @example //! if (value is bogus) //! type_check_error (msg, args, "My error message with %O %O.\n", foo, bar);
151fa92001-04-19Johan Sundström  //! @endexample
fe628d2001-03-01Martin Stjernholm  { if (sizeof (args2)) msg2 = sprintf (msg2, @args2); if (msg1) { if (sizeof (args1)) msg1 = sprintf (msg1, @args1); parse_error (msg1 + ": " + msg2); } else parse_error (msg2); } /*static*/ final mixed indirect_convert (mixed val, Type from) //! Converts @[val], which is a value of the type @[from], to this //! type. Uses indirect conversion via @[conversion_type] as //! necessary. Only intended as a helper function for @[encode], so //! it won't do a direct conversion from @[conversion_type] to this //! type. Throws RXML parse error on any conversion error. {
17c40b2001-03-13Martin Stjernholm  if (conversion_type) { if (from->conversion_type) { string fromconvname = from->conversion_type->name; if (conversion_type->name == fromconvname) return encode (from->decode ? from->decode (val) : val, conversion_type); if (this_object()->name == fromconvname) return from->decode ? from->decode (val) : val;
fe628d2001-03-01Martin Stjernholm  }
17c40b2001-03-13Martin Stjernholm  string name = this_object()->name; if (name == from->name) return val; // The following is not terribly efficient, but most situations // should be handled by the special cases above. int levels = 1; for (Type conv = from->conversion_type; conv; conv = conv->conversion_type, levels++) if (conv->name == name) { while (levels--) { val = from->decode ? from->decode (val) : val; from = from->conversion_type; } return val; } if (conversion_type->conversion_type && conversion_type->conversion_type->name == from->name) // indirect_convert should never do the job of encode.
fe628d2001-03-01Martin Stjernholm  return encode (conversion_type->encode (val, from), conversion_type);
17c40b2001-03-13Martin Stjernholm  else { #ifdef MODULE_DEBUG if (conversion_type->name == from->name) error ("This function shouldn't be used to convert " "from the conversion type %s to %s; use encode() for that.\n", conversion_type->name, this_object()->name); #endif
fe628d2001-03-01Martin Stjernholm  return encode (conversion_type->indirect_convert (val, from), conversion_type);
17c40b2001-03-13Martin Stjernholm  } } parse_error ("Cannot convert type %s to %s.\n", from->name, this_object()->name);
fe628d2001-03-01Martin Stjernholm  }
151fa92001-04-19Johan Sundström  //(!) Internals:
ed81751999-12-11Martin Stjernholm  /*private*/ mapping(program:Type) _t_obj_cache; // To avoid creating new type objects all the time in `(). // Cache used for parsers that doesn't depend on the tag set. private Parser clone_parser; // Used with Parser.clone(). private Parser free_parser; // The list of objects to reuse with Parser.reset(). // Cache used for parsers that depend on the tag set. /*private*/ mapping(TagSet:PCacheObj) _p_cache;
2bd21a2000-01-05Martin Stjernholm 
f02d002000-03-18Martin Stjernholm  MARK_OBJECT_ONLY;
ec027c2000-03-16Martin Stjernholm 
fe628d2001-03-01Martin Stjernholm  string _sprintf() {return "RXML.Type(" + this_object()->name + ")" + OBJ_COUNT;}
ec027c2000-03-16Martin Stjernholm } static class PCacheObj { int tag_set_gen; Parser clone_parser; Parser free_parser;
ed81751999-12-11Martin Stjernholm }
151fa92001-04-19Johan Sundström //(!) Special types:
fe628d2001-03-01Martin Stjernholm 
23a0882000-08-12Martin Stjernholm TAny t_any = TAny();
46c68f2000-08-12Martin Stjernholm //! A completely unspecified nonsequential type. Every type is a
0c9e5b2001-03-13Martin Stjernholm //! subtype of this one. //! //! This type is also special in that any value can be converted to //! and from this type without the value getting changed in any way, //! which means that the meaning of a value might change when this //! type is used as a middle step. //!
f55a012001-03-14Martin Stjernholm //! E.g if @tt{"<foo>"@} of type @[RXML.t_text] is converted directly
0c9e5b2001-03-13Martin Stjernholm //! to @[RXML.t_xml], it's quoted to @tt{"&lt;foo&gt;"@}, since
f55a012001-03-14Martin Stjernholm //! @[RXML.t_text] always is literal text. However if it's first
0c9e5b2001-03-13Martin Stjernholm //! converted to @[RXML.t_any] and then to @[RXML.t_xml], it still //! remains @tt{"<foo>"@}, which then carries a totally different //! meaning.
23a0882000-08-12Martin Stjernholm  static class TAny
01e43b2000-01-14Martin Stjernholm { inherit Type;
7dd3f82001-04-18Martin Stjernholm  constant name = "any"; constant supertype = 0;
fe628d2001-03-01Martin Stjernholm  constant conversion_type = 0;
7dd3f82001-04-18Martin Stjernholm  constant handle_literals = 1;
fe628d2001-03-01Martin Stjernholm  mixed encode (mixed val, void|Type from) { return val; }
f02d002000-03-18Martin Stjernholm  string _sprintf() {return "RXML.t_any" + OBJ_COUNT;}
01e43b2000-01-14Martin Stjernholm }
23a0882000-08-12Martin Stjernholm TNil t_nil = TNil();
46c68f2000-08-12Martin Stjernholm //! A sequential type accepting only the value nil. This type is by
fe628d2001-03-01Martin Stjernholm //! definition a subtype of every other type.
23a0882000-08-12Martin Stjernholm  static class TNil
49897a2000-02-13Martin Stjernholm { inherit Type;
46c68f2000-08-12Martin Stjernholm  constant name = "nil";
49897a2000-02-13Martin Stjernholm