1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
|
'''
Implementation of classes involved in the PLY-based translation of the mcdisplay "--trace output"
mini language.
Read the PLY documentation here: http://www.dabeaz.com/ply/ply.html#ply_nn23.
'''
from ply import lex, yacc
from .nodetree import Node
from .instrgeom import RayBundle, ParticleStory, ParticleCompGroup, ParticleState
class ParticleTraceParser:
'''
Python lex/yacc parser for the particle ray section of mcdisplay --trace output
'''
parsetree = None
def __init__(self, data=None, debug=False):
self.debug = debug
self.build_lexer()
self.build_parser()
if data:
self.parse(data)
# these tokens are handled in t_ID
reserved = {
'ENTER' : 'ENTER',
'COMP' : 'COMP',
'STATE' : 'STATE',
'SCATTER' : 'SCATTER',
'ABSORB' : 'ABSORB',
'LEAVE' : 'LEAVE',
}
# tokens
tokens = [
'COLON',
'QUOTE',
'COMMA',
'NL',
'DEC',
'ID',
] + list(set(reserved.values()))
t_COLON = r':'
t_QUOTE = r'"'
t_COMMA = r','
def t_ANY_NL(self, t):
r'\n'
self.lexer.lineno += 1
return t
def t_ABSPATH(self, t):
r'/[/\w\.]+'
return t
def t_DEC(self, t):
r'-?[\d.]+(e[+-][0-9]+)?'
return t
def t_ID(self, t):
r'[a-zA-Z_]\w*'
t.type = self.reserved.get(t.value, 'ID')
return t
# ignore whitespaces and tabs means that we do not tokenize them, but they are still applied in the regex checks defined above for our tokens
t_ignore = r' \t'
def t_error(self, t):
print('error: %s' % t.value)
##################################
# parsing rules and action code
##################################
def p_document(self, p):
'document : ray_statements'
print('particle rays parsed')
# assemble parse tree
self.parsetree = Node(type='raystatements', children=[self.rays])
rays = Node(type='rays')
def p_ray_statements(self, p):
'''ray_statements : ray_statement ray_statements
| ray_statement '''
# this function only receives a value in p[1] - the p[0] og p_ray_statement - when a ray is "finished" at that lower level
if p[1] != None:
self.rays.children.append(p[1])
p[0] = self.rays
ray = None
def p_ray_statement(self, p):
'''ray_statement : ray_enterstate
| ray_compstate
| ray_compstatestate
| ray_scatterstate
| ray_scatter
| ray_absorb
| ray_leavestate'''
if p[1].type=='ENTER' and self.ray==None:
self.ray = Node(type='ray', children=[p[1]])
elif p[1].type=='LEAVE':
self.ray.children.append(p[1])
p[0] = self.ray
self.ray = None
elif self.ray==None:
raise Exception('p_ray_statement: ray must begin start with a ray_enterstate')
else:
self.ray.children.append(p[1])
def p_ray_compstate(self, p):
'ray_compstate : COMP COLON QUOTE comp_name QUOTE NL STATE COLON 11dec NL'
p[0] = Node(type='COMPENTER', children=[p[4]], leaf=p[9].leaf)
def p_comp_name(self, p):
'comp_name : ID'
p[0] = Node(type="comp_name", leaf=p[1])
def p_ray_compstatestate(self, p):
'ray_compstatestate : COMP COLON QUOTE comp_name QUOTE NL STATE COLON 11dec NL STATE COLON 11dec NL'
p[0] = Node(type='COMPENTEREXIT', children=[p[4]], leaf=p[13].leaf)
def p_ray_scatterstate(self, p):
'ray_scatterstate : SCATTER COLON 11dec NL STATE COLON 11dec NL'
p[0] = Node(type='SCATTER', leaf=p[3].leaf)
def p_ray_scatter(self, p):
'ray_scatter : SCATTER COLON 11dec NL'
p[0] = Node(type='SCATTER', leaf=p[3].leaf)
def p_ray_absorb(self, p):
'ray_absorb : ABSORB COLON NL'
p[0] = Node(type='ABSORB')
def p_ray_enterstate(self, p):
'ray_enterstate : ENTER COLON NL STATE COLON 11dec NL'
p[0] = Node(type='ENTER', leaf=p[6].leaf)
def p_ray_leavestate(self, p):
'ray_leavestate : LEAVE COLON NL STATE COLON 11dec NL'
p[0] = Node(type='LEAVE', leaf=p[6].leaf)
def p_11dec(self, p):
'11dec : DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC COMMA DEC'
p[0] = Node(type='11dec', leaf=[p[1], p[3] ,p[5] ,p[7] ,p[9] ,p[11] ,p[13] ,p[15] ,p[17] ,p[19] ,p[21]])
# error rule for syntax errors
def p_error(self, p):
print("Syntax error in input!")
print(p)
##################################
# build and test
##################################
def build_lexer(self, **kwargs):
''' builds the lexer '''
self.lexer = lex.lex(module=self, **kwargs)
def test_lexer(self, data):
''' test built lexer on data '''
self.lexer.input(data)
for token in self.lexer:
print(token)
def build_parser(self, **kwargs):
''' builds the lexer '''
self.parser = yacc.yacc(module=self, debug=self.debug, write_tables=False)
def parse(self, data):
''' attempts to parse data '''
self.parser.parse(data, lexer=self.lexer)
class ParticleBundleRayFactory:
'''
Ray reconstruction from the syntax tree produced by ParticleTraceParser,
outputting a wholly interpreted instrument and ray model from the mcdisplay trace output.
'''
rays = None
def __init__(self, parsetreeroot):
self.root = parsetreeroot
if (type(self.root) is not Node) or (self.root.type != 'raystatements'):
raise Exception('TraceObjectConstructor: parsetreeroot must be a Node of type "raystatements"')
def build_rays(self):
''' builds the particle ray representation '''
rays = []
bundle = RayBundle(rays)
# iterate through parse tree
for dc in self.root.children:
# handle rays
if dc.type == 'rays':
for ray in dc.children:
# prepare state variables
story = None
comp_group = None
comp_name = None
# NOTE: the node tree is a bit weird in that there are no COMPEXIT's, these are just SCATTER
for event in ray.children:
if event.type == 'ENTER':
story = ParticleStory()
rays.append(story)
if event.type == 'COMPENTER' or event.type == 'COMPENTEREXIT':
# get component name
comp_name = event.children[0].leaf
# record entry
if event.type == 'COMPENTER' or self.iszerovector_str(event.leaf[0:3]):
comp_group = ParticleCompGroup(comp_name)
else:
comp_group = ParticleCompGroup(comp_name)
comp_group.add_event(ParticleState(event.leaf))
story.add_group(comp_group)
if event.type == 'SCATTER':
comp_group.add_event(ParticleState(event.leaf))
if event.type == 'ABSORB':
# TODO: when --trace has been updated with ABSORB coordinates
#comp_group.append(ParticleState(event.leaf))
pass
if event.type == 'LEAVE':
# append LEAVE state, although equal to SCATTER states, may be the result of COMPENTER, ABSORB
# TODO: read ABSORB todo and accomodate, removing this then completely redundant LEAVE state
comp_group.add_event(ParticleState(event.leaf))
comp_group = None
story = None
return bundle
def iszerovector_str(self, v):
''' returns true if v is the zero vector, otherwise false '''
if float(v[0]) == 0 and float(v[1]) == 0 and float(v[2]) == 0:
return True
else:
return False
|