a08cd72001-06-09Martin Stjernholm // $Id: module.pmod,v 1.162 2001/06/09 00:33:24 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>
e8dffa2001-05-14Per Hedbor 
b483382000-06-30Martin Stjernholm #include <request_trace.h>
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm #define MAGIC_HELP_ARG // #define OBJ_COUNT_DEBUG // #define RXML_VERBOSE
4293072001-05-08Martin Stjernholm // #define PROFILE_PARSER
e8dffa2001-05-14Per Hedbor 
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
d52f8f2001-05-19Martin Stjernholm #ifdef RXML_VERBOSE # define TAG_DEBUG_TEST(flags) 1 #elif defined (DEBUG) # define TAG_DEBUG_TEST(flags) ((flags) & FLAG_DEBUG) #else # define TAG_DEBUG_TEST(flags) 0 #endif
ac69772000-09-08Martin Stjernholm #ifdef DEBUG
4c2b512001-05-17Martin Stjernholm # define TAG_DEBUG(frame, msg, args...) \
d52f8f2001-05-19Martin Stjernholm  (TAG_DEBUG_TEST(frame->flags) && report_debug ("%O: " + (msg), (frame), args), 0)
7dd3f82001-04-18Martin Stjernholm # define DO_IF_DEBUG(code...) code
ac69772000-09-08Martin Stjernholm #else
4c2b512001-05-17Martin Stjernholm # define TAG_DEBUG(frame, msg, args...) 0
7dd3f82001-04-18Martin Stjernholm # define DO_IF_DEBUG(code...)
ac69772000-09-08Martin Stjernholm #endif
d52f8f2001-05-19Martin Stjernholm #ifdef MODULE_DEBUG # define DO_IF_MODULE_DEBUG(code...) code #else # define DO_IF_MODULE_DEBUG(code...) #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)
d4768a2001-05-18Martin Stjernholm #define UNWIND_STATE mapping(string|object:mixed|array)
a08cd72001-06-09Martin Stjernholm #define EVAL_ARGS_FUNC function(Context:mapping(string:mixed))
7dd3f82001-04-18Martin Stjernholm 
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  {
a08cd72001-06-09Martin Stjernholm  // Note: Code duplication in Frame._eval_args and Frame._prepare.
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
a08cd72001-06-09Martin Stjernholm  foreach (indices (args), string arg) args[arg] = (atypes[arg] || def_arg_type)->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 
a08cd72001-06-09Martin Stjernholm #define MAKE_FRAME(_frame, _ctx, _parser, _args) \
7dd3f82001-04-18Martin Stjernholm  make_new_frame: do { \
a08cd72001-06-09Martin Stjernholm  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; \
7dd3f82001-04-18Martin Stjernholm  break make_new_frame; \ } \
a08cd72001-06-09Martin Stjernholm  _frame = `() (0, nil); \
4c2b512001-05-17Martin Stjernholm  DO_IF_DEBUG( \
a08cd72001-06-09Martin Stjernholm  if (_args && ([mapping] (mixed) _args)["-debug-tag-"]) { \ _frame->flags |= FLAG_DEBUG; \ m_delete (_args, "-debug-tag-"); \
4c2b512001-05-17Martin Stjernholm  } \ ); \
a08cd72001-06-09Martin Stjernholm  TAG_DEBUG (_frame, "New frame\n"); \
7dd3f82001-04-18Martin Stjernholm  } while (0)
a08cd72001-06-09Martin Stjernholm #define EVAL_FRAME(_frame, _ctx, _parser, _type, _args, _content, _res) \
4c2b512001-05-17Martin Stjernholm  eval_frame: do { \ mixed err = catch { \
a08cd72001-06-09Martin Stjernholm  EVAL_ARGS_FUNC argfunc; \ if (!_frame->args) \ argfunc = _frame->_prepare (_ctx, _type, _args); \ _res = _frame->_eval (_ctx, _parser, _type, _content || ""); \ if (_parser->p_code) { \ _frame->args = argfunc; \ _parser->p_code->add (_frame); \ } \
4c2b512001-05-17Martin Stjernholm  break eval_frame; \ }; \ \ if (objectp (err) && ([object] err)->thrown_at_unwind) { \
a08cd72001-06-09Martin Stjernholm  UNWIND_STATE ustate = _ctx->unwind_state; \ if (!ustate) ustate = _ctx->unwind_state = ([]); \
4c2b512001-05-17Martin Stjernholm  DO_IF_DEBUG ( \
a08cd72001-06-09Martin Stjernholm  if (err != _frame) \
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Unexpected unwind object catched.\n"); \
a08cd72001-06-09Martin Stjernholm  if (ustate[_parser]) \
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Clobbering unwind state for parser.\n"); \
4c2b512001-05-17Martin Stjernholm  ); \
a08cd72001-06-09Martin Stjernholm  ustate[_parser] = ({_frame}); \ throw (_parser); \
4c2b512001-05-17Martin Stjernholm  } \ else { \ /* Will rethrow unknown errors. */ \
a08cd72001-06-09Martin Stjernholm  _ctx->handle_exception (err, _parser); \ _res = nil; \
4c2b512001-05-17Martin Stjernholm  } \
7dd3f82001-04-18Martin Stjernholm  } 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; 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();
d4768a2001-05-18Martin Stjernholm  else if (parser->p_code) parser->p_code_literal();
24f9e82000-08-05Martin Stjernholm 
56532d1999-12-19Martin Stjernholm  Context ctx = parser->context;
9d80e82000-02-11Martin Stjernholm 
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))
a08cd72001-06-09Martin Stjernholm  frame->raw_tag_text = parser->current_input();
51f88b2000-06-23Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  mixed result; EVAL_FRAME (frame, ctx, parser, type, args, content, result);
56532d1999-12-19Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  if (result != nil) { if (type->free_text && !parser->p_code) return ({result});
7dd3f82001-04-18Martin Stjernholm  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();
d4768a2001-05-18Martin Stjernholm  else if (parser->p_code) parser->p_code_literal();
7dd3f82001-04-18Martin Stjernholm 
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;
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);
a08cd72001-06-09Martin Stjernholm  if (result != nil) { if (type->free_text && !parser->p_code) return ({result});
7dd3f82001-04-18Martin Stjernholm  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))
d52f8f2001-05-19Martin Stjernholm  fatal_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).
a08cd72001-06-09Martin Stjernholm  //! //! @note //! This is intended for situations where you get a variable //! reference on the dot form in e.g. user input. In other cases, //! when the scope and variable is known, it's more efficient to use //! @[get_var].
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))
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Invalid variable specifier.\n");
1cfbeb2000-11-06Martin Stjernholm #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).
a08cd72001-06-09Martin Stjernholm  //! //! @note //! This is intended for situations where you get a variable //! reference on the dot form in e.g. user input. In other cases, //! when the scope and variable is known, it's more efficient to use //! @[set_var].
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))
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Invalid variable specifier.\n");
1cfbeb2000-11-06Martin Stjernholm #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).
a08cd72001-06-09Martin Stjernholm  //! //! @note //! This is intended for situations where you get a variable //! reference on the dot form in e.g. user input. In other cases, //! when the scope and variable is known, it's more efficient to use //! @[delete_var].
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
d52f8f2001-05-19Martin Stjernholm  if (!oldvars) fatal_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["_"]) {
d52f8f2001-05-19Martin Stjernholm  string scope_name = search (scopes, vars); do
3e59342000-01-18Martin Stjernholm  if (scope_name != "_") return scope_name;
d52f8f2001-05-19Martin Stjernholm  while ((scope_name = search (scopes, vars, 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
d52f8f2001-05-19Martin Stjernholm  if (!frame->vars) fatal_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  } }
d52f8f2001-05-19Martin Stjernholm #define ENTER_SCOPE(ctx, frame) \ (frame->vars && frame->vars != ctx->scopes["_"] && ctx->enter_scope (frame)) #define LEAVE_SCOPE(ctx, frame) \ (frame->vars && ctx->leave_scope (frame))
ed81751999-12-11Martin Stjernholm 
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. {
a08cd72001-06-09Martin Stjernholm  String.Buffer txt = String.Buffer(); txt->add (no_msg ? "" : "RXML" + (type ? " " + type : "") + " error");
8cb24d2000-02-13Martin Stjernholm  if (context) {
a08cd72001-06-09Martin Stjernholm  if (!no_msg) txt->add (": " + (msg || "(no error message)\n")); if (current_var) txt->add (" | &" + current_var + ";\n");
8cb24d2000-02-13Martin Stjernholm  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)
a08cd72001-06-09Martin Stjernholm  txt->add (" | <?" + name + "?>\n");
24f9e82000-08-05Martin Stjernholm  else {
a08cd72001-06-09Martin Stjernholm  txt->add (" | <" + name); if (mappingp (f->args))
24f9e82000-08-05Martin Stjernholm  foreach (sort (indices (f->args)), string arg) { mixed val = f->args[arg];
a08cd72001-06-09Martin Stjernholm  txt->add (" " + arg + "="); if (arrayp (val)) txt->add (map (val, error_print_val) * ","); else txt->add (error_print_val (val));
24f9e82000-08-05Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  else txt->add (" (no argmap)"); txt->add (">\n");
24f9e82000-08-05Martin Stjernholm  }
8cb24d2000-02-13Martin Stjernholm  } } else
a08cd72001-06-09Martin Stjernholm  if (!no_msg) txt->add (" (no context): " + (msg || "(no error message)\n")); return txt->get();
8cb24d2000-02-13Martin Stjernholm  } 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;
d4768a2001-05-18Martin Stjernholm //(!) If set, the arguments to the tag need not be the same (using //(!) @[equal]) as the cached args.
ed81751999-12-11Martin Stjernholm  constant FLAG_CACHE_DIFF_CONTENT = 0x00020000;
d4768a2001-05-18Martin Stjernholm //(!) If set, the content need not be the same.
ed81751999-12-11Martin Stjernholm  constant FLAG_CACHE_DIFF_RESULT_TYPE = 0x00040000;
d4768a2001-05-18Martin Stjernholm //(!) If set, the result type need not be the same. (Typically //(!) not useful unless @[cached_return] is used.)
ed81751999-12-11Martin Stjernholm 
8cb24d2000-02-13Martin Stjernholm constant FLAG_CACHE_DIFF_VARS = 0x00080000;
d4768a2001-05-18Martin 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.
ed81751999-12-11Martin Stjernholm 
46c68f2000-08-12Martin Stjernholm constant FLAG_CACHE_DIFF_TAG_INSTANCE = 0x00100000;
d4768a2001-05-18Martin Stjernholm //(!) 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;
d4768a2001-05-18Martin 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;
d4768a2001-05-18Martin Stjernholm  //! The @[RXML.Tag] object this frame was created from.
ed81751999-12-11Martin Stjernholm  int flags;
d4768a2001-05-18Martin Stjernholm  //! Various bit flags that affect parsing. See the @tt{FLAG_*@} //! constants. It's copied from @[Tag.flag] when the frame is //! created.
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  mapping(string:mixed)|EVAL_ARGS_FUNC 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
d4768a2001-05-18Martin Stjernholm  //! called. Not set for processing instruction (@[FLAG_PROC_INSTR])
24f9e82000-08-05Martin Stjernholm  //! tags.
a08cd72001-06-09Martin Stjernholm  //! //! This variable is also used to hold a function that generates the //! argument mapping between evaluations of the frame. It never does //! when any of the callbacks except @[cached_return] are called, //! though.
ed81751999-12-11Martin Stjernholm  Type content_type; //! The type of the content.
a08cd72001-06-09Martin Stjernholm  mixed|PCode content = nil;
d4768a2001-05-18Martin Stjernholm  //! The content, if any. Set before @[do_process] and @[do_return] //! are called. Initialized to @[RXML.nil] every time the frame //! executed.
a08cd72001-06-09Martin Stjernholm  //! //! This variable is also used to hold an unevaluated representation //! of the content between evaluations of the frame. It never does //! when any of the callbacks except @[cached_return] are called, //! though.
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
d4768a2001-05-18Martin Stjernholm  //! @[do_return] for details.
46c68f2000-08-12Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! This is set by the type inference from @[Tag.result_types] before
46c68f2000-08-12Martin Stjernholm  //! 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;
d4768a2001-05-18Martin Stjernholm  //! The result, which is assumed to be either @[RXML.nil] or a valid
46c68f2000-08-12Martin Stjernholm  //! value according to result_type. The exec arrays returned by e.g.
d4768a2001-05-18Martin Stjernholm  //! @[do_return] changes this. It may also be set directly. //! Initialized to @[RXML.nil] every time the frame executed.
46c68f2000-08-12Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! 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 
a08cd72001-06-09Martin Stjernholm  //! @decl optional mapping(string:mixed) vars;
d4768a2001-05-18Martin Stjernholm  //!
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
d4768a2001-05-18Martin Stjernholm  //! @[FLAG_PARENT_SCOPE]).
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  //! @decl optional string scope_name;
d4768a2001-05-18Martin Stjernholm  //!
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 
a08cd72001-06-09Martin Stjernholm  //! @decl optional TagSet additional_tags;
d4768a2001-05-18Martin Stjernholm  //!
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.
a08cd72001-06-09Martin Stjernholm  //! @decl optional TagSet local_tags;
d4768a2001-05-18Martin Stjernholm  //!
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.
a08cd72001-06-09Martin Stjernholm  //! @decl optional Frame parent_frame;
d4768a2001-05-18Martin Stjernholm  //!
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
d4768a2001-05-18Martin Stjernholm  //! @[additional_tags] or @[local_tags]. Useful to access the //! "mother tag" from the subtags it defines.
689dc62000-08-31Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  //! @decl optional string raw_tag_text;
d4768a2001-05-18Martin Stjernholm  //!
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.
a08cd72001-06-09Martin Stjernholm  //! @decl optional array do_enter (RequestID id); //! @decl optional array do_process (RequestID id, void|mixed piece); //! @decl optional array do_return (RequestID id);
291ade2000-01-25Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! @[do_enter] is called first thing when processing the tag. //! @[do_process] is called after (some of) the content has been //! processed. @[do_return] is called lastly before leaving the tag. //! //! For tags that loops more than one time (see @[do_iterate]): //! @[do_enter] is only called initially before the first call to //! @[do_iterate]. @[do_process] is called after each iteration. //! @[do_return] is called after the last call to @[do_process].
ed81751999-12-11Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! The @[result_type] variable is set to the type of result the //! parser 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 added to the surrounding content, otherwise //! it is used as value of the content, and there's an error of the //! content has a value already. 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
d4768a2001-05-18Martin Stjernholm  //! A so-called exec array to be handled by the parser. The
151fa92001-04-19Johan Sundström  //! 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)
a08cd72001-06-09Martin Stjernholm  //! A response mapping which will be returned instead of the //! evaluated page. The evaluation is stopped immediately after //! this. FIXME: Not yet implemented.
151fa92001-04-19Johan Sundström  //! @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
d4768a2001-05-18Martin Stjernholm  //! @[raw_tag_text] variable. For further details see the doc //! for @[propagate_tag] in this class.
151fa92001-04-19Johan Sundström  //! @endlist //! @item 0 //! Do nothing special. Exits the tag when used from
d4768a2001-05-18Martin Stjernholm  //! @[do_process] and @[FLAG_STREAM_RESULT] is set.
151fa92001-04-19Johan Sundström  //! @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
d4768a2001-05-18Martin Stjernholm  //! literal strings and @[RXML.Frame] objects where parsing (or, //! more accurately, evaluation) needs to be done.
291ade2000-01-25Martin Stjernholm  //! //! If an array instead of a function is given, the array is handled
d4768a2001-05-18Martin 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  //!
d4768a2001-05-18Martin Stjernholm  //! If there is no @[do_return] and the result from parsing the //! content is not @[RXML.nil], it's assigned to or added to the //! @[result] variable. Assignment is used if the content type is //! nonsequential, addition otherwise.
ed81751999-12-11Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! Regarding @[do_process] only:
9477392000-03-20Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! Normally the @[content] variable is set to the parsed content of //! 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  //!
d4768a2001-05-18Martin 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 never touched. @[do_process] is also called //! "normally" with no @[piece] argument afterwards. Note that tags //! that support streaming mode might still be used nonstreaming (it //! might also vary between iterations).
ed81751999-12-11Martin Stjernholm  //!
d4768a2001-05-18Martin Stjernholm  //! As long as @[FLAG_STREAM_RESULT] is set, @[do_process] will be
91c8982000-03-11Martin Stjernholm  //! 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 
a08cd72001-06-09Martin Stjernholm  //! @decl optional int do_iterate (RequestID id);
d4768a2001-05-18Martin Stjernholm  //!
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
d4768a2001-05-18Martin Stjernholm  //! @[do_process] is called.
291ade2000-01-25Martin Stjernholm  //!
d4768a2001-05-18Martin 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
291ade2000-01-25Martin Stjernholm  //! 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. //!
d4768a2001-05-18Martin Stjernholm  //! If @[do_iterate] is a positive integer, that many passes is done //! 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 
a08cd72001-06-09Martin Stjernholm  //! @decl optional int|function(RequestID:int) is_valid;
d4768a2001-05-18Martin Stjernholm  //!
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.
d4768a2001-05-18Martin Stjernholm  //! It's otherwise handled like @[do_return]. Note that the cached
ed81751999-12-11Martin Stjernholm  //! 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)
d4768a2001-05-18Martin Stjernholm  //! A wrapper for easy access to @[RXML.Context.get_var].
ffa6932000-02-20Martin Stjernholm  { 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)
d4768a2001-05-18Martin Stjernholm  //! A wrapper for easy access to @[RXML.Context.set_var].
ffa6932000-02-20Martin Stjernholm  { return get_context()->set_var (var, val, scope_name); }
938bfd2000-11-04Martin Stjernholm  final void delete_var (string var, void|string scope_name)
d4768a2001-05-18Martin Stjernholm  //! A wrapper for easy access to @[RXML.Context.delete_var].
ffa6932000-02-20Martin Stjernholm  { get_context()->delete_var (var, scope_name); }
bca67e2000-10-18Martin Stjernholm  void run_error (string msg, mixed... args)
d4768a2001-05-18Martin 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)
d4768a2001-05-18Martin 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)
d4768a2001-05-18Martin Stjernholm  //! Writes the message to the debug log if this tag has //! @[FLAG_DEBUG] set.
ac69772000-09-08Martin Stjernholm  {
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()
d4768a2001-05-18Martin 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
d4768a2001-05-18Martin Stjernholm  //! just stop, leaving the context intact. If this function returns, //! the parser is used in a place that doesn't support nonblocking, //! so it's then ok to 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
d4768a2001-05-18Martin Stjernholm  //! called @[suspend] will be called again.
ed81751999-12-11Martin Stjernholm  {
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
d4768a2001-05-18Martin Stjernholm  //! be a socket tag, i.e. to have @[FLAG_SOCKET_TAG] set (see //! @[Tag.plugin_name] for details). Indices are the //! @tt{plugin_name@} values for the plugin @[RXML.Tag] objects, //! values are the plugin objects themselves. Don't be destructive //! on the returned mapping.
cf3c902000-02-04Martin Stjernholm  { #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()
d4768a2001-05-18Martin Stjernholm  //! Returns the @[RXML.Tag] object the tag for this frame overrides, //! if any.
641d3c2000-10-12Martin Stjernholm  { 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
d4768a2001-05-18Martin Stjernholm  //! @[do_return] etc to propagate the tag to the next overridden tag
51f88b2000-06-23Martin Stjernholm  //! 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
d4768a2001-05-18Martin Stjernholm  //! 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.
51f88b2000-06-23Martin Stjernholm  {
a08cd72001-06-09Martin Stjernholm  Frame this = this_object();
4293072001-05-08Martin Stjernholm #ifdef MODULE_DEBUG #define CHECK_RAW_TEXT \
a08cd72001-06-09Martin Stjernholm  if (zero_type (this->raw_tag_text)) \
4293072001-05-08Martin Stjernholm  fatal_error ("The variable raw_tag_text must be defined.\n"); \
a08cd72001-06-09Martin Stjernholm  if (!stringp (this->raw_tag_text)) \
4293072001-05-08Martin Stjernholm  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); },
a08cd72001-06-09Martin Stjernholm  "?")->finish (this->raw_tag_text);
4293072001-05-08Martin Stjernholm  }) throw (err); #ifdef DEBUG if (!stringp (content)) fatal_error ("Failed to parse PI tag content for <?%s?> from %O.\n",
a08cd72001-06-09Martin Stjernholm  tag->name, this->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; }
a08cd72001-06-09Martin Stjernholm  })->finish (this->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",
a08cd72001-06-09Martin Stjernholm  tag->name, this->raw_tag_text);
4c2b512001-05-17Martin Stjernholm  if (!stringp (content) && !(flags & FLAG_EMPTY_ELEMENT))
4293072001-05-08Martin Stjernholm  fatal_error ("Failed to parse tag content for <%s> from %O.\n",
a08cd72001-06-09Martin Stjernholm  tag->name, this->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;
a08cd72001-06-09Martin Stjernholm  return this->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
a08cd72001-06-09Martin Stjernholm  s = t_xml (PEnt)->eval (this->raw_tag_text,
4293072001-05-08Martin Stjernholm  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; }
a08cd72001-06-09Martin Stjernholm  })->finish (this->raw_tag_text);
4293072001-05-08Martin Stjernholm  }) throw (err); #ifdef DEBUG if (!mappingp (args)) fatal_error ("Failed to parse tag args for <%s> from %O.\n",
a08cd72001-06-09Martin Stjernholm  tag->name, this->raw_tag_text);
4293072001-05-08Martin Stjernholm  if (!stringp (content)) fatal_error ("Failed to parse tag content for <%s> from %O.\n",
a08cd72001-06-09Martin Stjernholm  tag->name, this->raw_tag_text);
4293072001-05-08Martin Stjernholm #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
d52f8f2001-05-19Martin Stjernholm # define THIS_TAG_TOP_DEBUG(msg, args...) \ (TAG_DEBUG_TEST (flags) && report_debug ("%O: " + (msg), this_object(), args), 0) # define THIS_TAG_DEBUG(msg, args...) \ (TAG_DEBUG_TEST (flags) && report_debug ("%O: " + (msg), this_object(), args), 0) # define THIS_TAG_DEBUG_ENTER_SCOPE(ctx, this) \ if (this->vars && ctx->scopes["_"] != this->vars) \ THIS_TAG_DEBUG ("(Re)entering scope %O\n", this->scope_name) # define THIS_TAG_DEBUG_LEAVE_SCOPE(ctx, this) \ if (this->vars && ctx->scopes["_"] == this->vars) \ THIS_TAG_DEBUG ("Leaving scope %O\n", this->scope_name)
ac69772000-09-08Martin Stjernholm #else
4c2b512001-05-17Martin Stjernholm # define THIS_TAG_TOP_DEBUG(msg, args...) 0 # define THIS_TAG_DEBUG(msg, args...) 0
d52f8f2001-05-19Martin Stjernholm # define THIS_TAG_DEBUG_ENTER_SCOPE(ctx, this) 0 # define THIS_TAG_DEBUG_LEAVE_SCOPE(ctx, this) 0
ac69772000-09-08Martin Stjernholm #endif
d52f8f2001-05-19Martin Stjernholm #define SET_SEQUENTIAL(from, to, desc) \ do { \ THIS_TAG_DEBUG ("Adding %s to " desc "\n", \ utils->format_short (from)); \ to += from; \ } while (0) #define SET_NONSEQUENTIAL(from, to, to_type, desc) \ do { \ if (from != nil) { \ if (to != nil) \ parse_error ( \ "Cannot append another value %s to non-sequential " desc \ " of type %s.\n", utils->format_short (from), \ to_type->name); \ THIS_TAG_DEBUG ("Setting " desc " to %s\n", \ utils->format_short (from)); \ to = from; \ } \ } while (0) #define CONV_RESULT(from, from_type, to, to_type) \ do { \ if (from_type->name != to_type->name) { \ THIS_TAG_DEBUG ("Converting result from %s to %s of " \ "surrounding content\n", \ from_type->name, to_type->name); \ to = to_type->encode (from, from_type); \ } \ else to = from; \ } while (0)
a08cd72001-06-09Martin Stjernholm  private void _exec_array_fatal (string where, int pos, mixed elem, string msg, mixed... args) { if (sizeof (args)) msg = sprintf (msg, args); fatal_error ("Position %d in exec array from %s is %s: %s", pos, where, elem, msg); }; mixed _exec_array (Context ctx, TagSetParser|PCode evaler, array exec, string where)
ed81751999-12-11Martin Stjernholm  {
56532d1999-12-19Martin Stjernholm  Frame this = this_object();
d52f8f2001-05-19Martin Stjernholm  int i = 0, parent_scope = flags & FLAG_PARENT_SCOPE;
46c68f2000-08-12Martin Stjernholm  mixed res = nil;
ed81751999-12-11Martin Stjernholm  Parser subparser = 0; mixed err = catch {
ac69772000-09-08Martin Stjernholm  if (parent_scope) {
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG_LEAVE_SCOPE (ctx, this);
ac69772000-09-08Martin Stjernholm  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) {
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG ("Exec[%d]: String %s\n", i, utils->format_short (elem));
ed81751999-12-11Martin Stjernholm  piece = elem;
ac69772000-09-08Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm  else {
d52f8f2001-05-19Martin Stjernholm  subparser = result_type->get_parser (ctx, 0, evaler); if (evaler->recover_errors && !(flags & FLAG_DONT_RECOVER))
140f362000-10-19Martin Stjernholm  subparser->recover_errors = 1;
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG ("Exec[%d]: Parsing string %s with %O\n", i, utils->format_short (elem), 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":
a08cd72001-06-09Martin Stjernholm  THIS_TAG_DEBUG ("Exec[%d]: Response mapping\n", i); _exec_array_fatal (where, i, elem, "Response 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];
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG ("Exec[%d]: Verbatim value %s\n", i, utils->format_short (piece));
ac69772000-09-08Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  else _exec_array_fatal (where, i, elem, "Not exactly one value in multiset.\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) {
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("Exec[%d]: Evaluating frame %O\n", i, ([object] elem)); piece = ([object(Frame)] elem)->_eval (
a08cd72001-06-09Martin Stjernholm  ctx, evaler, result_type); // Might unwind. break;
5a455b2000-02-13Martin Stjernholm  } else if (([object] elem)->is_RXML_Parser) { // The subparser above unwound.
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("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.
a08cd72001-06-09Martin Stjernholm  break;
5a455b2000-02-13Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  _exec_array_fatal (where, i, elem, "Not a valid type.\n");
ed81751999-12-11Martin Stjernholm  }
d52f8f2001-05-19Martin Stjernholm  if (result_type->sequential) SET_SEQUENTIAL (piece, res, "result"); else SET_NONSEQUENTIAL (piece, result, result_type, "result");
ed81751999-12-11Martin Stjernholm  } if (result_type->sequential) result += res;
d52f8f2001-05-19Martin Stjernholm  else res = result;
ac69772000-09-08Martin Stjernholm  if (parent_scope) {
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this);
ac69772000-09-08Martin Stjernholm  ENTER_SCOPE (ctx, this); }
ed81751999-12-11Martin Stjernholm  return res; };
d52f8f2001-05-19Martin Stjernholm  if (result_type->sequential) result += res;
ac69772000-09-08Martin Stjernholm  if (parent_scope) {
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this);
ac69772000-09-08Martin Stjernholm  ENTER_SCOPE (ctx, this); }
ed81751999-12-11Martin Stjernholm 
56532d1999-12-19Martin Stjernholm  if (objectp (err) && ([object] err)->thrown_at_unwind) {
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("Exec: Interrupted at position %d\n", i);
d4768a2001-05-18Martin Stjernholm  UNWIND_STATE 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.
d52f8f2001-05-19Martin Stjernholm  if (result_type->name != evaler->type->name) res = evaler->type->encode (res, result_type);
ed81751999-12-11Martin Stjernholm  if (result_type->sequential)
d52f8f2001-05-19Martin Stjernholm  SET_SEQUENTIAL (ustate->stream_piece, res, "stream piece"); else SET_NONSEQUENTIAL (ustate->stream_piece, res, result_type, "stream piece"); 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) {
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("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) {
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("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) {
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("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  }
d52f8f2001-05-19Martin Stjernholm #define LOW_CALL_CALLBACK(res, cb, args...) \ do { \ THIS_TAG_DEBUG ("Calling " #cb "\n"); \ PROFILE_SWITCH (ctx, "rxml internal", "tag:" + tag->name); \ COND_PROF_ENTER(tag,tag->name,"tag"); \
a08cd72001-06-09Martin Stjernholm  res = (cb) (args); /* Might unwind. */ \
d52f8f2001-05-19Martin Stjernholm  COND_PROF_LEAVE(tag,tag->name,"tag"); \ PROFILE_SWITCH (ctx, "tag:" + tag->name, "rxml internal"); \ } while (0) #define EXEC_CALLBACK(ctx, evaler, exec, cb, args...) \ do { \ if (!exec) \ if (arrayp (cb)) { \ THIS_TAG_DEBUG ("Getting exec array from " #cb "\n"); \ exec = [array] cb; \ } \ else { \ LOW_CALL_CALLBACK (exec, cb, args); \ THIS_TAG_DEBUG ((exec ? "Exec array of length " + \ sizeof (exec) : "Zero") + \ " returned from " #cb "\n"); \ THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this); \ ENTER_SCOPE (ctx, this); \ if (evaler->is_RXML_Parser && ctx->new_runtime_tags) \ /* FIXME: When the evaler is a PCode object we should have a \ * debug check here that ensures that the same runtime tag \ * changes are done as in the first eval. */ \ _handle_runtime_tags (ctx, evaler); \ } \ } while (0) #define EXEC_ARRAY(ctx, evaler, exec, cb) \ do { \ if (exec) { \
a08cd72001-06-09Martin Stjernholm  mixed res = \ _exec_array (ctx, evaler, exec, #cb); /* Might unwind. */ \
d52f8f2001-05-19Martin Stjernholm  if (flags & FLAG_STREAM_RESULT) { \ DO_IF_DEBUG ( \ if (ctx->unwind_state) \ fatal_error ("Clobbering unwind_state to do streaming.\n"); \ if (piece != nil) \ fatal_error ("Thanks, we think about how nice it must be " \ "to play the harmonica...\n"); \ ); \ CONV_RESULT (res, result_type, res, type); \ ctx->unwind_state = (["stream_piece": res]); \ THIS_TAG_DEBUG ("Streaming %s from " #cb "\n", \ utils->format_short (res)); \ throw (this); \ } \ exec = 0; \ } \ } while (0)
a08cd72001-06-09Martin Stjernholm  private mapping(string:mixed) _eval_args (Context ctx, mapping(string:string) raw_args, mapping(string:Type) my_req_args) // Used for evaluating the dynamic arguments in the splice argument. // Destructive on raw_args. { // Note: Approximate code duplication in _prepare and Tag.eval_args(). mapping(string:Type) atypes = raw_args & (tag->req_arg_types | tag->opt_arg_types); if (my_req_args) { mapping(string:Type) missing = my_req_args - atypes; if (sizeof (missing)) parse_error ("Required " + (sizeof (missing) > 1 ? "arguments " + String.implode_nicely ( sort (indices (missing))) + " are" : "argument " + indices (missing)[0] + " is") + " missing.\n"); } #ifdef MODULE_DEBUG if (mixed err = catch { #endif foreach (indices (raw_args), string arg) { Type t = atypes[arg] || tag->def_arg_type; if (t->parser_prog != PNone) { Parser parser = t->get_parser (ctx, 0, 0); THIS_TAG_DEBUG ("Evaluating argument value %s with %O\n", utils->format_short (raw_args[arg]), parser); parser->finish (raw_args[arg]); // Should not unwind. raw_args[arg] = parser->eval(); // Should not unwind. THIS_TAG_DEBUG ("Setting dynamic argument %s to %s\n", utils->format_short (arg), utils->format_short (raw_args[arg])); t->give_back (parser); } } #ifdef MODULE_DEBUG }) { if (objectp (err) && ([object] err)->thrown_at_unwind) fatal_error ("Can't save parser state when evaluating dynamic arguments.\n"); throw_fatal (err); } #endif return raw_args; } EVAL_ARGS_FUNC _prepare (Context ctx, Type type, mapping(string:string) raw_args) // Evaluates raw_args simultaneously as generating the // EVAL_ARGS_FUNC function. The result of the evaluations is stored // in args. Might be destructive on raw_args. { Frame this = this_object(); mixed err = catch { #ifdef DEBUG if (!up) #endif up = ctx->frame; ctx->frame = this; // Push the frame to get proper backtraces. if (++ctx->frame_depth >= ctx->max_frame_depth) { ctx->frame_depth--; _run_error ("Too deep recursion -- exceeding %d nested tags.\n", ctx->max_frame_depth); } EVAL_ARGS_FUNC func; if (raw_args) { #ifdef MODULE_DEBUG if (flags & FLAG_PROC_INSTR) fatal_error ("Can't pass arguments to a processing instruction tag.\n"); #endif #ifdef MAGIC_HELP_ARG if (raw_args->help) { func = utils->return_help_arg; args = raw_args; } else #endif if (sizeof (raw_args)) { // Note: Approximate code duplication in _eval_args and Tag.eval_args(). string splice_arg = raw_args["::"]; if (splice_arg && type->entity_syntax) // Note: Not really accurate to look at entity_syntax here. m_delete (raw_args, "::"); else splice_arg = 0; mapping(string:Type) splice_req_types; mapping(string:Type) atypes = raw_args & tag->req_arg_types; if (sizeof (atypes) < sizeof (tag->req_arg_types)) if (splice_arg) splice_req_types = tag->req_arg_types - atypes; else { 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; PikeCompile comp = PikeCompile(); String.Buffer fn_text = String.Buffer(); if (splice_arg) { Parser p = splice_arg_type->get_pcode_parser (ctx, 0, 0); THIS_TAG_DEBUG ("Evaluating splice argument %s\n", utils->format_short (splice_arg)); #ifdef MODULE_DEBUG if (mixed err = catch { #endif p->finish (splice_arg); // Should not unwind. splice_arg = p->eval(); // Should not unwind. #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 string expr = sprintf ("%s (context, %s->parse_tag_args ((%s) || \"\"), %s)", comp->bind (_eval_args), comp->bind (xml_splice_arg_parser), p->p_code->_compile_text (comp), comp->bind (splice_req_types)); p->p_code = 0; splice_arg_type->give_back (p); args = _eval_args ( ctx, xml_splice_arg_parser->parse_tag_args (splice_arg || ""), splice_req_types); if (!zero_type (this->raw_tag_text)) { // expr = sprintf ( // "mapping(string:string) args = %s;\n" // "%s->raw_tag_text = %s->format_tag (" // "%s, args, %s, %d | RXML.FLAG_RAW_ARGS);\n" // "return args;", // expr, comp->bind (this), comp->bind (type), // comp->bind (tag->name), comp->bind (content), flags); // frame->raw_tag_text = // t_xml->format_tag (tagname, args, content, flags | FLAG_RAW_ARGS); } fn_text->add ("return "); fn_text->add (expr); fn_text->add (" + ([\n"); } else { args = raw_args; fn_text->add ("return ([\n"); } #ifdef MODULE_DEBUG if (mixed err = catch { #endif foreach (indices (raw_args), string arg) { Type t = atypes[arg] || tag->def_arg_type; if (t->parser_prog != PNone) { Parser parser = t->get_pcode_parser (ctx, 0, 0); THIS_TAG_DEBUG ("Evaluating argument value %s with %O\n", utils->format_short (raw_args[arg]), parser); parser->finish (raw_args[arg]); // Should not unwind. args[arg] = parser->eval(); // Should not unwind. THIS_TAG_DEBUG ("Setting argument %s to %s\n", utils->format_short (arg), utils->format_short (args[arg])); fn_text->add (sprintf ("%O: %s,\n", arg, parser->p_code->_compile_text (comp))); parser->p_code = 0; t->give_back (parser); } else { args[arg] = raw_args[arg]; fn_text->add (sprintf ("%O: %s,\n", arg, comp->bind (raw_args[arg]))); } } #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 fn_text->add ("]);\n"); string fn = comp->decl_func ( "mapping(string:mixed)", "RXML.Context context", fn_text->get()); func = comp->compile()()[fn]; } else { func = utils->return_empty_mapping; args = raw_args; } } else func = utils->return_zero; if (!zero_type (this->parent_frame)) if (up->local_tags && up->local_tags->has_tag (tag)) { THIS_TAG_DEBUG ("Setting parent_frame to %O from local_tags\n", up); this->parent_frame = up; } else { int nest = 1; Frame frame = up; for (; frame; frame = frame->up) if (frame->additional_tags && frame->additional_tags->has_tag (tag)) { if (!--nest) break; } else if (frame->tag == tag) nest++; THIS_TAG_DEBUG ("Setting parent_frame to %O from additional_tags\n", frame); this->parent_frame = frame; break; } if (!result_type) { #ifdef MODULE_DEBUG 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); #endif 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 (type)) { 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 (type->subtype_of (rtype)) { result_type = type (rtype->parser_prog, @rtype->parser_args); break find_result_type; } parse_error ( "Tag returns %s but %s is expected.\n", String.implode_nicely ([array(string)] tag->result_types->name, "or"), type->name); } THIS_TAG_DEBUG ("Resolved result_type to %s from surrounding %s\n", result_type->name, type->name); } else THIS_TAG_DEBUG ("Keeping result_type %s\n", result_type->name); if (!content_type) { #ifdef MODULE_DEBUG 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); #endif 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 ("Resolved t_same to content_type %O\n", content_type); } else THIS_TAG_DEBUG ("Setting content_type to %O from tag\n", content_type); } else THIS_TAG_DEBUG ("Keeping content_type %O\n", content_type); ctx->frame = up; return func; }; ctx->frame = up; throw (err); }
d52f8f2001-05-19Martin Stjernholm  mixed _eval (Context ctx, TagSetParser|PCode evaler, Type type,
a08cd72001-06-09Martin Stjernholm  void|string|PCode in_content)
d52f8f2001-05-19Martin Stjernholm  // Note: It might be somewhat tricky to override this function,
a08cd72001-06-09Martin Stjernholm  // since it handles unwinding through exceptions.
ed81751999-12-11Martin Stjernholm  {
56532d1999-12-19Martin Stjernholm  Frame this = this_object();
b483382000-06-30Martin Stjernholm  RequestID id = ctx->id;
ed81751999-12-11Martin Stjernholm 
c6245b1999-12-31Martin Stjernholm  // Unwind state data:
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;
a08cd72001-06-09Martin Stjernholm  EVAL_ARGS_FUNC in_args; //in_content;
291ade2000-01-25Martin Stjernholm  int iter;
ac69772000-09-08Martin Stjernholm #ifdef DEBUG int debug_iter = 1; #endif
a08cd72001-06-09Martin Stjernholm  object(Parser)|object(PCode) subevaler;
ed81751999-12-11Martin Stjernholm  mixed piece;
d52f8f2001-05-19Martin Stjernholm  array exec = 0;
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 
a08cd72001-06-09Martin 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())
d52f8f2001-05-19Martin Stjernholm  PRE_INIT_ERROR ("Context not current.\n"); if (!evaler->tag_set_eval) PRE_INIT_ERROR ("Calling _eval() with non-tag set parser.\n");
a08cd72001-06-09Martin Stjernholm  Frame prev_ctx_frame = ctx->frame;
2bd21a2000-01-05Martin Stjernholm #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 #undef PRE_INIT_ERROR
a08cd72001-06-09Martin Stjernholm 
d52f8f2001-05-19Martin Stjernholm  mixed conv_result = nil; // Result converted to the expected type.
7dd3f82001-04-18Martin Stjernholm  mixed err1 = 0;
b483382000-06-30Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  process_tag: do { if ((err1 = catch { // Catch errors but don't allow unwinds. if (array state = ctx->unwind_state && ctx->unwind_state[this]) { #ifdef DEBUG if (in_content) fatal_error ("Can't feed new content when resuming parse.\n");
b483382000-06-30Martin Stjernholm #endif
a08cd72001-06-09Martin Stjernholm  ctx->frame = this; object ignored; [ignored, eval_state, in_args, in_content, iter, subevaler, piece, exec, orig_tag_set, ctx->new_runtime_tags #ifdef DEBUG , debug_iter #endif ] = state; m_delete (ctx->unwind_state, this); if (!sizeof (ctx->unwind_state)) ctx->unwind_state = 0; THIS_TAG_TOP_DEBUG ("Continuing evaluation" + (piece ? " with stream piece\n" : "\n"));
7dd3f82001-04-18Martin Stjernholm  }
b483382000-06-30Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  else { // Initialize a new evaluation. if (tag) { TRACE_ENTER("tag &lt;" + tag->name + "&gt;", tag); #ifdef MODULE_LEVEL_SECURITY 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;
7dd3f82001-04-18Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm #endif
a08cd72001-06-09Martin Stjernholm  } if (in_content) { THIS_TAG_TOP_DEBUG ("Evaluating\n"); ctx->frame = this; if (functionp (args)) { THIS_TAG_DEBUG ("Evaluating compiled arguments\n"); in_args = args; args = in_args (ctx);
7dd3f82001-04-18Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  content = nil;
7dd3f82001-04-18Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  else if (flags & FLAG_UNPARSED) { #ifdef DEBUG if (args && !mappingp (args)) fatal_error ("args is not a mapping in unparsed frame.\n"); if (!stringp (content)) fatal_error ("content is not a string in unparsed frame.\n");
ed81751999-12-11Martin Stjernholm #endif
a08cd72001-06-09Martin Stjernholm  THIS_TAG_TOP_DEBUG ("Evaluating unparsed\n"); in_args = _prepare (ctx, type, args); ctx->frame = this; in_content = content; content = nil; flags &= ~FLAG_UNPARSED;
7dd3f82001-04-18Martin Stjernholm  }
d4768a2001-05-18Martin Stjernholm  else {
a08cd72001-06-09Martin Stjernholm  _prepare (ctx, type, 0); ctx->frame = this; THIS_TAG_TOP_DEBUG ("Evaluating with constant arguments and content.\n");
d4768a2001-05-18Martin Stjernholm  }
689dc62000-08-31Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  piece = result = nil;
87fb7e2000-03-25Martin Stjernholm  }
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm #ifdef DEBUG if (up != prev_ctx_frame) fatal_error ("Frame probably mixed between different simultaneous contexts " "(up: %O, previous ctx->frame: %O).\n", up, prev_ctx_frame);
a85a952000-02-13Martin Stjernholm #endif
ac69772000-09-08Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  })) {
a85a952000-02-13Martin Stjernholm #ifdef MODULE_DEBUG
a08cd72001-06-09Martin Stjernholm  if (objectp (err1) && ([object] err1)->thrown_at_unwind) err1 = catch ( fatal_error ("Can't save parser state when evaluating arguments.\n"));
a85a952000-02-13Martin Stjernholm #endif
a08cd72001-06-09Martin Stjernholm  break process_tag; }
ac69772000-09-08Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  if (mixed err2 = catch { // Catch errors and allow for unwinds. #ifdef MAGIC_HELP_ARG if (tag && (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), evaler); break process_tag;
7dd3f82001-04-18Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm #endif
b483382000-06-30Martin Stjernholm  switch (eval_state) { case EVSTAT_BEGIN: if (array|function(RequestID:array) do_enter = [array|function(RequestID:array)] this->do_enter) {
d52f8f2001-05-19Martin Stjernholm  EXEC_CALLBACK (ctx, evaler, exec, do_enter, id); EXEC_ARRAY (ctx, evaler, exec, do_enter); } else { THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this); ENTER_SCOPE (ctx, this);
291ade2000-01-25Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  eval_state = EVSTAT_ENTERED; /* Fall through. */
4c2b512001-05-17Martin Stjernholm 
b483382000-06-30Martin Stjernholm  case EVSTAT_ENTERED: case EVSTAT_LAST_ITER:
d52f8f2001-05-19Martin Stjernholm  int|function(RequestID:int) do_iterate = [int|function(RequestID:int)] this->do_iterate; array|function(RequestID:array) do_process = [array|function(RequestID:array)] this->do_process;
b483382000-06-30Martin Stjernholm  do { if (eval_state != EVSTAT_LAST_ITER) { if (intp (do_iterate)) { iter = [int] do_iterate || 1; eval_state = EVSTAT_LAST_ITER;
ac69772000-09-08Martin Stjernholm #ifdef DEBUG if (iter > 1)
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("Getting %d iterations from do_iterate\n", iter);
ac69772000-09-08Martin Stjernholm  else if (iter < 0) THIS_TAG_DEBUG ("Skipping to finish since do_iterate is negative\n"); #endif
b483382000-06-30Martin Stjernholm  } else {
d52f8f2001-05-19Martin Stjernholm  LOW_CALL_CALLBACK (iter, do_iterate, id);
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("%O returned from do_iterate\n", iter);
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG_ENTER_SCOPE (ctx, this); ENTER_SCOPE (ctx, this); if (evaler->is_RXML_Parser && ctx->new_runtime_tags) _handle_runtime_tags (ctx, evaler);
b483382000-06-30Martin Stjernholm  if (!iter) eval_state = EVSTAT_LAST_ITER; }
0ebc9d2000-02-15Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm 
d52f8f2001-05-19Martin Stjernholm  for (; iter > 0; iter-- DO_IF_DEBUG (, debug_iter++)) {
a08cd72001-06-09Martin Stjernholm  if (in_content) { // Got nested parsing to do. int finished = 1; if (subevaler) finished = 0; // Continuing an unwound subevaler. else if (stringp (in_content)) { if (in_content == "") subevaler = PCode (content_type); else if (flags & FLAG_EMPTY_ELEMENT) parse_error ("This tag doesn't handle content.\n"); else { // The nested content is not yet parsed.
0f998b2000-08-15Martin Stjernholm  if (this->local_tags) {
a08cd72001-06-09Martin Stjernholm  subevaler = content_type->get_pcode_parser (
d52f8f2001-05-19Martin Stjernholm  ctx, [object(TagSet)] this->local_tags, evaler);
a08cd72001-06-09Martin Stjernholm  subevaler->_local_tag_set = 1; THIS_TAG_DEBUG ("Iter[%d]: Parsing and evaluating content %s " "with %O from local_tags\n", debug_iter, utils->format_short (in_content), subevaler);
0f998b2000-08-15Martin Stjernholm  }
ac69772000-09-08Martin Stjernholm  else {
a08cd72001-06-09Martin Stjernholm #ifdef DEBUG if (orig_tag_set) fatal_error ("Didn't expect orig_tag_set here.\n"); #endif if (TagSet add_tags = [object(TagSet)] this->additional_tags) { TagSet tset = ctx->tag_set; if (!tset->has_effective_tags (add_tags)) { THIS_TAG_DEBUG ("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}); // Race, but it doesn't matter. local_tag_set_cache[hash] = local_ts; } ctx->tag_set = local_ts; } else THIS_TAG_DEBUG ("Not installing additional_tags %O " "since they're already in the tag set\n", add_tags); } subevaler = content_type->get_pcode_parser (ctx, 0, evaler);
ac69772000-09-08Martin Stjernholm #ifdef DEBUG
7dd3f82001-04-18Martin Stjernholm  if (content_type->parser_prog != PNone)
a08cd72001-06-09Martin Stjernholm  THIS_TAG_DEBUG ("Iter[%d]: Parsing and evaluating content %s " "with %O\n", debug_iter, utils->format_short (in_content), subevaler);
ac69772000-09-08Martin Stjernholm #endif }
d52f8f2001-05-19Martin Stjernholm  if (evaler->recover_errors && !(flags & FLAG_DONT_RECOVER))
a08cd72001-06-09Martin Stjernholm  subevaler->recover_errors = 1; subevaler->finish (in_content); // Might unwind. (in_content = subevaler->p_code)->finish();
0f998b2000-08-15Martin Stjernholm  finished = 1;
b483382000-06-30Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  } else { THIS_TAG_DEBUG ("Iter[%d]: Evaluating compiled content\n", debug_iter); subevaler = in_content; // Evaling with p-code. }
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  eval_sub: do { if (piece != nil && flags & FLAG_STREAM_CONTENT) { // Handle a stream piece. THIS_TAG_DEBUG ("Iter[%d]: Got %s stream piece %s\n", debug_iter, finished ? "ending" : "a", utils->format_short (piece)); if (!arrayp (do_process)) { EXEC_CALLBACK (ctx, evaler, exec, do_process, id, piece); if (exec) { mixed res = _exec_array ( ctx, evaler, exec, "do_process"); // Might unwind. if (flags & FLAG_STREAM_RESULT) {
ed81751999-12-11Martin Stjernholm #ifdef DEBUG
a08cd72001-06-09Martin Stjernholm  if (!zero_type (ctx->unwind_state->stream_piece)) fatal_error ("Clobbering unwind_state->stream_piece.\n");
ed81751999-12-11Martin Stjernholm #endif
a08cd72001-06-09Martin Stjernholm  CONV_RESULT (res, result_type, res, type); ctx->unwind_state->stream_piece = res; THIS_TAG_DEBUG ("Iter[%d]: Streaming %s from do_process\n", debug_iter, utils->format_short (res)); throw (this);
49897a2000-02-13Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  exec = 0;
49897a2000-02-13Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  else if (flags & FLAG_STREAM_RESULT) { THIS_TAG_DEBUG ("Iter[%d]: do_process finished the stream; " "ignoring remaining content\n", debug_iter); ctx->unwind_state = 0; piece = nil; break eval_sub;
0f998b2000-08-15Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  }
a08cd72001-06-09Martin Stjernholm  piece = nil; if (finished) break eval_sub; } else { // No streaming. piece = nil; if (finished) { mixed res = subevaler->_eval (ctx); // Might unwind. if (content_type->sequential) SET_SEQUENTIAL (res, content, "content"); else if (res != nil) SET_NONSEQUENTIAL (res, content, content_type, "content"); break eval_sub; } }
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  if (subevaler->is_RXML_Parser) { subevaler->finish(); // Might unwind. (in_content = subevaler->p_code)->finish(); } finished = 1; } while (1); // Only loops when an unwound subevaler has been recovered. subevaler = 0; }
b483382000-06-30Martin Stjernholm 
d52f8f2001-05-19Martin Stjernholm  if (do_process) { EXEC_CALLBACK (ctx, evaler, exec, do_process, id); EXEC_ARRAY (ctx, evaler, exec, do_process);
49897a2000-02-13Martin Stjernholm  }
b483382000-06-30Martin Stjernholm  } } while (eval_state != EVSTAT_LAST_ITER);
d52f8f2001-05-19Martin Stjernholm  eval_state = EVSTAT_ITER_DONE;
b483382000-06-30Martin Stjernholm  /* Fall through. */
4c2b512001-05-17Martin Stjernholm 
b483382000-06-30Martin Stjernholm  case EVSTAT_ITER_DONE: if (array|function(RequestID:array) do_return = [array|function(RequestID:array)] this->do_return) {
d52f8f2001-05-19Martin Stjernholm  EXEC_CALLBACK (ctx, evaler, exec, do_return, id);
b483382000-06-30Martin Stjernholm  if (exec) {
d52f8f2001-05-19Martin Stjernholm  // We don't use EXEC_ARRAY here since it's no idea to // come back even if any streaming should be done.
a08cd72001-06-09Martin Stjernholm  _exec_array (ctx, evaler, exec, "do_return"); // Might unwind.
e86f2d2000-02-15Martin Stjernholm  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) {
4c2b512001-05-17Martin Stjernholm  THIS_TAG_DEBUG ("Assigning content to result after "
d52f8f2001-05-19Martin Stjernholm  "conversion from %s to %s\n",
4c2b512001-05-17Martin Stjernholm  content_type->name, result_type->name);
ac69772000-09-08Martin Stjernholm  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)) { if (!exec) { THIS_TAG_DEBUG ("Parsing content with exec array " "for assignment to result\n"); exec = ({content}); }
a08cd72001-06-09Martin Stjernholm  _exec_array (ctx, evaler, exec, "content parse"); // Might unwind.
b483382000-06-30Martin Stjernholm  exec = 0; }
ac69772000-09-08Martin Stjernholm  }
d52f8f2001-05-19Martin Stjernholm  if (result != nil) CONV_RESULT (result, result_type, conv_result, type);
ac69772000-09-08Martin Stjernholm #ifdef DEBUG
d52f8f2001-05-19Martin Stjernholm  else THIS_TAG_DEBUG ("Skipping nil result\n");
ac69772000-09-08Martin Stjernholm #endif
4c2b512001-05-17Martin Stjernholm 
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG_LEAVE_SCOPE (ctx, this);
b483382000-06-30Martin Stjernholm  LEAVE_SCOPE (ctx, this);
f997202000-01-18Martin Stjernholm 
d52f8f2001-05-19Martin Stjernholm  if (evaler->is_RXML_Parser && ctx->new_runtime_tags) _handle_runtime_tags (ctx, evaler);
4c2b512001-05-17Martin Stjernholm  }
ffa6932000-02-20Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  }) {
d52f8f2001-05-19Martin Stjernholm  THIS_TAG_DEBUG_LEAVE_SCOPE (ctx, this);
b483382000-06-30Martin Stjernholm  LEAVE_SCOPE (ctx, this);
d52f8f2001-05-19Martin Stjernholm 
b483382000-06-30Martin Stjernholm  string action;
d52f8f2001-05-19Martin Stjernholm  exception: { unwind: if (objectp (err2) && ([object] err2)->thrown_at_unwind) { UNWIND_STATE ustate = ctx->unwind_state; if (!ustate) ustate = ctx->unwind_state = ([]);
ed81751999-12-11Martin Stjernholm #ifdef DEBUG
d52f8f2001-05-19Martin Stjernholm  if (ustate[this]) fatal_error ("Frame already has an unwind state.\n");
ed81751999-12-11Martin Stjernholm #endif
d52f8f2001-05-19Martin Stjernholm  if (ustate->exec_left) { exec = [array] ustate->exec_left; m_delete (ustate, "exec_left");
b483382000-06-30Martin Stjernholm  }
d52f8f2001-05-19Martin Stjernholm  if (err2 == this || exec && sizeof (exec) && err2 == exec[0]) // This frame or a frame in the exec array wants to stream. if (evaler->read && evaler->unwind_safe) { // Rethrow to continue in parent since we've already done // the appropriate do_process stuff in this frame in // either case. mixed piece = evaler->read(); if (err2 = catch { if (type->sequential) SET_SEQUENTIAL (ustate->stream_piece, piece, "stream piece"); else SET_NONSEQUENTIAL (ustate->stream_piece, piece, type, "stream piece"); }) break unwind; if (err2 == this) err2 = 0; if (orig_tag_set) ctx->tag_set = orig_tag_set, orig_tag_set = 0; action = "break"; THIS_TAG_TOP_DEBUG ("Breaking to parent frame to do streaming\n"); } else { // Can't stream since the parser doesn't allow that. // Just continue. m_delete (ustate, "stream_piece"); action = "continue"; THIS_TAG_TOP_DEBUG ("Not streaming since the parser " "doesn't allow that\n"); } 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;
b483382000-06-30Martin Stjernholm  m_delete (ustate, "stream_piece"); action = "continue"; }
d52f8f2001-05-19Martin Stjernholm  else { action = "break"; // Some other reason - back up to the top. THIS_TAG_TOP_DEBUG ("Interrupted\n"); }
ed81751999-12-11Martin Stjernholm 
a08cd72001-06-09Martin Stjernholm  ustate[this] = ({err2, eval_state, in_args, in_content, iter, subevaler, piece, exec, orig_tag_set, ctx->new_runtime_tags,
ac69772000-09-08Martin Stjernholm #ifdef DEBUG
d52f8f2001-05-19Martin Stjernholm  debug_iter,
ac69772000-09-08Martin Stjernholm #endif
d52f8f2001-05-19Martin Stjernholm  }); TRACE_LEAVE (action); break exception; }
ac69772000-09-08Martin Stjernholm  THIS_TAG_TOP_DEBUG ("Exception\n");
0f998b2000-08-15Martin Stjernholm  TRACE_LEAVE ("exception");
d52f8f2001-05-19Martin Stjernholm  ctx->handle_exception (err2, evaler); // 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
d52f8f2001-05-19Martin Stjernholm  if (!evaler->unwind_state) fatal_error ("Trying to unwind inside an evaluator " "that isn't unwind safe.\n");
56532d1999-12-19Martin Stjernholm #endif
b483382000-06-30Martin Stjernholm  throw (this);
a08cd72001-06-09Martin Stjernholm  case "continue": // Continue in this frame with the stored state. continue process_tag; case "return": // A normal return.
2531fa2001-03-23Martin Stjernholm  break process_tag;
b483382000-06-30Martin Stjernholm  default:
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Don't you come here and %O on me!\n", action);
b483382000-06-30Martin Stjernholm  }
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  }
a08cd72001-06-09Martin Stjernholm  break process_tag; } while (1); // Looping only when continuing in streaming mode.
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);
a08cd72001-06-09Martin Stjernholm  args = in_args, content = in_content;
4c2b512001-05-17Martin Stjernholm  return conv_result;
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
d4768a2001-05-18Martin Stjernholm //! corresponding functions in the @[Context] class for details).
f134b92000-11-27Martin Stjernholm 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
d4768a2001-05-18Martin Stjernholm //! 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,
4c2b512001-05-17Martin Stjernholm //! e.g. @tt{({"a", 2, "b"})@} corresponds to @tt{val["a"][2]["c"]@}. //! @[scope_name] is used to identify the context for the indexing.
1cfbeb2000-11-06Martin Stjernholm //! //! 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
d4768a2001-05-18Martin Stjernholm //! If a value is an object which has an @tt{rxml_var_eval@} //! identifier, it's treated as an @[RXML.Value] object and the //! @[RXML.Value.rxml_var_eval] function is called to produce its //! value.
151fa92001-04-19Johan Sundström //! @item //! If a value which is about to be indexed is an object which has a
d4768a2001-05-18Martin Stjernholm //! @tt{`[]@} function, it's called as an @[RXML.Scope] object (see //! @ref{RXML.Scope.`[]@}).
151fa92001-04-19Johan Sundström //! @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 //!
d4768a2001-05-18Martin Stjernholm //! If the @[want_type] argument is set, the result value is converted //! to that type with @[RXML.Type.encode]. If the value can't be //! converted, an RXML error is thrown.
1cfbeb2000-11-06Martin Stjernholm //! //! This function is mainly for internal use; you commonly want to use
d4768a2001-05-18Martin Stjernholm //! @[get_var], @[set_var], @[user_get_var] or @[user_set_var] //! instead.
1cfbeb2000-11-06Martin Stjernholm { #ifdef MODULE_DEBUG if (arrayp (index) ? !sizeof (index) : !(stringp (index) || intp (index)))
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Invalid index specifier.\n");
1cfbeb2000-11-06Martin Stjernholm #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)
d52f8f2001-05-19Martin Stjernholm  fatal_error ("%O->`[] didn't return a value of the correct type:\n%s", scope, err->msg);
fe628d2001-03-01Martin Stjernholm  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
a708be2001-06-01Johan Sundström  while (objectp (val) && ([object] val)->rxml_var_eval && !([object] val)->`[]) {
2b5f472001-02-11Martin Stjernholm #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])
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Cyclic rxml_var_eval chain detected in %O.\n" "All called objects:%{ %O%}\n", val, indices (called));
2b5f472001-02-11Martin Stjernholm  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])
d52f8f2001-05-19Martin Stjernholm  fatal_error ("Cyclic rxml_var_eval chain detected in %O.\n" "All called objects:%{ %O%}\n", val, indices (called));
2b5f472001-02-11Martin Stjernholm  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)
d52f8f2001-05-19Martin Stjernholm  fatal_error ("%O->rxml_var_eval didn't return a value of the correct type:\n%s", val_obj, err->msg);
fe628d2001-03-01Martin Stjernholm  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
d4768a2001-05-18Martin Stjernholm //! set. @[args] and @[content] are not parsed or evaluated; they're //! used as-is by the tag. If @[overridden_by] is given, the returned //! frame will come from the tag 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
d4768a2001-05-18Martin Stjernholm //! set. @[args] and @[content] are given unparsed in this variant; //! they're parsed and evaluated when the frame is about to be //! evaluated. If @[overridden_by] is given, the returned frame will //! come from the tag 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); 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();
d52f8f2001-05-19Martin Stjernholm  fatal_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();
d52f8f2001-05-19Martin Stjernholm  fatal_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  }
d52f8f2001-05-19Martin Stjernholm  mixed handle_var (TagSetParser|PCode evaler, string varref, Type want_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;
a08cd72001-06-09Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  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");
d52f8f2001-05-19Martin Stjernholm #ifdef DEBUG if (context->frame) TAG_DEBUG (context->frame, " Looking up variable %s in context of type %s\n", splitted * ".", (encoding ? t_string : want_type)->name); #endif
a08cd72001-06-09Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  mixed val;
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (context, "rxml internal", "var:" + varref);
7937df2001-05-16Per Hedbor  COND_PROF_ENTER(mixed id=context->id,varref,"entity");
7dd3f82001-04-18Martin Stjernholm  if (zero_type (val = context->get_var ( // May throw. splitted[1..], splitted[0],
d4768a2001-05-18Martin Stjernholm  encoding ? t_string : want_type)))
7dd3f82001-04-18Martin Stjernholm  val = nil;
7937df2001-05-16Per Hedbor  COND_PROF_LEAVE(mixed id=context->id,varref,"entity");
4293072001-05-08Martin Stjernholm  PROFILE_SWITCH (context, "var:" + varref, "rxml internal");
a08cd72001-06-09Martin Stjernholm 
d52f8f2001-05-19Martin Stjernholm  if (encoding) { if (!(val = Roxen->roxen_encode (val + "", encoding))) parse_error ("Unknown encoding %O.\n", encoding); #ifdef DEBUG if (context->frame) TAG_DEBUG (context->frame, " Got value %s after conversion " "with encoding %s\n", utils->format_short (val), encoding); #endif } #ifdef DEBUG else if (context->frame) TAG_DEBUG (context->frame, " Got value %s\n", utils->format_short (val)); #endif
a08cd72001-06-09Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  context->current_var = 0;
d52f8f2001-05-19Martin Stjernholm  if (evaler->p_code) evaler->p_code->add (VarRef (splitted[0], splitted[1..], encoding));
7dd3f82001-04-18Martin Stjernholm  return val;
a08cd72001-06-09Martin Stjernholm 
7dd3f82001-04-18Martin Stjernholm  }) { 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.)
a08cd72001-06-09Martin Stjernholm  PCode p_code;
d4768a2001-05-18Martin Stjernholm  //! Must be set to a new @[PCode] object before a stream is fed //! which should be compiled to p-code. The object will receive the //! compiled code during evaluation and can be used to repeat the
a08cd72001-06-09Martin Stjernholm  //! evaluation after the stream is finished.
ed81751999-12-11Martin Stjernholm 
151fa92001-04-19Johan Sundström  //! @decl int unwind_safe;
d4768a2001-05-18Martin Stjernholm  //!
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
d4768a2001-05-18Martin Stjernholm  //! scanning, parsing and evaluation before returning. Returns //! nonzero if there could be new data to get from eval().
ed81751999-12-11Martin Stjernholm  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
d52f8f2001-05-19Martin Stjernholm  //! result so far, but does not do any more evaluation. Returns //! RXML.nil if there's no data.
ed81751999-12-11Martin Stjernholm 
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 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 
a08cd72001-06-09Martin Stjernholm  mixed _eval (Context ignored) {return eval();} // To be call compatible with PCode.
ed81751999-12-11Martin Stjernholm  Parser _next_free; // Used to link together unused parser objects for reuse.
c6245b1999-12-31Martin Stjernholm  Parser _parent;
a08cd72001-06-09Martin Stjernholm  // The parent parser if this one is nested. This is only used to // register runtime tags.
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.
d4768a2001-05-18Martin Stjernholm  PCode p_code;
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;
d4768a2001-05-18Martin Stjernholm  p_code = 0;
2531fa2001-03-23Martin Stjernholm  }
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;
d4768a2001-05-18Martin Stjernholm  static string data = ""; static int evalpos = 0; PCode p_code;
ed81751999-12-11Martin Stjernholm  int feed (string in) { data += in; return 1; } void finish (void|string in) { if (in) data += in;
d4768a2001-05-18Martin Stjernholm  if (p_code) p_code->add (data);
ed81751999-12-11Martin Stjernholm  } string eval() { string res = data[evalpos..]; evalpos = sizeof (data); return res; } 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
d4768a2001-05-18Martin Stjernholm //! 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; }
a08cd72001-06-09Martin Stjernholm  inline final 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) &&
a08cd72001-06-09Martin Stjernholm  parent->clone && parent->type->name == this_object()->name) {
0ebc9d2000-02-15Martin Stjernholm  // 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.
d4768a2001-05-18Martin Stjernholm  p->data_callback = 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;
d4768a2001-05-18Martin Stjernholm  p->data_callback = 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; }
a08cd72001-06-09Martin Stjernholm  Parser get_pcode_parser (Context ctx, void|TagSet tag_set, void|Parser|PCode parent) //! Like @[get_parser], but also initializes a PCode object in the //! returned parser. { Parser p = get_parser (ctx, tag_set, parent); p->p_code = PCode (this_object()); return p; } inline final void give_back (Parser parser, void|TagSet tag_set) //! Returns the given parser object for reuse. Only has effect if //! the parser implements @[Parser.reset]. If the parser is a tag //! set parser, tag_set must specify the tag set it uses. { #ifdef DEBUG if (parser->type->name != this_object()->name) error ("Giving back parser to wrong type.\n"); #endif if (parser->reset) { parser->context = parser->recover_errors = parser->_parent = 0; #ifdef RXML_OBJ_DEBUG parser->__object_marker->create (p); #endif if (_p_cache) { if (PCacheObj pco = _p_cache[tag_set]) { // Relying on interpreter lock here. parser->_next_free = pco->free_parser; pco->free_parser = parser; } } else { // Relying on interpreter lock in this block. parser->_next_free = free_parser; free_parser = parser; } } }
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 {
a08cd72001-06-09Martin Stjernholm  if (!tag_set) tag_set = ctx->tag_set; Parser parser = get_parser (ctx, tag_set, parent); parser->_parent = parent; if (dont_switch_ctx) parser->finish (in); // Optimize the job in write_end(). else parser->write_end (in); res = parser->eval(); give_back (parser, tag_set);
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