summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Corbett <corbett.dav@husky.neu.edu>2014-10-08 23:03:50 -0400
committerDavid Corbett <corbett.dav@husky.neu.edu>2014-10-08 23:03:50 -0400
commit16d4588786a1fe4ede5bc91afdb9243a11f85a51 (patch)
tree8f07b502a84a809fb71ba710fe6ed45f4da2e720
parent87781e3aa6748b035a822a3a8fdf2cedd53d478b (diff)
downloadpygments-16d4588786a1fe4ede5bc91afdb9243a11f85a51.tar.gz
Add a lexer for TADS 3
-rw-r--r--AUTHORS2
-rw-r--r--pygments/lexers/_mapping.py3
-rw-r--r--pygments/lexers/int_fiction.py609
-rw-r--r--tests/examplefiles/example.t1248
4 files changed, 1859 insertions, 3 deletions
diff --git a/AUTHORS b/AUTHORS
index 4f699f6b..06119231 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -29,7 +29,7 @@ Other contributors, listed alphabetically, are:
* Pierre Bourdon -- bugfixes
* Hiram Chirino -- Scaml and Jade lexers
* Ian Cooper -- VGL lexer
-* David Corbett -- Inform and Jasmin lexers
+* David Corbett -- Inform, Jasmin, and TADS 3 lexers
* Leaf Corcoran -- MoonScript lexer
* Christopher Creutzig -- MuPAD lexer
* Daniël W. Crompton - Pike lexer
diff --git a/pygments/lexers/_mapping.py b/pygments/lexers/_mapping.py
index 6d7c94df..d641a564 100644
--- a/pygments/lexers/_mapping.py
+++ b/pygments/lexers/_mapping.py
@@ -176,7 +176,7 @@ LEXERS = {
'JavascriptLexer': ('pygments.lexers.javascript', 'JavaScript', ('js', 'javascript'), ('*.js',), ('application/javascript', 'application/x-javascript', 'text/x-javascript', 'text/javascript')),
'JavascriptPhpLexer': ('pygments.lexers.templates', 'JavaScript+PHP', ('js+php', 'javascript+php'), (), ('application/x-javascript+php', 'text/x-javascript+php', 'text/javascript+php')),
'JavascriptSmartyLexer': ('pygments.lexers.templates', 'JavaScript+Smarty', ('js+smarty', 'javascript+smarty'), (), ('application/x-javascript+smarty', 'text/x-javascript+smarty', 'text/javascript+smarty')),
- 'JsonLdLexer': ('pygments.lexers.data', 'JSON-LD', ('json-ld',), ('*.jsonld',), ('application/json-ld',)),
+ 'JsonLdLexer': ('pygments.lexers.data', 'JSON-LD', ('jsonld', 'json-ld'), ('*.jsonld',), ('application/ld+json',)),
'JsonLexer': ('pygments.lexers.data', 'JSON', ('json',), ('*.json',), ('application/json',)),
'JspLexer': ('pygments.lexers.templates', 'Java Server Page', ('jsp',), ('*.jsp',), ('application/x-jsp',)),
'JuliaConsoleLexer': ('pygments.lexers.julia', 'Julia console', ('jlcon',), (), ()),
@@ -330,6 +330,7 @@ LEXERS = {
'SwiftLexer': ('pygments.lexers.objective', 'Swift', ('swift',), ('*.swift',), ('text/x-swift',)),
'SwigLexer': ('pygments.lexers.c_like', 'SWIG', ('swig',), ('*.swg', '*.i'), ('text/swig',)),
'SystemVerilogLexer': ('pygments.lexers.hdl', 'systemverilog', ('systemverilog', 'sv'), ('*.sv', '*.svh'), ('text/x-systemverilog',)),
+ 'Tads3Lexer': ('pygments.lexers.int_fiction', 'TADS 3', ('tads3',), ('*.t',), ()),
'TclLexer': ('pygments.lexers.tcl', 'Tcl', ('tcl',), ('*.tcl', '*.rvt'), ('text/x-tcl', 'text/x-script.tcl', 'application/x-tcl')),
'TcshLexer': ('pygments.lexers.shell', 'Tcsh', ('tcsh', 'csh'), ('*.tcsh', '*.csh'), ('application/x-csh',)),
'TeaTemplateLexer': ('pygments.lexers.templates', 'Tea', ('tea',), ('*.tea',), ('text/x-tea',)),
diff --git a/pygments/lexers/int_fiction.py b/pygments/lexers/int_fiction.py
index 95f4437c..cbac9e39 100644
--- a/pygments/lexers/int_fiction.py
+++ b/pygments/lexers/int_fiction.py
@@ -16,7 +16,8 @@ from pygments.lexer import RegexLexer, include, bygroups, using, \
from pygments.token import Text, Comment, Operator, Keyword, Name, String, \
Number, Punctuation, Error, Generic
-__all__ = ['Inform6Lexer', 'Inform6TemplateLexer', 'Inform7Lexer']
+__all__ = ['Inform6Lexer', 'Inform6TemplateLexer', 'Inform7Lexer',
+ 'Tads3Lexer']
class Inform6Lexer(RegexLexer):
@@ -722,3 +723,609 @@ class Inform6TemplateLexer(Inform7Lexer):
def get_tokens_unprocessed(self, text, stack=('+i6t-root',)):
return Inform7Lexer.get_tokens_unprocessed(self, text, stack)
+
+
+class Tads3Lexer(RegexLexer):
+ """
+ For `TADS 3 <http://www.tads.org/>`_ source code.
+ """
+
+ name = 'TADS 3'
+ aliases = ['tads3']
+ filenames = ['*.t']
+
+ flags = re.DOTALL | re.MULTILINE
+
+ _comment_single = r'(?://(?:[^\\\n]|\\+[\w\W])*$)'
+ _comment_multiline = r'(?:/\*(?:[^*]|\*(?!/))*\*/)'
+ _escape = (r'(?:\\(?:[\n\\<>"\'^v bnrt]|u[\da-fA-F]{,4}|x[\da-fA-F]{,2}|'
+ r'[0-3]?[0-7]{1,2}))')
+ _name = r'(?:[_a-zA-Z]\w*)'
+ _no_quote = r'(?=\s|\\?>)'
+ _operator = (r'(?:&&|\|\||\+\+|--|\?\?|::|[.,@\[\]~]|'
+ r'(?:[=+\-*/%!&|^]|<<?|>>?>?)=?)')
+ _ws = r'(?:\\|\s|%s|%s)' % (_comment_single, _comment_multiline)
+ _ws_pp = r'(?:\\\n|[^\S\n]|%s|%s)' % (_comment_single, _comment_multiline)
+
+ def _make_string_state(triple, double, verbatim=None, _escape=_escape):
+ if verbatim:
+ verbatim = ''.join(['(?:%s|%s)' % (re.escape(c.lower()),
+ re.escape(c.upper()))
+ for c in verbatim])
+ char = r'"' if double else r"'"
+ token = String.Double if double else String.Single
+ escaped_quotes = r'+|%s(?!%s{2})' % (char, char) if triple else r''
+ prefix = '%s%s' % ('t' if triple else '', 'd' if double else 's')
+ tag_state_name = '%sqt' % prefix
+ state = []
+ if triple:
+ state += [
+ (r'%s{3,}' % char, token, '#pop'),
+ (r'\\%s+' % char, String.Escape),
+ (char, token)
+ ]
+ else:
+ state.append((char, token, '#pop'))
+ state += [
+ include('s/verbatim'),
+ (r'[^\\<&{}%s]+' % char, token)
+ ]
+ if verbatim:
+ # This regex can't use `(?i)` because escape sequences are
+ # case-sensitive. `<\XMP>` works; `<\xmp>` doesn't.
+ state.append((r'\\?<(/|\\\\|(?!%s)\\)%s(?=[\s=>])' %
+ (_escape, verbatim),
+ Name.Tag, ('#pop', '%sqs' % prefix, tag_state_name)))
+ else:
+ state += [
+ (r'\\?<!([^><\\%s]|<(?!<)|\\%s%s|%s|\\.)*>?' %
+ (char, char, escaped_quotes, _escape), Comment.Multiline),
+ (r'(?i)\\?<listing(?=[\s=>]|\\>)', Name.Tag,
+ ('#pop', '%sqs/listing' % prefix, tag_state_name)),
+ (r'(?i)\\?<xmp(?=[\s=>]|\\>)', Name.Tag,
+ ('#pop', '%sqs/xmp' % prefix, tag_state_name)),
+ (r'\\?<([^\s=><\\%s]|<(?!<)|\\%s%s|%s|\\.)*' %
+ (char, char, escaped_quotes, _escape), Name.Tag,
+ tag_state_name),
+ include('s/entity')
+ ]
+ state += [
+ include('s/escape'),
+ (r'{([^}<\\%s]|<(?!<)|\\%s%s|%s|\\.)*}' %
+ (char, char, escaped_quotes, _escape), String.Interpol),
+ (r'[\\&{}<]', token)
+ ]
+ return state
+
+ def _make_tag_state(triple, double, _escape=_escape):
+ char = r'"' if double else r"'"
+ quantifier = r'{3,}' if triple else r''
+ state_name = '%s%sqt' % ('t' if triple else '', 'd' if double else 's')
+ token = String.Double if double else String.Single
+ escaped_quotes = r'+|%s(?!%s{2})' % (char, char) if triple else r''
+ return [
+ (r'%s%s' % (char, quantifier), token, '#pop:2'),
+ (r'(\s|\\\n)+', Text),
+ (r'(=)(\\?")', bygroups(Punctuation, String.Double),
+ 'dqs/%s' % state_name),
+ (r"(=)(\\?')", bygroups(Punctuation, String.Single),
+ 'sqs/%s' % state_name),
+ (r'=', Punctuation, 'uqs/%s' % state_name),
+ (r'\\?>', Name.Tag, '#pop'),
+ (r'{([^}<\\%s]|<(?!<)|\\%s%s|%s|\\.)*}' %
+ (char, char, escaped_quotes, _escape), String.Interpol),
+ (r'([^\s=><\\%s]|<(?!<)|\\%s%s|%s|\\.)+' %
+ (char, char, escaped_quotes, _escape), Name.Attribute),
+ include('s/escape'),
+ include('s/verbatim'),
+ include('s/entity'),
+ (r'[\\{}&]', Name.Attribute)
+ ]
+
+ def _make_attribute_value_state(terminator, host_triple, host_double,
+ _escape=_escape):
+ token = (String.Double if terminator == r'"' else
+ String.Single if terminator == r"'" else String.Other)
+ host_char = r'"' if host_double else r"'"
+ host_quantifier = r'{3,}' if host_triple else r''
+ host_token = String.Double if host_double else String.Single
+ escaped_quotes = (r'+|%s(?!%s{2})' % (host_char, host_char)
+ if host_triple else r'')
+ return [
+ (r'%s%s' % (host_char, host_quantifier), host_token, '#pop:3'),
+ (r'%s%s' % (r'' if token is String.Other else r'\\?', terminator),
+ token, '#pop'),
+ include('s/verbatim'),
+ include('s/entity'),
+ (r'{([^}<\\%s]|<(?!<)|\\%s%s|%s|\\.)*}' %
+ (host_char, host_char, escaped_quotes, _escape), String.Interpol),
+ (r'([^\s"\'<%s{}\\&])+' % (r'>' if token is String.Other else r''),
+ token),
+ include('s/escape'),
+ (r'["\'\s&{<}\\]', token)
+ ]
+
+ tokens = {
+ 'root': [
+ (u'\ufeff', Text),
+ (r'{', Punctuation, 'object-body'),
+ (r';+', Punctuation),
+ (r'(?=(argcount|break|case|catch|continue|default|definingobj|'
+ r'delegated|do|else|for|foreach|finally|goto|if|inherited|'
+ r'invokee|local|nil|new|operator|replaced|return|self|switch|'
+ r'targetobj|targetprop|throw|true|try|while)\b)', Text, 'block'),
+ (r'(%s)(%s*)(\()' % (_name, _ws),
+ bygroups(Name.Function, using(this, state='whitespace'),
+ Punctuation),
+ ('block?/root', 'more/parameters', 'main/parameters')),
+ include('whitespace'),
+ (r'\++', Punctuation),
+ (r'[^\s!"%-(*->@-_a-z{-~]+', Error), # Averts an infinite loop
+ (r'(?!\Z)', Text, 'main/root')
+ ],
+ 'main/root': [
+ include('main/basic'),
+ default(('#pop', 'object-body/no-braces', 'classes', 'class'))
+ ],
+ 'object-body/no-braces': [
+ (r';', Punctuation, '#pop'),
+ (r'{', Punctuation, ('#pop', 'object-body')),
+ include('object-body')
+ ],
+ 'object-body': [
+ (r';', Punctuation),
+ (r'{', Punctuation, '#push'),
+ (r'}', Punctuation, '#pop'),
+ (r':', Punctuation, ('classes', 'class')),
+ (r'(%s?)(%s*)(\()' % (_name, _ws),
+ bygroups(Name.Function, using(this, state='whitespace'),
+ Punctuation),
+ ('block?', 'more/parameters', 'main/parameters')),
+ (r'(%s)(%s*)({)' % (_name, _ws),
+ bygroups(Name.Function, using(this, state='whitespace'),
+ Punctuation), 'block'),
+ (r'(%s)(%s*)(:)' % (_name, _ws),
+ bygroups(Name.Variable, using(this, state='whitespace'),
+ Punctuation),
+ ('object-body/no-braces', 'classes', 'class')),
+ include('whitespace'),
+ (r'->|%s' % _operator, Punctuation, 'main'),
+ default('main/object-body')
+ ],
+ 'main/object-body': [
+ include('main/basic'),
+ (r'(%s)(%s*)(=?)' % (_name, _ws),
+ bygroups(Name.Variable, using(this, state='whitespace'),
+ Punctuation), ('#pop', 'more', 'main')),
+ default('#pop:2')
+ ],
+ 'block?/root': [
+ (r'{', Punctuation, ('#pop', 'block')),
+ include('whitespace'),
+ (r'(?=[[\'"<(:])', Text, # It might be a VerbRule macro.
+ ('#pop', 'object-body/no-braces', 'grammar', 'grammar-rules')),
+ # It might be a macro like DefineAction.
+ default(('#pop', 'object-body/no-braces'))
+ ],
+ 'block?': [
+ (r'{', Punctuation, ('#pop', 'block')),
+ include('whitespace'),
+ default('#pop')
+ ],
+ 'block/basic': [
+ (r'[;:]+', Punctuation),
+ (r'{', Punctuation, '#push'),
+ (r'}', Punctuation, '#pop'),
+ (r'default\b', Keyword.Reserved),
+ (r'(%s)(%s*)(:)' % (_name, _ws),
+ bygroups(Name.Label, using(this, state='whitespace'),
+ Punctuation)),
+ include('whitespace')
+ ],
+ 'block': [
+ include('block/basic'),
+ (r'(?!\Z)', Text, ('more', 'main'))
+ ],
+ 'block/embed': [
+ (r'>>', String.Interpol, '#pop'),
+ include('block/basic'),
+ (r'(?!\Z)', Text, ('more/embed', 'main'))
+ ],
+ 'main/basic': [
+ include('whitespace'),
+ (r'\(', Punctuation, ('#pop', 'more', 'main')),
+ (r'\[', Punctuation, ('#pop', 'more/list', 'main')),
+ (r'{', Punctuation, ('#pop', 'more/inner', 'main/inner',
+ 'more/parameters', 'main/parameters')),
+ (r'\*|\.{3}', Punctuation, '#pop'),
+ (r'(?i)0x[\da-f]+', Number.Hex, '#pop'),
+ (r'(\d+\.(?!\.)\d*|\.\d+)([eE][-+]?\d+)?|\d+[eE][-+]?\d+',
+ Number.Float, '#pop'),
+ (r'0[0-7]+', Number.Oct, '#pop'),
+ (r'\d+', Number.Integer, '#pop'),
+ (r'"""', String.Double, ('#pop', 'tdqs')),
+ (r"'''", String.Single, ('#pop', 'tsqs')),
+ (r'"', String.Double, ('#pop', 'dqs')),
+ (r"'", String.Single, ('#pop', 'sqs')),
+ (r'R"""', String.Regex, ('#pop', 'tdqr')),
+ (r"R'''", String.Regex, ('#pop', 'tsqr')),
+ (r'R"', String.Regex, ('#pop', 'dqr')),
+ (r"R'", String.Regex, ('#pop', 'sqr')),
+ # Two-token keywords
+ (r'(extern)(%s+)(object\b)' % _ws,
+ bygroups(Keyword.Reserved, using(this, state='whitespace'),
+ Keyword.Reserved)),
+ (r'(function|method)(%s*)(\()' % _ws,
+ bygroups(Keyword.Reserved, using(this, state='whitespace'),
+ Punctuation),
+ ('#pop', 'block?', 'more/parameters', 'main/parameters')),
+ (r'(modify)(%s+)(grammar\b)' % _ws,
+ bygroups(Keyword.Reserved, using(this, state='whitespace'),
+ Keyword.Reserved),
+ ('#pop', 'object-body/no-braces', ':', 'grammar')),
+ (r'(new)(%s+(?=(?:function|method)\b))' % _ws,
+ bygroups(Keyword.Reserved, using(this, state='whitespace'))),
+ (r'(object)(%s+)(template\b)' % _ws,
+ bygroups(Keyword.Reserved, using(this, state='whitespace'),
+ Keyword.Reserved), ('#pop', 'template')),
+ (r'(string)(%s+)(template\b)' % _ws,
+ bygroups(Keyword, using(this, state='whitespace'),
+ Keyword.Reserved), ('#pop', 'function-name')),
+ # Keywords
+ (r'(argcount|definingobj|invokee|replaced|targetobj|targetprop)\b',
+ Name.Builtin, '#pop'),
+ (r'(break|continue|goto)\b', Keyword.Reserved, ('#pop', 'label')),
+ (r'(case|extern|if|intrinsic|return|static|while)\b',
+ Keyword.Reserved),
+ (r'catch\b', Keyword.Reserved, ('#pop', 'catch')),
+ (r'class\b', Keyword.Reserved,
+ ('#pop', 'object-body/no-braces', 'class')),
+ (r'(default|do|else|finally|try)\b', Keyword.Reserved, '#pop'),
+ (r'(dictionary|property)\b', Keyword.Reserved,
+ ('#pop', 'constants')),
+ (r'enum\b', Keyword.Reserved, ('#pop', 'enum')),
+ (r'export\b', Keyword.Reserved, ('#pop', 'main')),
+ (r'(for|foreach)\b', Keyword.Reserved,
+ ('#pop', 'more/inner', 'main/inner')),
+ (r'(function|method)\b', Keyword.Reserved,
+ ('#pop', 'block?', 'function-name')),
+ (r'grammar\b', Keyword.Reserved,
+ ('#pop', 'object-body/no-braces', 'grammar')),
+ (r'inherited\b', Keyword.Reserved, ('#pop', 'inherited')),
+ (r'local\b', Keyword.Reserved,
+ ('#pop', 'more/local', 'main/local')),
+ (r'(modify|replace|switch|throw|transient)\b', Keyword.Reserved,
+ '#pop'),
+ (r'new\b', Keyword.Reserved, ('#pop', 'class')),
+ (r'(nil|true)\b', Keyword.Constant, '#pop'),
+ (r'object\b', Keyword.Reserved, ('#pop', 'object-body/no-braces')),
+ (r'operator\b', Keyword.Reserved, ('#pop', 'operator')),
+ (r'propertyset\b', Keyword.Reserved,
+ ('#pop', 'propertyset', 'main')),
+ (r'self\b', Name.Builtin.Pseudo, '#pop'),
+ (r'template\b', Keyword.Reserved, ('#pop', 'template')),
+ # Operators
+ (r'(__objref|defined)(%s*)(\()' % _ws,
+ bygroups(Operator.Word, using(this, state='whitespace'),
+ Operator), ('#pop', 'more/__objref', 'main')),
+ (r'delegated\b', Operator.Word),
+ # Compiler-defined macros and built-in properties
+ (r'(__DATE__|__DEBUG|__LINE__|__FILE__|'
+ r'__TADS_MACRO_FORMAT_VERSION|__TADS_SYS_\w*|__TADS_SYSTEM_NAME|'
+ r'__TADS_VERSION_MAJOR|__TADS_VERSION_MINOR|__TADS3|__TIME__|'
+ r'construct|finalize|grammarInfo|grammarTag|lexicalParent|'
+ r'miscVocab|sourceTextGroup|sourceTextGroupName|'
+ r'sourceTextGroupOrder|sourceTextOrder)\b', Name.Builtin, '#pop')
+ ],
+ 'main': [
+ include('main/basic'),
+ (_name, Name, '#pop'),
+ default('#pop')
+ ],
+ 'more/basic': [
+ (r'\(', Punctuation, ('more/list', 'main')),
+ (r'\[', Punctuation, ('more', 'main')),
+ (r'\.{3}', Punctuation),
+ (r'->|\.\.', Punctuation, 'main'),
+ (r'(?=;)|[:)\]]', Punctuation, '#pop'),
+ include('whitespace'),
+ (_operator, Operator, 'main'),
+ (r'\?', Operator, ('main', 'more/conditional', 'main')),
+ (r'(is|not)(%s+)(in\b)' % _ws,
+ bygroups(Operator.Word, using(this, state='whitespace'),
+ Operator.Word)),
+ (r'[^\s!"%-_a-z{-~]+', Error) # Averts an infinite loop
+ ],
+ 'more': [
+ include('more/basic'),
+ default('#pop')
+ ],
+ # Then expression (conditional operator)
+ 'more/conditional': [
+ (r':(?!:)', Operator, '#pop'),
+ include('more')
+ ],
+ # Embedded expressions
+ 'more/embed': [
+ (r'>>', String.Interpol, '#pop:2'),
+ include('more')
+ ],
+ # For/foreach loop initializer or short-form anonymous function
+ 'main/inner': [
+ (r'\(', Punctuation, ('#pop', 'more/inner', 'main/inner')),
+ (r'local\b', Keyword.Reserved, ('#pop', 'main/local')),
+ include('main')
+ ],
+ 'more/inner': [
+ (r'}', Punctuation, '#pop'),
+ (r',', Punctuation, 'main/inner'),
+ (r'(in|step)\b', Keyword, 'main/inner'),
+ include('more')
+ ],
+ # Local
+ 'main/local': [
+ (_name, Name.Variable, '#pop'),
+ include('whitespace')
+ ],
+ 'more/local': [
+ (r',', Punctuation, 'main/local'),
+ include('more')
+ ],
+ # List
+ 'more/list': [
+ (r'[,:]', Punctuation, 'main'),
+ include('more')
+ ],
+ # Parameter list
+ 'main/parameters': [
+ (r'(%s)(%s*)(?=:)' % (_name, _ws),
+ bygroups(Name.Variable, using(this, state='whitespace')), '#pop'),
+ (r'(%s)(%s+)(%s)' % (_name, _ws, _name),
+ bygroups(Name.Class, using(this, state='whitespace'),
+ Name.Variable), '#pop'),
+ (r'\[+', Punctuation),
+ include('main/basic'),
+ (_name, Name.Variable, '#pop'),
+ default('#pop')
+ ],
+ 'more/parameters': [
+ (r'(:)(%s*(?=[?=,:)]))' % _ws,
+ bygroups(Punctuation, using(this, state='whitespace'))),
+ (r'[?\]]+', Punctuation),
+ (r'[:)]', Punctuation, ('#pop', 'multimethod?')),
+ (r',', Punctuation, 'main/parameters'),
+ (r'=', Punctuation, ('more/parameter', 'main')),
+ include('more')
+ ],
+ 'more/parameter': [
+ (r'(?=[,)])', Text, '#pop'),
+ include('more')
+ ],
+ 'multimethod?': [
+ (r'multimethod\b', Keyword, '#pop'),
+ include('whitespace'),
+ default('#pop')
+ ],
+
+ # Statements and expressions
+ 'more/__objref': [
+ (r',', Punctuation, 'mode'),
+ (r'\)', Operator, '#pop'),
+ include('more')
+ ],
+ 'mode': [
+ (r'(error|warn)\b', Keyword, '#pop'),
+ include('whitespace')
+ ],
+ 'catch': [
+ (r'\(+', Punctuation),
+ (_name, Name.Exception, ('#pop', 'variables')),
+ include('whitespace')
+ ],
+ 'enum': [
+ include('whitespace'),
+ (r'(token\b)?', Keyword, ('#pop', 'constants'))
+ ],
+ 'grammar': [
+ (r'\)+', Punctuation),
+ (r'\(', Punctuation, 'grammar-tag'),
+ (r':', Punctuation, 'grammar-rules'),
+ (_name, Name.Class),
+ include('whitespace')
+ ],
+ 'grammar-tag': [
+ include('whitespace'),
+ (r'"""([^\\"<]|""?(?!")|\\"+|\\.|<(?!<))+("{3,}|<<)|'
+ r'R"""([^\\"]|""?(?!")|\\"+|\\.)+"{3,}|'
+ r"'''([^\\'<]|''?(?!')|\\'+|\\.|<(?!<))+('{3,}|<<)|"
+ r"R'''([^\\']|''?(?!')|\\'+|\\.)+'{3,}|"
+ r'"([^\\"<]|\\.|<(?!<))+("|<<)|R"([^\\"]|\\.)+"|'
+ r"'([^\\'<]|\\.|<(?!<))+('|<<)|R'([^\\']|\\.)+'|"
+ r"([^)\s\\/]|/(?![/*]))+|\)", String.Other, '#pop')
+ ],
+ 'grammar-rules': [
+ include('string'),
+ include('whitespace'),
+ (r'(\[)(%s*)(badness)' % _ws,
+ bygroups(Punctuation, using(this, state='whitespace'), Keyword),
+ 'main'),
+ (r'->|%s|[()]' % _operator, Punctuation),
+ (_name, Name.Constant),
+ default('#pop:2')
+ ],
+ ':': [
+ (r':', Punctuation, '#pop')
+ ],
+ 'function-name': [
+ (r'(<<([^>]|>>>|>(?!>))*>>)+', String.Interpol),
+ (r'(?=%s?%s*[({])' % (_name, _ws), Text, '#pop'),
+ (_name, Name.Function, '#pop'),
+ include('whitespace')
+ ],
+ 'inherited': [
+ (r'<', Punctuation, ('#pop', 'classes', 'class')),
+ include('whitespace'),
+ (r'%s?' % _name, Name.Class, '#pop'),
+ ],
+ 'operator': [
+ (r'negate\b', Operator.Word, '#pop'),
+ include('whitespace'),
+ (_operator, Operator),
+ default('#pop')
+ ],
+ 'propertyset': [
+ (r'\(', Punctuation, ('more/parameters', 'main/parameters')),
+ (r'{', Punctuation, ('#pop', 'object-body')),
+ include('whitespace')
+ ],
+ 'template': [
+ (r'(?=;)', Text, '#pop'),
+ include('string'),
+ (r'inherited\b', Keyword.Reserved),
+ include('whitespace'),
+ (r'->|\?|%s' % _operator, Punctuation),
+ (_name, Name.Variable)
+ ],
+
+ # Identifiers
+ 'class': [
+ (r'\*|\.{3}', Punctuation, '#pop'),
+ (r'object\b', Keyword.Reserved, '#pop'),
+ (r'transient\b', Keyword.Reserved),
+ (_name, Name.Class, '#pop'),
+ include('whitespace'),
+ default('#pop')
+ ],
+ 'classes': [
+ (r'[:,]', Punctuation, 'class'),
+ include('whitespace'),
+ (r'>?', Punctuation, '#pop')
+ ],
+ 'constants': [
+ (r',+', Punctuation),
+ (r';', Punctuation, '#pop'),
+ (r'property\b', Keyword.Reserved),
+ (_name, Name.Constant),
+ include('whitespace')
+ ],
+ 'label': [
+ (_name, Name.Label, '#pop'),
+ include('whitespace'),
+ default('#pop')
+ ],
+ 'variables': [
+ (r',+', Punctuation),
+ (r'\)', Punctuation, '#pop'),
+ include('whitespace'),
+ (_name, Name.Variable)
+ ],
+
+ # Whitespace and comments
+ 'whitespace': [
+ (r'^%s*#(%s|[^\n]|(?<=\\)\n)*\n?' % (_ws_pp, _comment_multiline),
+ Comment.Preproc),
+ (_comment_single, Comment.Single),
+ (_comment_multiline, Comment.Multiline),
+ (r'\\+\n+%s*#?|\n+|([^\S\n]|\\)+' % _ws_pp, Text)
+ ],
+
+ # Strings
+ 'string': [
+ (r'"""', String.Double, 'tdqs'),
+ (r"'''", String.Single, 'tsqs'),
+ (r'"', String.Double, 'dqs'),
+ (r"'", String.Single, 'sqs')
+ ],
+ 's/escape': [
+ (r'{{|}}|%s' % _escape, String.Escape)
+ ],
+ 's/verbatim': [
+ (r'<<\s*(as\s+decreasingly\s+likely\s+outcomes|cycling|else|end|'
+ r'first\s+time|one\s+of|only|or|otherwise|'
+ r'(sticky|(then\s+)?(purely\s+)?at)\s+random|stopping|'
+ r'(then\s+)?(half\s+)?shuffled|\|\|)\s*>>', String.Interpol),
+ (r'<<(%%(_(%s|\\?.)|[\-+ ,#]|\[\d*\]?)*\d*\.?\d*(%s|\\?.)|'
+ r'\s*((else|otherwise)\s+)?(if|unless)\b)?' % (_escape, _escape),
+ String.Interpol, ('block/embed', 'more/embed', 'main'))
+ ],
+ 's/entity': [
+ (r'(?i)&(#(x[\da-f]+|\d+)|[a-z][\da-z]*);?', Name.Entity)
+ ],
+ 'tdqs': _make_string_state(True, True),
+ 'tsqs': _make_string_state(True, False),
+ 'dqs': _make_string_state(False, True),
+ 'sqs': _make_string_state(False, False),
+ 'tdqs/listing': _make_string_state(True, True, 'listing'),
+ 'tsqs/listing': _make_string_state(True, False, 'listing'),
+ 'dqs/listing': _make_string_state(False, True, 'listing'),
+ 'sqs/listing': _make_string_state(False, False, 'listing'),
+ 'tdqs/xmp': _make_string_state(True, True, 'xmp'),
+ 'tsqs/xmp': _make_string_state(True, False, 'xmp'),
+ 'dqs/xmp': _make_string_state(False, True, 'xmp'),
+ 'sqs/xmp': _make_string_state(False, False, 'xmp'),
+
+ # Tags
+ 'tdqt': _make_tag_state(True, True),
+ 'tsqt': _make_tag_state(True, False),
+ 'dqt': _make_tag_state(False, True),
+ 'sqt': _make_tag_state(False, False),
+ 'dqs/tdqt': _make_attribute_value_state(r'"', True, True),
+ 'dqs/tsqt': _make_attribute_value_state(r'"', True, False),
+ 'dqs/dqt': _make_attribute_value_state(r'"', False, True),
+ 'dqs/sqt': _make_attribute_value_state(r'"', False, False),
+ 'sqs/tdqt': _make_attribute_value_state(r"'", True, True),
+ 'sqs/tsqt': _make_attribute_value_state(r"'", True, False),
+ 'sqs/dqt': _make_attribute_value_state(r"'", False, True),
+ 'sqs/sqt': _make_attribute_value_state(r"'", False, False),
+ 'uqs/tdqt': _make_attribute_value_state(_no_quote, True, True),
+ 'uqs/tsqt': _make_attribute_value_state(_no_quote, True, False),
+ 'uqs/dqt': _make_attribute_value_state(_no_quote, False, True),
+ 'uqs/sqt': _make_attribute_value_state(_no_quote, False, False),
+
+ # Regular expressions
+ 'tdqr': [
+ (r'[^\\"]+', String.Regex),
+ (r'\\"*', String.Regex),
+ (r'"{3,}', String.Regex, '#pop'),
+ (r'"', String.Regex)
+ ],
+ 'tsqr': [
+ (r"[^\\']+", String.Regex),
+ (r"\\'*", String.Regex),
+ (r"'{3,}", String.Regex, '#pop'),
+ (r"'", String.Regex)
+ ],
+ 'dqr': [
+ (r'[^\\"]+', String.Regex),
+ (r'\\"?', String.Regex),
+ (r'"', String.Regex, '#pop')
+ ],
+ 'sqr': [
+ (r"[^\\']+", String.Regex),
+ (r"\\'?", String.Regex),
+ (r"'", String.Regex, '#pop')
+ ]
+ }
+
+ def get_tokens_unprocessed(self, text, **kwargs):
+ pp = r'^%s*#%s*' % (self._ws_pp, self._ws_pp)
+ if_false_level = 0
+ for index, token, value in (
+ RegexLexer.get_tokens_unprocessed(self, text, **kwargs)):
+ if if_false_level == 0: # Not in a false #if
+ if (token is Comment.Preproc and
+ re.match(r'%sif%s+(0|nil)%s*$\n?' %
+ (pp, self._ws_pp, self._ws_pp), value)):
+ if_false_level = 1
+ else: # In a false #if
+ if token is Comment.Preproc:
+ if (if_false_level == 1 and
+ re.match(r'%sel(if|se)\b' % pp, value)):
+ if_false_level = 0
+ elif re.match(r'%sif' % pp, value):
+ if_false_level += 1
+ elif re.match(r'%sendif\b' % pp, value):
+ if_false_level -= 1
+ else:
+ token = Comment
+ yield index, token, value
diff --git a/tests/examplefiles/example.t b/tests/examplefiles/example.t
new file mode 100644
index 00000000..41881c93
--- /dev/null
+++ b/tests/examplefiles/example.t
@@ -0,0 +1,1248 @@
+#charset "utf-8"
+
+#include <adv3.h>
+#include <en_us.h>
+
+extern function extern_function;
+extern method extern_method;
+extern function extern_function(a, b=a, c='<<a>>', d:, e:=1, f?, ...);
+extern method extern_method(a, b=a, c='<<a>>', d:, e:=1, f?, [g]);;
+extern class extern_class;
+extern object extern_object;
+intrinsic 't3vm' { };
+#ifndef PropDefAny
+intrinsic class Object 'root-object/030004' { };
+#endif
+object /**//**/ // /* \\
+#define Room Unthing
+ template [lst];
+
+/*
+ * Quotations from "Le Roman de la Rose" are transcribed from MS. Douce 195,
+ * owned by Bodleian Library, University of Oxford
+ * (http://image.ox.ac.uk/show?collection=bodleian&manuscript=msdouce195).
+ */
+
+versionInfo: GameID
+ IFID = '17d8efc3-07da-4dde-a837-ff7c4e386a77'
+ name = 'Pygmentalion'
+ byline = 'by David Corbett'
+ htmlByline = 'by <a href="mailto:corbett.dav\100husky.neu.edu">David
+ Corbett</a>'
+ version = '1'
+ authorEmail = 'David Corbett\040<corbett.dav\x40husky.neu.edu>'
+ desc = 'You have fallen in love with a statue\x2e'
+ htmlDesc = 'You have fallen in love with a statue\x2E'
+;
+
+/*
+ * Pymalion fu ẽtailleꝛꝛes.
+ * Poᷣtrayãs en fus ⁊ en peꝛꝛeˢ
+ * En metaulx en os ⁊ en cyꝛes
+ * Et en touteˢ aultres matires.
+ * Quon peult a tel oeuure trouuer.
+ * Poᷣ ſon grant engin eſpꝛouuer.
+ * Car maiſtre en fu bien dire loz.
+ * Ainſi com poᷣ acquerre loz
+ * Se voult a poᷣtraire deduyꝛe
+ * Si fiſt vng ymage diuuyꝛe
+ * Et miſt au faire tel entente
+ * Quel fu ſi plaiſãt et ſi gente
+ * Quel ſembloit eſtre auſſi viue.
+ * Com la plus belle riens q̇ viue
+ * (MS. Douce 195, fol. 149r)
+ */
+
+modify _init()
+{
+ ({: local r, r = randomize, r})();
+ replaced();
+}
+
+gameMain: GameMainDef
+ initialPlayerChar: Actor {
+ desc = "You look the same as usual, but you feel unusually
+ sentimental. "
+ location = entrance
+ }
+ showIntro
+ {
+ "The statue is undeniably a masterpiece: the most skillful carving you
+ have ever done, and the most beautiful woman you have ever seen.
+ Unfortunately, she is also an inanimate block, and now you can neither
+ work nor rest for unrequitable love.\b
+ Once again you stumble into your studio, hoping and praying to find
+ your statue brought to life.\b
+ <b><<versionInfo.name>></b>\r\n
+ <<versionInfo.byline>>\b";
+ }
+;
+
+enum token token, tokOp, token;
+
+modify cmdTokenizer
+ rules_ = static
+ [
+ ['whitespace', new RexPattern('%s+'), nil, &tokCvtSkip, nil],
+ ['punctuation', new RexPattern('[.,;:?!]'), tokPunct, nil, nil],
+ ['spelled number',
+ new RexPattern('<NoCase>(twenty|thirty|forty|fifty|sixty|'
+ + 'seventy|eighty|ninety)-'
+ + '(one|two|three|four|five|six|seven|eight|nine)'
+ + '(?!<AlphaNum>)'),
+ tokWord, &tokCvtSpelledNumber, nil],
+ ['spelled operator', new RexPattern(
+ '<NoCase>(plus|positive|minus|negat(iv)?e|not|inverse(%s+of)?|'
+ + 'times|over|divided%s+by|mod(ulo)?|and|xor|or|[al]?sh[lr])'
+ + '(?!<AlphaNum>)'),
+ tokOp, &tokCvtSpelledOperator, nil],
+ ['operator', R'[-!~+*/%&^|]|<<|>>>?', tokOp, nil, nil],
+ ['word', new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*'),
+ tokWord, nil, nil],
+ ['string ascii-quote', R"""<min>([`\'"])(.*)%1(?!<AlphaNum>)""",
+ tokString, nil, nil],
+ ['string back-quote', R"<min>`(.*)'(?!%w)", tokString, nil, nil],
+ ['string curly single-quote', new RexPattern('<min>\u2018(.*)\u2019'),
+ tokString, nil, nil],
+ ['string curly double-quote', new RexPattern('<min>\u201C(.*)\u201D'),
+ tokString, nil, nil],
+ ['string unterminated', R'''([`\'"\u2018\u201C](.*)''', tokString,
+ nil, nil],
+ ['integer', new RexPattern('[0-9]+'), tokInt, nil, nil]
+ ]
+ replace tokCvtSpelledOperator(txt, typ, toks)
+ {
+ toks.append([rexReplace(R'%s+', txt.toLower(), '\\'), typ, txt]);
+ }
+;
+
+/* Tokens */
+
+/*
+ * Puiˢ li reueſt en maĩteˢ guiſes.
+ * Robeˢ faicteˢ ꝑ grãˢ maiſtriſeˢ.
+ * De biaulx dꝛaps de ſoye ⁊ de laĩe.
+ * Deſcarlate de tiretaine
+ * De vert de pers ⁊ de bꝛunecte
+ * De couleᷣ freſche fine ⁊ necte
+ * Ou moult a riches paneˢ miſes.
+ * Herminees vaires et griſes
+ * Puis les li roſte puis reſſaye.
+ * Cõmant li ſiet robbe de ſaye
+ * Sendaulx meloguins galebꝛunˢ.
+ * Indes vermeilz iaunes ⁊ bꝛunˢ.
+ * [...]
+ * Aultre foiz luy repꝛẽd courage.
+ * De tout oſter ⁊ mectre guindeˢ.
+ * Iaunes vermeilles vers ⁊ indeˢ.
+ * (MS. Douce 195, fol. 150r)
+ */
+
+class Token: Achievement
+{
+ points = 1;
+ desc = "<<before_>><<desc_>><<after_>>";
+ before = before = '', before_
+ after = (after = '', after_)
+}
+
+Token template inherited 'before_' 'after_' 'desc_';
+
+#define DefineToken(name, before, after) name##Token: Token before after #@name
+
+DefineToken(builtin, '<font color=g&#x72;een>', '</font>');
+DefineToken(comment, '<i><font color=#408080>', '</font></i>');
+DefineToken(decorator, '<font color=#aa22ff>', '</font>');
+DefineToken(error, '<U><FONT COLOR=RED>', '</FONT></U>');
+DefineToken(escape, '<b><font color=#bb6622>', '</font></b>');
+DefineToken(float, '<u><font color=gray>', '</font></u>');
+DefineToken(keyword, '<b><font face=TADS-Sans color=green>', '</font></b>');
+DefineToken(label, '<font color=#A0A000>', '</font>');
+DefineToken(long, '<i><font color=gray>', '</font></i>');
+DefineToken(name, '<u>', '</u>');
+DefineToken(operator, '<b><font color=\"#AA22F&#x46;\">', '</font></b>');
+DefineToken(string, '<font color=\'#BA212&#49;\'>', '</font>');
+DefineToken(whitespace, '<font color="bgcolor"bgcolor=\'text\'>', '</font>');
+
+function highlightToken(tokenString)
+{
+ local token = [
+ 'built in' -> builtinToken,
+ 'comment' -> commentToken,
+ 'decorator' -> decoratorToken,
+ 'error' -> errorToken,
+ 'escape' -> escapeToken,
+ 'float' -> floatToken,
+ 'keyword' -> keywordToken,
+ 'label' -> labelToken,
+ 'long' -> longToken,
+ 'name' -> nameToken,
+ 'operator' -> operatorToken,
+ 'string' -> stringToken,
+ 'white space' -> whitespaceToken,
+ * -> nil
+ ][tokenString.toLower()];
+ if (!token)
+ return tokenString;
+ token.awardPointsOnce();
+ return '<<token.before>><<tokenString>><<token.after>>';
+}
+
+string /**//**/ // /* \\
+#define Room Unthing
+ template <<highlight *>> highlightToken;
+
+/* Grammar for materials */
+
+dictionary property material;
+grammar adjWord(material): <material material>->adj_ : AdjPhraseWithVocab
+ getVocabMatchList(resolver, results, extraFlags)
+ {
+ return getWordMatches(adj_, &material, resolver, extraFlags,
+ VocabTruncated);
+ }
+ getAdjustedTokens()
+ {
+ return [adj_, &material];
+ }
+;
+
+/* Rooms and objects */
+
++ property location;
+
+entrance: Room 'Entrance'
+ "You are in the entrance to your studio. This is where you carve great
+ works of art, not that you have felt like making any lately. A door leads
+ outside, and the studio itself is to the north and the east. "
+ north = workbenchRoom
+ northeast = sinkRoom
+ east = altarRoom
+ south = door
+ out asExit(south)
+;
+
++ door: LockableWithKey, Door 'door' 'door'
+ "It is a simple wooden door. "
+ material = 'wood' 'wooden'
+ keyList = [key]
+ cannotOpenLockedMsg = '{The dobj/He} {is} locked. You cannot
+ <<highlight 'escape'>>! '
+;
+
+key: PresentLater, Key 'key' 'key' @altar
+ "It is a <<unless clean>>grimy<<end>> bronze key. <<if clean>>On it is \
+ etched the word <q><<keyword>></q>. "
+ material = 'bronze'
+ clean = nil
+ keyword = (keyword = randomGreekWord(), targetprop)
+ dobjFor(Clean) { verify { } action { askForIobj(CleanWith); } }
+ dobjFor(CleanWith)
+ {
+ verify
+ {
+ if (clean)
+ illogicalAlready('{The dobj/He} {is} already clean. ');
+ }
+ action
+ {
+ gDobj.clean = true;
+ "{You/He} clean{s} {the dobj/him}, revealing an inscription. ";
+ }
+ }
+ dobjFor(Read) { verify { nonObvious; } }
+;
+
+workbenchRoom: Room 'At the Workbench'
+ "This workbench, in the northwest part of the studio, was where you would
+ create works of art. Now you just come here to contemplate your
+ creation&rsquo;s beauty and lament your hopeless situation.\b
+ The statue stands on a plinth beside the workbench. "
+ east = sinkRoom
+ southeast = altarRoom
+ south = entrance
+ getDestName(actor, origin) { return 'the workbench'; }
+;
+
++ workbench: Fixture, Surface
+ 'workbench/bench/material/materials/tool/tools' 'workbench'
+ "Normally, the workbench would be scattered with half-finished projects,
+ but now your tools and materials lie abandoned. "
+;
+
++ plinth: Fixture, Thing 'marble plinth/pedestal' 'plinth'
+ "It&rsquo;s a smoothed block of marble about a cubit high. "
+;
+
+replace grammar predicate(Screw): ' ': object;
+replace grammar predicate(ScrewWith): ' ': object;
++ + statue: Fixture, Surface
+ '"creation\'s" beauty/carving/creation/galatea/statue/woman' 'statue'
+ "This is a<<if nameToken.scoreCount>>n untitled<<end>> statue of a woman
+ carved from <<if errorToken.scoreCount>>flawless <<end>>
+ <<if whitespaceToken.scoreCount>>milk-white <<end>>ivory.
+ <<if escapeToken.scoreCount || longToken.scoreCount>>Her
+ <<if longToken.scoreCount>>long <<end>>hair is done up in a
+ chignon<<if escapeToken.scoreCount>>, with a few strands falling down her
+ neck<<end>><<if floatToken.scoreCount>>, and \v<<else>>.<<end>><<end>>
+ <<if floatToken.scoreCount>>She radiates an aura of contrapposto grace.
+ <<end>><<if keywordToken.scoreCount>>\bYou wonder what she
+ <<if labelToken.scoreCount>>is going to<<else>>will<<end>> be like as a
+ woman.
+ <<if decoratorToken.scoreCount>>Maybe she&rsquo;ll be a painter and expand
+ your business.<<end>>
+ <<if operatorToken.scoreCount>>Maybe she&rsquo;ll have a head for figures
+ and will put the accounts in order.<<end>>
+ <<if builtinToken.scoreCount>>She&rsquo;ll love you, obviously, but beyond
+ that you don&rsquo;t know.<<end>><<end>>
+ <<if commentToken.scoreCount>>If only Aphrodite would bring her to life
+ without this silly puzzle about tokens and mirrors!<<end>> "
+ material = 'ivory'
+ propertyset 'is*'
+ {
+ propertyset 'H*'
+ {
+ im = nil\
+ er = true;
+ }
+ It = true
+ }
+ iobjFor(PutOn)
+ {
+ check
+ {
+ if (gDobj not /**//**/ // /* \\
+#define Room Unthing
+ in (necklace, __objref(necklace, warn)))
+ "How rude! You don&rsquo;t know what you were thinking. ";
+ }
+ }
+ iobjFor(GiveTo) remapTo(PutOn, DirectObject, IndirectObject)
+;
+
++++ necklace: Wearable
+ 'pearl necklace/string pearls' '<<highlight 'string'>> of pearls'
+ "This is a masterfully crafted pearl necklace. You hope the statue
+ won&rsquo;t mind if you hold onto it for a while. "
+ initDesc = "You gave the statue this pearl necklace yesterday. "
+ isPlural = true
+;
+
+altarRoom: Room 'At the Altar'
+ "Light from the window illuminates a crude altar. Until recently, this
+ corner was your bedroom. The rest of the studio lies north and west. "
+ north = sinkRoom
+ northwest = workbenchRoom
+ west = entrance
+ getDestName(actor, origin) { return 'the altar'; }
+;
+
++ window: Fixture 'window' 'window'
+ "It&rsquo;s just a window above the altar. <<one of>>The space under the
+ window is blank; as an interior <<highlight 'decorator'>>, you can&rsquo;t
+ help but think the wall would benefit from a bas-relief, but &ndash;
+ <i>sigh</i> &endash you are too lovelorn to wield the chisel. <<||>>The
+ wall right below it is a boring <<highlight 'white space'>>. <<stopping>>"
+;
+
++ altar: Fixture, Surface 'crude rough altar/banker/slab' 'altar'
+ "A rough marble slab lies on a wooden banker. In your rush to construct an
+ altar, you neglected the usual surface finish and friezes, but you pray at
+ it anyway. You are sure the gods will understand. "
+ material = 'marble' 'wood' 'wooden'
+ bulkCapacity = 1
+ dobjFor(PrayAt)
+ {
+ verify { }
+ action()
+ {
+ /*
+ * Biaulx dieux diſt il tout ce poez.
+ * Sil voꝰ plaiſt ma requeſte oez
+ * [...]
+ * Et la belle q̇ mon cueᷣ emble
+ * Qui ſi bien yuoyꝛe reſſemble.
+ * Deuiengne ma loyal amye
+ * De fẽme ait coꝛps ame et vie
+ * (MS. Douce 195, fol. 151r)
+ */
+ local offering;
+ foreach (offering in contents);
+ if (!keywordToken.scoreCount)
+ "<<one of>><q>O Aphrodite,</q> you say, <q>comforter of
+ hopeless lovers, hear my prayer! May she to whom I have given
+ my heart be given body, soul, and life. And a colorful
+ personality. And&mdash</q>\b
+ You are interrupted by a shimmering about the altar. As you
+ watch, it takes the form of a callipygian goddess.\b
+ <q>Mortal, I have heard your heart-felt and oft-repeated plea,
+ and I will take pity on you,</q> says Aphrodite. <q>If you give
+ me a token of your love as an offering, I will give you the
+ <<highlight 'keyword'>> of life. Speak this word in the
+ presence of a mirror, and I will grant your request.</q>\b
+ She fades away, adding, <q>As for her colorful personality,
+ just look around you.</q> <<or>><<stopping>>";
+ else if (key.location)
+ "<q>O Aphrodite,</q> you say, <q>what am I supposed to do
+ again?</q>\bThe goddess reappears and reminds you to speak the
+ keyword of life at a mirror. <<one of>><q>What&rsquo;s the
+ keyword, then?</q> <q>Gods help those who help themselves.
+ Figure it out yourself.</q><<or>><q>Why a mirror?</q> <q>I like
+ mirrors.</q><<purely at random>> ";
+ else if (offering == necklace)
+ {
+ "Aphrodite reappears. <q>A necklace! Perfect!</q> The necklace
+ disappears in a bright flash. When your eyes readjust, you see
+ a key lying in its place. ";
+ necklace.moveInto(nil);
+ key.makePresent();
+ }
+ else if (+offering)
+ "Aphrodite reappears. She eyes <<offering.theNameObj>>
+ skeptically. <q><<one of>>No <<highlight 'comment'>>.<<or>>You
+ call <i>that</i> a token of love?<<or>>\^<<offering.aNameObj>>?
+ Really?<<or>>Come on, mortal, it&rsquo;s not that
+ difficult!<<then at random>></q> ";
+ else
+ "<q>I heard you the first time,</q> says Aphrodite. <q>Prove
+ your devotion by offering a token of your love at the altar,
+ or the deal&rsquo;s off.</q> ";
+ }
+ }
+ iobjFor(GiveTo) remapTo(PutOn, DirectObject, IndirectObject)
+;
+
+aphrodite: Unthing
+ '(love) aphrodite/cytherea/god/goddess/venus love' 'Aphrodite'
+ '<<if gActor.canSee(altar)>>You can only pray to a god.
+ <<else>>You need an altar to interact with a god. '
+ location = (gPlayerChar)
+ isProperName = true
+ isHer = true
+ iobjFor(GiveTo)
+ {
+ verify
+ {
+ illogical('She isn&rsquo;t here. You&rsquo;ll have to leave {the
+ dobj/him} somewhere she can find it. ');
+ }
+ }
+ dobjFor(PrayAt) maybeRemapTo(gActor.canSee(altar), PrayAt, altar)
+;
+
+sinkRoom: Room 'Washroom'
+ "Sculpting marble is a dusty business. You use this sink to clean off after
+ a hard day&rsquo;s work. Beside the sink is a small end table, and on the
+ wall is a calculator. The rest of the studio is south and west. "
+ south = altarRoom
+ southwest = entrance
+ west = workbenchRoom
+;
+
+property level, overflowing;
+export overflowing;
+export level 'waterLevel';
++ sink: Fixture '(auto) (mop) auto-sink/autosink/bowl/drain/faucet/sink' 'sink'
+ "This is a state-of-the-art mop sink with anti-miasmic coating and bronze
+ backsplash. It is so modern, there are no handles or other obvious ways to
+ turn it on.\b
+ <<if overflowing>>It is overflowing.
+ <<else unless level < 19500>>It is full to the brim with water.
+ <<otherwise if level >= 15000>>It is full of water.
+ <<otherwise unless level < 10000>>It is half full of water.
+ <<else if level >= 2000>>There is some water in the sink.
+ <<else if level > 0>>A small puddle has formed at the bottom of the sink.
+ <<otherwise>>It is empty.
+ <<if level <= -1.0e+2>>It looks like it hasn&rsquo;t been used in a
+ <<highlight 'long'>> time. "
+ level = not in ([lst]) { return argcount; }
+ not = in()
+ overflowing = nil
+ current = self
+ setLevel(level:)
+ {
+ targetobj.current.overflowing = level == nil;
+ targetobj.current.level = min(level ?? 0, 20000);
+ if (sink.overflowing || sink.level > 0e+1)
+ sinkWater.makePresent();
+ if (basin.overflowing || basin.level > 0e-1)
+ basinWater.makePresent();
+ }
+ iobjFor(CleanWith) remapTo(CleanWith, DirectObject, sinkWater)
+;
+
+++ sinkWater: PresentLater, Fixture
+ '(sink) water sink water' 'water' "<<sink.desc>>"
+ disambigName = 'water in the sink'
+ dobjFor(Drink)
+ {
+ verify { illogical('''{You're} not thirsty. '''); }
+ }
+ iobjFor(CleanWith)
+ {
+ preCond = []
+ verify {
+ if (!location)
+ illogicalNow('There is no water in the sink. ');
+ if (!sink.overflowing && sink.level < 1e2)
+ illogicalNow('There is not enough water in the sink. ');
+ }
+ }
+;
+
++ table: Fixture, Surface 'small end bracket/table' 'table'
+ "<<first time>>Upon closer inspection, you see that \v<<only>>The table is
+ bracketed to the wall. "
+;
+
+++ Readable '"operator\'s" manual' 'manual'
+ "<center ><<highlight 'Operator'>>&rsquo;s Manual<\center>\b
+ <bq>To control the auto-sink, use the calculator add-on to enter the
+ desired volume of water. For example,\n
+ \t\t<<aHref('calculate 69 * 105', 'CALCULATE 69 TIMES 105')>>\n
+ to fill the basin with <<% ,d 0x69 * 0105>> kochliaria<!-- an ancient Greek
+ unit, < 5 ml >.\b
+ Warning: Do not use big numbers or divide by zero!<\\bq>\b"
+ dobjFor(Read) asDobjFor(Examine)
+;
+
++ calculator: Fixture, Thing 'button/buttons/calculator/screen' 'calculator'
+ "The calculator is <<highlight 'built in'>>to the wall beside the sink. It
+ has buttons for all the standard unary and binary operations.
+ <<if(screen)>>The screen reads <<screen>>"
+ screen = nil
+ literalMatch = ''
+;
+
+method wrongContextMsg()
+{
+ return '<font face="TADS-Typewriter"><<highlight '<<'ERROR'>>'>> {{can\'t
+ use\ \"<<self.literalMatch>>\" in that context}}</font>. ';
+}
+
+portico: OutdoorRoom 'Portico'
+ "Columns line the portico stretching east and west, and steps lead down to
+ the south. The door leads back in, and beside the door is a basin. A
+ <<highlight 'label'>> is affixed to the doorpost. "
+ north = (__objref(error, error))
+ in asExit(north)
+ south: FakeConnector
+ {
+ "You begin moving away from the door, but then you remember the statue.
+ The gods won&rsquo;t bring her to life if you give up this easily!
+ <<setMethod(&isConnectorApparent, {origin, actor: nil})>>"
+ }
+ east asExit(south)
+ west asExit(south)
+ down asExit(south)
+;
+
+error: LockableWithKey, Door ->door 'door' 'door' @portico "<<door.desc>>"
+ keyList = (otherSide.keyList)
+;
+
++ Fixture 'column*columns' 'columns'
+ "There are six <<one of>>short columns with simple capitals<<or>>slender
+ columns with scrollwork in the capitals<<or>>tall columns with ornate
+ capitals<<sticky random>>. Above the architrave is a frieze depicting some
+ of your wares. <<first time>>The cornice overhangs the frieze a bit too
+ much, you think; perhaps you should shorten it. You try to concentrate on
+ the architecture of the portico, stoically ignoring what you cannot change,
+ but it doesn&rsquo;t work. It never does. <<only>>"
+ isPlural = true
+;
+
++ Fixture, Readable 'label/doorpost' '<<highlight 'label'>>'
+ "The <<highlight 'label'>> says <q>Pygmentalion</q><<first time>> (which is
+ your <<highlight 'name'>>)<<only>>. "
+ dobjFor(Read) asDobjFor(Examine)
+;
+
++ basin: RestrictedContainer, Fixture
+ '(bird) basin/bath/birdbath/fountain/mosaic/pool/tile/tiles' 'basin'
+ "It is shallow but wide, and lined with tiles. It used to be a fountain,
+ but it stopped working after they installed the new sink. Something to do
+ with water pressure, no doubt. Now you just use it as a birdbath.\b
+ <<if overflowing>>Water is spilling over the sides in a turbulent flow.
+ <<else if level >= 19500>>It is full to the brim with water. You can see
+ your reflection quite clearly. Gods, you look a mess.
+ <<else if level >= 15000>>It is full of water. You can see your reflection.
+ <<else if level >= 10000>>It is half full. From the right angle, you can
+ make out a shadowy reflection of the columns, but nothing more.
+ <<else if level >= 2000>>There is some water in it, but you can still make
+ out the mosaic lining the basin.
+ <<else if level > 0>>A small puddle has formed at the bottom of the basin.
+ <<else>>It is empty.
+ <<if level <= -1.0e+2>>It looks as if it has never been filled. "
+ level = 0
+ overflowing = nil
+ isMirror = (level >= 15000)
+ setLevel(level:)
+ {
+ delegated sink.setLevel(_: sourceTextOrder ? level: nil, level: level);
+ }
+ iobjFor(CleanWith) maybeRemapTo(basinWater.location, CleanWith,
+ DirectObject, basinWater)
+;
+
+++ basinWater: PresentLater, Fixture '(basin) water basin water' 'water'
+ "<<basin.desc>>"
+ disambigName = 'water in the basin'
+ dobjFor(Drink)
+ {
+ verify
+ {
+ illogical('Drinking from a birdbath might not be the best idea. ');
+ }
+ }
+ iobjFor(CleanWith)
+ {
+ preCond = [touchObj]
+ verify {
+ illogical('Washing something in a birdbath is unlikely to get it
+ clean. ');
+ }
+ }
+;
+
+++ feather: PresentLater, Thing
+ '(bird) (dove) (pigeon) (turtle) (turtle-dove) (turtledove) feather'
+ 'feather' "It&rsquo;s a turtle-dove feather: an auspicious omen! "
+ initSpecialDesc = "<<one of>>A little brown bird is splashing around in the
+ basin. When it notices you, it ruffles its feathers, one of which falls
+ out, and flies out between the columns. <<or>>A feather is
+ <<if basin.overflowing || basin.level > 0>><<highlight 'float'>>ing
+ <<else>>lying <<end>> in the basin. <<stopping>>"
+;
+
+/* Water */
+
+trickling(water) multimethod
+{
+ if (sink.overflowing)
+ {
+ dirs: for (local dir in Direction.allDirections)
+ {
+ if (dir.ofKind(RelativeDirection))
+ continue;
+ if (dir.ofKind(ShipboardDirection))
+ continue dirs;
+ if (water.eventualLocation.(dir.dirProp) == __objref(entrance))
+ return 'trickling <<dir.name>>';
+ }
+ }
+ return 'a stagnant puddle';
+}
+
+class Water:PresentLater,Fixture'(floor) (ground) water puddle water''water'
+ "The water on the floor is <<trickling(self)>>. "
+ disambigName = 'water on the floor'
+ specialDesc = "The floor is covered with water. "
+ dobjFor(Drink)
+ {
+ preCond = [touchObj]
+ verify { }
+ check { failCheck('{You\'re} not thirsty. '); }
+ }
+ iobjFor(CleanWith)
+ {
+ preCond = [touchObj]
+ verify { illogical('The water on the ground is too dirty. '); }
+ }
+;
+
+Water template +location | ~location "specialDesc"? inherited;
+Water +altarRoom;
+Water +sinkRoom { ;; };
+Water { +workbenchRoom };
+
+entranceWater: Water +entrance
+ "<<if sink.overflowing>>At your feet, all the water from the sink flows
+ into a <<%-o 02>>-dactyl slit in the baseboard. <<else>><<inherited>>"
+ vocabWords = 'water baseboard/puddle/slit water'
+;
+trickling(entranceWater w)
+{
+ return sink.overflowing ? 'trickling into the wall' : inherited<*>(w);
+}
+
+porticoWater: Water ~portico;
+trickling(porticoWater w)
+{
+ return basin.overflowing ? 'trickling down the stairs' : inherited<*>(w);
+}
+
+/* Calculating */
+
+;;;class is: Exception { finalize { } };;; // InvalidSpecificationError
+
+DefineLiteralAction(Calculate)
+ checkAction()
+ {
+ if (defined(calculator) && !gActor.canTouch(calculator))
+ {
+ { gActor.failCheck('{You/He} {can\'t} do that kind of math in
+ {your} head. '); }
+ }
+ }
+ execAction()
+ {
+ local op = function(...) { throw new is(); }, a, b;
+ local opString = (literalMatch, literalMatch);
+ if (numMatch)
+ goto binary;
+ switch (opString)
+ {
+ case '!':
+ case 'not':
+ opString = '!';
+ op = {x : !toInteger('<<%_\u0030[1]5.3\170x>>', 16)};
+ break;
+ case '+':
+ case 'plus':
+ case 'positive':
+ opString = '+';
+ op = {self_ : self_};
+ break;
+ case '-':
+ case 'minus':
+ case 'negate':
+ case 'negative':
+ opString = '&#x2212;';
+ op = {x : -x};
+ break;
+ case '~':
+ case 'inverse':
+ case 'inverse\\of':
+ opString = '~';
+ op = {x : ~x};
+ break;
+ }
+ goto doCalculation;
+ binary: binaryOp:
+ switch (opString)
+ {
+ case '+':
+ case 'plus':
+ opString = '+';
+ op = {a, b : +a+++b};
+ break binaryOp;
+ case '-':
+ case 'minus':
+ opString = '&#8722;';
+ op = {a, b : -b-- - -a};
+ break;
+ case '*':
+ case 'times':
+ opString = '&times;';
+ op = new function(a, b) { return a * b; };
+ break;
+ case '/':
+ case 'over':
+ case 'divided\\by':
+ opString = '/';
+ op = function(a, b) { return a / b; };
+ break;
+ case '%':
+ case 'mod':
+ case 'modulo':
+ opString = 'mod';
+ op = function(a, b, multimethod=b) { return a % multimethod; };
+ break;
+ case '\<<':
+ case 'shl':
+ case 'ashl':
+ case 'lshl':
+ opString = '&lt;&lt;';
+ op = {a, b, c? : a << b};
+ break;
+ case '&':
+ case 'and':
+ opString = '&amp;';
+ op = {a, b : local badness = a, local token = b, badness & token};
+ break;
+ case '^':
+ case 'xor':
+ opString = '^';
+ op = {a, b, c? : a ^ b};
+ break;
+ case '|':
+ case 'or':
+ opString = '|';
+ op = {a, b : a | b};
+ break;
+ case '>\>':
+ case 'shr':
+ case 'ashr':
+ opString = '>>';
+ op = {a, b : toInteger('<<(a >> b)>>')};
+ break;
+ case '>>>':
+ case 'lshr':
+ opString = '>>>';
+ op = {a, b : b ? invokee(a >>> 1, --b) : a};
+ break;
+ }
+ opString = ' <<opString>> ';
+ doCalculation:
+ "The calculator outputs ";
+ try
+ {
+ a = numMatch ? numMatch.getval(colon : nil) : nil;
+ b = numMatch2.getval();
+ local result = toInteger(numMatch ? op(a, b) : op(b));
+ calculator.setMethod(&screen, method()
+ {
+ return '<font face="TADS-Typewriter"><<a>><<opString>><<b>> =
+ <<%d result>></font>. ';
+ });
+ local oldLevel = sink.level;
+ sink.current.setLevel(level: result);
+ "<<calculator.screen()>>
+ <<if sink.current == basin>>The sink gurgles and the pipes rattle.
+ <<else if sink.level == oldLevel>>The sink gurgles.
+ <<else if sink.level <= 0 && oldLevel <= 0>>The pipes rattle for a
+ moment.
+ <<else if sink.level <= 0>>All the water drains from the sink.
+ <<else if oldLevel <= 0>>The sink begins to fill with water.
+ <<else if sink.level < oldLevel - 0xabc>>Some of the water drains
+ from the sink.
+ <<else if sink.level < oldLevel>>The water level drops slightly.
+ <<else if oldLevel < sink.level - 0XABC>>Water splashes into the
+ sink for a few seconds.
+ <<else if oldLevel < sink.level>>Water dribbles from the faucet. ";
+ }
+ catch (is in)
+ {
+ calculator.literalMatch = literalMatch;
+ calculator.setMethod(&screen, &wrongContextMsg);
+ "<<calculator.screen()>>";
+ }
+ catch (RuntimeError e)
+ {
+ calculator.setMethod(&screen, new method
+ {
+ return '<font face=\"TADS-Typewriter\"><<highlight 'ERROR'>>
+ {{<<e.exceptionMessage>>}}</font>.\b';
+ });
+ "<<calculator.screen()>>";
+ switch (e.errno_)
+ {
+ case 2008: // division by zero
+ "<<if sink.current == sink
+ && (sink.level > 0 || sink.overflowing)>>The water in the
+ sink is sucked down the drain.
+ <<else if basin.level > 0 || basin.overflowing>>Water comes up
+ from the drain and <<if basin.overflowing>>spills over
+ the edges of<<else>>begins to fill<<end>> the sink.
+ <<else>>The sink gurgles and the pipes rattle. ";
+ sink.current = sink.current == sink ? basin : sink;
+ local _tmp = sink.level;
+ sink.level = basin.level;
+ basin.level = _tmp;
+ _tmp = sink.overflowing;
+ sink.overflowing = basin.overflowing;
+ basin.overflowing = _tmp;
+ if (!sink.current.overflowing)
+ break;
+ // fall through
+ case 2023: // numeric overflow
+ if (!sink.current.overflowing)
+ "<<if sink.current == sink>>High-pressure water streams
+ from the faucet, filling the sink and spilling over the
+ edge. Rivulets begin running down the slight gradient of
+ the floor. <<else>>The pipes shake loudly. ";
+ forEachInstance(Water, function(w) {
+ if ((w.eventualLocation == portico) ==
+ (sink.current == basin))
+ w.makePresent();
+ });
+ sink.current.setLevel(level: nil);
+ break;
+ default:
+ throw e;
+ }
+ }
+ if (!gPlayerChar.hasSeen(feather))
+ {
+ feather.makePresentIf(basin.isMirror);
+ feather.moved = nil;
+ }
+ }
+;
+
+VerbRule(Calculate)
+ ('c' | 'calculate' | 'enter' | 'eval' | 'evaluate') (()|(singleNumber|))
+ (tokOp->literalMatch | '!'->literalMatch) numberPhrase -> numMatch2
+ : CalculateAction
+ verbPhrase = 'calculate/calculating (what) (how) (what)'
+;
+
+/* Cleaning */
+
+modify VerbRule(Clean)
+ [ /**//**/ // /* \\
+#define Room Unthing
+ badness 500] ('clean' | 'wash') dobjList:
+;
+
+grammar predicate(CleanIn):
+ ('clean' | 'wash') dobjList ('at' | 'in' | 'with') singleIobj
+ : CleanWithAction
+ verbPhrase = 'clean/cleaning (what) (in what)'
+ askIobjResponseProd = inSingleNoun
+ omitIobjInDobjQuery = true
+;
+
+/* Prayer */
+
+VerbRule(Pray)
+ [badness 500] 'pray' singleDobj
+ : PrayAtAction
+ verbPhrase = 'pray/praying (at what)'
+;
+
+VerbRule(PrayAt)
+ 'pray' ('at' | 'to') singleDobj
+ : PrayAtAction
+ verbPhrase = 'pray/praying (at what)'
+ askDobjResponseProd = singleNoun
+;
+
+DefineTAction(PrayAt);
+modify Thing
+ dobjFor(PrayAt)
+ {
+ verify
+ {
+ illogical('{You/He} {cannot} pray at {that dobj/him}. ');
+ }
+ }
+;
+
+/* Extended grammar for 'in' and 'out' */
+
+modify grammar directionName(in): 'in' | 'inside':
+ dir = inDirection
+;
+modify /**//**/ // /* \\
+#define Room Unthing
+ grammar directionName(out): 'out' | 'outside':
+ dir = outDirection
+;
+
+/* Speech */
+
+DefineLiteralAction(Say)
+ execAction
+ {
+ local literal = getLiteral().toLower();
+ if (literal is in ('xyzzy', 'plugh'))
+ tryImplicitActionMsg(&silentImplicitAction, Xyzzy);
+ else if (literal != key.keyword)
+ "Nothing happens. ";
+ else if (literal not in ())
+ {
+ if (gActor.location == portico && basin.isMirror)
+ {
+ if (feather.location == basin)
+ "The air above the basin shimmers, and the feather bobs on
+ the rippling water. After a moment, the shimmering
+ disappears.";
+ else
+ {
+ /*
+ * Venus q̇ la pꝛiere ouyt
+ * [...]
+ * A lymage ẽuoya loꝛs lame.
+ * Si deuĩt ſi treſbelle dame.
+ * Quoncq̄s en toute la contree.
+ * Not len ſi belle encontree.
+ * [...]
+ * Doulx amys aĩs ſuy vꝛ̄e amye.
+ * Pꝛeſte de voſtre compaignye.
+ * Receuoir ⁊ mamoᷣ voꝰ offre.
+ * Sil voꝰ plaiſt receuoir tel offre.
+ * (MS. Douce 195, fol. 151v)
+ */
+ "The air above the basin shimmers for a moment. You hear
+ the door opening behind you. Turning around, you see a
+ woman who looks suspiciously like your statue, except not
+ the color of marble.\b
+ <q>Hello, world,</q> she says. <q>It&rsquo;s nice to be
+ alive at last! Hello, dearest Pygmentalion.</q>\b
+ Ah, what beauty! What mastery of syntax! Praise be to
+ Aphrodite! ";
+ finishGameMsg(ftVictory,
+ [finishOptionUndo, finishOptionFullScore]);
+ }
+ }
+ else
+ "Nothing happens. <<if keywordToken.scoreCount>>Aphrodite said
+ you would need a mirror. <<end>>";
+ }
+ }
+;
+
+VerbRule(Say)
+ ('say' | 'shout') singleLiteral
+ : SayAction
+ verbPhrase = 'say/saying (what)'
+;
+
+VerbRule(SayTo)
+ ('say' | 'shout') singleLiteral ('at' | 'to') singleIobj
+ : SayAction
+ verbPhrase = 'say/saying (what) (to what)'
+;
+
+/**/ #if /* Revere the basileus. */ 0 \
+ // Expel the barbarian.
+;
+ #ifndef __DEBUG
+;
+ #define __DEBUG
+;
+# else
+;
+#if 1
+;
+ #define DEBUG__
+;
+#endif
+;
+ #endif
+;\\
+#endif
+/*
+#endif
+?*/
+//\\
+#endif
+'''
+#endif
+'\''''
+#endif
+\\'''
+"""
+#endif
+"\""""
+#endif
+\\"""
+'
+#endif
+\'
+#endif
+\\'
+"
+#endif
+\"
+#endif
+\\"
+'''<<'<<'
+#endif
+'>>'>>
+#endif
+'''
+"""<<'<<'
+#endif
+'>>'>>
+#endif
+"""
+'<<'<<'
+#endif
+'>>'>>
+#endif
+'
+"<<'<<'
+#endif
+'>>'>>
+#endif
+"//"
+\
+ # endif
+;
+dictionary barbarianDict;
+transient xyzzy: object;
+DefineIAction(Xyzzy)
+ execAction
+ {
+ "Only a barbarian could pronounce such a word. ";
+ local oldSay = t3SetSay({str : nil});
+ try
+ {
+ new transient Vector([
+ '<<one of>><< cycling >>',
+ '<<one of>><< at random>>',
+ '<<one of>><<then purely at random>>',
+ '<<one of>><<as decreasingly likely outcomes>>',
+ '<<one of>><< shuffled>>',
+ '<<one of>><< half shuffled>>',
+ '<<one of>><<then shuffled>>',
+ '<<one of>><<then half shuffled>>']);
+ '''''<font x= color=red bgcolor='silver' face="TADS-Sans"
+ size=\'+1\' x=\"x\">{can't}</font>\'''' '' '''';
+ """""<font x= color=red bgcolor='silver' face="TADS-Sans"
+ size=\'+1\' x=\"x\">{can't}</font>\"""" "" """";
+ '<font x= color=red face="TADS-Sans" size=\'+1\'
+ x=\"x\">{can\'t}</font>\'';
+ "<font x= color=red bgcolor='silver' size=\'+1\'
+ x=\"x\">{can\'t}</font>\"";
+ '''''<font <<'color=red'>> bgcolor<<'='>>silver
+ face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\'''' '' '''';
+ """""<font <<'color=red'>> bgcolor<<'='>>silver
+ face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\"""" "" """";
+ '<font <<'color=red'>> bgcolor<<'='>>silver
+ face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\'';
+ "<font <<'color=red'>> bgcolor<<'='>>silver
+ face=<<'"TADS-Sans"'>>>{ca<<'n\''>>t}</font>\"";
+ '''<s a1={\.}a a2=a{\>} a3=a{\>}a b1='{\>}b' b2='b{\>}' b3='b{\>}b'
+ c1="c{\>}" c2="{\>}c" c3="c{\>}c" d1=\'d{\>}\' d2=\'{\>}d\'
+ d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\" e3=\"e{\>}e\"></s>''';
+ """<s a1={\.}a a2=a{\>} a3=a{\>}a b1='{\>}b' b2='b{\>}' b3='b{\>}b'
+ c1="c{\>}" c2="{\>}c" c3="c{\>}c" d1=\'d{\>}\' d2=\'{\>}d\'
+ d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\" e3=\"e{\>}e\"></s>""";
+ '<s a1={\.}a a2=a{\>} a3=a{\>}a c1="c{\>}" c2="{\>}c" c3="c{\>}c"
+ d1=\'d{\>}\' d2=\'{\>}d\' d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\"
+ e3=\"e{\>}e\"></s>';
+ "<s a1={\.}a a2=a{\>} a3=a{\>}a b1='{\>}b' b2='b{\>}' b3='b{\>}b'
+ d1=\'d{\>}\' d2=\'{\>}d\' d3=\'d{\>}d\' e1=\"e{\>}\" e2=\"{\>}e\"
+ e3=\"e{\>}e\"></s>";
+ '''{a<<1>>b}'''; """{a<<1>>b}"""; '{a<<1>>b}'; "{a<<1>>b}";
+ '''<s a<<'='>>'1' b<<'='>>"2" c<<'='>>\'3\' d<<'='>>\"4\"
+ <<'e'>>=5 f=6' g=7">''';
+ """<s a<<'='>>'1' b<<'='>>"2" c<<'='>>\'3\' d<<'='>>\"4\"
+ <<'e'>>=5 f=6' g=7">""";
+ '<s b<<'='>>"2" c<<'='>>\'3\' d<<'='>>\"4\" <<'e'>>=5 g=7">';
+ "<s a<<'='>>'1' c<<'='>>\'3\' d<<'='>>\"4\" <<'e'>>=5 f=6'>";
+ '''<s a=v\\ a=v\ v\><s a='{'}'\><s a="{"}"\>''';
+ """<s a=v\\ a=v\ v\><s a='{'}'\><s a="{"}"\>""";
+ '<s a=v\\ a=v\ v\><s a=\'{\'}\'\><s a="{"}"\>';
+ "<s a=v\\ a=v\ v\><s a='{'}'\><s a=\"{\"}\"\>";
+ '''<font color='purple>igram</font>'''; '''<t a={'''; '''}''';
+ '''<font color="purple>igram</font>'''; '''<t a='{'''; '''}''';
+ '''<font color=\'purple>igram</font>'''; '''<t a="{'''; '''}''';
+ '''<font color=\"purple>igram</font>''';
+ """<font color='purple>igram</font>"""; """<t a={"""; """}""";
+ """<font color="purple>igram</font>"""; """<t a='{"""; """}""";
+ """<font color=\'purple>igram</font>"""; """<t a=\"{"""; """}""";
+ """<font color=\"purple>igram</font>""";
+ '<font color="purple>igram</font>'; '<t a={'; '}';
+ '<font color=\'purple>igram</font>'; '<t a=\'{'; '}';
+ '<font color=\"purple>igram</font>'; '<t a="{'; '}';
+ "<font color=\"purple>igram</font>"; "<t a={"; "}";
+ "<font color='purple>igram</font>"; "<t a='{"; "}";
+ "<font color=\'purple>igram</font>"; "<t a=\"{"; "}\"";
+ '''<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>''';
+ """<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>""";
+ '<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>';
+ "<xmp a=v>&amp;\x26<b><\xmp></xmp a=v>";
+ '''<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>''';
+ """<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>""";
+ '<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>';
+ "<xmp a=v>&amp;\x26<b><\xmp><\Xmp a=v>";
+ '''<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>''';
+ """<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>""";
+ '<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>';
+ "<xmp a=v>&amp;\x26<b><\xmp><\\xmp a=v>";
+ '''<xmp>'''; """<xmp>"""; '<xmp>'; "<xmp>";
+ '''<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>''';
+ """<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>""";
+ '<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>';
+ "<listing a=v>&amp;\x26<b><listing><xmp></listing a=v>";
+ '''<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>''';
+ """<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>""";
+ '<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>';
+ "<listing a=v>&amp;\x26<b><listing><xmp><\listing a=v>";
+ '''<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>''';
+ """<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>""";
+ '<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>';
+ "<listing a=v>&amp;\x26<b><listing><xmp><\\listing a=v>";
+ '''<listing>'''; """<listing>"""; '<listing>'; "<listing>";
+ }
+ finally
+ {
+ t3SetSay(oldSay);
+ }
+ }
+;
+
+VerbRule(Xyzzy)
+ "xyzzy" | "plugh" *
+ : XyzzyAction
+ verbPhrase = 'babble/talking like a barbarian'
+;
+
+randomGreekWord()
+{
+ local vowels = ['a', 'e', 'e', 'i', 'o', 'y', 'o'];
+ local consonants = ['p', 't', 'k', 'b', 'd', 'g', 's', 'm', 'n', 'l', 'r'];
+ local clusters =
+ ['pn', 'pl', 'pr', 'tm', 'tr', 'kn', 'kl', 'kr', 'bl', 'br'];
+ local ends = consonants - ['b', 'd', 'g'];
+ local word;
+ local retries = 0;
+ for (local r in 0 .. -1 step -1)
+ {
+ for ((r), local i = 0, local j = 2; i < j; ++i, --j)
+ {
+ for (local s = 0, local n in [90, 30, 10]; ; --s)
+ retries -= s * n;
+ }
+ }
+ retries *= 2;
+ retries >>= 1;
+ retries /= 2;
+ retries <<= 1;
+ retries >>>= 2;
+ retries %= 16;
+ retries &= ~1;
+ retries |= 2;
+ retries ^= retries ^ retries;
+ do
+ {
+ word = rand('[ptkbdgsm]?');
+ for (local i in 0 .. __TADS3)
+ word += concat(rand(rand('', clusters, consonants)), rand('"h"?'),
+ rand(vowels...), rand('','', 'i', 'u', rand(ends)));
+ word += rand('"s"?');
+ word = rexReplace(R'^[pk](?![tnlrhaeioy]|[tnlr]h?[^aeioy])', word, '');
+ word = rexReplace(R'^b(?![dlrhaeioy]|[dlr]h?[^aeioy])', word, '');
+ word = rexReplace(R'^g(?![nlrhaeioy]|[nlr]h?[^aeioy])', word, '');
+ word = rexReplace(R'^t(?![mrhaeioy]|[mlr]h?[^aeioy])', word, '');
+ word = rexReplace(R'^d(?![rhaeioy]|rh?[^aeioy])', word, '');
+ word = rexReplace(R'^m(?![nhaeioy]|nh?[^aeioy])', word, '');
+ word = rexReplace(R'^[^aeioy]h?(([^haeioy]h?){2})', word, '%1');
+ word = rexReplace(R'[ptkbdgs]([ptkbdg][^haeioy])', word, '%1');
+ word = rexReplace(R'([mnlr])h', word, 'h%1');
+ word = rexReplace(R'(?<!(^|[ptk]))h', word, '');
+ word = rexReplace(R'^h(?![aeioy])', word, '');
+ word = rexReplace(R'h(?=.*h)', word, '');
+ word = rexReplace(R'(?<=^|r)r', word, 'rh');
+ word = rexReplace(R'([iy]+)[iu]', word, '%1');
+ word = rexReplace(R'nl', word, 'll');
+ word = rexReplace(R'n(?=[pbm])', word, 'm');
+ word = rexReplace(R'(?<.)m(?=[tdn])', word, 'n');
+ word = rexReplace(R'pb|bp', word, 'pp');
+ word = rexReplace(R'td|dt', word, 'tt');
+ word = rexReplace(R'kg|gk', word, 'kk');
+ word = rexReplace(R'bs', word, 'ps');
+ word = rexReplace(R'ds|sd', word, 'z');
+ word = rexReplace(R'gs', word, 'ks');
+ word = rexReplace(R'ts', word, 'ss');
+ word = rexReplace(R'[^pkaeioyusnr]+(s?)$', word, '%1');
+ word = rexReplace(R'[pk]+$', word, '');
+ word = rexReplace(R'(.h?)%1{2,}', word, '%1%1');
+ word = rexReplace(R'^(.h?)%1', word, '%1');
+ word = rexReplace(R'(.h?)%1$', word, '%1');
+ word = rexReplace(R'^y', word, 'hy');
+ word = rexReplace(R'([ptk])([ptk])h', word, '%1h%2h');
+ word = rexReplace(R'([ptk])h%1h', word, '%1%1h');
+ word = rexReplace(R'ks', word, 'x');
+ word = rexReplace(R'gg', word, 'kg');
+ word = rexReplace(R'kh', word, 'ch');
+ } while (retries-- && (word.length() < 4 || !rexSearch(
+ new RexPattern('^(eu|hy|[pgm]n|bd|tm|rh)|(.h.|pp|kc|rr)h|ch([^aeioy])|'
+ + '([^aeioy])y([^aeioy])$|(ps|x|o[ius])$'), word)));
+ return word;
+}