summaryrefslogtreecommitdiffstats
path: root/ipc/ipdl/ipdl/cxx/code.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /ipc/ipdl/ipdl/cxx/code.py
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/ipdl/ipdl/cxx/code.py')
-rw-r--r--ipc/ipdl/ipdl/cxx/code.py187
1 files changed, 187 insertions, 0 deletions
diff --git a/ipc/ipdl/ipdl/cxx/code.py b/ipc/ipdl/ipdl/cxx/code.py
new file mode 100644
index 0000000000..0b5019b623
--- /dev/null
+++ b/ipc/ipdl/ipdl/cxx/code.py
@@ -0,0 +1,187 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This module contains functionality for adding formatted, opaque "code" blocks
+# into the IPDL ast. These code objects follow IPDL C++ ast patterns, and
+# perform lowering in much the same way.
+
+# In general it is recommended to use these for blocks of code which would
+# otherwise be specified by building a hardcoded IPDL-C++ AST, as users of this
+# API are often easier to read than users of the AST APIs in these cases.
+
+import re
+import math
+import textwrap
+
+from ipdl.cxx.ast import Node, Whitespace, GroupNode, VerbatimNode
+
+
+# -----------------------------------------------------------------------------
+# Public API.
+
+
+def StmtCode(tmpl, **kwargs):
+ """Perform template substitution to build opaque C++ AST nodes. See the
+ module documentation for more information on the templating syntax.
+
+ StmtCode nodes should be used where Stmt* nodes are used. They are placed
+ on their own line and indented."""
+ return _code(tmpl, False, kwargs)
+
+
+def ExprCode(tmpl, **kwargs):
+ """Perform template substitution to build opaque C++ AST nodes. See the
+ module documentation for more information on the templating syntax.
+
+ ExprCode nodes should be used where Expr* nodes are used. They are placed
+ inline, and no trailing newline is added."""
+ return _code(tmpl, True, kwargs)
+
+
+def StmtVerbatim(text):
+ """Build an opaque C++ AST node which emits input text verbatim.
+
+ StmtVerbatim nodes should be used where Stmt* nodes are used. They are placed
+ on their own line and indented."""
+ return _verbatim(text, False)
+
+
+def ExprVerbatim(text):
+ """Build an opaque C++ AST node which emits input text verbatim.
+
+ ExprVerbatim nodes should be used where Expr* nodes are used. They are
+ placed inline, and no trailing newline is added."""
+ return _verbatim(text, True)
+
+
+# -----------------------------------------------------------------------------
+# Implementation
+
+
+def _code(tmpl, inline, context):
+ # Remove common indentation, and strip the preceding newline from
+ # '''-quoting, because we usually don't want it.
+ if tmpl.startswith("\n"):
+ tmpl = tmpl[1:]
+ tmpl = textwrap.dedent(tmpl)
+
+ # Process each line in turn, building up a list of nodes.
+ nodes = []
+ for idx, line in enumerate(tmpl.splitlines()):
+ # Place newline tokens between lines in the input.
+ if idx > 0:
+ nodes.append(Whitespace.NL)
+
+ # Don't indent the first line if `inline` is set.
+ skip_indent = inline and idx == 0
+ nodes.append(_line(line.rstrip(), skip_indent, idx + 1, context))
+
+ # If we're inline, don't add the final trailing newline.
+ if not inline:
+ nodes.append(Whitespace.NL)
+ return GroupNode(nodes)
+
+
+def _verbatim(text, inline):
+ # For simplicitly, _verbatim is implemented using the same logic as _code,
+ # but with '$' characters escaped. This ensures we only need to worry about
+ # a single, albeit complex, codepath.
+ return _code(text.replace("$", "$$"), inline, {})
+
+
+# Pattern used to identify substitutions.
+_substPat = re.compile(
+ r"""
+ \$(?:
+ (?P<escaped>\$) | # '$$' is an escaped '$'
+ (?P<list>[*,])?{(?P<expr>[^}]+)} | # ${expr}, $*{expr}, or $,{expr}
+ (?P<invalid>) # For error reporting
+ )
+ """,
+ re.IGNORECASE | re.VERBOSE,
+)
+
+
+def _line(raw, skip_indent, lineno, context):
+ assert "\n" not in raw
+
+ # Determine the level of indentation used for this line
+ line = raw.lstrip()
+ offset = int(math.ceil((len(raw) - len(line)) / 4))
+
+ # If line starts with a directive, don't indent it.
+ if line.startswith("#"):
+ skip_indent = True
+
+ column = 0
+ children = []
+ for match in _substPat.finditer(line):
+ if match.group("invalid") is not None:
+ raise ValueError("Invalid substitution on line %d" % lineno)
+
+ # Any text from before the current entry should be written, and column
+ # advanced.
+ if match.start() > column:
+ before = line[column : match.start()]
+ children.append(VerbatimNode(before))
+ column = match.end()
+
+ # If we have an escaped group, emit a '$' node.
+ if match.group("escaped") is not None:
+ children.append(VerbatimNode("$"))
+ continue
+
+ # At this point we should have an expression.
+ list_chr = match.group("list")
+ expr = match.group("expr")
+ assert expr is not None
+
+ # Evaluate our expression in the context to get the values.
+ try:
+ values = eval(expr, context, {})
+ except Exception as e:
+ msg = "%s in substitution on line %d" % (repr(e), lineno)
+ raise ValueError(msg) from e
+
+ # If we aren't dealing with lists, wrap the result into a
+ # single-element list.
+ if list_chr is None:
+ values = [values]
+
+ # Check if this substitution is inline, or the entire line.
+ inline = match.span() != (0, len(line))
+
+ for idx, value in enumerate(values):
+ # If we're using ',' as list mode, put a comma between each node.
+ if idx > 0 and list_chr == ",":
+ children.append(VerbatimNode(", "))
+
+ # If our value isn't a node, turn it into one. Verbatim should be
+ # inline unless indent isn't being skipped, and the match isn't
+ # inline.
+ if not isinstance(value, Node):
+ value = _verbatim(str(value), skip_indent or inline)
+ children.append(value)
+
+ # If we were the entire line, indentation is handled by the added child
+ # nodes. Do this after the above loop such that created verbatims have
+ # the correct inline-ness.
+ if not inline:
+ skip_indent = True
+
+ # Add any remaining text in the line.
+ if len(line) > column:
+ children.append(VerbatimNode(line[column:]))
+
+ # If we have no children, just emit the empty string. This will become a
+ # blank line.
+ if len(children) == 0:
+ return VerbatimNode("")
+
+ # Add the initial indent if we aren't skipping it.
+ if not skip_indent:
+ children.insert(0, VerbatimNode("", indent=True))
+
+ # Wrap ourselves into a group node with the correct indent offset
+ return GroupNode(children, offset=offset)