///|
/// A custom type or a type alias, with `type` (and `pub`, `opaque`)
/// consumed.
fn Parser::parse_custom_type(
self : Parser,
start : Int32,
public : Bool,
opaque_ : Bool,
attributes : Attributes,
) -> @ast.Definition? raise ParseError {
let documentation = self.take_documentation(start)
let (name_start, name, parameters, end, name_end) = self.expect_type_name()
let name_location = sp(name_start, name_end)
let (constructors, end_position) : (Array[@ast.RecordConstructor], Int32) = match
self.token0 {
// `type Wibble {`: a custom type
Some({ kind: LeftBrace, .. }) => {
self.advance()
// A lower-case name here is a syntax error with a helpful message.
if self.token0 is Some({ kind: Name(_), span, .. }) {
raise self.invalid_record_constructor_error(
name,
public,
opaque_,
span.start,
span.end,
)
}
let constructors = self.series_of(p => p.parse_record_constructor(), None)
let close_end = self.expect_custom_type_close()
(constructors, close_end)
}
// `type Wibble =`: a type alias
Some({ kind: Equal, span: equal, .. }) => {
self.advance()
if opaque_ {
parse_error(OpaqueTypeAlias, sp(start, end))
}
match self.parse_type() {
Some(type_ast) => {
let publicity = publicity(public, attributes.internal)
return Some(
TypeAlias({
documentation,
location: sp(start, type_ast.location().end),
publicity,
alias_: name,
name_location,
parameters: parameters[:],
type_ast,
deprecation: attributes.deprecated,
}),
)
}
None => parse_error(ExpectedType, equal)
}
}
_ => ([], end)
}
let publicity = publicity(public, attributes.internal)
Some(
CustomType({
documentation,
location: sp(start, end),
end_position,
publicity,
opaque_,
name,
name_location,
parameters: parameters[:],
constructors: constructors[:],
deprecation: attributes.deprecated,
external_erlang: attributes.external_erlang,
external_javascript: attributes.external_javascript,
}),
)
}
///|
/// One variant: attributes (only `@deprecated` is allowed), an upper-case
/// name and optional `(fields)`.
fn Parser::parse_record_constructor(
self : Parser,
) -> @ast.RecordConstructor? raise ParseError {
let attributes = Attributes::new()
let attr_loc = self.parse_attributes(attributes)
if attr_loc is Some(attr_span) {
if attributes.external_erlang is Some(_) ||
attributes.external_javascript is Some(_) ||
attributes.target is Some(_) ||
attributes.internal is Some(_) {
parse_error(UnknownAttributeRecordVariant, attr_span)
}
}
match self.maybe_upname() {
Some((name_start, constructor_name, name_end)) => {
let documentation = self.take_documentation(name_start)
let (arguments, arguments_end) = self.parse_record_constructor_arguments()
Some({
location: sp(
name_start,
if arguments_end > name_end {
arguments_end
} else {
name_end
},
),
name_location: sp(name_start, name_end),
name: constructor_name,
arguments: arguments[:],
documentation,
deprecation: attributes.deprecated,
})
}
None => None
}
}
///|
/// A lower-case name where a variant was expected: `ExpectedRecordConstructor`
/// when the type looks like a record (fields, then `}`), otherwise
/// `IncorrectUpName`.
fn Parser::invalid_record_constructor_error(
self : Parser,
type_name : String,
public : Bool,
opaque_ : Bool,
name_start : Int32,
name_end : Int32,
) -> ParseError {
let fields = try
self.series_of(p => p.parse_record_constructor_field(), Some(Comma))
catch {
_ => None
} noraise {
f => Some(f)
}
match fields {
Some(fields) if self.token0 is Some({ kind: RightBrace, .. }) => {
let start = if fields.length() > 0 {
fields[0].location.start
} else {
name_start
}
let end = if fields.length() > 0 {
fields[fields.length() - 1].location.end
} else {
name_end
}
ParseError(
error=ExpectedRecordConstructor(
type_name~,
public~,
opaque_~,
fields=fields[:],
),
location=sp(start, end),
)
}
_ => ParseError(error=IncorrectUpName, location=sp(name_start, name_end))
}
}
///|
/// `(field, label: Type, …)` of a variant; `end` is 0 with no parentheses.
fn Parser::parse_record_constructor_arguments(
self : Parser,
) -> (Array[@ast.RecordConstructorArg], Int32) raise ParseError {
if self.maybe_one(LeftParen) is Some(_) {
let arguments = self.series_of(
p => p.parse_record_constructor_field(),
Some(Comma),
)
let (_, end) = self.expect_one_following_series(
RightParen,
"a constructor argument name",
)
(arguments, end)
} else {
([], 0)
}
}
///|
/// `label: Type` or `Type`, with the doc comment before it.
fn Parser::parse_record_constructor_field(
self : Parser,
) -> @ast.RecordConstructorArg? raise ParseError {
match (self.token0, self.token1) {
(
Some({ kind: Name(name), span, .. }),
Some({ kind: Colon, span: colon, .. }),
) => {
self.advance()
self.advance()
let doc = self.take_documentation(span.start)
match self.parse_type() {
Some(type_ast) =>
Some({
label: Some({ location: span, name, }),
ast: type_ast,
location: sp(span.start, type_ast.location().end),
doc,
})
None => parse_error(ExpectedType, sp(span.start, colon.end))
}
}
_ =>
match self.parse_type() {
Some(type_ast) => {
let doc = match self.token0 {
Some(t) => self.take_documentation(t.span.start)
None => None
}
Some({
label: None,
ast: type_ast,
location: type_ast.location(),
doc,
})
}
None => None
}
}
}
///|
/// `Name` or `Name(a, b)`: start, name, parameters, end after `)` (or the
/// name), and the name's end.
fn Parser::expect_type_name(
self : Parser,
) -> (Int32, String, Array[@ast.SpannedString], Int32, Int32) raise ParseError {
let (start, upname, end) = self.expect_upname()
match self.maybe_one(LeftParen) {
Some((par_s, _)) => {
let arguments = self.series_of(p => p.maybe_name(), Some(Comma))
let (_, par_e) = self.expect_one_following_series(RightParen, "a name")
if arguments.length() == 0 {
parse_error(TypeDefinitionNoArguments, sp(par_s, par_e))
}
let parameters = arguments.map(a => @ast.SpannedString::{
location: sp(a.0, a.2),
name: a.1,
})
(start, upname, parameters, par_e, end)
}
None =>
match self.maybe_one(Less) {
Some((less_start, less_end)) => {
// Names or upnames are accepted in this error path; the example in
// the message uses lower case.
let arguments = self.series_of(
p => {
match p.maybe_name() {
Some(n) => Some(n.1.to_lower())
None => p.maybe_upname().map(n => n.1.to_lower())
}
},
Some(Comma),
)
let arguments = if arguments.length() == 0 {
["value"]
} else {
arguments
}
parse_error(
TypeDefinitionAngleGenerics(name=upname, arguments=arguments[:]),
sp(less_start, less_end),
)
}
None => (start, upname, [], end, end)
}
}
}
///|
/// The `}` of a custom type, or `UnexpectedToken` with a hint when a
/// function follows (Gleam has no methods).
fn Parser::expect_custom_type_close(self : Parser) -> Int32 raise ParseError {
match self.maybe_one(RightBrace) {
Some((_, end)) => end
None =>
match self.next_token() {
None => parse_error(UnexpectedEof, sp(0, 0))
Some(t) => {
let hint = match (t.kind, self.token0) {
(Keyword(Fn), _)
| (Keyword(Pub), Some({ kind: Keyword(Fn), .. })) =>
Some(
@scanner.wrap(
"Gleam is not an object oriented programming language so\nfunctions are declared separately from types.",
),
)
_ => None
}
parse_error(
UnexpectedToken(
token=t.kind,
expected=[token_text(RightBrace), "a record constructor"][:],
hint~,
),
t.span,
)
}
}
}
}