diff --git a/renpy-parser/language/statement.h b/renpy-parser/language/statement.h index 5bfcb7f..c0f113b 100644 --- a/renpy-parser/language/statement.h +++ b/renpy-parser/language/statement.h @@ -30,6 +30,9 @@ namespace language { show, voice, with, + stop, + pause, + hide, }; struct jump { @@ -70,6 +73,18 @@ namespace language { struct with { }; + struct stop { + uint32_t channelIndex; + }; + + struct pause { + float duration; + }; + + struct hide { + uint32_t imageIndex; + }; + struct statement { enum type type; union { diff --git a/renpy-parser/lex.py b/renpy-parser/lex.py index 1c35441..495dd3c 100644 --- a/renpy-parser/lex.py +++ b/renpy-parser/lex.py @@ -45,6 +45,11 @@ class TT(Enum): RETURN = auto() INIT = auto() FADEOUT = auto() + TRANSFORM = auto() + STOP = auto() + NOLOOP = auto() + PAUSE = auto() + HIDE = auto() keywords = { b"play": TT.PLAY, @@ -61,6 +66,11 @@ keywords = { b"return": TT.RETURN, b"init": TT.INIT, b"fadeout": TT.FADEOUT, + b"transform": TT.TRANSFORM, + b"stop": TT.STOP, + b"noloop": TT.NOLOOP, + b"pause": TT.PAUSE, + b"hide": TT.HIDE, } @dataclass diff --git a/renpy-parser/parse.py b/renpy-parser/parse.py index d77c7e1..5b9c728 100644 --- a/renpy-parser/parse.py +++ b/renpy-parser/parse.py @@ -56,6 +56,14 @@ class Play: channel: lex.Token path: lex.Token fadeout: lex.Token + noloop: bool + + __repr__ = lexeme_repr + +@dataclass +class Stop: + channel: lex.Token + fadeout: lex.Token __repr__ = lexeme_repr @@ -88,6 +96,7 @@ class Voice: class Show: what: lex.Token transform: lex.Token + properties: list[tuple[lex.Token, lex.Token]] __repr__ = lexeme_repr @@ -102,6 +111,18 @@ class Jump: __repr__ = lexeme_repr +@dataclass +class Pause: + duration: lex.Token + + __repr__ = lexeme_repr + +@dataclass +class Hide: + what: lex.Token + + __repr__ = lexeme_repr + @dataclass class Return: pass @@ -242,11 +263,16 @@ def parse_play(tokens, index): if fadeout.type != TT.NUMBER: raise ParseException("expected number", fadeout) index += 2 + noloop = False + if token.type == TT.NOLOOP: + noloop = True + index += 1 play = Play( channel = channel, path = path, - fadeout = fadeout + fadeout = fadeout, + noloop = noloop, ) return index, play @@ -290,6 +316,12 @@ def parse_voice(tokens, index): return index + 1, voice def parse_show(tokens, index): + show = tokens[index + 0] + if show.type != TT.SHOW: + raise ParseException("expected show", show) + + index += 1 + index, what = parse_lhs(tokens, index) at = tokens[index + 0] @@ -300,11 +332,36 @@ def parse_show(tokens, index): if transform.type != TT.IDENTIFIER: raise ParseException("expected identifier", transform) + index += 2 + + properties = [] + if tokens[index + 0].type == TT.COLON: + index += 1 + while index < len(tokens): + token = tokens[index + 0] + if token.type == TT.NEWLINE: + index += 1 + continue + + if token.position.column <= show.position.column: + break + + if token.type != TT.IDENTIFIER: + raise ParseException("expected identifier") + + number = tokens[index + 1] + if number.type != TT.NUMBER: + raise ParseException("expected number") + + properties.append((token, number)) + index += 2 + show = Show( what = what, - transform = transform + transform = transform, + properties = properties ) - return index + 2, show + return index, show def parse_menu(tokens, index): menu = tokens[index + 0] @@ -369,7 +426,7 @@ def parse_init(tokens, index): colon = tokens[index + 1] if colon.type != TT.COLON: - raise ParseException("expected identifier", colon) + raise ParseException("expected colon", colon) index += 2 @@ -388,6 +445,74 @@ def parse_init(tokens, index): return index, None +def parse_transform(tokens, index): + transform = tokens[index + 0] + if transform.type != TT.TRANSFORM: + raise ParseException("expected transform", init) + + identifier = tokens[index + 1] + if identifier.type != TT.IDENTIFIER: + raise ParseException("expected identifier", identifier) + + colon = tokens[index + 2] + if colon.type != TT.COLON: + raise ParseException("expected colon", colon) + + index += 3 + + # skip all tokens inside block + while index < len(tokens): + token = tokens[index] + if token.type == TT.NEWLINE: + index += 1 + continue + + if token.position.column < transform.position.column: + raise ParseException("invalid init block dedent", token) + if token.position.column == transform.position.column: + break + index += 1 + + return index, None + +def parse_stop(tokens, index): + channel = tokens[index + 0] + if channel.type != TT.IDENTIFIER: + raise ParseException("expected identifier", channel) + + index += 1 + token = tokens[index] + fadeout = None + if token.type == TT.FADEOUT: + fadeout = tokens[index + 1] + if fadeout.type != TT.NUMBER: + raise ParseException("expected number", fadeout) + index += 2 + + stop = Stop( + channel = channel, + fadeout = fadeout + ) + return index, stop + +def parse_pause(tokens, index): + duration = tokens[index + 0] + if duration.type != TT.NUMBER: + raise ParseException("expected number", duration) + + pause = Pause( + duration = duration + ) + return index + 1, pause + +def parse_hide(tokens, index): + index, what = parse_lhs(tokens, index) + + hide = Hide( + what = what + ) + return index + 1, hide + def parse_one(tokens, index): token = tokens[index] if token.type == TT.NEWLINE: @@ -421,7 +546,7 @@ def parse_one(tokens, index): index, ast = parse_voice(tokens, index + 1) return index, ast elif token.type == TT.SHOW: - index, ast = parse_show(tokens, index + 1) + index, ast = parse_show(tokens, index) return index, ast elif token.type == TT.MENU: index, ast = parse_menu(tokens, index) @@ -434,6 +559,18 @@ def parse_one(tokens, index): elif token.type == TT.INIT: index, ast = parse_init(tokens, index) return index, ast + elif token.type == TT.TRANSFORM: + index, ast = parse_transform(tokens, index) + return index, ast + elif token.type == TT.STOP: + index, ast = parse_stop(tokens, index + 1) + return index, ast + elif token.type == TT.PAUSE: + index, ast = parse_pause(tokens, index + 1) + return index, ast + elif token.type == TT.HIDE: + index, ast = parse_hide(tokens, index + 1) + return index, ast else: raise ParseException("unexpected token", token) diff --git a/renpy-parser/transform.py b/renpy-parser/transform.py index a930b1b..30fa356 100644 --- a/renpy-parser/transform.py +++ b/renpy-parser/transform.py @@ -43,6 +43,9 @@ simple_statement_types = { parse.Show, parse.Voice, parse.With, + parse.Stop, + parse.Pause, + parse.Hide, } def pass1(state, ast): @@ -111,7 +114,7 @@ def pass2_statement(state, pc, statement): if type(statement) is parse.Play: comment = statement.path.lexeme.decode('utf-8') audio_index = state.audio_lookup[statement.path.lexeme] - yield f"{{ .type = type::play, .play = {{ .audioIndex = {audio_index} }} }}, // {pc} {comment}" + yield f"{{ .type = type::play, .play = {{ .audioIndex = {audio_index}, /* FIXME channel */ }} }}, // {pc} {comment}" elif type(statement) is parse.Scene: key = lhs_key(statement.name) image_index = state.images_lookup[key] @@ -152,6 +155,16 @@ def pass2_statement(state, pc, statement): yield f"{{ .type = type::jump, .jump = {{ .statementIndex = {statement_index} }} }}, // {pc} {comment}" elif type(statement) is parse.Return: yield f"{{ .type = type::_return }}, // {pc}" + elif type(statement) is parse.Stop: + yield f"{{ .type = type::stop, .stop = {{ /* FIXME channel */ }} }}, // {pc}" + elif type(statement) is parse.Pause: + duration = statement.duration.lexeme + yield f"{{ .type = type::pause, .pause = {{ .duration = {duration} }} }}, // {pc}" + elif type(statement) is parse.Hide: + key = lhs_key(statement.what) + image_index = state.images_lookup[key] + comment = ".".join(k.decode('utf-8') for k in key) + yield f"{{ .type = type::hide, .hide = {{ .imageIndex = {image_index} }} }}, // {pc} {comment}" else: pass assert False, (type(statement), statement) @@ -161,12 +174,14 @@ def pass2_statements(state): for pc, statement in enumerate(state.statements): yield from pass2_statement(state, pc, statement) yield "};" + yield "constexpr int statements_length = (sizeof (statements)) / (sizeof (statements[0]));" def pass2_strings(state): yield "char const * const strings[] = {" for string, i in sorted(state.string_lookup.items(), key=lambda kv: kv[1]): yield f"\"{string.decode('utf-8')}\", // {i}" yield "};" + yield "constexpr int strings_length = (sizeof (strings)) / (sizeof (strings[0]));" def pass2_characters(state): yield "const character characters[] = {" @@ -174,24 +189,42 @@ def pass2_characters(state): character_name, = character.value.args yield f"{{ .characterName = \"{character_name.lexeme.decode('utf-8')}\" }}, // {i}" yield "};" + yield "constexpr int characters_length = (sizeof (characters)) / (sizeof (characters[0]));" def pass2_audio(state): yield "const audio audio[] = {" for audio, i in sorted(state.audio_lookup.items(), key=lambda kv: kv[1]): - yield f"{{ .path = \"{audio.decode('utf-8')}\" }}, // {i}" + orig_path = audio.decode('utf-8') + path = orig_path + if path.endswith(".mp3"): + path = path.removesuffix(".mp3") + elif path.endswith(".ogg"): + path = path.removesuffix(".ogg") + else: + assert False, path + yield f"{{ .path = \"{path}.opus\" }}, // {i} {orig_path}" yield "};" + yield "constexpr int audio_length = (sizeof (audio)) / (sizeof (audio[0]));" def pass2_images(state): - yield "const image image[] = {" + yield "const image images[] = {" for i, image in enumerate(state.images): - yield f"{{ .path = \"{image.path.lexeme.decode('utf-8')}\" }}, // {i}" + orig_path = image.path.lexeme.decode('utf-8') + path = orig_path + if path.endswith(".png"): + path = path.removesuffix(".png") + else: + assert False, path + yield f"{{ .path = \"{path}.dds\" }}, // {i} {orig_path}" yield "};" + yield "constexpr int images_length = (sizeof (images)) / (sizeof (images[0]));" def pass2_options(state): yield "const option options[] = {" for i, (lexeme, statement_index) in sorted(state.entries.items(), key=lambda kv: kv[0]): yield f"{{ .string = \"{lexeme.decode('utf-8')}\", .statementIndex = {statement_index} }}, // {i}" yield "};" + yield "constexpr int options_length = (sizeof (options)) / (sizeof (options[0]));" def pass2(state): yield "#include \"statement.h\"" @@ -224,9 +257,11 @@ def main(): global_identifiers = set(), ) try: - ast_list = list(parse.parse_all(tokens)) + ast_list = [] + for ast in parse.parse_all(tokens): + ast_list.append(ast) except parse.ParseException as e: - print(e, e.token) + print(e, e.token, file=sys.stderr) raise for t in ast_list: