def yacc(tabmodule, package): """Create a parser from local variables. It automatically compiles the parser in optimized mode, writing to ``tabmodule`` in the same directory as the calling file. This function is thread-safe, and the returned parser is also thread-safe, provided that it does not share a lexer with any other parser. It is only intended to work with parsers defined within the calling function, rather than at class or module scope. Parameters ---------- tabmodule : str Name for the file to write with the generated tables, if it does not already exist (without ``.py`` suffix). package : str Name of a test package which should be run with pytest to regenerate the output file. This is inserted into a comment in the generated file. """ from astropy.extern.ply import yacc caller_file = yacc.get_caller_module_dict(2)['__file__'] tab_filename = os.path.join(os.path.dirname(caller_file), tabmodule + '.py') with _LOCK: tab_exists = os.path.exists(tab_filename) with _patch_get_caller_module_dict(yacc): parser = yacc.yacc(tabmodule=tabmodule, outputdir=os.path.dirname(caller_file), debug=False, optimize=True, write_tables=True) if not tab_exists: _add_tab_header(tab_filename, package) return ThreadSafeParser(parser)
def _make_parser(cls): """ The grammar here is based on the description in the `Specification of Physical Units within OGIP FITS files <https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_93_001/>`__, which is not terribly precise. The exact grammar is here is based on the YACC grammar in the `unity library <https://bitbucket.org/nxg/unity/>`_. """ from astropy.extern.ply import yacc tokens = cls._tokens def p_main(p): ''' main : UNKNOWN | complete_expression | scale_factor complete_expression | scale_factor WHITESPACE complete_expression ''' if len(p) == 4: p[0] = p[1] * p[3] elif len(p) == 3: p[0] = p[1] * p[2] else: p[0] = p[1] def p_complete_expression(p): ''' complete_expression : product_of_units ''' p[0] = p[1] def p_product_of_units(p): ''' product_of_units : unit_expression | division unit_expression | product_of_units product unit_expression | product_of_units division unit_expression ''' if len(p) == 4: if p[2] == 'DIVISION': p[0] = p[1] / p[3] else: p[0] = p[1] * p[3] elif len(p) == 3: p[0] = p[2]**-1 else: p[0] = p[1] def p_unit_expression(p): ''' unit_expression : unit | UNIT OPEN_PAREN complete_expression CLOSE_PAREN | OPEN_PAREN complete_expression CLOSE_PAREN | UNIT OPEN_PAREN complete_expression CLOSE_PAREN power numeric_power | OPEN_PAREN complete_expression CLOSE_PAREN power numeric_power ''' # If we run p[1] in cls._functions, it will try and parse each # item in the list into a unit, which is slow. Since we know that # all the items in the list are strings, we can simply convert # p[1] to a string instead. p1_str = str(p[1]) if p1_str in cls._functions and p1_str != 'sqrt': raise ValueError( "The function '{}' is valid in OGIP, but not understood " "by astropy.units.".format(p[1])) if len(p) == 7: if p1_str == 'sqrt': p[0] = p[1] * p[3]**(0.5 * p[6]) else: p[0] = p[1] * p[3]**p[6] elif len(p) == 6: p[0] = p[2]**p[5] elif len(p) == 5: if p1_str == 'sqrt': p[0] = p[3]**0.5 else: p[0] = p[1] * p[3] elif len(p) == 4: p[0] = p[2] else: p[0] = p[1] def p_scale_factor(p): ''' scale_factor : LIT10 power numeric_power | LIT10 | signed_float | signed_float power numeric_power | signed_int power numeric_power ''' if len(p) == 4: p[0] = 10**p[3] else: p[0] = p[1] # Can't use np.log10 here, because p[0] may be a Python long. if math.log10(p[0]) % 1.0 != 0.0: from astropy.units.core import UnitsWarning warnings.warn( "'{}' scale should be a power of 10 in " "OGIP format".format(p[0]), UnitsWarning) def p_division(p): ''' division : DIVISION | WHITESPACE DIVISION | WHITESPACE DIVISION WHITESPACE | DIVISION WHITESPACE ''' p[0] = 'DIVISION' def p_product(p): ''' product : WHITESPACE | STAR | WHITESPACE STAR | WHITESPACE STAR WHITESPACE | STAR WHITESPACE ''' p[0] = 'PRODUCT' def p_power(p): ''' power : STARSTAR ''' p[0] = 'POWER' def p_unit(p): ''' unit : UNIT | UNIT power numeric_power ''' if len(p) == 4: p[0] = p[1]**p[3] else: p[0] = p[1] def p_numeric_power(p): ''' numeric_power : UINT | signed_float | OPEN_PAREN signed_int CLOSE_PAREN | OPEN_PAREN signed_float CLOSE_PAREN | OPEN_PAREN signed_float division UINT CLOSE_PAREN ''' if len(p) == 6: p[0] = Fraction(int(p[2]), int(p[4])) elif len(p) == 4: p[0] = p[2] else: p[0] = p[1] def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_signed_int(p): ''' signed_int : SIGN UINT ''' p[0] = p[1] * p[2] def p_signed_float(p): ''' signed_float : sign UINT | sign UFLOAT ''' p[0] = p[1] * p[2] def p_error(p): raise ValueError() parser_exists = os.path.exists( os.path.join(os.path.dirname(__file__), 'ogip_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='ogip_parsetab', outputdir=os.path.dirname(__file__), write_tables=True) if not parser_exists: cls._add_tab_header('ogip_parsetab') return parser
def _make_parser(cls): """ The grammar here is based on the description in the `FITS standard <http://fits.gsfc.nasa.gov/standard30/fits_standard30aa.pdf>`_, Section 4.3, which is not terribly precise. The exact grammar is here is based on the YACC grammar in the `unity library <https://bitbucket.org/nxg/unity/>`_. This same grammar is used by the `"fits"` and `"vounit"` formats, the only difference being the set of available unit strings. """ from astropy.extern.ply import yacc tokens = cls._tokens def p_main(p): ''' main : product_of_units | factor product_of_units | factor product product_of_units | division_product_of_units | factor division_product_of_units | factor product division_product_of_units | inverse_unit | factor inverse_unit | factor product inverse_unit | factor ''' from astropy.units.core import Unit if len(p) == 2: p[0] = Unit(p[1]) elif len(p) == 3: p[0] = Unit(p[1] * p[2]) elif len(p) == 4: p[0] = Unit(p[1] * p[3]) def p_division_product_of_units(p): ''' division_product_of_units : division_product_of_units division product_of_units | product_of_units ''' from astropy.units.core import Unit if len(p) == 4: p[0] = Unit(p[1] / p[3]) else: p[0] = p[1] def p_inverse_unit(p): ''' inverse_unit : division unit_expression ''' p[0] = p[2]**-1 def p_factor(p): ''' factor : factor_fits | factor_float | factor_int ''' p[0] = p[1] def p_factor_float(p): ''' factor_float : signed_float | signed_float UINT signed_int | signed_float UINT power numeric_power ''' if cls.name == 'fits': raise ValueError("Numeric factor not supported by FITS") if len(p) == 4: p[0] = p[1] * p[2]**float(p[3]) elif len(p) == 5: p[0] = p[1] * p[2]**float(p[4]) elif len(p) == 2: p[0] = p[1] def p_factor_int(p): ''' factor_int : UINT | UINT signed_int | UINT power numeric_power | UINT UINT signed_int | UINT UINT power numeric_power ''' if cls.name == 'fits': raise ValueError("Numeric factor not supported by FITS") if len(p) == 2: p[0] = p[1] elif len(p) == 3: p[0] = p[1]**float(p[2]) elif len(p) == 4: if isinstance(p[2], int): p[0] = p[1] * p[2]**float(p[3]) else: p[0] = p[1]**float(p[3]) elif len(p) == 5: p[0] = p[1] * p[2]**p[4] def p_factor_fits(p): ''' factor_fits : UINT power OPEN_PAREN signed_int CLOSE_PAREN | UINT power OPEN_PAREN UINT CLOSE_PAREN | UINT power signed_int | UINT power UINT | UINT SIGN UINT | UINT OPEN_PAREN signed_int CLOSE_PAREN ''' if p[1] != 10: if cls.name == 'fits': raise ValueError("Base must be 10") else: return if len(p) == 4: if p[2] in ('**', '^'): p[0] = 10**p[3] else: p[0] = 10**(p[2] * p[3]) elif len(p) == 5: p[0] = 10**p[3] elif len(p) == 6: p[0] = 10**p[4] def p_product_of_units(p): ''' product_of_units : unit_expression product product_of_units | unit_expression product_of_units | unit_expression ''' if len(p) == 2: p[0] = p[1] elif len(p) == 3: p[0] = p[1] * p[2] else: p[0] = p[1] * p[3] def p_unit_expression(p): ''' unit_expression : function | unit_with_power | OPEN_PAREN product_of_units CLOSE_PAREN ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[2] def p_unit_with_power(p): ''' unit_with_power : UNIT power numeric_power | UNIT numeric_power | UNIT ''' if len(p) == 2: p[0] = p[1] elif len(p) == 3: p[0] = p[1]**p[2] else: p[0] = p[1]**p[3] def p_numeric_power(p): ''' numeric_power : sign UINT | OPEN_PAREN paren_expr CLOSE_PAREN ''' if len(p) == 3: p[0] = p[1] * p[2] elif len(p) == 4: p[0] = p[2] def p_paren_expr(p): ''' paren_expr : sign UINT | signed_float | frac ''' if len(p) == 3: p[0] = p[1] * p[2] else: p[0] = p[1] def p_frac(p): ''' frac : sign UINT division sign UINT ''' p[0] = Fraction(p[1] * p[2], p[4] * p[5]) def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1 def p_product(p): ''' product : STAR | PERIOD ''' pass def p_division(p): ''' division : SOLIDUS ''' pass def p_power(p): ''' power : DOUBLE_STAR | CARET ''' p[0] = p[1] def p_signed_int(p): ''' signed_int : SIGN UINT ''' p[0] = p[1] * p[2] def p_signed_float(p): ''' signed_float : sign UINT | sign UFLOAT ''' p[0] = p[1] * p[2] def p_function_name(p): ''' function_name : FUNCNAME ''' p[0] = p[1] def p_function(p): ''' function : function_name OPEN_PAREN main CLOSE_PAREN ''' if p[1] == 'sqrt': p[0] = p[3]**0.5 return elif p[1] in ('mag', 'dB', 'dex'): function_unit = cls._parse_unit(p[1]) # In Generic, this is callable, but that does not have to # be the case in subclasses (e.g., in VOUnit it is not). if callable(function_unit): p[0] = function_unit(p[3]) return raise ValueError("'{}' is not a recognized function".format(p[1])) def p_error(p): raise ValueError() parser_exists = os.path.exists( os.path.join(os.path.dirname(__file__), 'generic_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='generic_parsetab', outputdir=os.path.dirname(__file__)) if not parser_exists: cls._add_tab_header('generic_parsetab') return parser
def _make_parser(cls): """ The grammar here is based on the description in the `Standards for Astronomical Catalogues 2.0 <http://cds.u-strasbg.fr/doc/catstd-3.2.htx>`_, which is not terribly precise. The exact grammar is here is based on the YACC grammar in the `unity library <https://bitbucket.org/nxg/unity/>`_. """ from astropy.extern.ply import yacc tokens = cls._tokens def p_main(p): ''' main : factor combined_units | combined_units | factor ''' from astropy.units.core import Unit if len(p) == 3: p[0] = Unit(p[1] * p[2]) else: p[0] = Unit(p[1]) def p_combined_units(p): ''' combined_units : product_of_units | division_of_units ''' p[0] = p[1] def p_product_of_units(p): ''' product_of_units : unit_expression PRODUCT combined_units | unit_expression ''' if len(p) == 4: p[0] = p[1] * p[3] else: p[0] = p[1] def p_division_of_units(p): ''' division_of_units : DIVISION unit_expression | unit_expression DIVISION combined_units ''' if len(p) == 3: p[0] = p[2]**-1 else: p[0] = p[1] / p[3] def p_unit_expression(p): ''' unit_expression : unit_with_power | OPEN_PAREN combined_units CLOSE_PAREN ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[2] def p_factor(p): ''' factor : signed_float X UINT signed_int | UINT X UINT signed_int | UINT signed_int | UINT | signed_float ''' if len(p) == 5: if p[3] != 10: raise ValueError( "Only base ten exponents are allowed in CDS") p[0] = p[1] * 10.0**p[4] elif len(p) == 3: if p[1] != 10: raise ValueError( "Only base ten exponents are allowed in CDS") p[0] = 10.0**p[2] elif len(p) == 2: p[0] = p[1] def p_unit_with_power(p): ''' unit_with_power : UNIT numeric_power | UNIT ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[1]**p[2] def p_numeric_power(p): ''' numeric_power : sign UINT ''' p[0] = p[1] * p[2] def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_signed_int(p): ''' signed_int : SIGN UINT ''' p[0] = p[1] * p[2] def p_signed_float(p): ''' signed_float : sign UINT | sign UFLOAT ''' p[0] = p[1] * p[2] def p_error(p): raise ValueError() parser_exists = os.path.exists( os.path.join(os.path.dirname(__file__), 'cds_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='cds_parsetab', outputdir=os.path.dirname(__file__), write_tables=True) if not parser_exists: cls._add_tab_header('cds_parsetab') return parser
def _make_parser(cls): from astropy.extern.ply import lex, yacc # List of token names. tokens = ('SIGN', 'UINT', 'UFLOAT', 'COLON', 'DEGREE', 'HOUR', 'MINUTE', 'SECOND', 'SIMPLE_UNIT') # NOTE THE ORDERING OF THESE RULES IS IMPORTANT!! # Regular expression rules for simple tokens def t_UFLOAT(t): r'((\d+\.\d*)|(\.\d+))([eE][+-−]?\d+)?' # The above includes Unicode "MINUS SIGN" \u2212. It is # important to include the hyphen last, or the regex will # treat this as a range. t.value = float(t.value.replace('−', '-')) return t def t_UINT(t): r'\d+' t.value = int(t.value) return t def t_SIGN(t): r'[+−-]' # The above include Unicode "MINUS SIGN" \u2212. It is # important to include the hyphen last, or the regex will # treat this as a range. if t.value == '+': t.value = 1.0 else: t.value = -1.0 return t def t_SIMPLE_UNIT(t): t.value = u.Unit(t.value) return t t_SIMPLE_UNIT.__doc__ = '|'.join(f'(?:{x})' for x in cls._get_simple_unit_names()) t_COLON = ':' t_DEGREE = r'd(eg(ree(s)?)?)?|°' t_HOUR = r'hour(s)?|h(r)?|ʰ' t_MINUTE = r'm(in(ute(s)?)?)?|′|\'|ᵐ' t_SECOND = r's(ec(ond(s)?)?)?|″|\"|ˢ' # A string containing ignored characters (spaces) t_ignore = ' ' # Error handling rule def t_error(t): raise ValueError(f"Invalid character at col {t.lexpos}") lexer_exists = os.path.exists( os.path.join(os.path.dirname(__file__), 'angle_lextab.py')) # Build the lexer lexer = lex.lex(optimize=True, lextab='angle_lextab', outputdir=os.path.dirname(__file__)) if not lexer_exists: cls._add_tab_header('angle_lextab') def p_angle(p): ''' angle : hms | dms | arcsecond | arcminute | simple ''' p[0] = p[1] def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_ufloat(p): ''' ufloat : UFLOAT | UINT ''' p[0] = float(p[1]) def p_colon(p): ''' colon : sign UINT COLON ufloat | sign UINT COLON UINT COLON ufloat ''' if len(p) == 5: p[0] = (p[1] * p[2], p[4]) elif len(p) == 7: p[0] = (p[1] * p[2], p[4], p[6]) def p_spaced(p): ''' spaced : sign UINT ufloat | sign UINT UINT ufloat ''' if len(p) == 4: p[0] = (p[1] * p[2], p[3]) elif len(p) == 5: p[0] = (p[1] * p[2], p[3], p[4]) def p_generic(p): ''' generic : colon | spaced | sign UFLOAT | sign UINT ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[1] * p[2] def p_hms(p): ''' hms : sign UINT HOUR | sign UINT HOUR ufloat | sign UINT HOUR UINT MINUTE | sign UINT HOUR UFLOAT MINUTE | sign UINT HOUR UINT MINUTE ufloat | sign UINT HOUR UINT MINUTE ufloat SECOND | generic HOUR ''' if len(p) == 3: p[0] = (p[1], u.hourangle) elif len(p) == 4: p[0] = (p[1] * p[2], u.hourangle) elif len(p) in (5, 6): p[0] = ((p[1] * p[2], p[4]), u.hourangle) elif len(p) in (7, 8): p[0] = ((p[1] * p[2], p[4], p[6]), u.hourangle) def p_dms(p): ''' dms : sign UINT DEGREE | sign UINT DEGREE ufloat | sign UINT DEGREE UINT MINUTE | sign UINT DEGREE UFLOAT MINUTE | sign UINT DEGREE UINT MINUTE ufloat | sign UINT DEGREE UINT MINUTE ufloat SECOND | generic DEGREE ''' if len(p) == 3: p[0] = (p[1], u.degree) elif len(p) == 4: p[0] = (p[1] * p[2], u.degree) elif len(p) in (5, 6): p[0] = ((p[1] * p[2], p[4]), u.degree) elif len(p) in (7, 8): p[0] = ((p[1] * p[2], p[4], p[6]), u.degree) def p_simple(p): ''' simple : generic | generic SIMPLE_UNIT ''' if len(p) == 2: p[0] = (p[1], None) else: p[0] = (p[1], p[2]) def p_arcsecond(p): ''' arcsecond : generic SECOND ''' p[0] = (p[1], u.arcsecond) def p_arcminute(p): ''' arcminute : generic MINUTE ''' p[0] = (p[1], u.arcminute) def p_error(p): raise ValueError parser_exists = os.path.exists( os.path.join(os.path.dirname(__file__), 'angle_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='angle_parsetab', outputdir=os.path.dirname(__file__), write_tables=True) if not parser_exists: cls._add_tab_header('angle_parsetab') return parser, lexer
def _make_parser(cls): """ The grammar here is based on the description in the `Standards for Astronomical Catalogues 2.0 <http://cds.u-strasbg.fr/doc/catstd-3.2.htx>`_, which is not terribly precise. The exact grammar is here is based on the YACC grammar in the `unity library <https://bitbucket.org/nxg/unity/>`_. """ from astropy.extern.ply import yacc tokens = cls._tokens def p_main(p): ''' main : factor combined_units | combined_units | factor ''' from astropy.units.core import Unit if len(p) == 3: p[0] = Unit(p[1] * p[2]) else: p[0] = Unit(p[1]) def p_combined_units(p): ''' combined_units : product_of_units | division_of_units ''' p[0] = p[1] def p_product_of_units(p): ''' product_of_units : unit_expression PRODUCT combined_units | unit_expression ''' if len(p) == 4: p[0] = p[1] * p[3] else: p[0] = p[1] def p_division_of_units(p): ''' division_of_units : DIVISION unit_expression | unit_expression DIVISION combined_units ''' if len(p) == 3: p[0] = p[2] ** -1 else: p[0] = p[1] / p[3] def p_unit_expression(p): ''' unit_expression : unit_with_power | OPEN_PAREN combined_units CLOSE_PAREN ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[2] def p_factor(p): ''' factor : signed_float X UINT signed_int | UINT X UINT signed_int | UINT signed_int | UINT | signed_float ''' if len(p) == 5: if p[3] != 10: raise ValueError( "Only base ten exponents are allowed in CDS") p[0] = p[1] * 10.0 ** p[4] elif len(p) == 3: if p[1] != 10: raise ValueError( "Only base ten exponents are allowed in CDS") p[0] = 10.0 ** p[2] elif len(p) == 2: p[0] = p[1] def p_unit_with_power(p): ''' unit_with_power : UNIT numeric_power | UNIT ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[1] ** p[2] def p_numeric_power(p): ''' numeric_power : sign UINT ''' p[0] = p[1] * p[2] def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_signed_int(p): ''' signed_int : SIGN UINT ''' p[0] = p[1] * p[2] def p_signed_float(p): ''' signed_float : sign UINT | sign UFLOAT ''' p[0] = p[1] * p[2] def p_error(p): raise ValueError() parser_exists = os.path.exists(os.path.join(os.path.dirname(__file__), 'cds_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='cds_parsetab', outputdir=os.path.dirname(__file__), write_tables=True) if not parser_exists: cls._add_tab_header('cds_parsetab') return parser
def _make_parser(cls): """ The grammar here is based on the description in the `FITS standard <http://fits.gsfc.nasa.gov/standard30/fits_standard30aa.pdf>`_, Section 4.3, which is not terribly precise. The exact grammar is here is based on the YACC grammar in the `unity library <https://bitbucket.org/nxg/unity/>`_. This same grammar is used by the `"fits"` and `"vounit"` formats, the only difference being the set of available unit strings. """ from astropy.extern.ply import yacc tokens = cls._tokens def p_main(p): ''' main : product_of_units | factor product_of_units | factor product product_of_units | division_product_of_units | factor division_product_of_units | factor product division_product_of_units | inverse_unit | factor inverse_unit | factor product inverse_unit | factor ''' from astropy.units.core import Unit if len(p) == 2: p[0] = Unit(p[1]) elif len(p) == 3: p[0] = Unit(p[1] * p[2]) elif len(p) == 4: p[0] = Unit(p[1] * p[3]) def p_division_product_of_units(p): ''' division_product_of_units : division_product_of_units division product_of_units | product_of_units ''' from astropy.units.core import Unit if len(p) == 4: p[0] = Unit(p[1] / p[3]) else: p[0] = p[1] def p_inverse_unit(p): ''' inverse_unit : division unit_expression ''' p[0] = p[2] ** -1 def p_factor(p): ''' factor : factor_fits | factor_float | factor_int ''' p[0] = p[1] def p_factor_float(p): ''' factor_float : signed_float | signed_float UINT signed_int | signed_float UINT power numeric_power ''' if cls.name == 'fits': raise ValueError("Numeric factor not supported by FITS") if len(p) == 4: p[0] = p[1] * p[2] ** float(p[3]) elif len(p) == 5: p[0] = p[1] * p[2] ** float(p[4]) elif len(p) == 2: p[0] = p[1] def p_factor_int(p): ''' factor_int : UINT | UINT signed_int | UINT power numeric_power | UINT UINT signed_int | UINT UINT power numeric_power ''' if cls.name == 'fits': raise ValueError("Numeric factor not supported by FITS") if len(p) == 2: p[0] = p[1] elif len(p) == 3: p[0] = p[1] ** float(p[2]) elif len(p) == 4: if isinstance(p[2], int): p[0] = p[1] * p[2] ** float(p[3]) else: p[0] = p[1] ** float(p[3]) elif len(p) == 5: p[0] = p[1] * p[2] ** p[4] def p_factor_fits(p): ''' factor_fits : UINT power OPEN_PAREN signed_int CLOSE_PAREN | UINT power signed_int | UINT SIGN UINT | UINT OPEN_PAREN signed_int CLOSE_PAREN ''' if p[1] != 10: if cls.name == 'fits': raise ValueError("Base must be 10") else: return if len(p) == 4: if p[2] in ('**', '^'): p[0] = 10 ** p[3] else: p[0] = 10 ** (p[2] * p[3]) elif len(p) == 5: p[0] = 10 ** p[3] elif len(p) == 6: p[0] = 10 ** p[4] def p_product_of_units(p): ''' product_of_units : unit_expression product product_of_units | unit_expression product_of_units | unit_expression ''' if len(p) == 2: p[0] = p[1] elif len(p) == 3: p[0] = p[1] * p[2] else: p[0] = p[1] * p[3] def p_unit_expression(p): ''' unit_expression : function | unit_with_power | OPEN_PAREN product_of_units CLOSE_PAREN ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[2] def p_unit_with_power(p): ''' unit_with_power : UNIT power numeric_power | UNIT numeric_power | UNIT ''' if len(p) == 2: p[0] = p[1] elif len(p) == 3: p[0] = p[1] ** p[2] else: p[0] = p[1] ** p[3] def p_numeric_power(p): ''' numeric_power : sign UINT | OPEN_PAREN paren_expr CLOSE_PAREN ''' if len(p) == 3: p[0] = p[1] * p[2] elif len(p) == 4: p[0] = p[2] def p_paren_expr(p): ''' paren_expr : sign UINT | signed_float | frac ''' if len(p) == 3: p[0] = p[1] * p[2] else: p[0] = p[1] def p_frac(p): ''' frac : sign UINT division sign UINT ''' p[0] = (p[1] * p[2]) / (p[4] * p[5]) def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_product(p): ''' product : STAR | PERIOD ''' pass def p_division(p): ''' division : SOLIDUS ''' pass def p_power(p): ''' power : DOUBLE_STAR | CARET ''' p[0] = p[1] def p_signed_int(p): ''' signed_int : SIGN UINT ''' p[0] = p[1] * p[2] def p_signed_float(p): ''' signed_float : sign UINT | sign UFLOAT ''' p[0] = p[1] * p[2] def p_function_name(p): ''' function_name : FUNCNAME ''' p[0] = p[1] def p_function(p): ''' function : function_name OPEN_PAREN main CLOSE_PAREN ''' if p[1] == 'sqrt': p[0] = p[3] ** 0.5 return elif p[1] in ('mag', 'dB', 'dex'): function_unit = cls._parse_unit(p[1]) # In Generic, this is callable, but that does not have to # be the case in subclasses (e.g., in VOUnit it is not). if callable(function_unit): p[0] = function_unit(p[3]) return raise ValueError("'{0}' is not a recognized function".format(p[1])) def p_error(p): raise ValueError() parser_exists = os.path.exists(os.path.join(os.path.dirname(__file__), 'generic_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='generic_parsetab', outputdir=os.path.dirname(__file__)) if not parser_exists: cls._add_tab_header('generic_parsetab') return parser
def _make_parser(cls): """ The grammar here is based on the description in the `Specification of Physical Units within OGIP FITS files <https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_93_001/>`__, which is not terribly precise. The exact grammar is here is based on the YACC grammar in the `unity library <https://bitbucket.org/nxg/unity/>`_. """ from astropy.extern.ply import yacc tokens = cls._tokens def p_main(p): ''' main : UNKNOWN | complete_expression | scale_factor complete_expression | scale_factor WHITESPACE complete_expression ''' if len(p) == 4: p[0] = p[1] * p[3] elif len(p) == 3: p[0] = p[1] * p[2] else: p[0] = p[1] def p_complete_expression(p): ''' complete_expression : product_of_units ''' p[0] = p[1] def p_product_of_units(p): ''' product_of_units : unit_expression | division unit_expression | product_of_units product unit_expression | product_of_units division unit_expression ''' if len(p) == 4: if p[2] == 'DIVISION': p[0] = p[1] / p[3] else: p[0] = p[1] * p[3] elif len(p) == 3: p[0] = p[2] ** -1 else: p[0] = p[1] def p_unit_expression(p): ''' unit_expression : unit | UNIT OPEN_PAREN complete_expression CLOSE_PAREN | OPEN_PAREN complete_expression CLOSE_PAREN | UNIT OPEN_PAREN complete_expression CLOSE_PAREN power numeric_power | OPEN_PAREN complete_expression CLOSE_PAREN power numeric_power ''' # If we run p[1] in cls._functions, it will try and parse each # item in the list into a unit, which is slow. Since we know that # all the items in the list are strings, we can simply convert # p[1] to a string instead. p1_str = str(p[1]) if p1_str in cls._functions and p1_str != 'sqrt': raise ValueError( "The function '{0}' is valid in OGIP, but not understood " "by astropy.units.".format( p[1])) if len(p) == 7: if p1_str == 'sqrt': p[0] = p[1] * p[3] ** (0.5 * p[6]) else: p[0] = p[1] * p[3] ** p[6] elif len(p) == 6: p[0] = p[2] ** p[5] elif len(p) == 5: if p1_str == 'sqrt': p[0] = p[3] ** 0.5 else: p[0] = p[1] * p[3] elif len(p) == 4: p[0] = p[2] else: p[0] = p[1] def p_scale_factor(p): ''' scale_factor : LIT10 power numeric_power | LIT10 | signed_float | signed_float power numeric_power | signed_int power numeric_power ''' if len(p) == 4: p[0] = 10 ** p[3] else: p[0] = p[1] # Can't use np.log10 here, because p[0] may be a Python long. if math.log10(p[0]) % 1.0 != 0.0: from astropy.units.core import UnitsWarning warnings.warn( "'{0}' scale should be a power of 10 in " "OGIP format".format(p[0]), UnitsWarning) def p_division(p): ''' division : DIVISION | WHITESPACE DIVISION | WHITESPACE DIVISION WHITESPACE | DIVISION WHITESPACE ''' p[0] = 'DIVISION' def p_product(p): ''' product : WHITESPACE | STAR | WHITESPACE STAR | WHITESPACE STAR WHITESPACE | STAR WHITESPACE ''' p[0] = 'PRODUCT' def p_power(p): ''' power : STARSTAR ''' p[0] = 'POWER' def p_unit(p): ''' unit : UNIT | UNIT power numeric_power ''' if len(p) == 4: p[0] = p[1] ** p[3] else: p[0] = p[1] def p_numeric_power(p): ''' numeric_power : UINT | signed_float | OPEN_PAREN signed_int CLOSE_PAREN | OPEN_PAREN signed_float CLOSE_PAREN | OPEN_PAREN signed_float division UINT CLOSE_PAREN ''' if len(p) == 6: p[0] = Fraction(int(p[2]), int(p[4])) elif len(p) == 4: p[0] = p[2] else: p[0] = p[1] def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_signed_int(p): ''' signed_int : SIGN UINT ''' p[0] = p[1] * p[2] def p_signed_float(p): ''' signed_float : sign UINT | sign UFLOAT ''' p[0] = p[1] * p[2] def p_error(p): raise ValueError() parser_exists = os.path.exists(os.path.join(os.path.dirname(__file__), 'ogip_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='ogip_parsetab', outputdir=os.path.dirname(__file__), write_tables=True) if not parser_exists: cls._add_tab_header('ogip_parsetab') return parser
def _make_parser(cls): from astropy.extern.ply import lex, yacc # List of token names. tokens = ( 'SIGN', 'UINT', 'UFLOAT', 'COLON', 'DEGREE', 'HOUR', 'MINUTE', 'SECOND', 'SIMPLE_UNIT' ) # NOTE THE ORDERING OF THESE RULES IS IMPORTANT!! # Regular expression rules for simple tokens def t_UFLOAT(t): r'((\d+\.\d*)|(\.\d+))([eE][+-−]?\d+)?' # The above includes Unicode "MINUS SIGN" \u2212. It is # important to include the hyphen last, or the regex will # treat this as a range. t.value = float(t.value.replace('−', '-')) return t def t_UINT(t): r'\d+' t.value = int(t.value) return t def t_SIGN(t): r'[+−-]' # The above include Unicode "MINUS SIGN" \u2212. It is # important to include the hyphen last, or the regex will # treat this as a range. if t.value == '+': t.value = 1.0 else: t.value = -1.0 return t def t_SIMPLE_UNIT(t): t.value = u.Unit(t.value) return t t_SIMPLE_UNIT.__doc__ = '|'.join( '(?:{0})'.format(x) for x in cls._get_simple_unit_names()) t_COLON = ':' t_DEGREE = r'd(eg(ree(s)?)?)?|°' t_HOUR = r'hour(s)?|h(r)?|ʰ' t_MINUTE = r'm(in(ute(s)?)?)?|′|\'|ᵐ' t_SECOND = r's(ec(ond(s)?)?)?|″|\"|ˢ' # A string containing ignored characters (spaces) t_ignore = ' ' # Error handling rule def t_error(t): raise ValueError( "Invalid character at col {0}".format(t.lexpos)) lexer_exists = os.path.exists(os.path.join(os.path.dirname(__file__), 'angle_lextab.py')) # Build the lexer lexer = lex.lex(optimize=True, lextab='angle_lextab', outputdir=os.path.dirname(__file__)) if not lexer_exists: cls._add_tab_header('angle_lextab') def p_angle(p): ''' angle : hms | dms | arcsecond | arcminute | simple ''' p[0] = p[1] def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_ufloat(p): ''' ufloat : UFLOAT | UINT ''' p[0] = float(p[1]) def p_colon(p): ''' colon : sign UINT COLON ufloat | sign UINT COLON UINT COLON ufloat ''' if len(p) == 5: p[0] = (p[1] * p[2], p[4]) elif len(p) == 7: p[0] = (p[1] * p[2], p[4], p[6]) def p_spaced(p): ''' spaced : sign UINT ufloat | sign UINT UINT ufloat ''' if len(p) == 4: p[0] = (p[1] * p[2], p[3]) elif len(p) == 5: p[0] = (p[1] * p[2], p[3], p[4]) def p_generic(p): ''' generic : colon | spaced | sign UFLOAT | sign UINT ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[1] * p[2] def p_hms(p): ''' hms : sign UINT HOUR | sign UINT HOUR ufloat | sign UINT HOUR UINT MINUTE | sign UINT HOUR UFLOAT MINUTE | sign UINT HOUR UINT MINUTE ufloat | sign UINT HOUR UINT MINUTE ufloat SECOND | generic HOUR ''' if len(p) == 3: p[0] = (p[1], u.hourangle) elif len(p) == 4: p[0] = (p[1] * p[2], u.hourangle) elif len(p) in (5, 6): p[0] = ((p[1] * p[2], p[4]), u.hourangle) elif len(p) in (7, 8): p[0] = ((p[1] * p[2], p[4], p[6]), u.hourangle) def p_dms(p): ''' dms : sign UINT DEGREE | sign UINT DEGREE ufloat | sign UINT DEGREE UINT MINUTE | sign UINT DEGREE UFLOAT MINUTE | sign UINT DEGREE UINT MINUTE ufloat | sign UINT DEGREE UINT MINUTE ufloat SECOND | generic DEGREE ''' if len(p) == 3: p[0] = (p[1], u.degree) elif len(p) == 4: p[0] = (p[1] * p[2], u.degree) elif len(p) in (5, 6): p[0] = ((p[1] * p[2], p[4]), u.degree) elif len(p) in (7, 8): p[0] = ((p[1] * p[2], p[4], p[6]), u.degree) def p_simple(p): ''' simple : generic | generic SIMPLE_UNIT ''' if len(p) == 2: p[0] = (p[1], None) else: p[0] = (p[1], p[2]) def p_arcsecond(p): ''' arcsecond : generic SECOND ''' p[0] = (p[1], u.arcsecond) def p_arcminute(p): ''' arcminute : generic MINUTE ''' p[0] = (p[1], u.arcminute) def p_error(p): raise ValueError parser_exists = os.path.exists(os.path.join(os.path.dirname(__file__), 'angle_parsetab.py')) parser = yacc.yacc(debug=False, tabmodule='angle_parsetab', outputdir=os.path.dirname(__file__), write_tables=True) if not parser_exists: cls._add_tab_header('angle_parsetab') return parser, lexer