Kirill Simonov avatar Kirill Simonov committed 451f0ec

Removing `mark` attribute from command, syntax, and intermediate nodes.

Comments (0)

Files changed (29)

src/htsql/core/cmd/act.py

     adapt(DefaultCmd, Action)
 
     def __call__(self):
-        command = FetchCmd(self.command.syntax, self.command.mark)
+        command = FetchCmd(self.command.syntax)
         return act(command, self.action)
 
 
     assert isinstance(action, Action)
     if not isinstance(command, Command):
         command = recognize(command)
-    with act_guard(command.mark):
+    with act_guard(command):
         return Act.__invoke__(command, action)
 
 

src/htsql/core/cmd/command.py

 from ..util import Printable, maybe, dictof, oneof
 from ..fmt.format import Format
 from ..syn.syntax import Syntax
-from ..error import Mark, EmptyMark
 
 
 class Command(object):
-
-    def __init__(self, mark):
-        assert isinstance(mark, Mark)
-        self.mark = mark
+    pass
 
 
 class UniversalCmd(Command):
 
     def __init__(self, query):
         assert isinstance(query, (str, unicode))
-        super(UniversalCmd, self).__init__(EmptyMark())
         self.query = query
 
 
 
     def __init__(self, syntax):
         assert isinstance(syntax, Syntax)
-        super(DefaultCmd, self).__init__(syntax.mark)
         self.syntax = syntax
 
 
 
 class FetchCmd(Command):
 
-    def __init__(self, syntax, mark):
+    def __init__(self, syntax):
         assert isinstance(syntax, Syntax)
-        super(FetchCmd, self).__init__(mark)
         self.syntax = syntax
 
 
 class FormatCmd(Command):
 
-    def __init__(self, feed, format, mark):
+    def __init__(self, feed, format):
         assert isinstance(feed, Command)
         assert isinstance(format, Format)
-        super(FormatCmd, self).__init__(mark)
+        self.feed = feed
         self.format = format
-        self.feed = feed
 
 
 class SQLCmd(Command):
 
-    def __init__(self, feed, mark):
+    def __init__(self, feed):
         assert isinstance(feed, Command)
-        super(SQLCmd, self).__init__(mark)
         self.feed = feed
 
 

src/htsql/core/cmd/summon.py

 
 
 from ..adapter import Adapter, Protocol, adapt, call
-from ..error import Error, recognize_guard
+from ..error import Error, recognize_guard, point, MarkRef
 from ..util import to_name
 from ..syn.syntax import (Syntax, SkipSyntax, FunctionSyntax, PipeSyntax,
         ApplySyntax, CollectSyntax)
     adapt(SkipSyntax)
 
     def __call__(self):
-        return SkipCmd(self.syntax.mark)
+        command = SkipCmd()
+        return point(command, self.syntax)
 
 
 class RecognizeFunction(Recognize):
 
     def __call__(self):
         with recognize_guard(self.syntax):
-            return Summon.__invoke__(self.syntax)
+            command = Summon.__invoke__(self.syntax)
+            return point(command, self.syntax.identifier)
 
 
 class RecognizePipe(Recognize):
         if self.syntax.is_flow:
             return super(RecognizePipe, self).__call__()
         with recognize_guard(self.syntax):
-            return Summon.__invoke__(self.syntax)
+            command = Summon.__invoke__(self.syntax)
+            return point(command, self.syntax.identifier)
 
 
 class RecognizeCollect(Recognize):
         self.syntax = syntax
         self.name = syntax.name
         self.arguments = syntax.arguments
-        if isinstance(syntax, (FunctionSyntax, PipeSyntax)):
-            self.mark = syntax.identifier.mark
-        else:
-            self.mark = syntax.mark
 
     def __call__(self):
         return None
         if len(self.arguments) != 1:
             raise Error("Expected 1 argument")
         [syntax] = self.arguments
-        return FetchCmd(syntax, self.syntax.mark)
+        return FetchCmd(syntax)
 
 
 class SummonFormat(Summon):
         [syntax] = self.arguments
         feed = recognize(syntax)
         format = self.format()
-        return FormatCmd(feed, format, self.syntax.mark)
+        return FormatCmd(feed, format)
 
 
 class SummonTxt(SummonFormat):
             raise Error("Expected 1 argument")
         [syntax] = self.arguments
         feed = recognize(syntax)
-        return SQLCmd(feed, self.syntax.mark)
+        return SQLCmd(feed)
 
 
 def recognize(syntax):
     command = Recognize.__invoke__(syntax)
     if command is None:
         command = DefaultCmd(syntax)
+        mark = MarkRef.get_mark(syntax)
+        if mark is not None:
+            point(command, mark.clone(end=mark.start))
     return command
 
 

src/htsql/core/error.py

 #
 
 
-from .util import Printable, maybe, listof, oneof
+from .util import Clonable, Printable, maybe, listof, oneof, urlquote
+import weakref
 
 
 #
                              u"\n".join(u"    "+choice for choice in choices))
 
 
-class Mark(Printable):
+class Mark(Clonable, Printable):
     """
     A fragment of an HTSQL query.
 
         """
         # Get a list of `Mark` objects; if no marks are given, return an
         # empty mark.
-        marks = [node if isinstance(node, Mark) else node.mark
+        marks = [node if isinstance(node, Mark) else MarkRef.get_mark(node)
                  for node in nodes if node is not None]
         if not marks:
-            return EmptyMark()
+            return None
         # It might happen that different marks refer to different query strings.
         # In this case, we choose one of them and ignore marks associated with
         # other query strings.
     def __unicode__(self):
         return u"\n".join(self.excerpt())
 
+    def __repr__(self):
+        chunk = self.text[self.start:self.end]
+        return "<%s %s>" % (self.__class__.__name__,
+                            urlquote(chunk, '').encode('utf-8'))
+
     def __nonzero__(self):
         return bool(self.text)
 
 
-class EmptyMark(Mark):
-    """
-    An empty error context.
-    """
+class MarkRef(weakref.ref):
 
-    def __init__(self):
-        super(EmptyMark, self).__init__(u"", 0, 0)
+    __slots__ = ('oid', 'mark')
+
+    oid_to_ref = {}
+
+    @staticmethod
+    def cleanup(ref, oid_to_ref=oid_to_ref):
+        del oid_to_ref[ref.oid]
+
+    def __new__(cls, node, mark):
+        self = super(MarkRef, cls).__new__(cls, node, cls.cleanup)
+        self.oid = id(node)
+        self.mark = mark
+        cls.oid_to_ref[self.oid] = self
+        return self
+
+    def __init__(self, node, mark):
+        super(MarkRef, self).__init__(node, self.cleanup)
+
+    @classmethod
+    def get_mark(cls, node):
+        ref = cls.oid_to_ref.get(id(node))
+        if ref is not None:
+            return ref.mark
+
+    @classmethod
+    def set_mark(cls, node, mark):
+        cls(node, mark)
+
+    @classmethod
+    def point(cls, node, mark):
+        if node is None or mark is None:
+            return
+        if cls.get_mark(node) is not None:
+            return node
+        if not isinstance(mark, Mark):
+            mark = cls.get_mark(mark)
+        if mark is None:
+            return node
+        cls.set_mark(node, mark)
+        return node
+
+
+point = MarkRef.point
 
 
 class Error(BadRequestError):
 
     def __init__(self, message, mark):
         self.message = message
+        self.node = mark
         if mark is not None:
             if not isinstance(mark, Mark):
-                mark = mark.mark
+                mark = MarkRef.get_mark(mark)
         self.mark = mark
 
     def __enter__(self):

src/htsql/core/syn/grammar.py

 
 from ..util import (maybe, oneof, listof, omapof, trim_doc, toposort, omap,
         TextBuffer, Printable)
-from ..error import Error, Mark, parse_guard
+from ..error import Error, Mark, parse_guard, point
 from .token import Token
 import re
 
                 code = group.name
                 if group.is_symbol:
                     code = block
-                token = Token(code, block, mark)
+                token = Token(code, block)
+                point(token, mark)
                 tokens.append(token)
             # For an exit rule, exit the top context.
             if group.pop:
                         break
                     token = tokens[end]
                 if None in dfa[state]:
-                    token = Token(treatment.name, u"", tokens[start].mark)
+                    token = Token(treatment.name, u"")
+                    point(token, tokens[start])
                     tokens.insert(start, token)
                     start = end+1
                 else:
             return False
         return (node.code == code)
 
-    def mark(self):
+    def mark(self, node):
         # Makes a mark covering all the pulled nodes.
-        return Mark.union(*self.nodes[:self.index])
+        mark = Mark.union(*self.nodes[:self.index])
+        point(node, mark)
+        return node
 
     def __nonzero__(self):
         return (self.index < len(self.nodes))

src/htsql/core/syn/parse.py

             larm = stream.pull()    # specifier
             stream.pull()           # `:=`
             rarm = stream.pull()    # pipe | flow
-            yield AssignSyntax(larm, rarm, stream.mark())
+            yield stream.mark(AssignSyntax(larm, rarm))
         else:
             yield stream.pull()     # pipe | flow
 
                 rarm = stream.pull()    # parameter
                 rarms.append(rarm)
             stream.pull()               # `)`
-        yield SpecifySyntax(larms, rarms, stream.mark())
+        yield stream.mark(SpecifySyntax(larms, rarms))
 
     # Assignees and parameters in an assignment expression.
     parameter = grammar.add_rule('''
                     rarms.append(rarm)
             is_flow = False
             syntax = PipeSyntax(identifier, larm, rarms,
-                                is_flow, is_open, stream.mark())
+                                is_flow, is_open)
+            stream.mark(syntax)
         yield syntax
 
     # Flow pipe notation (`<larm> : <identifier>`) and sort direction
                 stream.pull()               # %DIRSIG
             if stream.peek(u'+') or stream.peek(u'-'):
                 direction = stream.pull()   # `+` | `-`
-                syntax = DirectSyntax(direction.text, syntax, stream.mark())
+                syntax = DirectSyntax(direction.text, syntax)
+                stream.mark(syntax)
             else:
                 larm = syntax
                 stream.pull()               # `:`
                         rarms.append(rarm)
                 is_flow = True
                 syntax = PipeSyntax(identifier, larm, rarms,
-                                    is_flow, is_open, stream.mark())
+                                    is_flow, is_open)
+                stream.mark(syntax)
         yield syntax
 
     # Flow operators (`?` and `^`) and selection (`<larm> {...}`).
                 larm = syntax
                 stream.pull()               # `?`
                 rarm = stream.pull()        # disjunction
-                syntax = FilterSyntax(larm, rarm, stream.mark())
+                syntax = FilterSyntax(larm, rarm)
+                stream.mark(syntax)
             elif stream.peek(u'^'):
                 larm = syntax
                 stream.pull()               # `^`
                 rarm = stream.pull()        # disjunction
-                syntax = ProjectSyntax(larm, rarm, stream.mark())
+                syntax = ProjectSyntax(larm, rarm)
+                stream.mark(syntax)
             else:
                 larm = syntax
                 rarm = stream.pull()        # record
-                syntax = SelectSyntax(larm, rarm, stream.mark())
+                syntax = SelectSyntax(larm, rarm)
+                stream.mark(syntax)
                 while stream.peek(u'.'):
                     stream.pull()           # `.`
                     larm = syntax
                     rarm = stream.pull()    # location
-                    syntax = ComposeSyntax(larm, rarm, stream.mark())
+                    syntax = ComposeSyntax(larm, rarm)
+                    stream.mark(syntax)
         yield syntax
 
     # Used by binary operator productions of the form:
             larm = syntax
             operator = stream.pull()    # (operator symbol)
             rarm = stream.pull()        # (right operand)
-            syntax = OperatorSyntax(operator.text, larm, rarm, stream.mark())
+            syntax = OperatorSyntax(operator.text, larm, rarm)
+            stream.mark(syntax)
         yield syntax
 
     # OR operator.
         if stream.peek(u'!'):
             operator = stream.pull()    # `!`
             arm = stream.pull()         # negation
-            syntax = PrefixSyntax(operator.text, arm, stream.mark())
+            syntax = PrefixSyntax(operator.text, arm)
+            stream.mark(syntax)
         else:
             syntax = stream.pull()      # comparison
         yield syntax
         if stream.peek(u'+') or stream.peek(u'-'):
             prefix = stream.pull()  # `+` | `-`
             arm = stream.pull()     # factor
-            syntax = PrefixSyntax(prefix.text, arm, stream.mark())
+            syntax = PrefixSyntax(prefix.text, arm)
+            stream.mark(syntax)
         else:
             syntax = stream.pull()  # linking
         yield syntax
             larm = syntax
             stream.pull()           # `->`
             rarm = stream.pull()    # flow
-            syntax = LinkSyntax(larm, rarm, stream.mark())
+            syntax = LinkSyntax(larm, rarm)
+            stream.mark(syntax)
         yield syntax
 
     # Composition expression.
             larm = syntax
             stream.pull()           # `.`
             rarm = stream.pull()    # location
-            syntax = ComposeSyntax(larm, rarm, stream.mark())
+            syntax = ComposeSyntax(larm, rarm)
+            stream.mark(syntax)
         yield syntax
 
     # Location expression (`<larm> [...]`)
         if stream:
             larm = syntax
             rarm = stream.pull()    # identity
-            syntax = LocateSyntax(larm, rarm, stream.mark())
+            syntax = LocateSyntax(larm, rarm)
+            stream.mark(syntax)
         yield syntax
 
     # Attachment operator.
             larm = syntax
             stream.pull()           # `@`
             rarm = stream.pull()    # atom
-            syntax = AttachSyntax(larm, rarm, stream.mark())
+            syntax = AttachSyntax(larm, rarm)
+            stream.mark(syntax)
         yield syntax
 
     # Atomic expressions.
     def match_collection(stream):
         stream.pull()               # `/`
         if not stream:
-            syntax = SkipSyntax(stream.mark())
+            syntax = SkipSyntax()
+            stream.mark(syntax)
         else:
             arm = stream.pull()     # flow_pipe
-            syntax = CollectSyntax(arm, stream.mark())
+            syntax = CollectSyntax(arm)
+            stream.mark(syntax)
         yield syntax
 
     # Detachment operator.
     def match_detachment(stream):
         stream.pull()               # `@`
         arm = stream.pull()         # atom
-        syntax = DetachSyntax(arm, stream.mark())
+        syntax = DetachSyntax(arm)
+        stream.mark(syntax)
         yield syntax
 
     # Unpacking expression.
             else:
                 index = stream.pull()   # %INTEGER
             index = int(index.text)
-        syntax = UnpackSyntax(index, is_open, stream.mark())
+        syntax = UnpackSyntax(index, is_open)
+        stream.mark(syntax)
         yield syntax
 
     # Reference expression.
     def match_reference(stream):
         stream.pull()                   # `$`
         identifier = stream.pull()      # identifier
-        syntax = ReferenceSyntax(identifier, stream.mark())
+        syntax = ReferenceSyntax(identifier)
+        stream.mark(syntax)
         yield syntax
 
     # Attribute or function call.
                 arm = stream.pull()     # assignment
                 arms.append(arm)
             stream.pull()               # `)`
-            syntax = FunctionSyntax(identifier, arms, stream.mark())
+            syntax = FunctionSyntax(identifier, arms)
+            stream.mark(syntax)
         yield syntax
 
     # An identifier.
     @identifier.set_match
     def match_identifier(stream):
         name = stream.pull()        # %NAME
-        syntax = IdentifierSyntax(name.text, stream.mark())
+        syntax = IdentifierSyntax(name.text)
+        stream.mark(syntax)
         yield syntax
 
     # Complement indicator (`^`).
     @complement.set_match
     def match_complement(stream):
         stream.pull()               # `^`
-        syntax = ComplementSyntax(stream.mark())
+        syntax = ComplementSyntax()
+        stream.mark(syntax)
         yield syntax
 
     # Record constructor.
             arm = stream.pull()     # assignment
             arms.append(arm)
         stream.pull()               # `}`
-        syntax = RecordSyntax(arms, stream.mark())
+        syntax = RecordSyntax(arms)
+        stream.mark(syntax)
         yield syntax
 
     # Grouping or list constructor.
         stream.pull()                       # `(`
         if stream.peek(u')'):
             stream.pull()                   # `)`
-            syntax = ListSyntax([], stream.mark())
+            syntax = ListSyntax([])
+            stream.mark(syntax)
         else:
             arm = stream.pull()             # assignment
             if stream.peek(u')'):
                 stream.pull()               # `)`
-                syntax = GroupSyntax(arm, stream.mark())
+                syntax = GroupSyntax(arm)
             else:
                 arms = [arm]
                 stream.pull()               # `,`
                     arm = stream.pull()     # assignment
                     arms.append(arm)
                 stream.pull()               # `)`
-                syntax = ListSyntax(arms, stream.mark())
+                syntax = ListSyntax(arms)
+            stream.mark(syntax)
         yield syntax
 
     # Identity constructor.
             arm = stream.pull()     # label
             arms.append(arm)
         stream.pull()               # `]` | `)`
-        syntax = IdentitySyntax(arms, is_hard, stream.mark())
+        syntax = IdentitySyntax(arms, is_hard)
+        stream.mark(syntax)
         yield syntax
 
     identity.set_match(match_identity)
     def match_label(stream):
         if stream.peek(LABEL):
             label = stream.pull()   # %LABEL
-            syntax = LabelSyntax(label.text, stream.mark())
+            syntax = LabelSyntax(label.text)
+            stream.mark(syntax)
             yield syntax
         elif stream.peek(STRING):
             literal = stream.pull() # %STRING
-            syntax = StringSyntax(literal.text, stream.mark())
+            syntax = StringSyntax(literal.text)
+            stream.mark(syntax)
             yield syntax
         else:
             yield stream.pull()     # label_group | reference
     def match_literal(stream):
         if stream.peek(STRING):
             literal = stream.pull()     # %STRING
-            syntax = StringSyntax(literal.text, literal.mark)
+            syntax = StringSyntax(literal.text)
         elif stream.peek(INTEGER):
             literal = stream.pull()     # %INTEGER
-            syntax = IntegerSyntax(literal.text, literal.mark)
+            syntax = IntegerSyntax(literal.text)
         elif stream.peek(DECIMAL):
             literal = stream.pull()     # %DECIMAL
-            syntax = DecimalSyntax(literal.text, literal.mark)
+            syntax = DecimalSyntax(literal.text)
         elif stream.peek(FLOAT):
             literal = stream.pull()     # %FLOAT
-            syntax = FloatSyntax(literal.text, literal.mark)
+            syntax = FloatSyntax(literal.text)
+        stream.mark(syntax)
         yield syntax
 
     # Generate and return the parser.

src/htsql/core/syn/syntax.py

 #
 
 
-from ..error import Mark, EmptyMark
 from ..util import (maybe, listof, oneof, Clonable, Hashable, Printable,
         to_name, to_literal)
 import re
 class Syntax(Clonable, Hashable, Printable):
     """
     A syntax node.
-
-    `mark`: :class:`.Mark`
-        Error context.
     """
 
-    def __init__(self, mark):
-        assert isinstance(mark, Mark)
-        self.mark = mark
+    def __init__(self):
+        # Need a dummy constructor for `Clonable`.
+        pass
 
 
 class VoidSyntax(Syntax):
     available.
     """
 
-    def __init__(self):
-        super(VoidSyntax, self).__init__(EmptyMark())
-
     def __basis__(self):
         return ()
 
         The assigned value.
     """
 
-    def __init__(self, larm, rarm, mark):
+    def __init__(self, larm, rarm):
         assert isinstance(larm, SpecifySyntax)
         assert isinstance(rarm, Syntax)
-        super(AssignSyntax, self).__init__(mark)
         self.larm = larm
         self.rarm = rarm
 
         Set only if the specifier is a reference.
     """
 
-    def __init__(self, larms, rarms, mark):
+    def __init__(self, larms, rarms):
         assert isinstance(larms, listof(oneof(IdentifierSyntax,
                                               ReferenceSyntax)))
         assert len(larms) > 0
         assert isinstance(rarms, maybe(listof(oneof(IdentifierSyntax,
                                                     ReferenceSyntax))))
-        super(SpecifySyntax, self).__init__(mark)
         self.larms = larms
         self.rarms = rarms
         # Unpack the specifier for common cases:
         Function arguments or operands of the operator.
     """
 
-    def __init__(self, name, arguments, mark):
+    def __init__(self, name, arguments):
         assert isinstance(name, unicode)
         assert isinstance(arguments, listof(Syntax))
-        super(ApplySyntax, self).__init__(mark)
         self.name = name
         self.arguments = arguments
 
         Function arguments.
     """
 
-    def __init__(self, identifier, arms, mark):
+    def __init__(self, identifier, arms):
         assert isinstance(identifier, IdentifierSyntax)
         assert isinstance(arms, listof(Syntax))
-        super(FunctionSyntax, self).__init__(identifier.name, arms, mark)
+        super(FunctionSyntax, self).__init__(identifier.name, arms)
         self.identifier = identifier
         self.arms = arms
 
         enclosed in parentheses.
     """
 
-    def __init__(self, identifier, larm, rarms, is_flow, is_open, mark):
+    def __init__(self, identifier, larm, rarms, is_flow, is_open):
         assert isinstance(identifier, IdentifierSyntax)
         assert isinstance(larm, Syntax)
         assert isinstance(rarms, listof(Syntax))
         assert isinstance(is_flow, bool)
         assert isinstance(is_open, bool)
         assert not is_open or len(rarms) <= 1
-        super(PipeSyntax, self).__init__(identifier.name, [larm]+rarms, mark)
+        super(PipeSyntax, self).__init__(identifier.name, [larm]+rarms)
         self.identifier = identifier
         self.larm = larm
         self.rarms = rarms
         The right-hand operand.
     """
 
-    def __init__(self, symbol, larm, rarm, mark):
+    def __init__(self, symbol, larm, rarm):
         assert isinstance(symbol, unicode)
         assert isinstance(larm, Syntax)
         assert isinstance(rarm, Syntax)
-        super(OperatorSyntax, self).__init__(symbol, [larm, rarm], mark)
+        super(OperatorSyntax, self).__init__(symbol, [larm, rarm])
         self.symbol = symbol
         self.larm = larm
         self.rarm = rarm
         The operand.
     """
 
-    def __init__(self, symbol, arm, mark):
+    def __init__(self, symbol, arm):
         assert isinstance(symbol, unicode)
         assert isinstance(arm, Syntax)
-        super(PrefixSyntax, self).__init__(symbol, [arm], mark)
+        super(PrefixSyntax, self).__init__(symbol, [arm])
         self.symbol = symbol
         self.arm = arm
 
         <larm> ? <rarm>
     """
 
-    def __init__(self, larm, rarm, mark):
-        super(FilterSyntax, self).__init__(u'?', larm, rarm, mark)
+    def __init__(self, larm, rarm):
+        super(FilterSyntax, self).__init__(u'?', larm, rarm)
 
 
 class ProjectSyntax(OperatorSyntax):
         <larm> ^ <rarm>
     """
 
-    def __init__(self, larm, rarm, mark):
-        super(ProjectSyntax, self).__init__(u'^', larm, rarm, mark)
+    def __init__(self, larm, rarm):
+        super(ProjectSyntax, self).__init__(u'^', larm, rarm)
 
 
 class LinkSyntax(OperatorSyntax):
         <larm> -> <rarm>
     """
 
-    def __init__(self, larm, rarm, mark):
-        super(LinkSyntax, self).__init__(u'->', larm, rarm, mark)
+    def __init__(self, larm, rarm):
+        super(LinkSyntax, self).__init__(u'->', larm, rarm)
 
 
 class AttachSyntax(OperatorSyntax):
         <larm> @ <rarm>
     """
 
-    def __init__(self, larm, rarm, mark):
-        super(AttachSyntax, self).__init__(u'@', larm, rarm, mark)
+    def __init__(self, larm, rarm):
+        super(AttachSyntax, self).__init__(u'@', larm, rarm)
 
 
 class DetachSyntax(PrefixSyntax):
         @ <arm>
     """
 
-    def __init__(self, arm, mark):
-        super(DetachSyntax, self).__init__(u'@', arm, mark)
+    def __init__(self, arm):
+        super(DetachSyntax, self).__init__(u'@', arm)
 
 
 class CollectSyntax(PrefixSyntax):
         / <arm>
     """
 
-    def __init__(self, arm, mark):
-        super(CollectSyntax, self).__init__(u'/', arm, mark)
+    def __init__(self, arm):
+        super(CollectSyntax, self).__init__(u'/', arm)
 
 
 class DirectSyntax(Syntax):
         The operand.
     """
 
-    def __init__(self, symbol, arm, mark):
+    def __init__(self, symbol, arm):
         assert isinstance(symbol, unicode) and symbol in [u'+', u'-']
         assert isinstance(arm, Syntax)
-        super(DirectSyntax, self).__init__(mark)
         self.symbol = symbol
         self.arm = arm
 
         The right-hand operand.
     """
 
-    def __init__(self, larm, rarm, mark):
+    def __init__(self, larm, rarm):
         assert isinstance(larm, Syntax)
         assert isinstance(rarm, Syntax)
-        super(ComposeSyntax, self).__init__(mark)
         self.larm = larm
         self.rarm = rarm
 
         ``True`` if no ``()``.
     """
 
-    def __init__(self, index, is_open, mark):
-        super(UnpackSyntax, self).__init__(mark)
+    def __init__(self, index, is_open):
         assert index is None or (isinstance(index, (int, long)) and index >= 0)
         assert isinstance(is_open, bool)
         self.index = index
         The expression.
     """
 
-    def __init__(self, arm, mark):
+    def __init__(self, arm):
         assert isinstance(arm, Syntax)
-        super(GroupSyntax, self).__init__(mark)
         self.arm = arm
 
     def __basis__(self):
         The selection record.
     """
 
-    def __init__(self, larm, rarm, mark):
+    def __init__(self, larm, rarm):
         assert isinstance(larm, Syntax)
         assert isinstance(rarm, RecordSyntax)
-        super(SelectSyntax, self).__init__(mark)
         self.larm = larm
         self.rarm = rarm
 
         The identity.
     """
 
-    def __init__(self, larm, rarm, mark):
+    def __init__(self, larm, rarm):
         assert isinstance(larm, Syntax)
         assert isinstance(rarm, IdentitySyntax)
-        super(LocateSyntax, self).__init__(mark)
         self.larm = larm
         self.rarm = rarm
 
         Record fields.
     """
 
-    def __init__(self, arms, mark):
+    def __init__(self, arms):
         assert isinstance(arms, listof(Syntax))
-        super(RecordSyntax, self).__init__(mark)
         self.arms = arms
 
     def __basis__(self):
         List elements.
     """
 
-    def __init__(self, arms, mark):
+    def __init__(self, arms):
         assert isinstance(arms, listof(Syntax))
-        super(ListSyntax, self).__init__(mark)
         self.arms = arms
 
     def __basis__(self):
         (``()``).
     """
 
-    def __init__(self, arms, is_hard, mark):
+    def __init__(self, arms, is_hard):
         assert isinstance(arms, listof(Syntax)) and len(arms) > 0
         assert isinstance(is_hard, bool)
-        super(IdentitySyntax, self).__init__(mark)
         self.arms = arms
         self.is_hard = is_hard
 
         Normalized identifier name.
     """
 
-    def __init__(self, identifier, mark):
+    def __init__(self, identifier):
         assert isinstance(identifier, IdentifierSyntax)
-        super(ReferenceSyntax, self).__init__(mark)
         self.identifier = identifier
         self.name = identifier.name
 
         Normalized name.
     """
 
-    def __init__(self, text, mark):
+    def __init__(self, text):
         assert isinstance(text, unicode)
-        super(IdentifierSyntax, self).__init__(mark)
         self.text = text
         self.name = to_name(text)
 
         The value of the literal.
     """
 
-    def __init__(self, text, mark):
+    def __init__(self, text):
         assert isinstance(text, unicode)
-        super(LiteralSyntax, self).__init__(mark)
         self.text = text
 
     def __basis__(self):
     is_decimal = False
     is_float = False
 
-    def __init__(self, text, value, mark):
-        super(NumberSyntax, self).__init__(text, mark)
+    def __init__(self, text, value):
+        super(NumberSyntax, self).__init__(text)
         self.value = value
 
 
 
     is_integer = True
 
-    def __init__(self, text, mark):
-        super(IntegerSyntax, self).__init__(text, int(text), mark)
+    def __init__(self, text):
+        super(IntegerSyntax, self).__init__(text, int(text))
 
 
 class DecimalSyntax(NumberSyntax):
 
     is_decimal = True
 
-    def __init__(self, text, mark):
-        super(DecimalSyntax, self).__init__(text, decimal.Decimal(text), mark)
+    def __init__(self, text):
+        super(DecimalSyntax, self).__init__(text, decimal.Decimal(text))
 
 
 class FloatSyntax(NumberSyntax):
 
     is_float = True
 
-    def __init__(self, text, mark):
-        super(FloatSyntax, self).__init__(text, float(text), mark)
+    def __init__(self, text):
+        super(FloatSyntax, self).__init__(text, float(text))
 
 

src/htsql/core/syn/token.py

 #
 
 
-from ..error import Mark
 from ..util import Clonable, Hashable, Printable
 import urllib
 
 
     `text`: ``unicode``
         The token value.
-
-    `mark`: :class:`.Mark`
-        Error context.
     """
 
-    def __init__(self, code, text, mark):
+    def __init__(self, code, text):
         assert isinstance(code, unicode)
         assert isinstance(text, unicode)
-        assert isinstance(mark, Mark)
         self.code = code
         self.text = text
-        self.mark = mark
 
     def __basis__(self):
         return (self.code, self.text)

src/htsql/core/tr/bind.py

         FloatDomain, UntypedDomain, EntityDomain, RecordDomain, ListDomain,
         IdentityDomain, VoidDomain)
 from ..classify import normalize
-from ..error import Error, QuotePara, translate_guard, choices_guard
+from ..error import Error, QuotePara, translate_guard, choices_guard, point
 from ..syn.syntax import (Syntax, CollectSyntax, SelectSyntax, ApplySyntax,
         FunctionSyntax, PipeSyntax, OperatorSyntax, PrefixSyntax,
         ProjectSyntax, FilterSyntax, LinkSyntax, DetachSyntax, AssignSyntax,
             if not (0 <= index < len(recipes)):
                 raise Error("Expected value in range 1-%s" % len(recipes))
             syntax, recipe = recipes[index]
-            syntax = syntax.clone(mark=self.syntax.mark)
+            syntax = point(syntax, self.syntax)
             return self.state.use(recipe, syntax)
         # Otherwise, generate a selection node.
         elements = []
         for syntax, recipe in recipes:
-            syntax = syntax.clone(mark=self.syntax.mark)
+            syntax = point(syntax, self.syntax)
             element = self.state.use(recipe, syntax)
             elements.append(element)
         fields = [decorate(element) for element in elements]

src/htsql/core/tr/binding.py

 from ..entity import TableEntity, ColumnEntity, Join
 from ..domain import (Domain, VoidDomain, BooleanDomain, ListDomain,
         RecordDomain, EntityDomain, IdentityDomain, Profile)
+from ..error import point
 from ..syn.syntax import Syntax, VoidSyntax, IdentifierSyntax, StringSyntax
 from .signature import Signature, Bag, Formula
 from ..cmd.command import Command
         for presentation or error reporting only, there is no guarantee
         that that the syntax node is semantically, or even syntaxically
         valid.
-
-    Other attributes:
-
-    `mark` (:class:`htsql.core.mark.Mark`)
-        The location of the node in the original query (for error reporting).
     """
 
     def __init__(self, base, domain, syntax):
         self.base = base
         self.domain = domain
         self.syntax = syntax
-        self.mark = syntax.mark
+        point(self, syntax)
 
     def __str__(self):
         # Display an HTSQL fragment that (approximately) corresponds

src/htsql/core/tr/flow.py

                     cachedproperty)
 from ..entity import TableEntity, ColumnEntity, Join
 from ..domain import Domain, BooleanDomain, ListDomain, IdentityDomain
+from ..error import point
 from .binding import Binding, QueryBinding, SegmentBinding
 from .signature import Signature, Bag, Formula
 
         assert isinstance(binding, Binding)
         self.binding = binding
         self.syntax = binding.syntax
-        self.mark = binding.syntax.mark
+        point(self, binding)
 
     def __str__(self):
         # Display the syntex node that gave rise to the expression.

src/htsql/core/tr/fn/bind.py

         QueryBinding, Binding, BindingRecipe, ComplementRecipe, KernelRecipe,
         SubstitutionRecipe, ClosedRecipe)
 from ..bind import BindByName, BindingState
-from ...error import Error, translate_guard, QuotePara, ErrorGuard
+from ...error import Error, translate_guard, QuotePara, ErrorGuard, point
 from ..coerce import coerce
 from ..decorate import decorate
 from ..lookup import direct, expand, identify, guess_tag, lookup_command
             if recipes is not None:
                 for syntax, recipe in recipes:
                     if not isinstance(syntax, (IdentifierSyntax, GroupSyntax)):
-                        syntax = GroupSyntax(syntax, syntax.mark)
-                    syntax = ComposeSyntax(element.syntax, syntax,
-                                           syntax.mark)
+                        syntax = point(GroupSyntax(syntax), syntax)
+                    syntax = point(ComposeSyntax(element.syntax, syntax),
+                                   syntax)
                     elements.append(self.state.use(recipe, syntax))
             else:
                 elements.append(element)

src/htsql/core/tr/frame.py

 from ..util import listof, tupleof, maybe, Clonable, Hashable, Printable
 from ..entity import TableEntity, ColumnEntity
 from ..domain import Domain, BooleanDomain
+from ..error import point
 from .coerce import coerce
 from .flow import Expression
 from .term import Term, QueryTerm
         self.expression = expression
         self.binding = expression.binding
         self.syntax = expression.syntax
-        self.mark = expression.mark
+        point(self, expression)
 
     def __str__(self):
         return str(self.expression)

src/htsql/core/tr/lookup.py

         SyntaxArc, InvalidArc, AmbiguousArc)
 from ..classify import classify, relabel, localize, normalize
 from ..syn.syntax import IdentifierSyntax
+from ..error import point
 from .binding import (Binding, ScopingBinding, ChainingBinding,
         WrappingBinding, SegmentBinding, HomeBinding, RootBinding,
         TableBinding, FreeTableBinding, AttachedTableBinding, ColumnBinding,
             for label in labels:
                 if not label.is_public:
                     continue
-                identifier = IdentifierSyntax(label.name, self.binding.mark)
+                identifier = IdentifierSyntax(label.name)
+                point(identifier, self.binding)
                 recipe = prescribe(label.arc, self.binding)
                 recipes.append((identifier, recipe))
             return recipes
             if not label.is_public:
                 continue
             # Create a "virtual" syntax node for each column
-            identifier = IdentifierSyntax(label.name, self.binding.mark)
+            identifier = IdentifierSyntax(label.name)
+            point(identifier, self.binding)
             recipe = prescribe(label.arc, self.binding)
             yield (identifier, recipe)
 

src/htsql/core/tr/rewrite.py

         if expression in self.rewrite_cache:
             return self.rewrite_cache[expression]
         # Apply `Rewrite` adapter.
-        with translate_guard(expression):
-            replacement = rewrite(expression, self)
+        replacement = rewrite(expression, self)
         # Cache the output.
         self.rewrite_cache[expression] = replacement
         return replacement
         # If the key is not in the cache, apply the `Unmask` adapter and store
         # the result in the cache.
         if key not in self.unmask_cache:
-            replacement = Unmask.__invoke__(expression, self)
+            with translate_guard(expression):
+                replacement = Unmask.__invoke__(expression, self)
             self.unmask_cache[key] = replacement
         # Otherwise, fetch the result from the cache.
         else:
         `expression` (:class:`htsql.core.tr.flow.Expression`)
             The expression to collect units from.
         """
-        Collect.__invoke__(expression, self)
+        with translate_guard(expression):
+            Collect.__invoke__(expression, self)
 
     def recombine(self):
         """
         if expression in self.replace_cache:
             return self.replace_cache[expression]
         # If not, apply the `Replace` adapter.
-        replacement = Replace.__invoke__(expression, self)
+        with translate_guard(expression):
+            replacement = Replace.__invoke__(expression, self)
         # Store the result in the cache and return it.
         self.replace_cache[expression] = replacement
         return replacement
     if state is None:
         state = RewritingState()
     # Apply the `Rewrite` adapter.
-    return Rewrite.__invoke__(expression, state)
+    with translate_guard(expression):
+        return Rewrite.__invoke__(expression, state)
 
 

src/htsql/core/tr/stitch.py

                     # but in order to produce a better string representation,
                     # we replace the associated syntax node with a new
                     # identifier named after the column.
-                    identifier = IdentifierSyntax(normalize(column.name),
-                                                  self.flow.mark)
+                    identifier = IdentifierSyntax(normalize(column.name))
                     binding = self.flow.binding.clone(syntax=identifier)
                     code = ColumnUnit(column, flow, binding)
                     yield (code, +1)

src/htsql/core/tr/term.py

 from ..util import (listof, dictof, tupleof, maybe,
                     Clonable, Printable, Hashable)
 from ..domain import BooleanDomain
+from ..error import point
 from .flow import Expression, Flow, Code, Unit, QueryExpr, SegmentCode
 
 
         self.expression = expression
         self.binding = expression.binding
         self.syntax = expression.syntax
-        self.mark = expression.mark
+        point(self, expression)
 
     def __str__(self):
         return str(self.expression)

src/htsql/tweak/etl/cmd/command.py

 
 class ETLCmd(Command):
 
-    def __init__(self, feed, mark):
-        super(ETLCmd, self).__init__(mark)
+    def __init__(self, feed):
         assert isinstance(feed, Command)
         self.feed = feed
 
 
 class TruncateCmd(Command):
 
-    def __init__(self, table, mark):
-        super(TruncateCmd, self).__init__(mark)
+    def __init__(self, table):
         assert isinstance(table, TableEntity)
         self.table = table
 
 
 class DoCmd(Command):
 
-    def __init__(self, blocks, mark):
-        super(DoCmd, self).__init__(mark)
+    def __init__(self, blocks):
         assert isinstance(blocks, listof(tupleof(maybe(unicode), Command)))
         self.blocks = blocks
 

src/htsql/tweak/etl/cmd/insert.py

 
 from ....core.util import listof
 from ....core.adapter import Utility, Adapter, adapt, adapt_many
-from ....core.error import EmptyMark, Error, QuotePara
+from ....core.error import Error, QuotePara
 from ....core.connect import transaction, scramble, unscramble
 from ....core.domain import (Domain, ListDomain, RecordDomain, BooleanDomain,
         IntegerDomain, FloatDomain, DecimalDomain, TextDomain, DateDomain,
         labels = relabel(TableArc(self.table))
         if labels:
             label = labels[0]
-            identifier = IdentifierSyntax(label.name, EmptyMark())
+            identifier = IdentifierSyntax(label.name)
             binding = AliasBinding(binding, identifier)
         state.pop_scope()
         state.pop_scope()

src/htsql/tweak/etl/cmd/summon.py

             raise Error("Expected 1 argument")
         [syntax] = self.arguments
         feed = recognize(syntax)
-        command = self.cmd(feed, self.syntax.mark)
+        command = self.cmd(feed)
         return command
 
 
             if not isinstance(arc, TableArc):
                 raise Error("Expected a table")
         table = arc.table
-        command = TruncateCmd(table, self.syntax.mark)
+        command = TruncateCmd(table)
         return command
 
 
                 syntax = argument.rarm
             command = recognize(syntax)
             blocks.append((name, command))
-        command = DoCmd(blocks, self.syntax.mark)
+        command = DoCmd(blocks)
         return command
 
 

src/htsql/tweak/gateway/command.py

 
 class GatewayCmd(Command):
 
-    def __init__(self, instance, command, mark):
-        super(GatewayCmd, self).__init__(mark)
+    def __init__(self, instance, command):
         self.instance = instance
         self.command = command
 
         [syntax] = self.arguments
         with self.instance:
             command = recognize(syntax)
-        return GatewayCmd(self.instance, command, self.syntax.mark)
+        return GatewayCmd(self.instance, command)
 
 
 class ActGateway(Act):

src/htsql/tweak/meta/command.py

 
 class MetaCmd(Command):
 
-    def __init__(self, command, mark):
-        super(MetaCmd, self).__init__(mark)
+    def __init__(self, command):
         self.command = command
 
 
         slave_app = get_slave_app()
         with slave_app:
             command = recognize(syntax)
-        return MetaCmd(command, self.syntax.mark)
+        return MetaCmd(command)
 
 
 class ActMeta(Act):

src/htsql/tweak/shell/command.py

 from ...core.util import maybe, listof
 from ...core.context import context
 from ...core.adapter import Adapter, adapt, adapt_many, call
-from ...core.error import PermissionError, Error, PointerPara
+from ...core.error import PermissionError, Error, PointerPara, recognize_guard
 from ...core.domain import (Domain, BooleanDomain, NumberDomain, DateTimeDomain,
                             ListDomain, RecordDomain)
 from ...core.syn.syntax import StringSyntax, IntegerSyntax, IdentifierSyntax
 
 class ShellCmd(Command):
 
-    def __init__(self, query=None, is_implicit=False, mark=None):
-        super(ShellCmd, self).__init__(mark)
+    def __init__(self, query=None, is_implicit=False):
         assert isinstance(query, maybe(unicode))
         assert isinstance(is_implicit, bool)
         self.query = query
 
 class CompleteCmd(Command):
 
-    def __init__(self, names, mark):
-        super(CompleteCmd, self).__init__(mark)
+    def __init__(self, names):
         assert isinstance(names, listof(unicode))
         self.names = names
 
 
 class ProduceCmd(Command):
 
-    def __init__(self, query, page=None, mark=None):
-        super(ProduceCmd, self).__init__(mark)
+    def __init__(self, query, page=None):
         assert isinstance(query, unicode)
         assert isinstance(page, maybe(int))
         if page is None:
 
 class AnalyzeCmd(Command):
 
-    def __init__(self, query, mark):
-        super(AnalyzeCmd, self).__init__(mark)
+    def __init__(self, query):
         assert isinstance(query, unicode)
         self.query = query
 
 
 class WithPermissionsCmd(Command):
 
-    def __init__(self, command, can_read, can_write, mark):
-        super(WithPermissionsCmd, self).__init__(mark)
+    def __init__(self, command, can_read, can_write):
         assert isinstance(command, Command)
         assert isinstance(can_read, bool)
         assert isinstance(can_write, bool)
         query = None
         if self.arguments:
             if len(self.arguments) != 1:
-                raise Error("expected no or 1 argument",
-                            self.syntax.mark)
+                raise Error("Expected no or 1 argument")
             [syntax] = self.arguments
             if isinstance(syntax, StringSyntax):
                 query = syntax.text
             else:
                 query = unicode(syntax)
-        command = ShellCmd(query, False, self.syntax.mark)
+        command = ShellCmd(query, False)
         return command
 
 
         names = []
         for syntax in self.arguments:
             if not isinstance(syntax, (IdentifierSyntax, StringSyntax)):
-                raise Error("an identifier is required", syntax.mark)
+                with recognize_guard(syntax):
+                    raise Error("Expected an identifier")
             if isinstance(syntax, IdentifierSyntax):
                 name = syntax.name
             else:
                 name = syntax.text
             names.append(name)
-        command = CompleteCmd(names, self.syntax.mark)
+        command = CompleteCmd(names)
         return command
 
 
 
     def __call__(self):
         if not (1 <= len(self.arguments) <= 2):
-            raise Error("expected 1 or 2 arguments",
-                                 self.syntax.mark)
+            raise Error("Expected 1 or 2 arguments")
         query = self.arguments[0]
         if not isinstance(query, StringSyntax):
-            raise Error("a string literal is required", query.mark)
+            with recognize_guard(query):
+                raise Error("Expected a string literal")
         query = query.text
         page = None
         if len(self.arguments) == 2:
             page = self.arguments[1]
             if not isinstance(page, IntegerSyntax):
-                raise Error("an integer literal is required", page.mark)
+                with recognize_guard(page):
+                    raise Error("Expected an integer literal")
             page = page.value
-        command = ProduceCmd(query, page, self.syntax.mark)
+        command = ProduceCmd(query, page)
         return command
 
 
 
     def __call__(self):
         if len(self.arguments) != 1:
-            raise Error("expected 1 argument",
-                        self.syntax.mark)
+            raise Error("Expected 1 argument")
         query = self.arguments[0]
         if not isinstance(query, StringSyntax):
-            raise Error("a string literal is required", query.mark)
+            with recognize_guard(query):
+                raise Error("Expected a string literal")
         query = query.text
-        command = AnalyzeCmd(query, self.syntax.mark)
+        command = AnalyzeCmd(query)
         return command
 
 
 
     def __call__(self):
         if len(self.arguments) != 3:
-            raise Error("expected 3 arguments", self.syntax.mark)
+            raise Error("Expected 3 arguments")
         query, can_read, can_write = self.arguments
         literals = [can_read, can_write]
         values = []
         domain = BooleanDomain()
         for literal in literals:
-            if not isinstance(literal, StringSyntax):
-                raise Error("a string literal is required",
-                                     literal.mark)
-            try:
-                value = domain.parse(literal.text)
-            except ValueError, exc:
-                raise Error(str(exc), literal.mark)
+            with recognize_guard(literal):
+                if not isinstance(literal, StringSyntax):
+                        raise Error("Expected a string literal")
+                try:
+                    value = domain.parse(literal.text)
+                except ValueError, exc:
+                    raise Error(str(exc))
             values.append(value)
         can_read, can_write = values
         with context.env(can_read=context.env.can_read and can_read,
                          can_write=context.env.can_write and can_write):
             query = recognize(query)
-        command = WithPermissionsCmd(query, can_read, can_write,
-                                     self.syntax.mark)
+        command = WithPermissionsCmd(query, can_read, can_write)
         return command
 
 

src/htsql/tweak/shell/default/act.py

         try:
             syntax = parse(self.command.query)
             if addon.on_root and isinstance(syntax, SkipSyntax):
-                command = ShellCmd(is_implicit=True, mark=syntax.mark)
+                command = ShellCmd(is_implicit=True)
             else:
                 command = Recognize.__invoke__(syntax)
                 if command is None:
                             not isinstance(syntax, SkipSyntax)):
                         query = unquote(self.command.query)
                         query = query.decode('utf-8', 'replace')
-                        command = ShellCmd(query, is_implicit=True,
-                                           mark=syntax.mark)
+                        command = ShellCmd(query, is_implicit=True)
                     else:
                         command = DefaultCmd(syntax)
             return act(command, self.action)
                 raise
             query = unquote(self.command.query)
             query = query.decode('utf-8', 'replace')
-            command = ShellCmd(query, is_implicit=True, mark=self.command.mark)
+            command = ShellCmd(query, is_implicit=True)
             return act(command, self.action)
 
 

test/output/mssql.yaml

           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{}{true()}
-                ^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^
         - uri: /school^{true()}{true()}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{true()}{true()}
-                ^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /school?false()^{true()}{true()}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school?false()^{true()}{true()}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^^^^^^^^^
         - uri: /school^{}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^
         - uri: /school^{true()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{true()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /school?false()^{true()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school?false()^{true()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^^^^^^^^^
         - uri: /school^{null()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{null()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /{school^{}{count(school)}, school?false()^{}{count(school)}}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /{school^{}{count(school)}, school?false()^{}{count(school)}}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                  ^^^^^^^^^
         - uri: /school{program^{}{count(program)}, program?false()^{}{count(program)}}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school{program^{}{count(program)}, program?false()^{}{count(program)}}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                        ^^^^^^^^^^
         - uri: /{count(school^true()), count(school?false()^true()), count(school^null())}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /{count(school^true()), count(school?false()^true()), count(school^null())}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                        ^^^^^^^^^^^^^
         - uri: /(school^null()).^
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /(school^null()).^
-                ^^^^^^^^^^^^^^^^^^
+                  ^^^^^^^^^^^^^
         - uri: /department^school_code{school_code.name, name}
           status: 400 Bad Request
           headers:
             unsupported action
             While processing:
                 /school/:html/:json
-                ^^^^^^^^^^^^^
+                         ^^^^
         - uri: /school/:html/:sql
           status: 400 Bad Request
           headers:
             unsupported action
             While processing:
                 /school/:html/:sql
-                ^^^^^^^^^^^^^
+                         ^^^^
   - include: test/input/addon.yaml
     output:
       id: addon
           status: 403 Forbidden
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
-          - [Set-Cookie, htsql-csrf-token=ba078254337ae22872366163cbcf953a; Path=/]
+          - [Set-Cookie, htsql-csrf-token=abf74627aad24b0670dd92ea48925bc2; Path=/]
           body: |
             not enough permissions to execute the query
             While processing:
                 /school
-                ^^^^^^^
+                ^
         - py: pass-csrf-token
           stdout: |+
             200 OK
           status: 403 Forbidden
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
-          - [Set-Cookie, htsql-csrf-token=421ee207aadac0bc83d7aa49e32434c2; Path=/]
+          - [Set-Cookie, htsql-csrf-token=976ef3fa416d882655618a9faf6598d2; Path=/]
           body: |
             not enough permissions to execute the query
             While processing:
                 /meta(/table)
-                      ^^^^^^
+                      ^
       - id: tweak.override
         tests:
         - ctl: [ext, tweak.override]
           body: |
             {
               "type": "permissions",
-              "detail": "not enough permissions to execute the query\nWhile processing:\n    \/school:top\n    ^^^^^^^^^^^"
+              "detail": "not enough permissions to execute the query\nWhile processing:\n    \/school:top\n    ^"
             }
         - uri: /
           status: 200 OK
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^true()
-                ^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^
         - uri: /date(true())
           status: 400 Bad Request
           headers:

test/output/mysql.yaml

           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{}{true()}
-                ^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^
         - uri: /school^{true()}{true()}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{true()}{true()}
-                ^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /school?false()^{true()}{true()}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school?false()^{true()}{true()}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^^^^^^^^^
         - uri: /school^{}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^
         - uri: /school^{true()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{true()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /school?false()^{true()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school?false()^{true()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^^^^^^^^^
         - uri: /school^{null()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{null()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /{school^{}{count(school)}, school?false()^{}{count(school)}}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /{school^{}{count(school)}, school?false()^{}{count(school)}}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                  ^^^^^^^^^
         - uri: /school{program^{}{count(program)}, program?false()^{}{count(program)}}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school{program^{}{count(program)}, program?false()^{}{count(program)}}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                        ^^^^^^^^^^
         - uri: /{count(school^true()), count(school?false()^true()), count(school^null())}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /{count(school^true()), count(school?false()^true()), count(school^null())}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                        ^^^^^^^^^^^^^
         - uri: /(school^null()).^
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /(school^null()).^
-                ^^^^^^^^^^^^^^^^^^
+                  ^^^^^^^^^^^^^
         - uri: /department^school_code{school_code.name, name}
           status: 400 Bad Request
           headers:
             unsupported action
             While processing:
                 /school/:html/:json
-                ^^^^^^^^^^^^^
+                         ^^^^
         - uri: /school/:html/:sql
           status: 400 Bad Request
           headers:
             unsupported action
             While processing:
                 /school/:html/:sql
-                ^^^^^^^^^^^^^
+                         ^^^^
   - include: test/input/addon.yaml
     output:
       id: addon
           status: 403 Forbidden
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
-          - [Set-Cookie, htsql-csrf-token=d1e4f773e5127343dd1f700ed98db7b5; Path=/]
+          - [Set-Cookie, htsql-csrf-token=4780d3ab6ca5c074cc8654f4a52fe4ba; Path=/]
           body: |
             not enough permissions to execute the query
             While processing:
                 /school
-                ^^^^^^^
+                ^
         - py: pass-csrf-token
           stdout: |+
             200 OK
           status: 403 Forbidden
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
-          - [Set-Cookie, htsql-csrf-token=ea40a0a2d34c17e18ae87dd9942f6729; Path=/]
+          - [Set-Cookie, htsql-csrf-token=340fbc8056469569b31881a44510ea6a; Path=/]
           body: |
             not enough permissions to execute the query
             While processing:
                 /meta(/table)
-                      ^^^^^^
+                      ^
       - id: tweak.override
         tests:
         - ctl: [ext, tweak.override]
           body: |
             {
               "type": "permissions",
-              "detail": "not enough permissions to execute the query\nWhile processing:\n    \/school:top\n    ^^^^^^^^^^^"
+              "detail": "not enough permissions to execute the query\nWhile processing:\n    \/school:top\n    ^"
             }
         - uri: /
           status: 200 OK
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^true()
-                ^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^
         - uri: /date(true())
           status: 400 Bad Request
           headers:

test/output/oracle.yaml

           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{}{true()}
-                ^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^
         - uri: /school^{true()}{true()}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{true()}{true()}
-                ^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /school?false()^{true()}{true()}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school?false()^{true()}{true()}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^^^^^^^^^
         - uri: /school^{}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^
         - uri: /school^{true()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{true()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /school?false()^{true()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school?false()^{true()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^^^^^^^^^
         - uri: /school^{null()}{*, count(school)}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /school^{null()}{*, count(school)}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                 ^^^^^^^^^^^^^^^
         - uri: /{school^{}{count(school)}, school?false()^{}{count(school)}}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing:
+            While translating:
                 /{school^{}{count(school)}, school?false()^{}{count(school)}}
-                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                  ^^^^^^^^^
         - uri: /school{program^{}{count(program)}, program?false()^{}{count(program)}}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
             Found an empty or constant kernel
-            While processing: