def _make_parser(cls): """ The grammar here is based on the description in the `Standards for Astronomical Catalogues 2.0 <http://vizier.u-strasbg.fr/vizier/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/>`_. """ tokens = cls._tokens def p_main(p): ''' main : factor combined_units | combined_units | DIMENSIONLESS | OPEN_BRACKET combined_units CLOSE_BRACKET | OPEN_BRACKET DIMENSIONLESS CLOSE_BRACKET | factor ''' from astropy.units import dex from astropy.units.core import Unit if len(p) == 3: p[0] = Unit(p[1] * p[2]) elif len(p) == 4: p[0] = dex(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() return parsing.yacc(tabmodule='cds_parsetab', package='astropy/units')
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. """ 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(f"'{p[1]}' is not a recognized function") def p_error(p): raise ValueError() return parsing.yacc(tabmodule='generic_parsetab', package='astropy/units')
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', 'EASTWEST', 'NORTHSOUTH' ) # 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_EASTWEST(t): r'[EW]$' t.value = -1.0 if t.value == 'W' else 1.0 return t def t_NORTHSOUTH(t): r'[NS]$' # We cannot use lower-case letters otherwise we'll confuse # s[outh] with s[econd] t.value = -1.0 if t.value == 'S' else 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 = parsing.lex(lextab='angle_lextab', package='astropy/coordinates') def p_angle(p): ''' angle : sign hms eastwest | sign dms dir | sign arcsecond dir | sign arcminute dir | sign simple dir ''' sign = p[1] * p[3] value, unit = p[2] if isinstance(value, tuple): p[0] = ((sign * value[0],) + value[1:], unit) else: p[0] = (sign * value, unit) def p_sign(p): ''' sign : SIGN | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_eastwest(p): ''' eastwest : EASTWEST | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_dir(p): ''' dir : EASTWEST | NORTHSOUTH | ''' if len(p) == 2: p[0] = p[1] else: p[0] = 1.0 def p_ufloat(p): ''' ufloat : UFLOAT | UINT ''' p[0] = p[1] def p_colon(p): ''' colon : UINT COLON ufloat | UINT COLON UINT COLON ufloat ''' if len(p) == 4: p[0] = (p[1], p[3]) elif len(p) == 6: p[0] = (p[1], p[3], p[5]) def p_spaced(p): ''' spaced : UINT ufloat | UINT UINT ufloat ''' if len(p) == 3: p[0] = (p[1], p[2]) elif len(p) == 4: p[0] = (p[1], p[2], p[3]) def p_generic(p): ''' generic : colon | spaced | ufloat ''' p[0] = p[1] def p_hms(p): ''' hms : UINT HOUR | UINT HOUR ufloat | UINT HOUR UINT MINUTE | UINT HOUR UFLOAT MINUTE | UINT HOUR UINT MINUTE ufloat | UINT HOUR UINT MINUTE ufloat SECOND | generic HOUR ''' if len(p) == 3: p[0] = (p[1], u.hourangle) elif len(p) in (4, 5): p[0] = ((p[1], p[3]), u.hourangle) elif len(p) in (6, 7): p[0] = ((p[1], p[3], p[5]), u.hourangle) def p_dms(p): ''' dms : UINT DEGREE | UINT DEGREE ufloat | UINT DEGREE UINT MINUTE | UINT DEGREE UFLOAT MINUTE | UINT DEGREE UINT MINUTE ufloat | UINT DEGREE UINT MINUTE ufloat SECOND | generic DEGREE ''' if len(p) == 3: p[0] = (p[1], u.degree) elif len(p) in (4, 5): p[0] = ((p[1], p[3]), u.degree) elif len(p) in (6, 7): p[0] = ((p[1], p[3], p[5]), 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 = parsing.yacc(tabmodule='angle_parsetab', package='astropy/coordinates') return parser, lexer
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/>`_. """ 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() return parsing.yacc(tabmodule='ogip_parsetab', package='astropy/units')
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. """ tokens = cls._tokens def p_main(p): ''' main : unit | structured_unit | structured_subunit ''' if isinstance(p[1], tuple): # Unpack possible StructuredUnit inside a tuple, ie., # ignore any set of very outer parentheses. p[0] = p[1][0] else: p[0] = p[1] def p_structured_subunit(p): ''' structured_subunit : OPEN_PAREN structured_unit CLOSE_PAREN ''' # We hide a structured unit enclosed by parentheses inside # a tuple, so that we can easily distinguish units like # "(au, au/day), yr" from "au, au/day, yr". p[0] = (p[2], ) def p_structured_unit(p): ''' structured_unit : subunit COMMA | subunit COMMA subunit ''' from ..structured import StructuredUnit inputs = (p[1], ) if len(p) == 3 else (p[1], p[3]) units = () for subunit in inputs: if isinstance(subunit, tuple): # Structured unit that should be its own entry in the # new StructuredUnit (was enclosed in parentheses). units += subunit elif isinstance(subunit, StructuredUnit): # Structured unit whose entries should be # individiually added to the new StructuredUnit. units += subunit.values() else: # Regular unit to be added to the StructuredUnit. units += (subunit, ) p[0] = StructuredUnit(units) def p_subunit(p): ''' subunit : unit | structured_unit | structured_subunit ''' p[0] = p[1] def p_unit(p): ''' unit : 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(f"'{p[1]}' is not a recognized function") def p_error(p): raise ValueError() return parsing.yacc(tabmodule='generic_parsetab', package='astropy/units')