11
22import math
3+ import os
34import re
45import sys
56from collections import OrderedDict , Counter
1011from fractions import Fraction
1112
1213
14+ def get_datafile_lines (datafile ):
15+ """Resolve includes and yield (lineno, line) where lineno is a descriptive string of
16+ 'line number', eg. for an include it might look like "1:foo:5" for line 5 of file foo
17+ included from line 1."""
18+ with open (datafile ) as f :
19+ for n , line in enumerate (f ):
20+ if line .startswith ('include ' ):
21+ path = line [len ('include ' ):- 1 ] # -1 for trailing newline
22+ path = os .path .join (os .path .dirname (datafile ), path )
23+ for lineno , line in get_datafile_lines (path ):
24+ yield '{}:{}:{}' .format (n , path , lineno ), line
25+ else :
26+ yield n + 1 , line
27+
28+
1329def get_recipes (datafile , module_priorities , verbose = False , beacon_speed = 0 ):
14- """Data file consists of one entry per line. Each entry is either a recipe, building or module.
30+ """Data file consists of one entry per line. Each entry is either an include, a recipe, building or module.
31+ Include lines look like:
32+ include PATH
33+ and result in the other path (relative to the directory this file is in) being read as though it were
34+ part of this file.
1535 Building lines look like:
1636 BUILDING builds at SPEED[ with N modules]
1737 For example:
@@ -33,63 +53,61 @@ def get_recipes(datafile, module_priorities, verbose=False, beacon_speed=0):
3353
3454 This function returns a dict {item: (building, throughput per building, {input: input amount for 1 output}, list of modules used in building)}
3555 """
36- with open (datafile ) as f :
37- buildings = {}
38- items = {}
39- modules = {}
40- for n , line in enumerate (f ):
41- n += 1 # 1-based, not 0-based line numbers
42- line = line .strip ()
43- if not line or line .startswith ('#' ):
56+ buildings = {}
57+ items = {}
58+ modules = {}
59+ for lineno , line in get_datafile_lines (datafile ):
60+ line = line .strip ()
61+ if not line or line .startswith ('#' ):
62+ continue
63+
64+ try :
65+ match = re .match ('^([^,]+) builds at ([0-9.]+(?:/[0-9.]+)?)(?: with (\d+) modules)?$' , line )
66+ if match :
67+ name , speed , mods = match .groups ()
68+ mods = int (mods ) if mods else 0
69+ name = name .lower ()
70+ if name in buildings :
71+ raise ValueError ('Building {!r} already declared' .format (name ))
72+ buildings [name ] = Fraction (speed ), mods
73+ continue
74+
75+ match = re .match ('^(\d+ )?(.+) takes ([0-9.]+) in ([^,]+)((?:, \d+ [^,]+)*)(, can take productivity)?$' , line )
76+ if match :
77+ amount , name , time , building , inputs_str , prod = match .groups ()
78+ amount = int (amount ) if amount else 1
79+ time = Fraction (time )
80+ name = name .lower ()
81+ building = building .lower ()
82+ inputs = {}
83+ if inputs_str :
84+ for part in inputs_str .split (',' ):
85+ part = part .strip ()
86+ if not part :
87+ continue
88+ input_amount , input_name = part .split (' ' , 1 )
89+ input_amount = int (input_amount )
90+ inputs [input_name ] = input_amount
91+ if name in items :
92+ raise ValueError ('Recipe for {!r} already declared' .format (name ))
93+ items [name ] = amount , time , building , inputs , prod
94+ continue
95+
96+ match = re .match ('^([^,]+) module affects speed ([^,]+)(?:, prod ([^,]+))?$' , line )
97+ if match :
98+ name , speed , prod = match .groups ()
99+ speed = Fraction (speed )
100+ prod = Fraction (prod ) if prod else 0
101+ name = name .lower ()
102+ if name in modules :
103+ raise ValueError ('Module {!r} already declared' .format (name ))
104+ modules [name ] = speed , prod
44105 continue
45106
46- try :
47- match = re .match ('^([^,]+) builds at ([0-9.]+)(?: with (\d+) modules)?$' , line )
48- if match :
49- name , speed , mods = match .groups ()
50- mods = int (mods ) if mods else 0
51- name = name .lower ()
52- if name in buildings :
53- raise ValueError ('Building {!r} already declared' .format (name ))
54- buildings [name ] = Fraction (speed ), mods
55- continue
56-
57- match = re .match ('^(\d+ )?(.+) takes ([0-9.]+) in ([^,]+)((?:, \d+ [^,]+)*)(, can take productivity)?$' , line )
58- if match :
59- amount , name , time , building , inputs_str , prod = match .groups ()
60- amount = int (amount ) if amount else 1
61- time = Fraction (time )
62- name = name .lower ()
63- building = building .lower ()
64- inputs = {}
65- if inputs_str :
66- for part in inputs_str .split (',' ):
67- part = part .strip ()
68- if not part :
69- continue
70- input_amount , input_name = part .split (' ' , 1 )
71- input_amount = int (input_amount )
72- inputs [input_name ] = input_amount
73- if name in items :
74- raise ValueError ('Recipe for {!r} already declared' .format (name ))
75- items [name ] = amount , time , building , inputs , prod
76- continue
77-
78- match = re .match ('^([^,]+) module affects speed ([^,]+)(?:, prod ([^,]+))?$' , line )
79- if match :
80- name , speed , prod = match .groups ()
81- speed = Fraction (speed )
82- prod = Fraction (prod ) if prod else 0
83- name = name .lower ()
84- if name in modules :
85- raise ValueError ('Module {!r} already declared' .format (name ))
86- modules [name ] = speed , prod
87- continue
88-
89- raise ValueError ("Unknown entry syntax" )
90- except Exception as e :
91- _ , _ , tb = sys .exc_info ()
92- raise ValueError , ValueError ("Error in line {} of {!r}: {}" .format (n , datafile , e )), tb
107+ raise ValueError ("Unknown entry syntax" )
108+ except Exception as e :
109+ _ , _ , tb = sys .exc_info ()
110+ raise ValueError , ValueError ("Error in line {} of {!r}: {}" .format (lineno , datafile , e )), tb
93111
94112 # validate module list
95113 for mod in module_priorities :
0 commit comments