Sign in

    partiql-syntax

    A PartiQL parser for MoonBit.

    partiql
    sql
    query
    parser
    ast
    Download zip
    Author
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    12 hours ago
    Downloads
    12

    #moonrockz/partiql-syntax

    A PartiQL parser and printer for MoonBit. Parse queries, expressions and graph MATCH patterns into typed syntax trees with source spans, then print canonical or width-limited text. DML and DDL forms raise ParseError::Unsupported.

    #Installation and imports

    moon add moonrockz/partiql-syntax

    Add an alias because the module name contains a hyphen:

    import {
    "moonrockz/partiql-syntax" @syntax,
    }

    The checked examples use moonrockz/expect for assertions. Add it with moon add moonrockz/expect and put this import in moon.pkg when copying the test blocks:

    import { "moonrockz/expect" } for "test"

    The examples use the root package. Import a subpackage when working directly with expression nodes, lexer tokens, logical document layouts or diagnostics.

    PackageContent
    moonrockz/partiql-syntaxParse, recovery, print, format and diagnostic facade
    moonrockz/partiql-syntax/astPublic syntax ADTs, Span, ParseError, comments and scripts
    moonrockz/partiql-syntax/lexerlex, lex_recovering, token ADTs
    moonrockz/partiql-syntax/parserStatement, expression and script parsers
    moonrockz/partiql-syntax/printerCanonical and pretty printing
    moonrockz/partiql-syntax/diagnosticSource positions and rendered messages
    moonrockz/partiql-syntax/sexpS-expression rendering of syntax trees

    #Parse and print a statement

    parse requires exactly one statement, allowing an optional final semicolon. print omits the semicolon and comments. Use parse_script and print_script for multiple statements and preserved comments.

    test "parse and print" {
    let statement = @syntax.parse("select a from t where a > 1")
    @expect.expect(@syntax.print(statement)).to_equal("SELECT a FROM t WHERE a > 1")
    }

    Names retain their spelling and whether they were quoted. Literal nodes retain numeric source text, datetime precision and parsed Ion backtick payloads. Tree equality therefore compares syntax, including source spans. To compare a print round trip, print the reparsed result or normalize spans first.

    #Format a script

    format parses the whole script, retains comments and prints each statement with a terminating semicolon and newline. Without width, it uses compact canonical layout. With width, it chooses line breaks; long indivisible tokens may exceed that width. Errors raise before any formatted output is returned.

    test "format with comments" {
    @expect.expect(@syntax.format("select a from t -- note")).to_equal("SELECT a FROM t;\n-- note\n")
    }

    test "format at a chosen width" {
    @expect.expect(@syntax.format("select a, b from t where x = 1", width=20)).to_equal("SELECT a, b\nFROM t\nWHERE x = 1;\n")
    }

    #Recover from invalid input

    Strict parsers stop at the first error. parse_script_recovering instead returns a partial script and every diagnostic in source order. Broken regions contain Error nodes; inspect errors before using a recovered tree for execution or rewriting. On valid input it returns the strict parser's tree.

    test "recover a script" {
    let result = @syntax.parse_script_recovering("SELECT a, , b FROM t; SELECT 1")
    @expect.expect((result.errors.length(), result.script.statements.length())).to_equal((1, 2))
    }

    #Report source locations

    Span uses half-open UTF-16 offsets into the original source. position returns 1-based line and Unicode code-point column numbers. The AST helper Span::line_column returns positions for both endpoints with UTF-16 columns. Use the original source when rendering a diagnostic. color=true enables ANSI color sequences; the default is plain text.

    test "render a diagnostic" {
    let source = "SELECT FROM t"
    let text = try {
    ignore(@syntax.parse(source))
    ""
    } catch {
    error => @syntax.render(error, source, name="query.sql")
    }
    @expect.expect(text.contains("query.sql:1:8")).to_be_true()
    }

    ParseError::Syntax(message~, span~, expected~, found~) describes invalid input. Unsupported distinguishes recognized forms that are not implemented. render_message also renders evaluator errors at a supplied span. to_sexp produces a readable S-expression with an optional layout width.

    test "Unicode source position" {
    let position = @syntax.position("'😀' + x", 7)
    @expect.expect((position.line, position.column)).to_equal((1, 7))
    }

    Input nested more than 100 levels deep, including parentheses, subqueries, collections and graph groups, raises Syntax with "nesting is too deep" on every target. The parser accepts forms beyond the evaluator's current support; parsing successfully does not promise successful evaluation.

    All checked examples run through mise run test:docs. See the repository's architecture and conformance report.

    Comment

    A comment as written: -- note (without the line break) or /* note */.

    CommentKind

    Whether a preserved source comment uses line or block delimiters.

    ParseError

    Why a statement could not be parsed.

    Position

    A 1-based line and a 1-based column counted in code points.

    Recovered

    The result of parse_script_recovering: the partial script and every error, in source order (spec M3d §3).

    Script

    The statements of a script, and the comments of its source in source order.

    Span

    A range of source text: UTF-16 code unit offsets, end exclusive.

    Statement

    A parsed statement. M2 parses queries (DQL).

    format

    fn format(source : String, width? : Int) -> String raise
    ParseError

    Parses source as a script and prints it, keeping the comments: canonical without width, the pretty layout at width with it.

    test {
    @expect.expect(@syntax.format("select 1; select 2")).to_equal(
    "SELECT 1;\nSELECT 2;\n",
    )
    }

    parse

    Parses one PartiQL statement: a query, with an optional final ;. Raises ParseError::Syntax for invalid input and ParseError::Unsupported for valid input that this version does not parse yet (DML and DDL statements such as INSERT or CREATE, which later milestones parse).

    test {
    let statement = @syntax.parse("select value 1 + 2")
    @expect.expect(@syntax.print(statement)).to_equal("SELECT VALUE 1 + 2")
    }

    parse_script

    Parses a script: statements separated by ;, with its comments.

    parse_script_recovering

    fn parse_script_recovering(source : String) ->
    Recovered

    Parses a script and recovers from syntax errors: the partial script and every error, in source order (spec M3d).

    position

    fn position(source : String, offset : Int) ->
    Position

    The 1-based line and code-point column of a UTF-16 offset.

    print

    fn print(statement :
    Statement
    , width? : Int) -> String

    The text of a statement (no comments, no final ;): canonical without width, the pretty layout at width with it.
    fn print_script(script :
    Script
    , width? : Int) -> String

    The text of a script, with its comments: canonical without width, the pretty layout at width with it.

    render

    fn render(error :
    ParseError
    , source : String, name~ : String, color? : Bool) -> String

    The rendered diagnostic of a parse error (spec M3c §5).

    render_message

    fn render_message(message : String, span :
    Span
    , source : String, name~ : String, color? : Bool) -> String

    The rendered diagnostic of message at span (for example an evaluation error), in the layout of render.

    to_sexp

    fn to_sexp(statement :
    Statement
    , width? : Int) -> String

    The S-expression of a statement (spec M3c §7).

    Source Files

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io