///|
/// The name the compiler gives a capture's argument (`CAPTURE_VARIABLE`).
let capture_variable : String = "_capture"
///|
/// A parsed call argument: a value, or a hole (`_`) that makes the call a
/// capture.
priv enum ParserArg {
Arg(@ast.CallArg[@ast.Expr])
Hole(
name~ : String,
arg_location~ : @span.SrcSpan,
discard_location~ : @span.SrcSpan,
label~ : String?
)
}
///|
/// A function head and body, after `fn` (and `pub`). `is_anon` for a
/// function literal: no name, no labels. Returns `Definition::Function`.
fn Parser::parse_function(
self : Parser,
start : Int32,
public : Bool,
is_anon : Bool,
attributes : Attributes,
) -> @ast.Definition? raise ParseError {
let documentation = if is_anon {
None
} else {
self.take_documentation(start)
}
let mut name : @ast.SpannedString? = None
if !is_anon {
let (name_start, n, name_end) = self.expect_name(Function)
name = Some({ location: sp(name_start, name_end), name: n, })
}
if self.maybe_one(Less) is Some((less_start, less_end)) {
parse_error(FunctionDefinitionAngleGenerics, sp(less_start, less_end))
}
let _ = try self.expect_one(LeftParen) catch {
e => raise add_anonymous_fn_hint(e)
} noraise {
v => v
}
let arguments = self.series_of(p => p.parse_fn_param(is_anon), Some(Comma))
let (_, rpar_e) = self.expect_one_following_series(
RightParen,
"a function parameter",
)
// TypeScript-style return type annotation
if self.maybe_one(Colon) is Some((colon_start, colon_end)) {
parse_error(
UnexpectedToken(
token=Colon,
expected=[token_text(RArrow)][:],
hint=Some("Return type annotations are written using `->`, not `:`"),
),
sp(colon_start, colon_end),
)
}
let return_annotation = self.parse_type_annotation(RArrow)
let (body_start, body, end, end_position) = match self.maybe_one(LeftBrace) {
Some((left_brace_start, _)) => {
let some_body = self.parse_statement_sequence()
let (_, right_brace_end) = self.expect_one(RightBrace)
let end = match return_annotation {
Some(t) => t.location().end
None => rpar_e
}
let body : Array[@ast.Statement] = match some_body {
None =>
[
Expression(
Todo(
kind=EmptyFunction(function_location=sp(start, end)),
location=sp(left_brace_start + 1, right_brace_end),
message=None,
),
),
]
Some((body, _)) => body
}
(Some(left_brace_start), body, end, right_brace_end)
}
None => (None, [], rpar_e, rpar_e)
}
let publicity = publicity(public, attributes.internal)
Some(
Function({
documentation,
location: sp(start, end),
end_position,
body_start,
publicity,
name,
arguments: arguments[:],
body: body[:],
return_annotation,
deprecation: attributes.deprecated,
external_erlang: attributes.external_erlang,
external_javascript: attributes.external_javascript,
}),
)
}
///|
/// `Public`, `Private` or `Internal`; `@internal` on a private definition
/// is `RedundantInternalAttribute` at the attribute.
fn publicity(
public : Bool,
internal : @span.SrcSpan?,
) -> @ast.Publicity raise ParseError {
match (internal, public) {
(None, true) => Public
(None, false) => Private
(Some(location), true) => Internal(attribute_location=Some(location))
(Some(location), false) => parse_error(RedundantInternalAttribute, location)
}
}
///|
/// One parameter: `_`, `a`, `label a`, `label _`, each with `: Type`.
fn Parser::parse_fn_param(
self : Parser,
is_anon : Bool,
) -> @ast.Arg? raise ParseError {
let (start, names, end0) : (Int32, @ast.ArgNames, Int32) = match
(self.token0, self.token1) {
// labelled discard
(
Some({ kind: Name(label), span: label_span, .. }),
Some({ kind: DiscardName(name), span: name_span, .. }),
) => {
if is_anon {
parse_error(UnexpectedLabel, label_span)
}
self.advance()
self.advance()
(
label_span.start,
LabelledDiscard(
name~,
name_location=name_span,
label~,
label_location=label_span,
),
name_span.end,
)
}
// discard
(Some({ kind: DiscardName(name), span, .. }), _) => {
self.advance()
(span.start, Discard(name~, location=span), span.end)
}
// labelled name
(
Some({ kind: Name(label), span: label_span, .. }),
Some({ kind: Name(name), span: name_span, .. }),
) => {
if is_anon {
parse_error(UnexpectedLabel, label_span)
}
self.advance()
self.advance()
(
label_span.start,
NamedLabelled(
name~,
name_location=name_span,
label~,
label_location=label_span,
),
name_span.end,
)
}
// name
(Some({ kind: Name(name), span, .. }), _) => {
self.advance()
(span.start, Named(name~, location=span), span.end)
}
_ => return None
}
let annotation = self.parse_type_annotation(Colon)
let end = match annotation {
Some(a) => a.location().end
None => end0
}
Some({ location: sp(start, end), names, annotation, })
}
///|
/// Call arguments without the parentheses.
fn Parser::parse_fn_arguments(
self : Parser,
) -> Array[ParserArg] raise ParseError {
self.series_of(p => p.parse_fn_argument(), Some(Comma))
}
///|
/// One call argument: `_`, `expr`, `a: _`, `a: expr`, `a:`.
fn Parser::parse_fn_argument(self : Parser) -> ParserArg? raise ParseError {
let label : (Int32, String, Int32)? = match (self.token0, self.token1) {
(
Some({ kind: Name(name), span, .. }),
Some({ kind: Colon, span: colon, .. }),
) => {
self.advance()
self.advance()
Some((span.start, name, colon.end))
}
_ => None
}
match self.parse_expression() {
Some(value) =>
match label {
Some((start, label, _)) =>
Some(
Arg({
implicit: None,
label: Some(label),
location: sp(start, value.location().end),
value,
}),
)
None =>
Some(
Arg({
implicit: None,
label: None,
location: value.location(),
value,
}),
)
}
None =>
match self.maybe_discard_name() {
Some((name_start, name, name_end)) =>
match label {
Some((label_start, label, _)) =>
Some(
Hole(
label=Some(label),
arg_location=sp(label_start, name_end),
discard_location=sp(name_start, name_end),
name~,
),
)
None =>
Some(
Hole(
label=None,
arg_location=sp(name_start, name_end),
discard_location=sp(name_start, name_end),
name~,
),
)
}
None =>
match label {
Some((start, label, end)) =>
Some(
Arg({
implicit: None,
label: Some(label),
location: sp(start, end),
value: Var(name=label, location=sp(start, end)),
}),
)
None => None
}
}
}
}
///|
/// A call, or a capture (`f(_, 1)`) when one argument is a hole.
fn make_call(
fun : @ast.Expr,
arguments : Array[ParserArg],
start : Int32,
end : Int32,
open_parenthesis : Int32,
) -> @ast.Expr raise ParseError {
let mut hole_location : @span.SrcSpan? = None
let call_arguments : Array[@ast.CallArg[@ast.Expr]] = []
for argument in arguments {
match argument {
Arg(arg) => call_arguments.push(arg)
Hole(name~, arg_location~, discard_location~, label~) => {
if hole_location is Some(_) {
parse_error(TooManyArgHoles, sp(start, end))
}
hole_location = Some(discard_location)
if name != "_" {
parse_error(
UnexpectedToken(
token=Name(name),
expected=["An expression", "An underscore"][:],
hint=None,
),
arg_location,
)
}
call_arguments.push({
implicit: None,
label,
location: arg_location,
value: Var(location=discard_location, name=capture_variable),
})
}
}
}
let call : @ast.Expr = Call(
location=sp(start, end),
fun~,
arguments=call_arguments[:],
open_parenthesis~,
)
match hole_location {
None => call
Some(hole) =>
Fn(
location=sp(start, end),
end_of_head_byte_index=end,
kind=Capture(hole~),
arguments=[
@ast.Arg::{
location: hole,
annotation: None,
names: Named(name=capture_variable, location=hole),
},
][:],
body=[@ast.Statement::Expression(call)][:],
return_annotation=None,
)
}
}
///|
/// The doc comments before `until` that no regular comment separates from
/// it, joined with `\n` after each line, and their start. A doc comment
/// with a regular comment between it and `until` is detached (a warning)
/// and dropped. Doc comments at or after `until` stay for the next
/// definition.
fn Parser::take_documentation(
self : Parser,
until : Int32,
) -> @ast.Documentation? {
let content = StringBuilder()
let mut doc_start = 0x7fffffff
while self.doc_head < self.doc_comments.length() {
let (span, line) = self.doc_comments[self.doc_head]
if span.start < doc_start {
doc_start = span.start
}
if span.start >= until {
break
}
if self.has_comment_between(span.start, until) {
// A doc comment before a regular comment is ignored.
self.doc_head += 1
self.detached_doc_comments.push(span)
continue
}
content.write_string(line)
content.write_string("\n")
self.doc_head += 1
}
let text = content.to_string()
if text == "" {
None
} else {
Some({ start: doc_start, text, })
}
}
///|
/// Whether a regular comment overlaps `start..end` (inclusive at both
/// ends), as `ModuleExtra::has_comment_between`.
fn Parser::has_comment_between(
self : Parser,
start : Int32,
end : Int32,
) -> Bool {
for c in self.comments {
if c.end >= start && c.start <= end {
return true
}
if c.start > end {
break
}
}
false
}