1
0
mirror of synced 2024-11-27 22:40:49 +01:00

parsers.py: Simplify if/elses in split_tja_lines_into_course()

Some highlights:

- Strip comments ahead of time
- _Only_ parse #START in split_tja_lines_into_course()
- Leave all other parsing to parse_tja_course_data()

This makes the function behavior line up with what the docstring suggests, and removes some convoluted parsing in split_tja_lines_into_course().
This commit is contained in:
Viv 2023-07-25 23:06:47 -04:00
parent fc1b0d1bbc
commit 24b5828833

View File

@ -54,6 +54,10 @@ def split_tja_lines_into_courses(lines):
The data for each TJACourse can then be parsed individually using the The data for each TJACourse can then be parsed individually using the
`parse_tja_course_data()` function. `parse_tja_course_data()` function.
""" """
# Strip leading/trailing whitespace and comments ('// Comment')
lines = [line.split("//")[0].strip() for line in lines
if line.split("//")[0].strip()]
parsed_tja = None parsed_tja = None
current_course = '' current_course = ''
current_course_cached = '' current_course_cached = ''
@ -61,8 +65,11 @@ def split_tja_lines_into_courses(lines):
song_offset = 0 song_offset = 0
for line in lines: for line in lines:
# Case 1: Metadata lines # Only metadata and #START commands are relevant for this function
match_metadata = re.match(r"^([A-Z]+):(.*)", line) match_metadata = re.match(r"^([A-Z]+):(.*)", line)
match_start = re.match(r"^#START(?:\s+(.+))?", line)
# Case 1: Metadata lines
if match_metadata: if match_metadata:
name_upper = match_metadata.group(1).upper() name_upper = match_metadata.group(1).upper()
value = match_metadata.group(2).strip() value = match_metadata.group(2).strip()
@ -105,37 +112,27 @@ def split_tja_lines_into_courses(lines):
else: else:
pass # Ignore 'TITLE', 'SUBTITLE', 'WAVE', etc. pass # Ignore 'TITLE', 'SUBTITLE', 'WAVE', etc.
# Case 2: Commands and note data (to be further processed # Case 2: #START commands
# course-by-course later on) elif match_start:
elif not re.match(r"//.*", line): # Exclude comment-only lines ('//') value = match_start.group(1) if match_start.group(1) else ''
match_command = re.match(r"^#([A-Z]+)(?:\s+(.+))?", line) # For STYLE:Double, #START P1/P2 indicates the start of a new
match_notes = re.match(r"^(([0-9]|A|B|C|F|G)*,?).*$", line) # chart. But, we want multiplayer charts to inherit the
if match_command: # metadata from the course as a whole, so we deepcopy the
name_upper = match_command.group(1).upper() # existing course for that difficulty.
value = (match_command.group(2).strip() if value in ["P1", "P2"]:
if match_command.group(2) else '') current_course = current_course_cached + value
# For STYLE:Double, #START P1/P2 indicates the start of a new parsed_tja.courses[current_course] = \
# chart. But, we want multiplayer charts to inherit the deepcopy(parsed_tja.courses[current_course_cached])
# metadata from the course as a whole, so we deepcopy the parsed_tja.courses[current_course].data = list()
# existing course for that difficulty. elif value:
if name_upper == "START": raise ValueError(f"Invalid value '{value}' for #START.")
if value in ["P1", "P2"]:
current_course = current_course_cached + value # Since P1/P2 has been handled, we can just use a normal '#START'
parsed_tja.courses[current_course] = \ parsed_tja.courses[current_course].data.append("#START")
deepcopy(parsed_tja.courses[current_course_cached])
parsed_tja.courses[current_course].data = list() # Case 3: For other commands and data, simply copy as-is (parse later)
# Once we've made the new course, we can reset else:
# #START P1/P2 to a normal #START command parsed_tja.courses[current_course].data.append(line)
value = ''
elif value:
raise ValueError(f"Invalid value '{value}' for "
f"#START command.")
elif match_notes:
name_upper = 'NOTES'
value = match_notes.group(1)
parsed_tja.courses[current_course].data.append(
TJAData(name_upper, value)
)
# If a course has no song data, then this is likely because the course has # If a course has no song data, then this is likely because the course has
# "STYLE: Double" but no "STYLE: Single". To fix this, we copy over the P1 # "STYLE: Double" but no "STYLE: Single". To fix this, we copy over the P1
@ -181,7 +178,7 @@ def parse_tja_course_data(course):
This provides a faithful, easy-to-inspect tree-style representation of the This provides a faithful, easy-to-inspect tree-style representation of the
branches and measures within each course of the .tja file. branches and measures within each course of the .tja file.
""" """
has_branches = bool([d for d in course.data if d.name == 'BRANCHSTART']) has_branches = bool([d for d in course.data if d.startswith('#BRANCH')])
current_branch = 'all' if has_branches else 'normal' current_branch = 'all' if has_branches else 'normal'
branch_condition = None branch_condition = None
flag_levelhold = False flag_levelhold = False
@ -190,9 +187,16 @@ def parse_tja_course_data(course):
idx_m = 0 idx_m = 0
idx_m_branchstart = 0 idx_m_branchstart = 0
for idx_l, line in enumerate(course.data): for idx_l, line in enumerate(course.data):
# 0. Check to see whether line is a command or note data
command, value, notes = None, None, None
match_command = re.match(r"^#([A-Z]+)(?:\s+(.+))?", line)
if match_command:
command, value = match_command.groups()
else:
notes = line # If not a command, then line must be note data
# 1. Parse measure notes # 1. Parse measure notes
if line.name == 'NOTES': if notes:
notes = line.value
# If measure has ended, then add notes to the current measure, # If measure has ended, then add notes to the current measure,
# then start a new measure by incrementing idx_m # then start a new measure by incrementing idx_m
if notes.endswith(','): if notes.endswith(','):
@ -210,37 +214,37 @@ def parse_tja_course_data(course):
course.branches[branch][idx_m].notes += notes course.branches[branch][idx_m].notes += notes
# 2. Parse measure commands that produce an "event" # 2. Parse measure commands that produce an "event"
elif line.name in ['GOGOSTART', 'GOGOEND', 'BARLINEON', 'BARLINEOFF', elif command in ['GOGOSTART', 'GOGOEND', 'BARLINEON', 'BARLINEOFF',
'DELAY', 'SCROLL', 'BPMCHANGE', 'MEASURE', 'DELAY', 'SCROLL', 'BPMCHANGE', 'MEASURE',
'SECTION', 'BRANCHSTART']: 'SECTION', 'BRANCHSTART']:
# Get position of the event # Get position of the event
for branch in (course.branches.keys() if current_branch == 'all' for branch in (course.branches.keys() if current_branch == 'all'
else [current_branch]): else [current_branch]):
pos = len(course.branches[branch][idx_m].notes) pos = len(course.branches[branch][idx_m].notes)
# Parse event type # Parse event type
if line.name == 'GOGOSTART': if command == 'GOGOSTART':
current_event = TJAData('gogo', '1', pos) current_event = TJAData('gogo', '1', pos)
elif line.name == 'GOGOEND': elif command == 'GOGOEND':
current_event = TJAData('gogo', '0', pos) current_event = TJAData('gogo', '0', pos)
elif line.name == 'BARLINEON': elif command == 'BARLINEON':
current_event = TJAData('barline', '1', pos) current_event = TJAData('barline', '1', pos)
elif line.name == 'BARLINEOFF': elif command == 'BARLINEOFF':
current_event = TJAData('barline', '0', pos) current_event = TJAData('barline', '0', pos)
elif line.name == 'DELAY': elif command == 'DELAY':
current_event = TJAData('delay', float(line.value), pos) current_event = TJAData('delay', float(value), pos)
elif line.name == 'SCROLL': elif command == 'SCROLL':
current_event = TJAData('scroll', float(line.value), pos) current_event = TJAData('scroll', float(value), pos)
elif line.name == 'BPMCHANGE': elif command == 'BPMCHANGE':
current_event = TJAData('bpm', float(line.value), pos) current_event = TJAData('bpm', float(value), pos)
elif line.name == 'MEASURE': elif command == 'MEASURE':
current_event = TJAData('measure', line.value, pos) current_event = TJAData('measure', value, pos)
elif line.name == 'SECTION': elif command == 'SECTION':
# If #SECTION occurs before a #BRANCHSTART, then ensure that # If #SECTION occurs before a #BRANCHSTART, then ensure that
# it's present on every branch. Otherwise, #SECTION will only # it's present on every branch. Otherwise, #SECTION will only
# be present on the current branch, and so the `branch_info` # be present on the current branch, and so the `branch_info`
# values won't be correctly set for the other two branches. # values won't be correctly set for the other two branches.
if course.data[idx_l+1].name == 'BRANCHSTART': if course.data[idx_l+1].startswith('#BRANCHSTART'):
current_event = TJAData('section', None, pos) current_event = TJAData('section', None, pos)
current_branch = 'all' current_branch = 'all'
# Otherwise, #SECTION exists in isolation. In this case, to # Otherwise, #SECTION exists in isolation. In this case, to
@ -248,12 +252,12 @@ def parse_tja_course_data(course):
else: else:
current_event = TJAData('branch_start', branch_condition, current_event = TJAData('branch_start', branch_condition,
pos) pos)
elif line.name == 'BRANCHSTART': elif command == 'BRANCHSTART':
if flag_levelhold: if flag_levelhold:
continue continue
# Ensure that the #BRANCHSTART command is added to all branches # Ensure that the #BRANCHSTART command is added to all branches
current_branch = 'all' current_branch = 'all'
branch_condition = line.value.split(',') branch_condition = value.split(',')
if branch_condition[0] == 'r': # r = drumRoll if branch_condition[0] == 'r': # r = drumRoll
branch_condition[1] = int(branch_condition[1]) # drumrolls branch_condition[1] = int(branch_condition[1]) # drumrolls
branch_condition[2] = int(branch_condition[2]) # drumrolls branch_condition[2] = int(branch_condition[2]) # drumrolls
@ -272,25 +276,25 @@ def parse_tja_course_data(course):
# 3. Parse commands that don't create an event # 3. Parse commands that don't create an event
# (e.g. simply changing the current branch) # (e.g. simply changing the current branch)
else: else:
if line.name == 'START' or line.name == 'END': if command == 'START' or command == 'END':
current_branch = 'all' if has_branches else 'normal' current_branch = 'all' if has_branches else 'normal'
flag_levelhold = False flag_levelhold = False
elif line.name == 'LEVELHOLD': elif command == 'LEVELHOLD':
flag_levelhold = True flag_levelhold = True
elif line.name == 'N': elif command == 'N':
current_branch = 'normal' current_branch = 'normal'
idx_m = idx_m_branchstart idx_m = idx_m_branchstart
elif line.name == 'E': elif command == 'E':
current_branch = 'professional' current_branch = 'professional'
idx_m = idx_m_branchstart idx_m = idx_m_branchstart
elif line.name == 'M': elif command == 'M':
current_branch = 'master' current_branch = 'master'
idx_m = idx_m_branchstart idx_m = idx_m_branchstart
elif line.name == 'BRANCHEND': elif command == 'BRANCHEND':
current_branch = 'all' current_branch = 'all'
else: else:
print(f"Ignoring unsupported command '{line.name}'") print(f"Ignoring unsupported command '{command}'")
# Delete the last measure in the branch if no notes or events # Delete the last measure in the branch if no notes or events
# were added to it (due to preallocating empty measures) # were added to it (due to preallocating empty measures)