///|
/// What `parse_module` returns: the AST (`module_`), the compiler's
/// `extra`, the warnings, the CST (every token, grouped per definition, also
/// the skipped ones) and the diagnostics. A definition with a syntax error
/// is not in `module_`; its tokens are a `Skipped` run in `cst`.
///
/// ```mbt check
/// test {
/// let r = @parser.parse_text("pub fn main() {\n 1\n}\n")
/// inspect(r.module_.definitions.length(), content="1")
/// inspect(r.has_errors(), content="false")
/// inspect(r.cst.to_source(), content="pub fn main() {\n 1\n}\n")
/// }
/// ```
pub(all) struct ParseResult {
module_ : @ast.Module
extra : @ast.ModuleExtra
warnings : ArrayView[@ast.Warning]
cst : @cst.ModuleCst
diagnostics : ArrayView[@diagnostic.Diagnostic]
} derive(Eq, Debug)
///|
/// The oracle's `Parsed` shape: module, extra and warnings.
pub fn ParseResult::parsed(self : ParseResult) -> @ast.Parsed {
{ module_: self.module_, extra: self.extra, warnings: self.warnings, }
}
///|
/// Whether any diagnostic is an error.
pub fn ParseResult::has_errors(self : ParseResult) -> Bool {
self.diagnostics.any(d => d.is_error())
}
///|
/// Parse a token stream into a module. Recovery per definition: an error is
/// reported, the parser skips to the next definition start (`import`,
/// `const`, `pub`, `fn`, `type`, `opaque` or `@` in column 1) and goes on.
/// The first error diagnostic has the compiler's kind and span.
pub fn parse_module(
stream : @scanner.TokenStream,
dialect? : @dialect.Dialect,
) -> ParseResult {
let dialect = match dialect {
Some(d) => d
None => @dialect.Dialect::gleam_1_19()
}
let p = Parser::new(stream, dialect)
let definitions : Array[@ast.TargetedDefinition] = []
let cst_definitions : Array[@cst.DefinitionCst] = []
let parse_diagnostics : Array[@diagnostic.Diagnostic] = []
let mut first_error_lookahead : Int32? = None
fn report(e : ParseError) {
parse_diagnostics.push(e.to_diagnostic())
if first_error_lookahead is None {
first_error_lookahead = Some(p.farthest)
}
}
while p.index0 < p.tokens.length() {
let start_index = p.index0
p.depth = 0
let outcome = try p.parse_definition() catch {
e => Err(e)
} noraise {
d => Ok(d)
}
match outcome {
Ok(Some(targeted)) => {
let tokens = p.tokens[start_index:p.index0]
if definition_too_deep(targeted.definition) {
report(
ParseError(
error=TooMuchNesting,
location=targeted.definition.location(),
),
)
cst_definitions.push(
@cst.DefinitionCst::new(kind=Skipped, name=None, tokens~),
)
} else {
definitions.push(targeted)
let (kind, name) = cst_kind(targeted.definition)
cst_definitions.push(@cst.DefinitionCst::new(kind~, name~, tokens~))
}
}
Ok(None) => {
// Not a definition start: a stray token (reported as the compiler
// does) or a hole left by a lexical error (reported by the scanner).
if p.token0 is Some(t) {
report(
ParseError(
error=UnexpectedToken(
token=t.kind,
expected=["An import, const, type, or function."][:],
hint=None,
),
location=t.span,
),
)
}
p.recover(start_index, cst_definitions)
}
Err(e) => {
report(e)
p.recover(start_index, cst_definitions)
}
}
}
let warnings : Array[@ast.Warning] = p.warnings.copy()
for span in p.detached_doc_comments {
warnings.push(DetachedDocComment(location=span))
}
for w in warnings {
parse_diagnostics.push(warning_diagnostic(w))
}
let trivia = @cst.trivia_extra(stream)
let extra : @ast.ModuleExtra = {
..trivia,
trailing_commas: p.trailing_commas[:],
}
{
module_: { definitions: definitions[:], },
extra,
warnings: warnings[:],
cst: @cst.ModuleCst::new(stream, cst_definitions),
diagnostics: order_diagnostics(
stream.diagnostics,
parse_diagnostics,
first_error_lookahead,
p.farthest,
)[:],
}
}
///|
/// `parse_module` of the tokens of `text`.
pub fn parse_text(text : String, dialect? : @dialect.Dialect) -> ParseResult {
parse_module(@scanner.tokenize_text(text), dialect?)
}
///|
fn cst_kind(d : @ast.Definition) -> (@cst.DefinitionKind, String?) {
match d {
Function(f) => (Function, f.name.map(n => n.name))
TypeAlias(t) => (TypeAlias, Some(t.alias_))
CustomType(c) => (CustomType, Some(c.name))
Import(i) => (Import, Some(i.module_))
ModuleConstant(c) => (Constant, Some(c.name))
}
}
///|
/// Skip from the failed definition's first token to the next definition
/// start (or the end), record the run as a `Skipped` CST definition and
/// point the lookahead at the start.
fn Parser::recover(
self : Parser,
from : Int32,
cst_definitions : Array[@cst.DefinitionCst],
) -> Unit {
let mut i = from + 1
while i < self.tokens.length() && !self.at_definition_start(i) {
i += 1
}
cst_definitions.push(
@cst.DefinitionCst::new(kind=Skipped, name=None, tokens=self.tokens[from:i]),
)
// Doc comments inside the skipped run belong to the skipped definition,
// not to the next one.
if i > from {
let run_end = self.tokens[i - 1].span.end
while self.doc_head < self.doc_comments.length() &&
self.doc_comments[self.doc_head].0.start < run_end {
self.doc_head += 1
}
}
self.seek(i)
}
///|
/// The compiler's order: a lexical error that its lexer reached before the
/// parser stopped (start before `first_error_lookahead`) comes first; then
/// everything else by position, `UnexpectedEof` (span `0..0`) at the end
/// of the text.
fn order_diagnostics(
scanner : ArrayView[@diagnostic.Diagnostic],
parser : Array[@diagnostic.Diagnostic],
first_error_lookahead : Int32?,
text_end : Int32,
) -> Array[@diagnostic.Diagnostic] {
let first : Array[@diagnostic.Diagnostic] = []
let rest : Array[@diagnostic.Diagnostic] = []
for d in scanner {
match first_error_lookahead {
Some(lookahead) if d.span.start < lookahead => first.push(d)
_ => rest.push(d)
}
}
for d in parser {
rest.push(d)
}
fn key(d : @diagnostic.Diagnostic) -> Int32 {
if d.kind == "UnexpectedEof" {
text_end
} else {
d.span.start
}
}
// A stable sort by position: insertion into `first`.
for d in rest {
let mut i = first.length()
while i > 0 &&
key(first[i - 1]) > key(d) &&
!(first_error_lookahead is Some(l) &&
first[i - 1].code == "GL-SCAN-001" &&
first[i - 1].span.start < l) {
i -= 1
}
first.insert(i, d)
}
first
}
///|
/// One definition with its attributes; `None` when `token0` does not start
/// one (and no attribute was seen).
fn Parser::parse_definition(
self : Parser,
) -> @ast.TargetedDefinition? raise ParseError {
let attributes = Attributes::new()
let location = self.parse_attributes(attributes)
let def : @ast.Definition? = match (self.token0, self.token1) {
(Some({ kind: Keyword(Import), span, .. }), _) => {
self.advance()
self.parse_import(span.start)
}
(Some({ kind: Keyword(Const), span, .. }), _) => {
self.advance()
self.parse_module_const(span.start, false, attributes)
}
(Some({ kind: Keyword(Pub), span, .. }), Some({ kind: Keyword(Const), .. })) => {
self.advance()
self.advance()
self.parse_module_const(span.start, true, attributes)
}
(Some({ kind: Keyword(Fn), span, .. }), _) => {
self.advance()
self.parse_function(span.start, false, false, attributes)
}
(Some({ kind: Keyword(Pub), span, .. }), Some({ kind: Keyword(Fn), .. })) => {
self.advance()
self.advance()
self.parse_function(span.start, true, false, attributes)
}
(Some({ kind: Keyword(Type), span, .. }), _) => {
self.advance()
self.parse_custom_type(span.start, false, false, attributes)
}
(Some({ kind: Keyword(Pub), span, .. }), Some({ kind: Keyword(Opaque), .. })
) => {
self.advance()
self.advance()
let _ = self.expect_one(Keyword(Type))
self.parse_custom_type(span.start, true, true, attributes)
}
(Some({ kind: Keyword(Pub), span, .. }), Some({ kind: Keyword(Type), .. })) => {
self.advance()
self.advance()
self.parse_custom_type(span.start, true, false, attributes)
}
// A private opaque type is an analysis error, not a parse error.
(
Some({ kind: Keyword(Opaque), span, .. }),
Some({ kind: Keyword(Type), .. }),
) => {
self.advance()
self.advance()
self.parse_custom_type(span.start, false, true, attributes)
}
_ => None
}
match (def, location) {
(Some(definition), _) if definition is Function(_) ||
definition is CustomType(_) =>
Some({ definition, target: attributes.target, })
(Some(definition), None) => Some({ definition, target: attributes.target, })
(_, Some(location)) if attributes.has_function_only() =>
parse_error(ExpectedFunctionDefinition, location)
(Some(definition), _) => Some({ definition, target: attributes.target, })
(_, Some(location)) => parse_error(ExpectedDefinition, location)
(None, None) => None
}
}
///|
/// `@name(...)` attributes; their span, or `None` without any.
fn Parser::parse_attributes(
self : Parser,
attributes : Attributes,
) -> @span.SrcSpan? raise ParseError {
let mut attributes_span : @span.SrcSpan? = None
while self.maybe_one(At) is Some((start, end)) {
if attributes_span is None {
attributes_span = Some(sp(start, end))
}
let end = self.parse_attribute(start, attributes)
attributes_span = attributes_span.map(s => sp(s.start, end))
}
attributes_span
}
///|
fn Parser::parse_attribute(
self : Parser,
start : Int32,
attributes : Attributes,
) -> Int32 raise ParseError {
let (_, name, end) = self.expect_name(Attribute)
if !self.dialect.knows_attribute(name) {
parse_error(UnknownAttribute, sp(start, end))
}
match name {
"external" => {
if self.maybe_one(LeftParen) is None {
parse_error(ExpectedExternalArguments, sp(start, end))
}
self.parse_external_attribute(start, end, attributes)
}
"target" => self.parse_target_attribute(start, end, attributes)
"deprecated" => self.parse_deprecated_attribute(start, end, attributes)
"internal" => parse_internal_attribute(start, end, attributes)
_ => parse_error(UnknownAttribute, sp(start, end))
}
}
///|
fn Parser::parse_target_attribute(
self : Parser,
start : Int32,
end : Int32,
attributes : Attributes,
) -> Int32 raise ParseError {
let (paren_start, paren_end) = self.expect_one(LeftParen)
let target = self.expect_target(sp(paren_start, paren_end))
if attributes.target is Some(_) {
parse_error(DuplicateAttribute, sp(start, end))
}
let (_, close) = self.expect_one(RightParen)
attributes.target = Some(target)
close
}
///|
/// `erlang` or `javascript`; the shorthands `erl` and `js` with a
/// deprecation warning; `UnknownTarget` for another name;
/// `ExpectedTargetName` at the parenthesis for anything else.
fn Parser::expect_target(
self : Parser,
paren_location : @span.SrcSpan,
) -> @ast.Target raise ParseError {
match self.next_token() {
None => parse_error(UnexpectedEof, sp(0, 0))
Some({ kind: Name(name), span, .. }) =>
match name {
"js" => {
self.warn(DeprecatedTargetShorthand(location=span, target=JavaScript))
JavaScript
}
"erl" => {
self.warn(DeprecatedTargetShorthand(location=span, target=Erlang))
Erlang
}
_ =>
match @ast.Target::of(name) {
Some(t) if self.dialect.knows_target(name) => t
_ => parse_error(UnknownTarget, span)
}
}
Some(_) => parse_error(ExpectedTargetName, paren_location)
}
}
///|
fn Parser::parse_external_attribute(
self : Parser,
start : Int32,
end : Int32,
attributes : Attributes,
) -> Int32 raise ParseError {
let (_, target_name, _) = self.expect_name(Target)
let target : @ast.Target = match @ast.Target::of(target_name) {
Some(t) if self.dialect.knows_target(target_name) => t
_ => parse_error(UnknownTarget, sp(start, end))
}
let _ = self.expect_one(Comma)
let (_, module_, _) = self.expect_string()
let _ = self.expect_one(Comma)
let (_, function, _) = self.expect_string()
let _ = self.maybe_one(Comma)
let (_, close) = self.expect_one(RightParen)
if attributes.has_external_for(target) {
parse_error(DuplicateAttribute, sp(start, close))
}
attributes.set_external_for(target, {
module_,
function,
location: sp(start, close),
})
close
}
///|
fn Parser::parse_deprecated_attribute(
self : Parser,
start : Int32,
end : Int32,
attributes : Attributes,
) -> Int32 raise ParseError {
// As the compiler, `expect_one` and `expect_string` consume a token when
// they fail (its lexer then reads one token further); the error is
// replaced by `ExpectedDeprecationMessage`.
let _ = try self.expect_one(LeftParen) catch {
_ => parse_error(ExpectedDeprecationMessage, sp(start, end))
} noraise {
v => v
}
if attributes.deprecated is Deprecated(..) {
parse_error(DuplicateAttribute, sp(start, end))
}
let (_, message, _) = try self.expect_string() catch {
_ => parse_error(ExpectedDeprecationMessage, sp(start, end))
} noraise {
v => v
}
let (_, close) = self.expect_one(RightParen)
attributes.deprecated = Deprecated(message~)
close
}
///|
fn parse_internal_attribute(
start : Int32,
end : Int32,
attributes : Attributes,
) -> Int32 raise ParseError {
match attributes.internal {
Some(_) => parse_error(DuplicateAttribute, sp(start, end))
None => {
attributes.internal = Some(sp(start, end))
end
}
}
}