class TreeParsers(TextParsers): # Grammar rules using Parsita, with semantic routines exam = opt(title) & questionBox # > make_binop questionBox = rep(question) question = reg(r"Question ") & opt(listt) & text & opt(hint) & choiceBox choiceBox = rep(choiceCorrect | choice) choice = reg(r"Choice ") & opt(reg(r"\s*")) & text & reg(r"\s*") & opt(feedback) choiceCorrect = reg(r"Choice ") & opt(reg(r"\s*correct\s*")) & text & reg(r"\s*") & opt(feedback) feedback = reg(r"Feedback ") listt = reg(r"\[\".+\"(?:,.+)?\]") hint = reg(r"Hint ") & text title = reg(r"^Exam ") & text text = reg(r"\".+\"(?:\n,\n)?")
class FormatTextParsers(TextParsers, whitespace=None): integer = reg(r'[0-9]+') > int dense = lit('d') > constant(Mode.dense) compressed = lit('s') > constant(Mode.compressed) mode = dense | compressed # Use eof to ensure each parser goes to end format_without_orderings = rep(mode) << eof > ( lambda modes: Format(tuple(modes), tuple(range(len(modes))))) format_with_orderings = rep(mode & integer) << eof > make_format_with_orderings format = format_without_orderings | format_with_orderings
class Homework(TextParsers): number = reg(r"\d+") > int plus = lit("+") > constant(add) times = lit("*") > constant(mul) operator = plus | times # No precedence base = "(" >> unprecedented << ")" | number unprecedented = base & rep(operator & base) > reduce # Addition first, then multiplication base = "(" >> multiplication << ")" | number addition = base & rep(plus & base) > reduce multiplication = addition & rep(times & addition) > reduce
class TreeParsers(TextParsers): exam = opt(title) & questions > formatString questions = rep(actualQuestion) actualQuestion = reg(r"Question ") & opt(listt) & text & opt( hint) & mulChoices mulChoices = rep(choiceCorrect | singleChoice) singleChoice = reg(r"Choice ") & opt( reg(r"\s*")) & text & reg(r"\s*") & opt(feedback) choiceCorrect = reg(r"Choice ") & opt( reg(r"\s*correct\s*")) & text & reg(r"\s*") & opt(feedback) feedback = reg(r"Feedback ") listt = reg(r"\[\".+\"(?:,.+)?\]") hint = reg(r"Hint ") & text title = reg(r"^Exam ") & text text = reg(r"\".+\"(?:\n,\n)?")
class KarmaParser(TextParsers): anything = reg(r".") > constant(None) word_topic = reg(r'[^"\s]+?(?=[+-]{2})') string_topic = reg(r'".*?(?<!\\)(\\\\)*?"(?=[+-]{2})') topic = (word_topic > (lambda t: [t, False])) | (string_topic > (lambda t: [t[1:-1], True])) op_positive = reg(r"(?<![+-])\+\+(?![+-])") > constant( KarmaOperation.POSITIVE) op_neutral = (reg(r"(?<![+-])\+-(?![+-])") | reg(r"(?<![+-])-\+(?![+-])")) > constant( KarmaOperation.NEUTRAL) op_negative = reg(r"(?<![+-])--(?![+-])") > constant( KarmaOperation.NEGATIVE) operator = op_positive | op_neutral | op_negative bracket_reason = reg(r"\(.+?\)") > (lambda s: s[1:-1]) quote_reason = reg(r'".*?(?<!\\)(\\\\)*?"(?![+-]{2})') > ( lambda s: s[1:-1]) reason_words = reg(r"(?i)because") | reg(r"(?i)for") text_reason = reason_words >> (reg(r'[^",]+') | quote_reason) reason = bracket_reason | quote_reason | text_reason karma = (topic & operator & opt(reason)) > make_karma parse_all = rep(karma | anything) > filter_out_none
class TreeParsers(TextParsers): # Grammar rules using Parsita, with semantic routines exam = opt(title) & rep(questionBox) # > make_binop questionBox = question & opt(hint) & choiceBox question = reg(r"Question(\s)*") & opt(hintType) & text hintType = reg( r"\[((\"|\')[a-zA-Z0-9]*((\"|\')(\s)*\,(\s)*)?)*(\"|\')[a-zA-Z0-9]*(\"|\')\]" ) choiceBox = repsep(choice | choiceCorrect) # choiceBox = choice choice = reg(r"Choice[\s]+") & text choiceCorrect = reg(r"Choice correct(\s)*") & text hint = reg(r"Hint[\s]+") & text title = reg(r"Exam[\s]+") & text text = reg(r"[\w\W]*\n")
class JsonStringParsers(TextParsers, whitespace=None): quote = lit(r'\"') reverse_solidus = lit(r'\\') solidus = lit(r'\/') backspace = lit(r'\b') form_feed = lit(r'\f') line_feed = lit(r'\n') carriage_return = lit(r'\r') tab = lit(r'\t') uni = reg(r'\\u([0-9a-fA-F]{4})') escaped = (quote | reverse_solidus | solidus | backspace | form_feed | line_feed | carriage_return | tab | uni) unescaped = reg(r'[\u0020-\u0021\u0023-\u005B\u005D-\U0010FFFF]+') string = '"' >> rep(escaped | unescaped) << '"' > ''.join
class TensorExpressionParsers(TextParsers): name = reg(r'[A-Za-z][A-Za-z0-9]*') # taco does not support negatives or exponents floating_point = reg(r'[0-9]+\.[0-9]+') > (lambda x: Float(float(x))) integer = reg(r'[0-9]+') > (lambda x: Integer(int(x))) number = floating_point | integer # taco requires at least one index; scalar tensors are not parsed as `a()` # taco also allows for `y_{i}` and `y_i` to mean `y(i)`, but that is not supported here tensor = name & '(' >> rep1sep(name, ',') << ')' > splat(Tensor) scalar = name > Scalar variable = tensor | scalar parentheses = '(' >> expression << ')' # noqa: F821 factor = variable | number | parentheses term = rep1sep(factor, '*') > (lambda x: reduce(Multiply, x)) expression = term & rep(lit('+', '-') & term) > splat(make_expression) simple_assignment = variable & '=' >> expression > splat(Assignment) add_assignment = variable & '+=' >> expression > splat(lambda v, e: Assignment(v, Add(v, e))) assignment = simple_assignment | add_assignment
class ProgramParser(TextParsers): # Actual grammar split1 = lambda item, separator: item & rep(separator & item) split = lambda item, separator: opt(split1(item, separator)) identifier = reg(r"[a-zA-Z]\w*") string = reg(r'".*?(?<!\\)(\\\\)*?"') | reg(r"'.*?(?<!\\)(\\\\)*?'") > ( lambda s: TokenString(s[1:-1]) ) num_int = reg(r"\d+") > int num_float = reg(r"(\d*\.\d+|\d+\.\d*)") > float num_positive = num_float | num_int num_negative = "-" >> num > (lambda x: -x) num = num_negative | num_positive number = num > TokenNumber op_eq = lit("==") > constant(Operator.EQ) op_ne = lit("!=") > constant(Operator.NE) op_ge = lit(">=") > constant(Operator.GE) op_gt = lit(">") > constant(Operator.GT) op_le = lit("<=") > constant(Operator.LE) op_lt = lit("<") > constant(Operator.LT) op_and = lit("&") > constant(Operator.AND) op_or = lit("|") > constant(Operator.OR) op_add = lit("+") > constant(Operator.ADD) op_sub = lit("-") > constant(Operator.SUB) op_mul = lit("*") > constant(Operator.MUL) op_div = lit("/") > constant(Operator.DIV) op_pow = lit("^") > constant(Operator.POW) op_not = lit("!") > constant(Operator.NOT) op_neg = lit("-") > constant(Operator.NEG) equality_op = op_eq | op_ne comparison_op = op_ge | op_gt | op_le | op_lt logic_op = op_and | op_or term_op = op_add | op_sub factor_op = op_mul | op_div power_op = op_pow unary_op = op_neg | op_not case_pair = expr << "->" & expr assignment = identifier << "=" & expr let_stmt = "^" >> rep1sep(assignment, ";") << "$" & expr > let anon_func = (lit("\\") | lit("\\\\")) >> rep1(identifier) & "->" >> expr > anon variable = identifier > TokenVariable expr = rep1sep(equality, reg(r"\s*")) > maybe_application equality = split1(comparison, equality_op) > bin_operator comparison = split1(logic, comparison_op) > bin_operator logic = split1(term, logic_op) > bin_operator term = split1(factor, term_op) > bin_operator factor = split1(power, factor_op) > bin_operator power = split1(ternary, power_op) > bin_operator_right ternary = case & opt("?" >> expr << ":" & expr) > maybe_ternary case = dice & opt(lit("$") >> "(" >> rep1sep(case_pair, ";") << ")") > maybe_case dice = unary & opt("d" >> unary) > maybe_dice unary = unary_op & unary | primary > mon_operator primary = number | string | bracketed | let_stmt | anon_func | variable bracketed = "(" >> expr << ")" func = identifier & "=" >> expr > function program = repsep("@" >> func | expr, ";") << opt(";") > Program main = program
class DiceParser(TextParsers): split1 = lambda item, separator: item & rep(separator & item) split = lambda item, separator: opt(split1(item, separator)) comment = "/*" >> expr << "*/" > constant(None) string = reg(r'".*?(?<!\\)(\\\\)*?"') > (lambda s: ValueString(s[1:-1])) num_int = reg(r"\d+") > int num_float = reg(r"(\d*\.\d+|\d+\.\d*)") > float num_positive = num_float | num_int num_negative = "-" >> num > (lambda x: -x) num = num_negative | num_positive number = num > ValueNumber # set = "(" >> repsep(expr, ",") << ")" > ValueSet op_eq = lit("==") > constant(Operator.EQ) op_ne = lit("!=") > constant(Operator.NE) op_ge = lit(">=") > constant(Operator.GE) op_gt = lit(">") > constant(Operator.GT) op_le = lit("<=") > constant(Operator.LE) op_lt = lit("<") > constant(Operator.LT) op_and = lit("&") > constant(Operator.AND) op_or = lit("|") > constant(Operator.OR) op_add = lit("+") > constant(Operator.ADD) op_sub = lit("-") > constant(Operator.SUB) op_mul = lit("*") > constant(Operator.MUL) op_div = lit("/") > constant(Operator.DIV) op_pow = lit("^") > constant(Operator.POW) op_not = lit("!") > constant(Operator.NOT) op_neg = lit("-") > constant(Operator.NEG) equality_op = op_eq | op_ne comparison_op = op_ge | op_gt | op_le | op_lt logic_op = op_and | op_or term_op = op_add | op_sub factor_op = op_mul | op_div power_op = op_pow unary_op = op_neg | op_not case_pair = expr << "," & expr program = repsep(expr, ";") > Program expr = equality equality = split1(comparison, equality_op) > bin_operator comparison = split1(logic, comparison_op) > bin_operator logic = split1(term, logic_op) > bin_operator term = split1(factor, term_op) > bin_operator factor = split1(power, factor_op) > bin_operator power = split1(dice, power_op) > bin_operator_right dice = opt(opt(ternary) << "d") & ternary > maybe_dice ternary = case & opt("?" >> expr << ":" & expr) > maybe_ternary case = unary & opt( lit(":") >> "(" >> repsep(case_pair, ";") << ")") > maybe_case unary = unary_op & unary | primary > mon_operator primary = number | string | bracketed bracketed = "(" >> expr << ")" parse_all = program
class ModelParsers(TextParsers, whitespace=r'[ \t]*'): number = reg(r'[+-]?\d+(\.\d+)?([Ee][+-]?\d+)?') > float name = reg(r'[A-Za-z_][A-Za-z_0-9]*') symbol = name > sy.Symbol def make_function(x): func_name, arguments = x if func_name in sy.functions.__dict__: func_handle = sy.__dict__[func_name] else: raise ValueError(f'Function "{func_name}" not found. Only functions in sympy.* may be used.') return func_handle(*arguments) function = name & '(' >> repsep(expression, ',') << ')' > make_function factor = number | function | symbol | '(' >> expression << ')' def make_exponent(x): x = list(reversed(x)) # Exponentiation is right associative so reverse the list value = x[0] rest = x[1:] for item in rest: value = sy.Pow(item, value) return value exponent = rep1sep(factor, '^') > make_exponent def make_term(x): first, rest = x value = first for op, exponent in rest: if op == '/': exponent = sy.Pow(exponent, -1) # This is how sympy handles divide value = sy.Mul(value, exponent) return value term = exponent & rep(lit('*', '/') & exponent) > make_term def make_unary_term(x): ops, value = x for op in ops: if op == '-': value = -value return value unary_term = rep(lit('+', '-')) & term > make_unary_term def make_expression(x): first, rest = x value = first for op, term in rest: if op == '-': term = sy.Mul(-1, term) # This is how sympy handles minus value = sy.Add(value, term) return value expression = unary_term & rep(lit('+', '-') & unary_term) > make_expression def make_one_sided_time_constant(x): direction, time = x if direction == '<': return -inf, time else: return time, inf one_sided_time = '(' >> lit('t') >> lit('<', '>') & number << ')' > make_one_sided_time_constant two_sided_time = '(' >> number << '<' << 't' << '<' & number << ')' time_range = one_sided_time | two_sided_time def make_constant(x): name, op, value = x if op == '+=': additive = True else: additive = False return name, Constant(name, value, additive) constant = name & lit('=', '+=') & number > make_constant def make_rule(x): name, domain, op, expr = x if not domain: first = -inf last = inf else: first, last = domain[0] if op == '+=': additive = True else: additive = False return name, Rule(name, AnalyticSegment(first, last, expr), additive) rule = name & opt(time_range) & lit('=', '+=') & expression > make_rule def make_initial(x): name, op, value = x if op == '+=': additive = True else: additive = False return name, Initial(value, additive) initial = name << '*' & lit('=', '+=') & expression > make_initial def make_ode(x): name, domain, op, expr = x if not domain: first = -inf last = inf else: first, last = domain[0] if op == '+=': additive = True else: additive = False return name, Ode(AnalyticSegment(first, last, expr), additive) ode = name << "'" & opt(time_range) & lit('=', '+=') & expression > make_ode def make_dose(x): name, time, op, value = x if op == '+=': value += sy.Symbol(name) return name, Dose(time, value) dose = name << "(" & number << ")" & lit('=', '+=') & expression > make_dose def make_effect(x): name, op, value = x if op == '+=': value += sy.Symbol(name) return Effect(name, value) effect = name & lit('=', '+=') & expression > make_effect def make_event(x): left, direction, right, effects = x trigger = left - right if direction == '<': effect_direction = EventDirection.down else: effect_direction = EventDirection.up return Event(trigger, effect_direction, True, effects) event = lit('@') >> '(' >> expression & lit('<', '>') & expression << ")" & rep1sep(effect, ',') > make_event component = constant | rule | initial | ode | dose | event eol = reg(r'(((#.*)?\n)+)|(((#.*)?\n)*(#.*)?\Z)') options_section = '%' >> lit('options') >> eol >> rep(failure('not implemented') << eol) components_section = '%' >> lit('components') >> eol >> rep(component << eol) def make_model(x): maybe_options, components = x if maybe_options: raise NotImplementedError parts, events = collapse_components(components) new_model = Model(parts, events) return new_model model = opt(options_section) & components_section > make_model