#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from io
import StringIO
from pathlib
import Path
from typing
import List, Optional, Any
import os
import re
import sys
SCRIPT_DIR = Path(os.path.dirname(sys.argv[
0]))
# This file is included verbatim at the start of the in-memory python script.
SCRIPT_SETUP_CODE = SCRIPT_DIR /
"common/gen_setup.py"
INTERP_DEFS_FILE = SCRIPT_DIR /
"../../../libdexfile/dex/dex_instruction_list.h"
NUM_OPCODES =
256
# Extract an ordered list of instructions from the VM sources. We use the
# "goto table" definition macro, which has exactly NUM_OPCODES entries.
def getOpcodeList():
opcodes: List[str] = []
opcode_fp = open(INTERP_DEFS_FILE)
opcode_re = re.compile(r
"^\s*V\((....), (\w+),.*", re.DOTALL)
for line
in opcode_fp:
match = opcode_re.match(line)
if not match:
continue
opcodes.append(
"op_" + match.group(
2).lower())
opcode_fp.close()
if len(opcodes) != NUM_OPCODES:
print(
"ERROR: found ", len(opcodes),
" opcodes in Interp.h (expected ", NUM_OPCODES,
")")
raise SyntaxError(
"bad opcode count")
return opcodes
# All lines must follow this generic pattern. Indentation is meaningfull.
line_re = re.compile(r
"(?:%| |)(?P<indent>\s*)(?P<code>.*?)\s*")
# The following lines must be followed by increased indentation.
indent_re = [
re.compile(r
"%\s*(?P<name>\w*).*:\s*(?:#.*)?"),
re.compile(r
"\s*(?P<name>\.macro|\.if|\.ifnc|\.else|\.elseif)\b.*"),
re.compile(r
"\s*(?P<name>OAT_ENTRY|ENTRY|DEFINE_FUNCTION|NAME_START).*"),
]
# Exceptions. The following lines will just use the current indentation.
automatic_indent_re = {
re.compile(r
"#(\w)+.*"), # C++ style preprocessor.
re.compile(r
"[^% ]*:\s*"),
# Assembly label.
}
# Finds variable references in text: $foo or ${foo}
variable_re = re.compile(r
'''
(?<!\$)
# Must not be preceded by another $.
\$
# Starts with $.
(\{)?
# May be enclosed by { } pair.
(?P<name>\w+)
# Save the symbol in named group.
(?(
1)\})
# Expect } if and only if { was present.
''', re.VERBOSE | re.ASCII)
def generate_script(output_filename: Path, input_filenames: List[Path]):
errors: List[Any] = []
# Create new python script and write the initial setup code.
output = StringIO()
# File-like in-memory buffer.
output.write(
"# DO NOT EDIT: This file was generated by gen-mterp.py.\n")
output.write(SCRIPT_SETUP_CODE.read_text())
output.write(
"def opcodes():\n")
for i, opcode
in enumerate(getOpcodeList()):
output.write(f
' write_opcode({i}, "{opcode}", {opcode})\n')
# Read all template files and translate them into python code.
for filename
in sorted(input_filenames):
location = f
"{filename.parent.name}/{filename.name}"
# Remove all C++ style comments to simplify further processing.
input = StringIO(filename.read_text())
for comment
in re.finditer(r
"//[^\n]*|/\*.*?\*/", input.getvalue(), re.DOTALL):
input.seek(comment.start())
input.write(
"".join((
"\n" if c ==
"\n" else " ")
for c
in comment.group()))
lines = list(enumerate(input.getvalue().split(
"\n"),
1))
# Remove line-continations (merge any slash-ending line with the next line).
for i
in reversed(range(len(lines)-
1)):
if lines[i][
1].endswith(
"\\"):
lines[i] = (lines[i][
0], lines[i][
1][:-
1] + lines[i+
1][
1].lstrip(
'% '))
lines[i+
1] = (lines[i+
1][
0],
"")
lines = list((n, c)
for n, c
in lines
if c.strip())
def print_error(i: int, msg: str):
nonlocal location, lines
linenum, _ = lines[i]
errors.append((location, linenum, msg))
print(f
"ERROR[{location}:{linenum}]: {msg}", file=sys.stderr)
for j
in range(max(
0, i-
2), min(i+
2, len(lines))):
n, c = lines[j]
print(f
"{">>
" if j == i else " "}{n:6}: {c}", file=sys.stderr)
print(
"", file=sys.stderr)
indents: List[str] = [
""]
indent_stmt: Optional[re.Match[str]] =
None
last_auto_indent: Optional[str] =
None
for i, (linenum, line)
in enumerate(lines):
# Extract indentation and code line parts.
m = line_re.fullmatch(line)
assert m, f
"Can't parse '{line}'"
indent, code = m.group(
"indent"), m.group(
"code")
if not code:
continue
# Convert non-python blocks into pythonic syntax.
if not line.startswith(
"%"):
code = code.replace(
"\\",
"\\\\").replace(
"'",
"\\'")
code = variable_re.sub(r
"' + to_string(\g<name>) + '", code)
code = code.replace(
"$$",
"$")
code = f
"write_line('{code}')"
# Normalize the indentation to 2-spaces in the output.
if any(m.fullmatch(line)
for m
in automatic_indent_re):
output_indent =
" " * (len(indents) -
1 + (
1 if indent_stmt
else 0))
last_auto_indent = output_indent
else:
if len(indent) > len(indents[-
1]):
if not indent_stmt:
print_error(i,
"Unexpected increased indentation")
indents.append(indent)
elif indent_stmt:
print_error(i, f
"Expected increased indentation after {indent_stmt.group('name')}")
indent_stmt =
None
while len(indent) < len(indents[-
1]):
if len(indents) >=
2 and len(indent) > len(indents[-
2]):
print_error(i,
"Decreased indentation does not match any outer indentation")
indents.pop()
output_indent =
" " * (len(indents) -
1)
if last_auto_indent
is not None and last_auto_indent != output_indent:
print_error(i,
"Indents before and after label/pragma must match (add '% pass')")
last_auto_indent =
None
if m := next((m
for m
in [e.fullmatch(line)
for e
in indent_re]
if m),
None):
if not line.startswith(
"%"):
code = f
"if {code} or True:" # Assembly-based scoped block.
indent_stmt = m
output.write(f
"{(output_indent + code):100} # {location}:{linenum}\n")
if errors:
sys.exit(
1)
output.write(f
"generate('{output_filename}')\n")
return output.getvalue()
if len(sys.argv) <=
3:
print(
"Usage: output_file input_file(s)")
sys.exit(
1)
# Generate the script and execute it.
output_filename = Path(sys.argv[
1])
input_filenames = list(map(Path, sys.argv[
2:]))
script = generate_script(output_filename, input_filenames)
script_filename = output_filename.with_suffix(
".S.py")
script_filename.write_text(script)
# Write to disk for debugging.
exec(compile(script, script_filename, mode=
'exec'))