#!/usr/bin/env python3 # SPDX-FileCopyrightText: © 2023-2024 ZeldaRET # SPDX-License-Identifier: MIT # # Schedule scripting language compiler # # For a reference about the language see https://github.com/zeldaret/mm/blob/main/docs/schedule_scripting_language.md # # # Version history: # # 1.0.0: # # * Initial release # # TODO: Check for repeated labels # TODO: think on a catchy name for the schedule language and the compiler # TODO: Warning/Error for control flows that do not led to a return. Maybe consider inserting return_none on those cases # TODO: consider adding optimization passes/options # TODO: consider adding and/or operators from __future__ import annotations # Short for "schedule compiler" __prog_name__ = "schc" __version__ = "1.0.0" import colorama colorama.init() import argparse import dataclasses import enum from pathlib import Path import re from typing import NoReturn import sys class SchcError(Exception): pass def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) DEBUG = False def debugPrint(*args, **kwargs): if not DEBUG: return eprint(*args, **kwargs) def fatalError(message: str, filename: str, lineNumber: int, columnNumber: int, **kwargs) -> NoReturn: # Print the filename/linenumber in a format that some IDEs can follow by ctrl-click on them eprint(f"{colorama.Style.BRIGHT}{filename}:{lineNumber}:{columnNumber}{colorama.Style.RESET_ALL}: {colorama.Style.BRIGHT}{colorama.Fore.RED}Error{colorama.Style.RESET_ALL}: {message}") if DEBUG: # Get the info from the caller frame = sys._getframe().f_back if frame is not None: funcName = frame.f_code.co_name debugPrint(f" Halted from: {funcName} at {frame.f_code.co_filename}:{frame.f_lineno}") for key, value in kwargs.items(): debugPrint(f" {key}: {value}") # TODO: Add fun error messages exit(1) def warning(message: str, filename: str, lineNumber: int, columnNumber: int, **kwargs) -> None: # Print the filename/linenumber in a format that some IDEs can follow by ctrl-click on them eprint(f"{colorama.Style.BRIGHT}{filename}:{lineNumber}:{columnNumber}{colorama.Style.RESET_ALL}: {colorama.Style.BRIGHT}{colorama.Fore.MAGENTA}Warning{colorama.Style.RESET_ALL}: {message}") if DEBUG: # Get the info from the caller frame = sys._getframe().f_back if frame is not None: funcName = frame.f_code.co_name debugPrint(f" Warning triggered in: {funcName} at {frame.f_code.co_filename}:{frame.f_lineno}") for key, value in kwargs.items(): debugPrint(f" {key}: {value}") class TokenType(enum.Enum): # Schedule commands IF_WEEKEVENTREG_S = "if_week_event_reg_s" IF_WEEKEVENTREG_L = "if_week_event_reg_l" IF_TIMERANGE_S = "if_time_range_s" IF_TIMERANGE_L = "if_time_range_l" RETURN_S = "return_s" RETURN_L = "return_l" RETURN_NONE = "return_none" RETURN_EMPTY = "return_empty" IF_MISC_S = "if_misc_s" IF_SCENE_S = "if_scene_s" IF_SCENE_L = "if_scene_l" IF_DAY_S = "if_day_s" IF_DAY_L = "if_day_l" NOP = "nop" RETURN_TIME = "return_time" IF_BEFORETIME_S = "if_before_time_s" IF_BEFORETIME_L = "if_before_time_l" IF_SINCETIME_S = "if_since_time_s" IF_SINCETIME_L = "if_since_time_l" BRANCH_S = "branch_s" BRANCH_L = "branch_l" # Generics IF_WEEKEVENTREG = "if_week_event_reg" IF_TIMERANGE = "if_time_range" IF_MISC = "if_misc" IF_SCENE = "if_scene" IF_DAY = "if_day" IF_BEFORETIME = "if_before_time" IF_SINCETIME = "if_since_time" BRANCH = "branch" # Extra tokens ELSE = "else" BRACE_OPEN = "{" BRACE_CLOSE = "}" ARGS = "(args)" NOT = "not" LABEL = "label" IDENTIFIER = "" @dataclasses.dataclass class TokenProperties: macro: str|None = None cmdLength: int|None = None isExtraToken: bool = False isConditionalBranch: bool = False isUnconditionalBranch: bool = False hasArguments: bool = False isGeneric: bool = False needsToInvert: bool = False isShort: bool = False shortVersion: TokenType|None=None longVersion: TokenType|None=None @property def isAnyBranch(self) -> bool: return self.isConditionalBranch or self.isUnconditionalBranch tokenPropertiesDict: dict[TokenType, TokenProperties] = { # Schedule commands TokenType.IF_WEEKEVENTREG_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_WEEK_EVENT_REG_S", cmdLength=0x04, isConditionalBranch=True, hasArguments=True, needsToInvert=True, isShort=True, shortVersion=TokenType.IF_WEEKEVENTREG_S, longVersion=TokenType.IF_WEEKEVENTREG_L), TokenType.IF_WEEKEVENTREG_L: TokenProperties(macro="SCHEDULE_CMD_CHECK_WEEK_EVENT_REG_L", cmdLength=0x05, isConditionalBranch=True, hasArguments=True, needsToInvert=True, shortVersion=TokenType.IF_WEEKEVENTREG_S, longVersion=TokenType.IF_WEEKEVENTREG_L), TokenType.IF_TIMERANGE_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_TIME_RANGE_S", cmdLength=0x06, isConditionalBranch=True, hasArguments=True, needsToInvert=True, isShort=True, shortVersion=TokenType.IF_TIMERANGE_S, longVersion=TokenType.IF_TIMERANGE_L), TokenType.IF_TIMERANGE_L: TokenProperties(macro="SCHEDULE_CMD_CHECK_TIME_RANGE_L", cmdLength=0x07, isConditionalBranch=True, hasArguments=True, needsToInvert=True, shortVersion=TokenType.IF_TIMERANGE_S, longVersion=TokenType.IF_TIMERANGE_L), TokenType.RETURN_S: TokenProperties(macro="SCHEDULE_CMD_RET_VAL_S", cmdLength=0x02, hasArguments=True, isShort=True), TokenType.RETURN_L: TokenProperties(macro="SCHEDULE_CMD_RET_VAL_L", cmdLength=0x03, hasArguments=True), TokenType.RETURN_NONE: TokenProperties(macro="SCHEDULE_CMD_RET_NONE", cmdLength=0x01), TokenType.RETURN_EMPTY: TokenProperties(macro="SCHEDULE_CMD_RET_EMPTY", cmdLength=0x01), TokenType.IF_MISC_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_MISC_S", cmdLength=0x03, isConditionalBranch=True, hasArguments=True, needsToInvert=True, isShort=True, shortVersion=TokenType.IF_MISC_S), TokenType.IF_SCENE_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_NOT_IN_SCENE_S", cmdLength=0x04, isConditionalBranch=True, hasArguments=True, isShort=True, shortVersion=TokenType.IF_SCENE_S, longVersion=TokenType.IF_SCENE_L), TokenType.IF_SCENE_L: TokenProperties(macro="SCHEDULE_CMD_CHECK_NOT_IN_SCENE_L", cmdLength=0x05, isConditionalBranch=True, hasArguments=True, shortVersion=TokenType.IF_SCENE_S, longVersion=TokenType.IF_SCENE_L), TokenType.IF_DAY_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_NOT_IN_DAY_S", cmdLength=0x04, isConditionalBranch=True, hasArguments=True, isShort=True, shortVersion=TokenType.IF_DAY_S, longVersion=TokenType.IF_DAY_L), TokenType.IF_DAY_L: TokenProperties(macro="SCHEDULE_CMD_CHECK_NOT_IN_DAY_L", cmdLength=0x05, isConditionalBranch=True, hasArguments=True, shortVersion=TokenType.IF_DAY_S, longVersion=TokenType.IF_DAY_L), TokenType.NOP: TokenProperties(macro="SCHEDULE_CMD_NOP", cmdLength=0x04, hasArguments=True), TokenType.RETURN_TIME: TokenProperties(macro="SCHEDULE_CMD_RET_TIME", cmdLength=0x06, hasArguments=True), TokenType.IF_BEFORETIME_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_BEFORE_TIME_S", cmdLength=0x04, isConditionalBranch=True, hasArguments=True, needsToInvert=True, isShort=True, shortVersion=TokenType.IF_BEFORETIME_S, longVersion=TokenType.IF_BEFORETIME_L), TokenType.IF_BEFORETIME_L: TokenProperties(macro="SCHEDULE_CMD_CHECK_BEFORE_TIME_L", cmdLength=0x05, isConditionalBranch=True, hasArguments=True, needsToInvert=True, shortVersion=TokenType.IF_BEFORETIME_S, longVersion=TokenType.IF_BEFORETIME_L), TokenType.IF_SINCETIME_S: TokenProperties(macro="SCHEDULE_CMD_CHECK_BEFORE_TIME_S", cmdLength=0x04, isConditionalBranch=True, hasArguments=True, isShort=True, shortVersion=TokenType.IF_SINCETIME_S, longVersion=TokenType.IF_SINCETIME_L), TokenType.IF_SINCETIME_L: TokenProperties(macro="SCHEDULE_CMD_CHECK_BEFORE_TIME_L", cmdLength=0x05, isConditionalBranch=True, hasArguments=True, shortVersion=TokenType.IF_SINCETIME_S, longVersion=TokenType.IF_SINCETIME_L), TokenType.BRANCH_S: TokenProperties(macro="SCHEDULE_CMD_BRANCH_S", cmdLength=0x02, isUnconditionalBranch=True, hasArguments=True, isShort=True, shortVersion=TokenType.BRANCH_S, longVersion=TokenType.BRANCH_L), TokenType.BRANCH_L: TokenProperties(macro="SCHEDULE_CMD_BRANCH_L", cmdLength=0x03, isUnconditionalBranch=True, hasArguments=True, shortVersion=TokenType.BRANCH_S, longVersion=TokenType.BRANCH_L), # Generics TokenType.IF_WEEKEVENTREG: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, needsToInvert=True, shortVersion=TokenType.IF_WEEKEVENTREG_S, longVersion=TokenType.IF_WEEKEVENTREG_L), TokenType.IF_TIMERANGE: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, needsToInvert=True, shortVersion=TokenType.IF_TIMERANGE_S, longVersion=TokenType.IF_TIMERANGE_L), TokenType.IF_MISC: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, needsToInvert=True, shortVersion=TokenType.IF_MISC_S), TokenType.IF_SCENE: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, shortVersion=TokenType.IF_SCENE_S, longVersion=TokenType.IF_SCENE_L), TokenType.IF_DAY: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, shortVersion=TokenType.IF_DAY_S, longVersion=TokenType.IF_DAY_L), TokenType.IF_BEFORETIME: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, needsToInvert=True, shortVersion=TokenType.IF_BEFORETIME_S, longVersion=TokenType.IF_BEFORETIME_L), TokenType.IF_SINCETIME: TokenProperties(isConditionalBranch=True, hasArguments=True, isGeneric=True, shortVersion=TokenType.IF_SINCETIME_S, longVersion=TokenType.IF_SINCETIME_L), TokenType.BRANCH: TokenProperties(isUnconditionalBranch=True, hasArguments=True, isGeneric=True, shortVersion=TokenType.BRANCH_S, longVersion=TokenType.BRANCH_L), # Extra tokens TokenType.ELSE: TokenProperties(isExtraToken=True), TokenType.BRACE_OPEN: TokenProperties(isExtraToken=True), TokenType.BRACE_CLOSE: TokenProperties(isExtraToken=True), TokenType.ARGS: TokenProperties(isExtraToken=True), TokenType.NOT: TokenProperties(isExtraToken=True), TokenType.LABEL: TokenProperties(isExtraToken=True), TokenType.IDENTIFIER: TokenProperties(isExtraToken=True), } tokenLiterals: dict[str, TokenType] = {x.value: x for x in TokenType} regex_label = re.compile(r"(?P