prodigy.pgcl.ppy_parser

Parser for .ppy

A Lark-based parser for the Python-like surface syntax .ppy (see the design in docs/source/language-design.md). It produces the same AST as prodigy.pgcl.parser for everything the old syntax can express, so the type checker, the compiler and the analysis do not change.

Sugar is translated into the existing AST:

  • a missing else gives an empty else branch, else if a nested IfInstr;

  • switch (c) { case 1: {…} case 2, 3: {…} default: {…} } gives nested if (c == 1) … else if (c == 2 or c == 3) … else …;

  • categorical({v: p, …}) and categorical(p0, …, pn) give a CategoricalExpr;

  • declarations at the top level of the program or of a function are collected into the declarations, an initial value becomes an assignment at the place of the declaration;

  • loop(N) with a constant N uses the constant’s literal value.

Two constructs have no counterpart in the old AST. They are parsed into LocalDeclInstr (a declaration inside a block) and Binop.FLOOR_DIVIDE (//), and the type checker rejects both as not implemented yet.

Errors are raised as PpySyntaxError with line and column.

>>> parse_ppy("x: nat; x = unif(1, 6);").instructions
[AsgnInstr(lhs='x', rhs=FunctionCallExpr(function='unif', params=([NatLitExpr(1), NatLitExpr(6)], {})))]
PPY_GRAMMAR = '\n    start: item*\n\n    ?item: statement\n         | "const" NAME (":" type)? "=" expression ";"     -> const_decl\n         | "param" NAME ":" type ";"                       -> param_decl\n         | "def" NAME "(" [def_params] ")" ("->" type)? "{" statement* "return" expression ";" "}" -> def_decl\n         | "query" NAME "[" expression "]" ";"             -> query\n         | "print" ";"                                     -> print_query\n         | "plot" "(" NAME ("," NAME)? ("," plot_literal)? ")" ";" -> plot\n         | "optimize" "(" expression "," NAME "," NAME ")" ";" -> optimize\n\n    def_params: def_param ("," def_param)*\n    def_param: NAME ":" type\n\n    ?statement: "skip" ";"                                 -> skip\n              | "abort" ";"                                -> abort\n              | NAME "=" expression ";"                    -> assign\n              | names ":" type ("=" expression)? ";"       -> var_decl\n              | "tick" "(" expression ")" ";"              -> tick\n              | "observe" "(" expression ")" ";"           -> observe\n              | "while" "(" expression ")" block           -> while_\n              | if_statement\n              | block "[" expression "]" block             -> choice\n              | "loop" "(" (INT | NAME) ")" block          -> loop\n              | "switch" "(" NAME ")" "{" case+ default? "}" -> switch\n              | "query" block                              -> query_block\n              | ";"                                        -> empty\n\n    if_statement: "if" "(" expression ")" block ("else" (block | if_statement))?\n\n    case: "case" expression ("," expression)* ":" block\n    default: "default" ":" block\n\n    block: "{" statement* "}"\n\n    names: NAME ("," NAME)*\n\n    type: "nat" bounds?  -> nat_type\n        | "real"         -> real_type\n        | "bool"         -> bool_type\n        | "dist"         -> dist_type\n\n    bounds: "[" expression "," expression "]"\n\n    plot_literal: INT    -> nat_lit\n                | FLOAT  -> real_lit\n                | INFINITY -> infinity\n\n    ?expression: or_expr\n\n    ?or_expr: and_expr\n            | or_expr "or" and_expr      -> or_\n            | or_expr "||" and_expr      -> or_\n\n    ?and_expr: not_expr\n             | and_expr "and" not_expr   -> and_\n             | and_expr "&&" not_expr    -> and_\n\n    ?not_expr: comparison\n             | "not" not_expr            -> not_\n             | "!" not_expr              -> not_\n\n    ?comparison: sum\n               | sum "<=" sum            -> leq\n               | sum "<" sum             -> lt\n               | sum ">" sum             -> gt\n               | sum ">=" sum            -> geq\n               | sum "==" sum            -> eq\n\n    ?sum: term\n        | sum "+" term                   -> plus\n        | sum "-" term                   -> minus\n\n    ?term: factor\n         | term "*" factor               -> times\n         | term "/" factor               -> divide\n         | term "//" factor              -> floor_divide\n         | term "%" factor               -> mod\n\n    ?factor: power\n           | "-" factor                  -> uminus\n\n    ?power: atom\n          | atom "**" factor             -> power\n\n    ?atom: INT                           -> nat_lit\n         | FLOAT                         -> real_lit\n         | INFINITY                      -> infinity\n         | "true"                        -> true\n         | "false"                       -> false\n         | NAME                          -> var\n         | NAME "(" [arguments] ")"      -> call\n         | "categorical" "(" categories ")" -> categorical\n         | "infer" "(" NAME ("," arguments)? ")" -> infer\n         | "sample" "(" NAME ")"         -> sample\n         | "(" expression ")"\n         | "[" expression "]"            -> iverson\n\n    arguments: argument ("," argument)*\n    ?argument: expression\n             | NAME "=" expression       -> named_argument\n\n    ?categories: "{" category ("," category)* "}" -> category_dict\n               | expression ("," expression)*     -> category_list\n    category: expression ":" expression\n\n    INFINITY: "∞" | "\\\\infty"\n\n    %import common.CNAME -> NAME\n    %import common.INT\n    %import common.FLOAT\n    %import common.WS\n\n    COMMENT: /#[^\\n]*/\n    %ignore COMMENT\n    %ignore WS\n'

or < and < not < comparisons < + - < * / // % < unary - < **.

Type:

The Lark grammar of .ppy. Precedences follow Python

PPY_KEYWORDS = frozenset({'abort', 'and', 'bool', 'case', 'categorical', 'const', 'def', 'default', 'dist', 'else', 'false', 'if', 'infer', 'loop', 'nat', 'not', 'observe', 'optimize', 'or', 'param', 'plot', 'print', 'query', 'real', 'return', 'sample', 'skip', 'switch', 'tick', 'true', 'while'})

Words that cannot be used as names in .ppy.

exception PpySyntaxError(message, line, column)[source]

Bases: SyntaxError

A syntax error in a .ppy program, with line and column (1-based).

Parameters:
parse_ppy(code)[source]

Parse a program in the .ppy syntax.

>>> parse_ppy("x: nat = 3; if (x == 3) { skip; }").instructions[1]
IfInstr(cond=BinopExpr(operator=Binop.EQ, lhs=VarExpr('x'), rhs=NatLitExpr(3)), true=[SkipInstr()], false=[])

>>> parse_ppy("x: nat; x = 1 +;")
Traceback (most recent call last):
    ...
prodigy.pgcl.ppy_parser.PpySyntaxError: line 1, column 16: Unexpected ';', expected ... or a number.
Parameters:

code (str)

Return type:

Program

parse_ppy_expr(code)[source]

Parse an expression in the .ppy syntax.

>>> parse_ppy_expr("not z or x")
BinopExpr(operator=Binop.OR, lhs=UnopExpr(operator=Unop.NEG, expr=VarExpr('z')), rhs=VarExpr('x'))
Parameters:

code (str)

Return type:

VarExpr | BoolLitExpr | NatLitExpr | RealLitExpr | UnopExpr | BinopExpr | CategoricalExpr | SubstExpr | TickExpr | FunctionCallExpr | InferExpr | SampleExpr