Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/preppipe/frontend/vnmodel/vncodegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,14 +886,15 @@ def visit_default_handler(self, node : VNASTNodeBase):
zh_hk="以下命令結點在一個已經結束的基本塊中,內容不會被處理: {node}。請將內容移至可能結束該基本塊的指令前(比如跳轉、選項等)。",
)

def check_blocklocal_cond(self, node : VNASTNodeBase) -> bool:
def check_blocklocal_cond(self, node : VNASTNodeBase, suppress_warning : bool = False) -> bool:
# 检查该指令是否在正常的、未结束的块中
# 是的话返回 False, 不在正常情况下时生成错误并返回 True
if self.cur_terminator is None:
return False
msg = self._tr_unhandled_node_in_terminated_block.format(node=node.get_short_str(0))
err = ErrorOp.create(error_code='vncodegen-unhandled-node-in-terminated-block', context=self.context, error_msg=StringLiteral.get(msg, self.context), loc=node.location)
err.insert_before(self.cur_terminator)
if not suppress_warning:
msg = self._tr_unhandled_node_in_terminated_block.format(node=node.get_short_str(0))
err = ErrorOp.create(error_code='vncodegen-unhandled-node-in-terminated-block', context=self.context, error_msg=StringLiteral.get(msg, self.context), loc=node.location)
err.insert_before(self.cur_terminator)
return True

def check_block_or_function_local_cond(self, node : VNASTNodeBase) -> bool:
Expand Down Expand Up @@ -1918,7 +1919,9 @@ def visitVNASTBreakNode(self, node : VNASTBreakNode) -> VNTerminatorInstBase | N
return None

def visitVNASTReturnNode(self, node : VNASTReturnNode) -> VNTerminatorInstBase | None:
if self.check_block_or_function_local_cond(node):
# 章节结束命令只能在函数内使用
# 我们允许额外的章节结束命令(可能在转至章节命令后面)且不提供额外警告
if self.check_blocklocal_cond(node, suppress_warning=True):
return None
ret = VNReturnInst.create(context=self.context, start_time=self.starttime, name=node.name, loc=node.location)
self.destblock.push_back(ret)
Expand Down
26 changes: 23 additions & 3 deletions src/preppipe/irbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,12 @@ def dump(self) -> None:
dump = writer.write_op(self)
print(dump.decode('utf-8'))

def dump_html(self, index : int = 0, parentdir : str = '') -> None:
# for debugging
writer = IRWriter(self.context, True, None, None)
dump = writer.write_op(self)
_save_content_html_helper(dump, self.name, type(self).__name__, index, parentdir)

@IRObjectJsonTypeName("symbol_op")
class Symbol(Operation):

Expand Down Expand Up @@ -4094,19 +4100,33 @@ def write_block(self, b : Block) -> bytes:
self._walk_block(b, 0)
return self._output_body.getvalue()

def _view_content_helper(dump : bytes, name : str, typename : str):
name_portion = 'anon'
def _get_sanitized_name_for_dump(name : str) -> str | None:
if len(name) > 0:
sanitized_name = get_sanitized_filename(name)
if len(sanitized_name) > 0:
name_portion = sanitized_name
return sanitized_name
return None

def _view_content_helper(dump : bytes, name : str, typename : str):
name_portion = _get_sanitized_name_for_dump(name)
if name_portion is None:
name_portion = 'anon'
file = tempfile.NamedTemporaryFile('w+b', suffix='_viewdump.html', prefix='preppipe_' + typename + '_' + name_portion + '_', delete=False)
file.write(dump)
file.close()
path = os.path.abspath(file.name)
print('Opening HTML dump at ' + path)
webbrowser.open_new_tab('file:///' + path)

def _save_content_html_helper(dump : bytes, name : str, typename : str, index : int = 0, parentdir : str = ''):
name_portion = _get_sanitized_name_for_dump(name)
if name_portion is None:
name_portion = f"anon_{index}"
filename = f"{name_portion}_{typename}.html"
path = os.path.join(parentdir, filename) if len(parentdir) > 0 else filename
with open(path, 'wb') as f:
f.write(dump)

# ------------------------------------------------------------------------------
# IR verification
# ------------------------------------------------------------------------------
Expand Down
29 changes: 25 additions & 4 deletions src/preppipe/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,11 +517,32 @@ class _SaveIR(TransformBase):
def run(self) -> None:
raise PPNotImplementedError

@BackendDecl('dump', input_decl=Operation, output_decl=IODecl('<No output>', nargs=0))
class _DumpIR(TransformBase):
@TransformArgumentGroup('debugdump', "Options for Debug Dump")
@BackendDecl('debugdump', input_decl=Operation, output_decl=IODecl('<IR files>', nargs=0))
class _DebugDump(TransformBase):
dumpdir : typing.ClassVar[str] = "dumps"

@staticmethod
def install_arguments(argument_group : argparse._ArgumentGroup):
argument_group.add_argument("--debugdump-dir", required=False, type=str, nargs=1, default=_DebugDump.dumpdir, help="Directory to save the dumps")

@staticmethod
def handle_arguments(args : argparse.Namespace):
if dumpdir := args.debugdump_dir:
assert isinstance(dumpdir, list) and len(dumpdir) == 1
_DebugDump.dumpdir = dumpdir[0]
assert isinstance(_DebugDump.dumpdir, str)
if not os.path.exists(_DebugDump.dumpdir):
os.makedirs(_DebugDump.dumpdir, exist_ok=True)
elif not os.path.isdir(_DebugDump.dumpdir):
raise PPInternalError("Debug dump directory path exists but is not a directory")

def run(self) -> None:
for op in self.inputs:
op.dump()
for index, op in enumerate(self.inputs):
if isinstance(op, Operation):
op.dump_html(index, _DebugDump.dumpdir)
else:
raise PPInternalError("Debug dump input is not an operation")

@BackendDecl('view', input_decl=Operation, output_decl=IODecl('<No output>', nargs=0))
class _ViewIR(TransformBase):
Expand Down
34 changes: 29 additions & 5 deletions src/preppipe_gui_pyside6/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
from preppipe.language import *
from .settingsdict import *

TR_gui_execution = TranslationDomain("gui_execution")

@dataclasses.dataclass
class SpecifiedOutputInfo:
# 有指定输出路径的输出项
# 有指定输出路径的输出项,将在输出列表中出现。目前也包含未指定输出路径的项(因为最终它们也会有路径)
field_name: Translatable | str
argindex: int = -1
auxiliary: bool = False # 是否为辅助输出(如调试输出等),是的话我们会把它们放在其他输出之后

@dataclasses.dataclass
class UnspecifiedPathInfo:
Expand All @@ -28,15 +31,16 @@ class ExecutionInfo:
envs: dict[str, str] = dataclasses.field(default_factory=dict)
unspecified_paths: dict[int, UnspecifiedPathInfo] = dataclasses.field(default_factory=dict)
specified_outputs: list[SpecifiedOutputInfo] = dataclasses.field(default_factory=list)
enable_debug_dump: bool = False

def add_output_specified(self, field_name : Translatable | str, path : str):
def add_output_specified(self, field_name : Translatable | str, path : str, auxiliary : bool = False):
argindex = len(self.args)
self.specified_outputs.append(SpecifiedOutputInfo(field_name, argindex))
self.specified_outputs.append(SpecifiedOutputInfo(field_name, argindex, auxiliary))
self.args.append(path)

def add_output_unspecified(self, field_name : Translatable | str, default_name: Translatable | str, is_dir : bool = False):
def add_output_unspecified(self, field_name : Translatable | str, default_name: Translatable | str, is_dir : bool = False, auxiliary : bool = False):
argindex = len(self.args)
self.specified_outputs.append(SpecifiedOutputInfo(field_name, argindex))
self.specified_outputs.append(SpecifiedOutputInfo(field_name, argindex, auxiliary))
self.unspecified_paths[argindex] = UnspecifiedPathInfo(default_name, is_dir)
self.args.append('')

Expand All @@ -46,6 +50,11 @@ def add_output_unspecified(self, field_name : Translatable | str, default_name:
"md": "--md",
"txt": "--txt",
}
_tr_debug_dump = TR_gui_execution.tr("output_debug_dump",
en="Debug dumps",
zh_cn="调试输出",
zh_hk="調試輸出",
)

@staticmethod
def init_common():
Expand All @@ -71,6 +80,12 @@ def init_main_pipeline(inputs : list[str]):
result.args.append("--searchpath")
result.args.extend(searchpaths)

# 设置 IR 保存路径,如果需要的话
result.enable_debug_dump = SettingsDict.instance().get("mainpipeline/debug", False)
if result.enable_debug_dump:
result.args.append("--debugdump-dir")
result.add_output_unspecified(ExecutionInfo._tr_debug_dump, "debug_dump", is_dir=True, auxiliary=True)

# 给输入文件选择合适的读取选项
last_input_flag = ''
for i in inputs:
Expand All @@ -83,16 +98,25 @@ def init_main_pipeline(inputs : list[str]):
last_input_flag = flag
result.args.append(i)

result.add_debug_dump()
# 添加前端命令
result.args.extend([
"--cmdsyntax",
"--vnparse",
])
result.add_debug_dump()
result.args.extend([
"--vncodegen",
"--vn-blocksorting",
"--vn-entryinference",
])
result.add_debug_dump()
return result

def add_debug_dump(self):
if self.enable_debug_dump:
self.args.append("--debugdump")

class ExecutionState(enum.Enum):
INIT = 0
FAILED_TEMPDIR_CREATION = enum.auto()
Expand Down
46 changes: 44 additions & 2 deletions src/preppipe_gui_pyside6/forms/settingwidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>576</width>
<height>435</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -46,6 +46,48 @@
<item row="0" column="1">
<widget class="QComboBox" name="languageComboBox"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="mainPipelineGroupBox">
<property name="title">
<string>Main Pipeline</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="debugModeCheckBox">
<property name="text">
<string>Generate Debug Outputs</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>0</width>
<height>10</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
Expand Down
4 changes: 3 additions & 1 deletion src/preppipe_gui_pyside6/toolwidgets/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ def setData(self, execinfo : ExecutionInfo):
self.appendPlainText('='*20 + '\n')
self.exec.outputAvailable.connect(self.handleOutput)

for out in execinfo.specified_outputs:
auxiliary_outputs = [out for out in execinfo.specified_outputs if out.auxiliary]
main_outputs = [out for out in execinfo.specified_outputs if not out.auxiliary]
for out in main_outputs + auxiliary_outputs:
value = self.exec.composed_args[out.argindex]
w = OutputEntryWidget(self)#, out.field_name, value)
w.setData(out.field_name, value)
Expand Down
2 changes: 2 additions & 0 deletions src/preppipe_gui_pyside6/toolwidgets/maininput.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def request_export_renpy(self):
return
info = ExecutionInfo.init_main_pipeline(filelist)
info.args.append("--renpy-codegen")
info.add_debug_dump()
info.args.append("--renpy-export")
info.add_output_unspecified(self._tr_export_path, "game", is_dir=True)
MainWindowInterface.getHandle(self).requestExecution(info)
Expand All @@ -133,6 +134,7 @@ def request_export_webgal(self):
return
info = ExecutionInfo.init_main_pipeline(filelist)
info.args.append("--webgal-codegen")
info.add_debug_dump()
info.args.append("--webgal-export")
info.add_output_unspecified(self._tr_export_path, "game", is_dir=True)
MainWindowInterface.getHandle(self).requestExecution(info)
31 changes: 21 additions & 10 deletions src/preppipe_gui_pyside6/toolwidgets/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,25 @@ class SettingWidget(QWidget, ToolWidgetInterface):
zh_cn="语言",
zh_hk="語言",
)
_tr_general_debug = TR_gui_setting.tr("general_debug",
en="Generate Debug Outputs",
zh_cn="生成调试输出",
zh_hk="生成調試輸出",
)
_tr_general_debug_desc = TR_gui_setting.tr("general_debug_desc",
en="Enable debug mode to dump internal information (IRs, etc) to files. This makes execution slower.",
zh_cn="启用调试模式以将内部信息(IR等)保存到文件中。执行过程会变慢。",
zh_hk="啟用調試模式以將內部信息(IR等)保存到文件中。執行過程會變慢。",
)
_langs_dict = {
"en": "English",
"zh_cn": "中文(简体)",
"zh_hk": "中文(繁體)",
}
_tr_desc = TR_gui_setting.tr("desc",
en="Edit settings here. Currently only language is supported.",
zh_cn="在这里编辑设置。目前仅支持语言设置。",
zh_hk="在這裡編輯設置。目前僅支持語言設置。",
en="Edit settings here. Currently only language and debug settings are supported.",
zh_cn="在这里编辑设置。目前仅支持语言与调试设置。",
zh_hk="在這裡編輯設置。目前僅支持語言與調試設置。",
)

def __init__(self, parent : QWidget):
Expand All @@ -38,16 +48,24 @@ def __init__(self, parent : QWidget):
self.ui.setupUi(self)
self.bind_text(lambda s : self.ui.tabWidget.setTabText(0, s), self._tr_tab_general)
self.bind_text(self.ui.languageLabel.setText, self._tr_general_language)
self.bind_text(self.ui.mainPipelineGroupBox.setTitle, MainWindowInterface.tr_toolname_maininput)
self.bind_text(self.ui.debugModeCheckBox.setText, self._tr_general_debug)
self.bind_text(self.ui.debugModeCheckBox.setToolTip, self._tr_general_debug_desc)
self.ui.languageComboBox.clear()
for lang_code, lang_name in SettingsDict._langs_dict.items():
self.ui.languageComboBox.addItem(lang_name, lang_code)
self.ui.languageComboBox.setCurrentIndex(self.ui.languageComboBox.findData(SettingsDict.get_current_language()))
self.ui.languageComboBox.currentIndexChanged.connect(self.on_languageComboBox_currentIndexChanged)
self.ui.debugModeCheckBox.setChecked(True if SettingsDict.instance().get("mainpipeline/debug", False) else False)
self.ui.debugModeCheckBox.toggled.connect(self.on_debugModeCheckBox_toggled)

def on_languageComboBox_currentIndexChanged(self, index):
lang_code = self.ui.languageComboBox.currentData()
self.language_updated(lang_code)

def on_debugModeCheckBox_toggled(self, checked):
SettingsDict.instance()["mainpipeline/debug"] = True if checked else False

@classmethod
def getToolInfo(cls, **kwargs) -> ToolWidgetInfo:
return ToolWidgetInfo(
Expand All @@ -62,13 +80,6 @@ def initialize():
if lang := SettingsDict.instance().get("language"):
SettingWidget.setLanguage(lang)

def get_initial_value(self, key : str):
match key:
case "language":
return SettingsDict.get_current_language()
case _:
raise RuntimeError("Unexpected key")

def language_updated(self, lang):
if lang == SettingsDict.get_current_language():
return
Expand Down