///|
/// `Elm.Parser.Expression.expression`.
fn expression(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
expression_above(c, 0, false)
}
///|
/// Precedence climbing as elm-syntax does it: after the left operand, take
/// operators whose precedence is above `min` (or equal when `or_equal`).
fn expression_above(
c : Cursor,
min : Int,
or_equal : Bool,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let mut left = application(c)
while c.positively_indented() {
guard c.peek() is Some({ kind: Operator(op), .. } as op_token) else {
break
}
guard c.dialect.operator(op) is Some({ precedence, direction, .. }) else {
raise c.error_at(
"unknown infix operator `\{op}`",
op_token.span,
"UNKNOWN OPERATOR",
"I do not recognize this operator:",
[
plain(
"Elm has a fixed set of operators; this is not one of them. Is there a typo?",
),
],
)
}
let accepted = if or_equal { precedence >= min } else { precedence > min }
if !accepted {
break
}
ignore(c.advance())
let right = match direction {
Left => expression_above(c, precedence, false)
Right => c.chain(() => expression_above(c, precedence, true))
Non => non_associative_right(c, precedence)
}
left = node(
combine(left.range, right.range),
OperatorApplication(op, direction, left, right),
)
}
left
}
///|
/// Right operand of a non-associative operator: higher precedence only; an
/// operator of the same precedence is an error.
fn non_associative_right(
c : Cursor,
precedence : Int,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let right = expression_above(c, precedence, false)
if c.positively_indented() &&
c.peek() is Some({ kind: Operator(op), .. } as t) &&
c.dialect.operator(op) is Some({ precedence: p, .. }) &&
p == precedence {
raise c.error_at(
"cannot mix non-associative infix operators without parentheses",
t.span,
"INFIX PROBLEM",
"You cannot mix these two operators without parentheses:",
[
plain(
"They do not associate, so I cannot tell which one goes first. Add parentheses to say what you mean.",
),
],
)
}
right
}
///|
/// A sub-expression followed by positively indented arguments.
fn application(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
let head = sub_expression(c)
let args = [head]
while c.positively_indented() && starts_argument(c) {
args.push(sub_expression(c))
}
if args.length() == 1 {
head
} else {
node(combine(head.range, args.last().unwrap().range), Application(args))
}
}
///|
/// Whether a `-` at the cursor is a negation: the character before it is a
/// space, `(`, `)`, `}` or `,` (or nothing), and the operand touches it.
fn negation_here(c : Cursor) -> Bool {
guard c.peek() is Some({ kind: Operator("-"), .. } as minus) else {
return false
}
let before = match minus.trivia_before.last() {
Some(Whitespace(_, _)) => Some(' ')
Some(Newline(_, _)) => Some('\n')
Some(Comment(comment)) => comment.text.to_array().last()
None =>
match c.previous() {
Some(prev) => prev.lexeme.to_array().last()
None => None
}
}
let allowed = match before {
None | Some(' ' | '(' | ')' | '}' | ',') => true
_ => false
}
allowed &&
(match c.peek_at(1) {
Some(next) => touching(minus, next) && next.trivia_before.is_empty()
None => false
})
}
///|
fn starts_argument(c : Cursor) -> Bool {
guard c.peek() is Some(t) else { return false }
match t.kind {
StringLiteral
| CharLiteral
| IntLiteral
| FloatLiteral
| Glsl
| LParen
| LBracket
| LBrace
| Backslash
| Identifier
| Keyword(Case | Let | If) => true
Dot => c.peek_at(1) is Some(n) && is_lower(n) && touching(t, n)
Operator("-") => negation_here(c)
_ => false
}
}
///|
fn sub_expression(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
c.nested(() => sub_expression_at_depth(c))
}
///|
fn sub_expression_at_depth(
c : Cursor,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
guard c.peek() is Some(t) else { raise c.fail("an expression") }
match t.kind {
StringLiteral => {
ignore(c.advance())
token_node(t, Literal(string_value(t, c.dialect)))
}
CharLiteral => {
ignore(c.advance())
token_node(t, CharLiteral(char_value(t, c.dialect)))
}
IntLiteral => {
ignore(c.advance())
weird_number_check(c, t)
match int_value(c, t) {
Ok(n) => token_node(t, Integer(n))
Err(n) => token_node(t, Hex(n))
}
}
FloatLiteral => {
ignore(c.advance())
weird_number_check(c, t)
token_node(t, Floatable(float_value(c, t)))
}
Glsl => {
ignore(c.advance())
let text = t.lexeme
let source = text.unsafe_substring(start=6, end=text.length() - 2)
// elm-syntax keeps an elm/parser quirk: the range ends two columns late.
let range = range_of(t)
node(
{ start: range.start, end: shift(range.end, 2), },
GLSLExpression(source),
)
}
LParen => c.within(Parens, () => parens_expression(c))
LBracket => c.within(List, () => list_expression(c))
LBrace => c.within(Record, () => record_expression(c))
Keyword(Case) => c.within(Case, () => case_expression(c))
Keyword(Let) => c.within(Let, () => let_expression(c))
Keyword(If) => c.within(If, () => if_expression(c))
Backslash => c.within(Lambda, () => lambda_expression(c))
Dot => {
let dot = c.advance()
if !(c.at_lower() && c.next_touches()) {
raise c.fail("a field name after `.`")
}
let field = c.advance()
node(range_from(dot, field), RecordAccessFunction("." + field.lexeme))
}
Operator("-") => {
if !negation_here(c) {
raise c.fail("an expression")
}
let minus = c.advance()
let operand = sub_expression(c)
node(
{ start: location(minus.span.start), end: operand.range.end, },
Negation(operand),
)
}
Identifier if is_lower(t) => {
ignore(c.advance())
record_accesses(
c,
token_node(t, @ast.Expression::FunctionOrValue([], t.lexeme)),
)
}
Identifier => qualified_reference(c)
_ => raise c.fail("an expression")
}
}
///|
/// A number followed by a touching `.` with nothing after it, like `1.`.
fn weird_number_check(
c : Cursor,
number : @scanner.Token,
) -> Unit raise SyntaxError {
let text = number.lexeme
let is_decimal = !text.has_prefix("0x")
if c.dialect.has(LeadingZero) &&
(text.has_prefix("0e") || text.has_prefix("0E")) {
raise c.error_at(
"a number cannot start with 0 before an exponent [rule: leading-zero]",
{
start: shift_position(number.span.start, 1),
end: shift_position(number.span.start, 1),
},
"WEIRD NUMBER",
"I thought I was reading a number, but I ran into some weird stuff here:",
[
plain("I recognize numbers in the following formats:"),
Example("42\n3.14\n6.022e23\n0x002B"),
plain("So is there a way to write it like one of those?"),
],
)
}
if c.dialect.has(LeadingZero) &&
is_decimal &&
text.length() > 1 &&
text.has_prefix("0") &&
!text.has_prefix("0.") &&
!text.has_prefix("0e") &&
!text.has_prefix("0E") {
raise c.error_at(
"numbers cannot start with zeros [rule: leading-zero]",
{
start: shift_position(number.span.start, 1),
end: shift_position(number.span.start, 1),
},
"LEADING ZEROS",
"I do not accept numbers with leading zeros:",
[plain("Just delete the leading zeros and it should work!")],
)
}
if c.peek() is Some({ kind: Identifier, lexeme: name, .. } as next) &&
touching(number, next) {
let first = name.get_char(0).unwrap_or(' ')
if c.dialect.has(EmptyHex) && text == "0" && first == 'x' {
raise c.error_at(
"expected hex digits after `0x` [rule: empty-hex]",
{ start: next.span.start, end: next.span.start, },
"WEIRD HEXIDECIMAL",
"I thought I was reading a hexidecimal number until I got here:",
[
Text([
Plain(
"Valid hexidecimal digits include 0123456789abcdefABCDEF, so I can only recognize things like this:",
),
]),
Example("0x2B\n0x002B\n0x00ffb3"),
],
)
}
let weird = (
c.dialect.has(UppercaseHexPrefix) && text == "0" && first == 'X'
) ||
(c.dialect.has(ExponentWithoutDigits) && (first == 'e' || first == 'E'))
if weird {
raise c.error_at(
"unexpected characters after a number",
{ start: next.span.start, end: next.span.start, },
"WEIRD NUMBER",
"I thought I was reading a number, but I ran into some weird stuff here:",
[
plain("I recognize numbers in the following formats:"),
Example("42\n3.14\n6.022e23\n0x002B"),
plain("So is there a way to write it like one of those?"),
],
)
}
}
if c.peek() is Some({ kind: Dot, .. } as dot) &&
touching(number, dot) &&
!(c.peek_at(1) is Some(n) && touching(dot, n) && is_lower(n)) {
raise c.error_at(
"a number cannot end with a dot",
dot.span,
"WEIRD NUMBER",
"I thought I was reading a number, but I ran into some weird stuff here:",
[
Text([
Plain("Floats need digits after the dot, like "),
@scanner.Chunk::code("1.0"),
Plain("."),
]),
],
)
}
}
///|
/// `.field` accesses that touch the expression before them.
fn record_accesses(
c : Cursor,
base : @ast.Node[@ast.Expression],
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let mut left = base
while c.dotted_name(is_lower) is Some(_) {
ignore(c.advance())
let field = c.advance()
let field_node = token_node(field, field.lexeme)
left = node(
combine(left.range, field_node.range),
RecordAccess(left, field_node),
)
}
left
}
///|
/// `Module.Name`, `Module.value`, `Name`, then record accesses.
fn qualified_reference(
c : Cursor,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let first = c.upper("a name")
let modules = [first.lexeme]
let mut last = first
let mut lower_name : String? = None
while c.dotted_name(n => is_upper(n) || is_lower(n)) is Some(n) {
if is_upper(n) {
ignore(c.advance())
last = c.advance()
modules.push(last.lexeme)
} else {
ignore(c.advance())
last = c.advance()
lower_name = Some(last.lexeme)
break
}
}
let reference = match lower_name {
Some(name) =>
node(
range_from(first, last),
@ast.Expression::FunctionOrValue(modules, name),
)
None => {
let name = modules.pop().unwrap()
node(
range_from(first, last),
@ast.Expression::FunctionOrValue(modules, name),
)
}
}
if lower_name is Some(_) {
record_accesses(c, reference)
} else {
reference
}
}
///|
fn parens_expression(
c : Cursor,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let open = c.advance()
if c.at_kind(RParen) && c.next_touches() {
let close = c.advance()
return node(range_from(open, close), UnitExpr)
}
if c.peek() is Some({ kind: Operator(op), .. } as op_token) &&
touching(open, op_token) &&
c.peek_at(1) is Some({ kind: RParen, .. } as close) &&
touching(op_token, close) {
ignore(c.advance())
ignore(c.advance())
return node(range_from(open, close), PrefixOperator(op))
}
if c.peek() is Some({ kind: Operator(_), .. } as op_token) &&
touching(open, op_token) &&
!negation_here(c) {
ignore(c.advance())
raise c.fail_titled(
"expected `)` after the operator",
"UNFINISHED OPERATOR FUNCTION",
[
Plain("I was parsing an operator function like "),
@scanner.Chunk::code("(+)"),
Plain(", but I got stuck here:"),
],
[plain("I was expecting a closing parenthesis right after the operator.")],
)
}
let first = expression(c)
if c.at_kind(RParen) {
let close = c.advance()
return record_accesses(
c,
node(range_from(open, close), ParenthesizedExpression(first)),
)
}
ignore(c.expect(Comma, "`,` or `)`"))
let parts = [first, expression(c)]
if c.at_kind(Comma) {
ignore(c.advance())
parts.push(expression(c))
}
let close = c.expect(RParen, "`)`")
node(range_from(open, close), TupledExpression(parts))
}
///|
fn list_expression(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
let open = c.advance()
let items = []
if !c.at_kind(RBracket) {
items.push(expression(c))
while c.at_kind(Comma) {
ignore(c.advance())
items.push(expression(c))
}
}
let close = c.expect(RBracket, "`,` or `]`")
node(range_from(open, close), ListExpr(items))
}
///|
/// A record setter whose range ends where the next token starts (elm-syntax
/// includes the trailing whitespace).
fn record_setter(c : Cursor) -> @ast.Node[@ast.RecordSetter] raise SyntaxError {
let field = c.lower("a field name")
ignore(c.expect(Equals, "`=`"))
let value = expression(c)
let end = match c.peek() {
Some(next) => location(next.span.start)
None => value.range.end
}
node({ start: location(field.span.start), end, }, {
field: token_node(field, field.lexeme),
expression: value,
})
}
///|
fn record_expression(
c : Cursor,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let open = c.advance()
if c.at_kind(RBrace) {
let close = c.advance()
return record_accesses(c, node(range_from(open, close), RecordExpr([])))
}
if c.at_lower() && c.peek_at(1) is Some({ kind: Pipe, .. }) {
let name = c.advance()
ignore(c.advance())
let setters = [record_setter(c)]
while c.at_kind(Comma) {
ignore(c.advance())
setters.push(record_setter(c))
}
let close = c.expect(RBrace, "`,` or `}`")
return record_accesses(
c,
node(
range_from(open, close),
RecordUpdateExpression(token_node(name, name.lexeme), setters),
),
)
}
let first_name = c.lower("a field name")
ignore(c.expect(Equals, "`=` or `|`"))
let first_value = expression(c)
// elm-syntax quirk: the first setter ends at its value; later setters end
// where the next token starts (see record_setter).
let setters : Array[@ast.Node[@ast.RecordSetter]] = [
node(combine(range_of(first_name), first_value.range), {
field: token_node(first_name, first_name.lexeme),
expression: first_value,
}),
]
while c.at_kind(Comma) {
ignore(c.advance())
setters.push(record_setter(c))
}
let close = c.expect(RBrace, "`,` or `}`")
record_accesses(c, node(range_from(open, close), RecordExpr(setters)))
}
///|
fn lambda_expression(
c : Cursor,
) -> @ast.Node[@ast.Expression] raise SyntaxError {
let backslash = c.advance()
let args = [argument_pattern(c)]
while !c.at_kind(Arrow) {
args.push(argument_pattern(c))
}
// The loop stopped at `->`.
ignore(c.advance())
let body = expression(c)
node(
{ start: location(backslash.span.start), end: body.range.end, },
LambdaExpression({ args, expression: body, }),
)
}
///|
fn if_expression(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
let if_token = c.advance()
let condition = expression(c)
ignore(c.expect(Keyword(Then), "`then`"))
let then_branch = expression(c)
ignore(c.expect(Keyword(Else), "`else`"))
let else_branch = expression(c)
node(
{ start: location(if_token.span.start), end: else_branch.range.end, },
IfBlock(condition, then_branch, else_branch),
)
}
///|
fn case_branch(c : Cursor) -> @ast.Case raise SyntaxError {
c.start_item()
let pat = pattern(c)
ignore(c.expect(Arrow, "`->`"))
{ pattern: pat, expression: expression(c), }
}
///|
fn case_expression(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
let case_token = c.advance()
let subject = expression(c)
ignore(c.expect(Keyword(Of), "`of`"))
guard c.peek() is Some(first) else { raise c.fail("a case branch") }
let column = first.span.start.column
if c.dialect.has(IndentedContinuation) && column <= c.indent {
c.layout_failure = true
let error = c.fail("a case branch indented more")
c.layout_failure = false
raise error
}
let cases = c.with_indent(column, () => {
let cases = [case_branch(c)]
// A token at the branch column that cannot start a pattern (for example
// an operator) ends the branches, as in elm-syntax.
while c.peek() is Some(t) &&
t.span.start.column == column &&
starts_pattern(c) {
cases.push(case_branch(c))
}
cases
})
let end = cases.last().unwrap().expression.range.end
node(
{ start: location(case_token.span.start), end, },
CaseExpression({ expression: subject, cases, }),
)
}
///|
fn let_expression(c : Cursor) -> @ast.Node[@ast.Expression] raise SyntaxError {
let let_token = c.advance()
guard c.peek() is Some(first) else { raise c.fail("a let declaration") }
let column = first.span.start.column
if c.dialect.has(LetDeclarationColumn) &&
column <= let_token.span.start.column {
c.layout_failure = true
let error = c.fail("a let declaration indented more than `let`")
c.layout_failure = false
raise error
}
let declarations = c.with_indent(column, () => {
let declarations = [let_declaration(c)]
while !c.is_keyword(In) {
guard c.peek() is Some(t) && t.span.start.column == column else {
raise c.fail("`in` or a let declaration")
}
declarations.push(let_declaration(c))
}
declarations
})
ignore(c.expect(Keyword(In), "`in`"))
let body = expression(c)
node(
{ start: location(let_token.span.start), end: body.range.end, },
LetExpression({ declarations, expression: body, }),
)
}
///|
fn let_declaration(
c : Cursor,
) -> @ast.Node[@ast.LetDeclaration] raise SyntaxError {
c.start_item()
if c.peek() is Some(t) && is_lower(t) {
let f = c.within(Definition(t.lexeme), () => function_parts(c, None))
node(f.range, LetFunction(f.value))
} else {
let pat = argument_pattern(c)
ignore(c.expect(Equals, "`=`"))
let value = expression(c)
node(combine(pat.range, value.range), LetDestructuring(pat, value))
}
}
///|
/// A function: `name : type` on its own line (optional), then
/// `name args = expression`. The range starts at `start` (the doc comment)
/// or at the first name.
fn function_parts(
c : Cursor,
documentation : @ast.Node[String]?,
) -> @ast.Node[@ast.Function] raise SyntaxError {
let start_name = c.lower("a function name")
let mut signature : @ast.Node[@ast.Signature]? = None
let mut name = start_name
if c.at_kind(Colon) {
ignore(c.advance())
let annotation = type_annotation(c)
guard c.peek() is Some(impl_name) &&
is_lower(impl_name) &&
impl_name.span.start.column == c.indent else {
raise c.fail("the implementation of `\{start_name.lexeme}`")
}
c.start_item()
if impl_name.lexeme != start_name.lexeme {
raise c.error_at(
"expected to find the same name for declaration and signature",
impl_name.span,
"NAME MISMATCH",
"I just saw a type annotation for `\{start_name.lexeme}`, but it is followed by a definition for `\{impl_name.lexeme}`:",
[plain("These names do not match! Is there a typo?")],
)
}
ignore(c.advance())
signature = Some(
node(combine(range_of(start_name), annotation.range), {
name: token_node(start_name, start_name.lexeme),
type_annotation: annotation,
}),
)
name = impl_name
}
let arguments = []
while !c.at_kind(Equals) {
arguments.push(argument_pattern(c))
}
ignore(c.advance())
let body = expression(c)
let implementation : @ast.Node[@ast.FunctionImplementation] = node(
{ start: location(name.span.start), end: body.range.end, },
{ name: token_node(name, name.lexeme), arguments, expression: body, },
)
let start = match documentation {
Some(doc) => doc.range.start
None => location(start_name.span.start)
}
node({ start, end: body.range.end, }, {
documentation,
signature,
declaration: implementation,
})
}