Coverage for src/prisma/generator/_dsl_parser/parser.py: 100%
32 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-27 18:25 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-27 18:25 +0000
1"""Lark parser for our custom DSL inside Prisma Schemas, e.g.
3```prisma
4/// @Python(name: foo)
5model bar {
6 // ...
7}
8```
9"""
11from __future__ import annotations
13from typing import Union
14from typing_extensions import Literal, TypedDict
16from .transformer import TransformResult, DefinitionTransformer
17from ..._vendor.lark_schema_parser import Lark_StandAlone as LarkParser, UnexpectedInput
18from ..._vendor.lark_schema_scan_parser import Lark_StandAlone as LarkScanner
20scanner = LarkScanner() # type: ignore[no-untyped-call]
21schema_extension_parser = LarkParser() # type: ignore[no-untyped-call]
23transformer = DefinitionTransformer()
26def parse_schema_dsl(text: str) -> ParseResult:
27 """Given a string like `@Python(foo: bar)`
28 returns `{ 'type': 'ok', 'value': { 'arguments': { 'foo': 'bar' } } }`.
30 If the string is not valid syntax, then `{'type': 'invalid', 'error': 'msg'}`
31 is returned.
33 If the string is not actually even attempting to represent the `@Python`
34 DSL, then `{'type': 'not_applicable'}` is returned.
36 Note, currently `not_applicable` will be returned if there are no arguments given
37 to `@Python`, e.g. `@Python()`. This currently doesn't matter, but it may be changed
38 in the future.
39 """
40 parts = scan_for_declarations(text)
41 if not parts:
42 return {'type': 'not_applicable'}
44 if len(parts) > 1:
45 # TODO: include context in error message
46 return {'type': 'invalid', 'error': f'Encountered multiple `@Python` declarations'}
48 start, end = parts[0]
50 snippet = text[start:end]
52 try:
53 parsed = schema_extension_parser.parse(snippet)
54 except UnexpectedInput as exc:
55 return {'type': 'invalid', 'error': str(exc) + exc.get_context(snippet)}
57 transformed = transformer.transform(parsed)
58 return {'type': 'ok', 'value': transformed}
61def scan_for_declarations(text: str) -> list[tuple[int, int]]:
62 """Returns a list of (start, end) of parts of the text that
63 look like `@Python(...)`.
65 Note: this is just needed until Lark provides a more complete
66 way to scan for a grammar and also provide syntax errors.
68 https://github.com/lark-parser/lark/discussions/1390#discussioncomment-8354420
69 """
70 return [indices for indices, _ in scanner.scan(text)]
73class ParseResultOk(TypedDict):
74 type: Literal['ok']
75 value: TransformResult
78class ParseResultInvalid(TypedDict):
79 type: Literal['invalid']
80 error: str
83class ParseResultNotApplicable(TypedDict):
84 type: Literal['not_applicable']
87ParseResult = Union[ParseResultOk, ParseResultInvalid, ParseResultNotApplicable]