| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
PostgresqlSyntax.Algebra
Synopsis
- class IsAst a where
- toTextBuilder :: Settings -> a -> TextBuilder
- parser :: Settings -> Parser a
- toText :: IsAst a => Settings -> a -> Text
- parse :: IsAst a => Settings -> Text -> Either Text a
- parseWithPosError :: IsAst a => Settings -> Text -> Either (NonEmpty (Int, Text)) a
- parseWithSourcePosError :: IsAst a => Settings -> Text -> Either (NonEmpty (SourcePos, Text)) a
- isAstProperties :: (IsAst a, Eq a, Show a, Arbitrary a) => [(String, Property)]
- class IsAst a => Canonicalizes a where
- canonicalize :: a -> a
- canonicalizesProperties :: (Canonicalizes a, Eq a, Show a, Arbitrary a) => [(String, Property)]
- class Refines sub sup where
- refinesProperties :: (Refines sub sup, IsAst sub, Eq sub, Show sub, Arbitrary sub) => [(String, Property)]
- class IsAst a => LeftRecursive a where
- class (LeftRecursive base, Refines ext base) => Extends base ext | ext -> base where
- parseExtensions :: Settings -> base -> Parser ext
- extendedByProperties :: (Extends base ext, IsAst base, Eq base, Show base, Arbitrary base) => [(String, Property)]
- parseMaybeExtended :: forall ext base. Extends base ext => Settings -> Parser base
- parseExtended :: Extends base ext => Settings -> Parser ext
- parseExtensionChain :: Parser item -> Parser (NonEmpty item)
- type Parser = HeadedParsec Void Text
Documentation
Class of AST types that can be rendered to SQL text and parsed back again.
Laws:
- Roundtrips:
parse settings (toText settings a) = Right afor everySettings— rendering and parsing are inverses. - Congruent rendering:
a == b => toTextBuilder settings a == toTextBuilder settings bfor everySettings— rendering only depends on the value, not on how it was constructed. This is what makes it meaningful to say two structurally different shapes can still render to identical text — the ambiguity thatCanonicalizesexists to resolve.
Methods
toTextBuilder :: Settings -> a -> TextBuilder Source #
Render an AST value to a TextBuilder using the given Settings.
This is the low-level rendering primitive; toText wraps it.
Instances
toText :: IsAst a => Settings -> a -> Text Source #
Render a value to Text via its toTextBuilder method.
parseWithPosError :: IsAst a => Settings -> Text -> Either (NonEmpty (Int, Text)) a Source #
Like parse but returns the structured error list (each error paired with
its byte offset) instead of a single pretty-printed message.
parseWithSourcePosError :: IsAst a => Settings -> Text -> Either (NonEmpty (SourcePos, Text)) a Source #
Like parseWithPosError but pairs each error with its
SourcePos instead of a raw byte offset.
class IsAst a => Canonicalizes a where Source #
Laws:
- Idempotent:
canonicalize . canonicalize = canonicalize - Parse-agreement (the property this class exists to provide):
parse settings . toText settings = Right . canonicalizefor everySettings
Minimal complete definition
Nothing
Methods
canonicalize :: a -> a Source #
Instances
| Canonicalizes AExpr Source # | Collapses the non-canonical
|
Defined in PostgresqlSyntax.Ast.AExpr Methods canonicalize :: AExpr -> AExpr Source # | |
| Canonicalizes CExpr Source # | Collapses the non-canonical
|
Defined in PostgresqlSyntax.Ast.CExpr Methods canonicalize :: CExpr -> CExpr Source # | |
| Canonicalizes InExpr Source # | Collapses the non-canonical |
Defined in PostgresqlSyntax.Ast.InExpr Methods canonicalize :: InExpr -> InExpr Source # | |
| Canonicalizes SelectWithParens Source # | Collapses the non-canonical |
Defined in PostgresqlSyntax.Ast.SelectWithParens Methods canonicalize :: SelectWithParens -> SelectWithParens Source # | |
| Canonicalizes SimpleSelect Source # | Collapses an arbitrary-shaped |
Defined in PostgresqlSyntax.Ast.SimpleSelect Methods | |
canonicalizesProperties :: (Canonicalizes a, Eq a, Show a, Arbitrary a) => [(String, Property)] Source #
Property-checkers for Canonicalizes's documented laws, keyed by
name. "Parse-agreement" is tested at mempty Settings, matching how
isAstProperties's "Renders equal values equally" property handles "for
every Settings" — no Arbitrary Settings instance exists or is
being added.
class Refines sub sup where Source #
Laws:
- Refinement law:
project . embed = Just
Expresses embedding relationships between AST node types across module
boundaries where cross-module pattern matching is unavailable. Instances
hold between two types when the sub type can be trivially embedded into
sup, and trivial sup values can be recognized as such a sub and
extracted back out.
Instances
refinesProperties :: (Refines sub sup, IsAst sub, Eq sub, Show sub, Arbitrary sub) => [(String, Property)] Source #
class IsAst a => LeftRecursive a where Source #
A type some of whose grammar productions are left-recursive — i.e. there
is a larger recursive form built by extending a value of this type on its
left. parseBase is everything that is not one of those productions:
the β of A -> Aα | β.
This is a strictly weaker claim than Extends, which additionally
names the specific ext of one such hub. A type can be LeftRecursive
without being any hub's base — JoinedTable and
SimpleSelect both are, since each is reached by
extending a different type (table_ref and select_clause
respectively) yet still has non-left-recursive productions of its own.
Separating this from Extends is what lets each instance live with
the type it constructs: parseBase mentions only its own type, so it
belongs to that type's module, while a hub's parseExtensions belongs to
the module defining ext. Because both are class methods, either module
can reach the other's parser through an hs-boot instance declaration
without exporting a bare helper.
Note that "non-recursive" here means non-left-recursive only: a
production may still recurse, so long as it doesn't begin with the
recursive position (e.g. '(' joined_table ')').
Methods
parseBase :: Settings -> Parser a Source #
Parse only the productions that don't left-recurse (β).
Instances
| LeftRecursive JoinedTable Source # | The one | '(' joined_table ')'
It still recurses — just not on the left, since the opening parenthesis
has to be consumed first. PostgresqlSyntax.Ast.TableRef reaches it
through this class method, which is why |
Defined in PostgresqlSyntax.Ast.JoinedTable | |
| LeftRecursive SelectClause Source # | Every |
Defined in PostgresqlSyntax.Ast.SelectClause | |
| LeftRecursive SimpleSelect Source # | The |
Defined in PostgresqlSyntax.Ast.SimpleSelect | |
| LeftRecursive TableRef Source # | Every The two |
class (LeftRecursive base, Refines ext base) => Extends base ext | ext -> base where Source #
The two halves of a left-recursive grammar production, split apart by
left-recursion elimination (A -> Aα | β becomes A -> β α*): base is
A (and supplies β via its LeftRecursive instance), and ext is
what one or more α's, applied to a base, produce.
Laws:
- Base-parser agreement:
parser @base = parseMaybeExtended @base - Maximal munch:
parseExtensionsmust not return while a further extension is available — instances build this onparseExtensionChainwhere possible, which already guarantees it.
Methods
parseExtensions :: Settings -> base -> Parser ext Source #
Parse one or more extensions onto an already-parsed left operand, folding as it goes, and return the fully-extended result.
Instances
| Extends SelectClause SimpleSelect Source # | The left-recursion-eliminated form of Keeps the collect-then-fold shape — parsing every |
Defined in PostgresqlSyntax.Ast.SimpleSelect Methods parseExtensions :: Settings -> SelectClause -> Parser SimpleSelect Source # | |
| Extends TableRef JoinedTable Source # | The left-recursion-eliminated form of |
Defined in PostgresqlSyntax.Ast.JoinedTable Methods parseExtensions :: Settings -> TableRef -> Parser JoinedTable Source # | |
extendedByProperties :: (Extends base ext, IsAst base, Eq base, Show base, Arbitrary base) => [(String, Property)] Source #
Property-checker for Extends's "Base-parser agreement" law,
keyed by name. "Maximal munch" isn't checked here: it's a per-instance
parsing obligation, not something a generated base value can exercise
through parser alone. The agreement check is itself up to whether
parsing succeeds, ignoring error message text, since a base's parser
may wrap parseMaybeExtended in a label or similar
that changes failure messages without changing what's accepted.
parseMaybeExtended :: forall ext base. Extends base ext => Settings -> Parser base Source #
Parses zero or more extensions onto a parseBase, via parseExtensions.
This is what A -> β α* (the whole of A) means as a parser.
parseExtended :: Extends base ext => Settings -> Parser ext Source #
Like parseMaybeExtended, but requires at least one extension to follow
the base, and so returns the fully-applied ext type directly rather
than base. This is what a bare α* (one or more) means as a parser,
for hubs where a chain of at least one extension is itself the
interesting type (e.g. a joined_table, which is never a bare
table_ref with zero joins).
Unlike parseMaybeExtended, parseBase here is wrapped in
wrapToHead. Without it, if parseBase itself commits past an
internal endHead (e.g. by matching a nested, fully-parenthesized
instance of the very thing this function's caller is one alternative
for), that commitment silently swallows the immediately-following "is
there at least one extension?" check: a missing extension would fail as
a hard, uncatchable error instead of a clean one this function's own
caller can backtrack from. wrapToHead resets that, forcing the check
to fail cleanly. parseMaybeExtended doesn't need this: its own
extension check already goes through optional, which independently
wraps in try regardless of what parseBase committed to.
parseExtensionChain :: Parser item -> Parser (NonEmpty item) Source #
Parses one or more items back-to-back, wrapping each in
wrapToHead/endHead so that, once an item's own head has
matched, backtracking out of the whole chain (back to "there are no more
items") is no longer attempted — matching the hand-written
recursive-descent loops this replaces. This is the shared backtracking
protocol underlying Extends's "Maximal munch" law: an instance
building parseExtensions on top of this combinator gets the law for
free, since go only stops once a further item genuinely isn't
available.