mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 04:08:19 +00:00
Merge branch 'main' into rust1_65
Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>
This commit is contained in:
commit
bbf35af8fa
179 changed files with 7089 additions and 3846 deletions
|
@ -1,384 +0,0 @@
|
|||
app "generator"
|
||||
packages {
|
||||
pf: "https://github.com/roc-lang/basic-cli/releases/download/0.1.3/5SXwdW7rH8QAOnD71IkHcFxCmBEPtFSLAIkclPEgjHQ.tar.br",
|
||||
}
|
||||
imports [
|
||||
pf.Stdout,
|
||||
pf.File,
|
||||
pf.Path,
|
||||
pf.Task.{ Task },
|
||||
]
|
||||
|
||||
provides [main] to pf
|
||||
|
||||
RenderTree : [
|
||||
Text Str,
|
||||
Items (List RenderTree),
|
||||
Line (List RenderTree),
|
||||
Indent (List RenderTree),
|
||||
Import {modu: List Str, name: Str},
|
||||
]
|
||||
|
||||
renderFile : RenderTree -> Str
|
||||
renderFile = \tree ->
|
||||
render (Items [
|
||||
formatImports (findImports tree),
|
||||
tree
|
||||
])
|
||||
|
||||
findImports : RenderTree -> Set { modu: (List Str), name: Str }
|
||||
findImports = \tree ->
|
||||
when tree is
|
||||
Text _ -> Set.empty
|
||||
Items list | Indent list | Line list ->
|
||||
List.walk list Set.empty \acc, item -> Set.union acc (findImports item)
|
||||
Import import -> Set.single import
|
||||
|
||||
formatImports : Set { modu: (List Str), name: Str } -> RenderTree
|
||||
formatImports = \set ->
|
||||
if hasDupImports set then
|
||||
crash "Duplicate imports!"
|
||||
else
|
||||
Items (
|
||||
set
|
||||
|> Set.toList
|
||||
# TODO: Sort by module name
|
||||
|> List.map \{ modu, name } ->
|
||||
Line [
|
||||
Text "use ",
|
||||
Text (Str.joinWith (List.map modu \m -> Str.concat m "::") ""),
|
||||
Text name,
|
||||
Text ";",
|
||||
]
|
||||
)
|
||||
|
||||
hasDupImports : Set { modu: (List Str), name: Str } -> Bool
|
||||
hasDupImports = \set ->
|
||||
nameSet =
|
||||
set
|
||||
|> Set.toList
|
||||
|> List.map \{ modu: _, name } -> name
|
||||
|> Set.fromList
|
||||
|
||||
Set.len nameSet != Set.len nameSet
|
||||
|
||||
render : RenderTree -> Str
|
||||
render = \tree ->
|
||||
Tuple text _ = renderInner tree 0 Bool.true
|
||||
text
|
||||
|
||||
renderGroup : List RenderTree, Nat, Bool -> [Tuple Str Bool]
|
||||
renderGroup = \list, indent, newlineBefore ->
|
||||
List.walk list (Tuple "" newlineBefore) \(Tuple text nlb), item ->
|
||||
Tuple ntext nla = renderInner item indent nlb
|
||||
(Tuple
|
||||
(Str.concat text ntext)
|
||||
nla
|
||||
)
|
||||
|
||||
|
||||
renderInner : RenderTree, Nat, Bool -> [Tuple Str Bool]
|
||||
renderInner = \tree, indent, newlineBefore ->
|
||||
when tree is
|
||||
Text text ->
|
||||
result = if newlineBefore then
|
||||
Str.concat (Str.repeat " " (4*indent)) text
|
||||
else
|
||||
text
|
||||
Tuple result Bool.false
|
||||
Items list -> renderGroup list indent newlineBefore
|
||||
Line list ->
|
||||
Tuple ntext nla = renderGroup list indent Bool.true
|
||||
res = if newlineBefore then
|
||||
# Already added the newline, no need!
|
||||
ntext
|
||||
else
|
||||
Str.concat "\n" ntext
|
||||
res2 = if nla then
|
||||
res
|
||||
else
|
||||
Str.concat res "\n"
|
||||
(Tuple res2 Bool.true)
|
||||
Indent list -> renderGroup list (indent + 1) newlineBefore
|
||||
Import {modu: _, name} ->
|
||||
Tuple name Bool.false
|
||||
|
||||
parserTrait = \t, e ->
|
||||
genCall (Import {modu: ["crate", "parser"], name: "Parser"}) [Text "'a", t, e]
|
||||
|
||||
parseFunction : Str, RenderTree, RenderTree, RenderTree -> RenderTree
|
||||
parseFunction = \name, ty, err, body ->
|
||||
Items [
|
||||
Line [
|
||||
Text "pub fn \(name)<'a>() -> impl ",
|
||||
parserTrait ty err,
|
||||
Text " {",
|
||||
],
|
||||
Indent [body, Line [Text ".trace(\"\(name)\")" ] ],
|
||||
Line [Text "}"],
|
||||
Line [Text ""],
|
||||
]
|
||||
|
||||
Type : RenderTree
|
||||
ErrTy : RenderTree
|
||||
|
||||
Parser : [
|
||||
Loc Parser,
|
||||
Specialize ErrTy Parser,
|
||||
Record Str (List { name: Str, parser: Parser }),
|
||||
Builtin RenderTree Type,
|
||||
# Named ParserName (World -> Parser),
|
||||
]
|
||||
|
||||
errHeader : Str -> ErrTy
|
||||
errHeader = \name ->
|
||||
Items [
|
||||
Import { modu: ["crate", "parser"], name: "EHeader" },
|
||||
Text "::",
|
||||
Text name,
|
||||
]
|
||||
|
||||
fnCall : RenderTree, List RenderTree -> RenderTree
|
||||
fnCall = \fnName, args ->
|
||||
Items [
|
||||
fnName,
|
||||
Text "(",
|
||||
Items (List.intersperse args (Text ",")),
|
||||
Text ")",
|
||||
]
|
||||
|
||||
fn : RenderTree -> (List RenderTree -> RenderTree)
|
||||
fn = \fnName -> \args -> fnCall fnName args
|
||||
|
||||
genCall : RenderTree, List RenderTree -> RenderTree
|
||||
genCall = \genName, args ->
|
||||
Items [
|
||||
genName,
|
||||
Text "<",
|
||||
Items (List.intersperse args (Text ", ")),
|
||||
Text ">",
|
||||
]
|
||||
|
||||
gen : RenderTree -> (List RenderTree -> RenderTree)
|
||||
gen = \genName -> \args -> genCall genName args
|
||||
|
||||
ref : RenderTree -> RenderTree
|
||||
ref = \name -> Items [Text "&'a ", name]
|
||||
|
||||
slice : RenderTree -> RenderTree
|
||||
slice = \name -> Items [Text "[", name, Text "]"]
|
||||
|
||||
refSlice : RenderTree -> RenderTree
|
||||
refSlice = \name -> ref (slice name)
|
||||
|
||||
commentOrNewline = genCall (Import {modu: ["crate", "ast"], name: "CommentOrNewline"}) [ Text "'a" ]
|
||||
|
||||
exposedName = genCall (Import {modu: ["crate", "header"], name: "ExposedName"}) [ Text "'a" ]
|
||||
importsEntry = genCall (Import {modu: ["crate", "header"], name: "ImportsEntry"}) [ Text "'a" ]
|
||||
uppercaseIdent = genCall (Import {modu: ["crate", "ident"], name: "UppercaseIdent"}) [ Text "'a" ]
|
||||
|
||||
moduleName = genCall (Import {modu: ["crate", "header"], name: "ModuleName"}) [ Text "'a" ]
|
||||
|
||||
space0E = fn (Import { modu: ["crate", "blankspace"], name: "space0_e" })
|
||||
|
||||
keyword = \keywordName ->
|
||||
Import { modu: ["crate", "header"], name: keywordName }
|
||||
|
||||
spaces = \errorName ->
|
||||
Builtin (space0E [errorName]) (refSlice commentOrNewline)
|
||||
|
||||
loc = gen (Import {modu: ["roc_region", "all"], name: "Loc"})
|
||||
|
||||
keywordItem = \kw, ty ->
|
||||
genCall (Import {modu: ["crate", "header"], name: "KeywordItem"}) [ Text "'a", kw, ty ]
|
||||
|
||||
collection = \ty ->
|
||||
genCall (Import {modu: ["crate", "ast"], name: "Collection"}) [ Text "'a", ty ]
|
||||
|
||||
spaced = \ty ->
|
||||
genCall (Import {modu: ["crate", "ast"], name: "Spaced"}) [ Text "'a", ty ]
|
||||
|
||||
moduleNameHelp = \err ->
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "module_name_help" }) [ err ]) moduleName
|
||||
|
||||
exposesValues =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "exposes_values"}) []) (keywordItem (keyword "ExposesKeyword") (collection (loc [spaced exposedName])))
|
||||
|
||||
imports =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "imports"}) []) (keywordItem (keyword "ImportsKeyword") (collection (loc [spaced importsEntry])))
|
||||
|
||||
generates =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "generates"}) []) (keywordItem (keyword "GeneratesKeyword") uppercaseIdent)
|
||||
|
||||
generatesWith =
|
||||
Builtin (fnCall (Import {modu: ["crate", "module"], name: "generates_with"}) []) (keywordItem (keyword "WithKeyword") (collection (loc [spaced exposedName])))
|
||||
|
||||
|
||||
interfaceHeader = Record "InterfaceHeader" [
|
||||
{name: "before_name", parser: spaces (errHeader "IndentStart")},
|
||||
{name: "name", parser: Loc (moduleNameHelp (errHeader "ModuleName"))},
|
||||
{name: "exposes", parser: Specialize (errHeader "Exposes") exposesValues },
|
||||
{name: "imports", parser: Specialize (errHeader "Imports") imports },
|
||||
]
|
||||
|
||||
hostedHeader = Record "HostedHeader" [
|
||||
{name: "before_name", parser: spaces (errHeader "IndentStart")},
|
||||
{name: "name", parser: Loc (moduleNameHelp (errHeader "ModuleName"))},
|
||||
{name: "exposes", parser: Specialize (errHeader "Exposes") exposesValues},
|
||||
{name: "imports", parser: Specialize (errHeader "Imports") imports},
|
||||
{name: "generates", parser: Specialize (errHeader "Generates") generates},
|
||||
{name: "generates_with", parser: Specialize (errHeader "GeneratesWith") generatesWith},
|
||||
]
|
||||
|
||||
printCombinatorParserFunction = \parser ->
|
||||
parseFunction (lowerName (resolveName parser)) (resolveType parser) (Text "EHeader<'a>") (printCombinatorParser parser)
|
||||
|
||||
resolveName : Parser -> Str
|
||||
resolveName = \parser ->
|
||||
when parser is
|
||||
Loc _p -> crash "Unnamed parser!"
|
||||
Specialize _err _p -> crash "Unnamed parser!"
|
||||
Builtin _name _ty -> crash "Unnamed parser!"
|
||||
Record name _fields -> name
|
||||
|
||||
underscoreScalar = 95
|
||||
aLowerScalar = 97
|
||||
aUpperScalar = 65
|
||||
zUpperScalar = 90
|
||||
|
||||
# map from a lower_case_name to a UpperCaseName
|
||||
upperName : Str -> Str
|
||||
upperName = \name ->
|
||||
result = Str.walkScalars name {text: "", needUpper: Bool.true} \{text, needUpper}, c ->
|
||||
if c == underscoreScalar then
|
||||
{text, needUpper: Bool.true}
|
||||
else
|
||||
newText =
|
||||
if needUpper then
|
||||
Str.appendScalar text (c - aLowerScalar + aUpperScalar) |> orCrash
|
||||
else
|
||||
Str.appendScalar text c |> orCrash
|
||||
{text: newText, needUpper: Bool.false}
|
||||
result.text
|
||||
|
||||
expect (upperName "hello_world") == "HelloWorld"
|
||||
|
||||
orCrash : Result a e -> a
|
||||
orCrash = \result ->
|
||||
when result is
|
||||
Ok a -> a
|
||||
Err _e -> crash "orCrash"
|
||||
|
||||
lowerName : Str -> Str
|
||||
lowerName = \name ->
|
||||
result = Str.walkScalars name {text: "", needUnder: Bool.false} \{text, needUnder}, c ->
|
||||
newText =
|
||||
if c >= aUpperScalar && c <= zUpperScalar then
|
||||
if needUnder then
|
||||
text
|
||||
|> Str.appendScalar underscoreScalar
|
||||
|> orCrash
|
||||
|> Str.appendScalar (c - aUpperScalar + aLowerScalar)
|
||||
|> orCrash
|
||||
else
|
||||
text
|
||||
|> Str.appendScalar (c - aUpperScalar + aLowerScalar)
|
||||
|> orCrash
|
||||
else
|
||||
Str.appendScalar text c |> orCrash
|
||||
|
||||
{text: newText, needUnder: Bool.true}
|
||||
|
||||
result.text
|
||||
|
||||
expect
|
||||
theResult = (lowerName "HelloWorld")
|
||||
theResult == "hello_world"
|
||||
|
||||
resolveType : Parser -> RenderTree
|
||||
resolveType = \parser ->
|
||||
when parser is
|
||||
Loc p -> loc [resolveType p]
|
||||
Specialize _err p -> resolveType p
|
||||
Record name _fields -> Items [ Import {modu: ["crate", "generated_ast"], name}, Text "<'a>"]
|
||||
Builtin _name ty -> ty
|
||||
|
||||
printCombinatorParser : Parser -> RenderTree
|
||||
printCombinatorParser = \parser ->
|
||||
when parser is
|
||||
Loc p ->
|
||||
printed = printCombinatorParser p
|
||||
value : RenderTree
|
||||
value = Items [ (Text "loc!("), printed, (Text ")") ]
|
||||
value
|
||||
Specialize err p ->
|
||||
printed = printCombinatorParser p
|
||||
Items [
|
||||
Import {modu: ["crate", "parser"], name: "specialize"},
|
||||
Text "(",
|
||||
err,
|
||||
(Text ", "),
|
||||
printed,
|
||||
(Text ")"),
|
||||
]
|
||||
Record name fields ->
|
||||
Items [
|
||||
Text "record!(\(name) {",
|
||||
(Indent
|
||||
(fields
|
||||
|> List.map \f ->
|
||||
Line [Text "\(f.name): ", printCombinatorParser f.parser, Text ","]
|
||||
)
|
||||
),
|
||||
Text "})"
|
||||
]
|
||||
Builtin name _ty -> name
|
||||
|
||||
printAst : Parser -> RenderTree
|
||||
printAst = \parser ->
|
||||
when parser is
|
||||
Record name fields ->
|
||||
Items [
|
||||
Line [ Text "#[derive(Clone, Debug, PartialEq)]" ],
|
||||
Line [ Text "pub struct \(name)<'a> {" ],
|
||||
(Indent (
|
||||
fields
|
||||
|> List.map \f ->
|
||||
Line [Text "pub \(f.name): ", resolveType f.parser, Text ","]
|
||||
)),
|
||||
Line [Text "}"],
|
||||
Line [Text ""],
|
||||
]
|
||||
_ -> crash "Not implemented"
|
||||
|
||||
expect (render (Text "foo")) == "foo"
|
||||
expect (render (Line [Text "foo"])) == "foo\n"
|
||||
expect (render (Indent [Text "foo"])) == " foo"
|
||||
expect (render (Line [Indent [Text "foo"]])) == " foo\n"
|
||||
|
||||
expect
|
||||
res = (render (Items [Text "{", Indent [Line [Text "foo"]], Text "}"]))
|
||||
res ==
|
||||
"""
|
||||
{
|
||||
foo
|
||||
}
|
||||
"""
|
||||
|
||||
allSyntaxItems = [interfaceHeader, hostedHeader]
|
||||
|
||||
printedAstItems = Items (allSyntaxItems |> List.map printAst)
|
||||
printedParserItems = Items (allSyntaxItems |> List.map printCombinatorParserFunction)
|
||||
|
||||
|
||||
# main : Task {} []*
|
||||
main =
|
||||
task =
|
||||
_ <- File.writeUtf8 (Path.fromStr "generated_ast.rs") (renderFile printedAstItems) |> Task.await
|
||||
|
||||
File.writeUtf8 (Path.fromStr "generated_parser.rs") (renderFile printedParserItems)
|
||||
|
||||
Task.attempt task \result ->
|
||||
when result is
|
||||
Ok _ -> Stdout.line "Success!"
|
||||
Err _e -> Stdout.line "Failed to write file"
|
|
@ -1,7 +1,6 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader};
|
||||
use crate::ident::Ident;
|
||||
use crate::parser::ESingleQuote;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use bumpalo::Bump;
|
||||
|
@ -807,51 +806,6 @@ pub enum Base {
|
|||
}
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
pub fn from_ident(arena: &'a Bump, ident: Ident<'a>) -> Pattern<'a> {
|
||||
match ident {
|
||||
Ident::Tag(string) => Pattern::Tag(string),
|
||||
Ident::OpaqueRef(string) => Pattern::OpaqueRef(string),
|
||||
Ident::Access { module_name, parts } => {
|
||||
if parts.len() == 1 {
|
||||
// This is valid iff there is no module.
|
||||
let ident = parts.iter().next().unwrap();
|
||||
|
||||
if module_name.is_empty() {
|
||||
Pattern::Identifier(ident)
|
||||
} else {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
}
|
||||
} else {
|
||||
// This is definitely malformed.
|
||||
let mut buf =
|
||||
String::with_capacity_in(module_name.len() + (2 * parts.len()), arena);
|
||||
let mut any_parts_printed = if module_name.is_empty() {
|
||||
false
|
||||
} else {
|
||||
buf.push_str(module_name);
|
||||
|
||||
true
|
||||
};
|
||||
|
||||
for part in parts.iter() {
|
||||
if any_parts_printed {
|
||||
buf.push('.');
|
||||
} else {
|
||||
any_parts_printed = true;
|
||||
}
|
||||
|
||||
buf.push_str(part);
|
||||
}
|
||||
|
||||
Pattern::Malformed(buf.into_bump_str())
|
||||
}
|
||||
}
|
||||
Ident::RecordAccessorFunction(string) => Pattern::Malformed(string),
|
||||
Ident::TupleAccessorFunction(string) => Pattern::Malformed(string),
|
||||
Ident::Malformed(string, _problem) => Pattern::Malformed(string),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that patterns are equivalent, meaning they have the same shape, but may have
|
||||
/// different locations/whitespace
|
||||
pub fn equivalent(&self, other: &Self) -> bool {
|
||||
|
|
|
@ -378,6 +378,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn begins_with_crlf(bytes: &[u8]) -> bool {
|
||||
bytes.len() >= 2 && bytes[0] == b'\r' && bytes[1] == b'\n'
|
||||
}
|
||||
|
||||
pub fn spaces<'a, E>() -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
|
||||
where
|
||||
E: 'a + SpaceProblem,
|
||||
|
@ -399,6 +403,7 @@ where
|
|||
let is_doc_comment = state.bytes().first() == Some(&b'#')
|
||||
&& (state.bytes().get(1) == Some(&b' ')
|
||||
|| state.bytes().get(1) == Some(&b'\n')
|
||||
|| begins_with_crlf(&state.bytes()[1..])
|
||||
|| Option::is_none(&state.bytes().get(1)));
|
||||
|
||||
if is_doc_comment {
|
||||
|
@ -422,7 +427,10 @@ where
|
|||
newlines.push(comment);
|
||||
state.advance_mut(len);
|
||||
|
||||
if state.bytes().first() == Some(&b'\n') {
|
||||
if begins_with_crlf(state.bytes()) {
|
||||
state.advance_mut(1);
|
||||
state = state.advance_newline();
|
||||
} else if state.bytes().first() == Some(&b'\n') {
|
||||
state = state.advance_newline();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::ast::{
|
|||
};
|
||||
use crate::blankspace::{
|
||||
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
|
||||
space0_e, spaces, spaces_around, spaces_before,
|
||||
space0_before_optional_after, space0_e, spaces, spaces_around, spaces_before,
|
||||
};
|
||||
use crate::ident::{integer_ident, lowercase_ident, parse_ident, Accessor, Ident};
|
||||
use crate::keyword;
|
||||
|
@ -42,7 +42,7 @@ pub fn test_parse_expr<'a>(
|
|||
state: State<'a>,
|
||||
) -> Result<Loc<Expr<'a>>, EExpr<'a>> {
|
||||
let parser = skip_second!(
|
||||
space0_before_e(loc_expr(true), EExpr::IndentStart,),
|
||||
space0_before_optional_after(loc_expr(true), EExpr::IndentStart, EExpr::IndentEnd),
|
||||
expr_end()
|
||||
);
|
||||
|
||||
|
@ -255,7 +255,10 @@ fn loc_possibly_negative_or_negated_term<'a>(
|
|||
// this will parse negative numbers, which the unary negate thing up top doesn't (for now)
|
||||
loc!(specialize(EExpr::Number, number_literal_help())),
|
||||
loc!(map_with_arena!(
|
||||
and!(loc!(word1(b'!', EExpr::Start)), loc_term(options)),
|
||||
and!(
|
||||
loc!(word1(b'!', EExpr::Start)),
|
||||
space0_before_e(loc_term(options), EExpr::IndentStart)
|
||||
),
|
||||
|arena: &'a Bump, (loc_op, loc_expr): (Loc<_>, _)| {
|
||||
Expr::UnaryOp(arena.alloc(loc_expr), Loc::at(loc_op.region, UnaryOp::Not))
|
||||
}
|
||||
|
@ -668,7 +671,7 @@ pub fn parse_single_def<'a>(
|
|||
let (_, ann_type, state) = parser.parse(arena, state, min_indent)?;
|
||||
let region = Region::span_across(&loc_pattern.region, &ann_type.region);
|
||||
|
||||
match &loc_pattern.value {
|
||||
match &loc_pattern.value.extract_spaces().item {
|
||||
Pattern::Apply(
|
||||
Loc {
|
||||
value: Pattern::Tag(name),
|
||||
|
@ -740,7 +743,7 @@ pub fn parse_single_def<'a>(
|
|||
opaque_signature_with_space_before().parse(arena, state, min_indent + 1)?;
|
||||
let region = Region::span_across(&loc_pattern.region, &signature.region);
|
||||
|
||||
match &loc_pattern.value {
|
||||
match &loc_pattern.value.extract_spaces().item {
|
||||
Pattern::Apply(
|
||||
Loc {
|
||||
value: Pattern::Tag(name),
|
||||
|
@ -921,7 +924,7 @@ macro_rules! join_alias_to_body {
|
|||
}
|
||||
|
||||
fn parse_defs_end<'a>(
|
||||
_options: ExprParseOptions,
|
||||
options: ExprParseOptions,
|
||||
min_indent: u32,
|
||||
mut defs: Defs<'a>,
|
||||
arena: &'a Bump,
|
||||
|
@ -932,7 +935,7 @@ fn parse_defs_end<'a>(
|
|||
loop {
|
||||
let state = global_state;
|
||||
|
||||
global_state = match parse_single_def(_options, min_indent, arena, state) {
|
||||
global_state = match parse_single_def(options, min_indent, arena, state) {
|
||||
Ok((_, Some(single_def), next_state)) => {
|
||||
let region = single_def.region;
|
||||
let spaces_before_current = single_def.spaces_before;
|
||||
|
@ -1890,7 +1893,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
|
||||
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
|
||||
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(string)),
|
||||
Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)),
|
||||
Expr::MalformedIdent(string, problem) => Ok(Pattern::MalformedIdent(string, *problem)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1950,7 +1953,7 @@ pub fn toplevel_defs<'a>() -> impl Parser<'a, Defs<'a>, EExpr<'a>> {
|
|||
let start_column = state.column();
|
||||
|
||||
let options = ExprParseOptions {
|
||||
accept_multi_backpassing: false,
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
};
|
||||
|
||||
|
|
|
@ -290,6 +290,10 @@ fn chomp_integer_part(buffer: &[u8]) -> Result<&str, Progress> {
|
|||
)
|
||||
}
|
||||
|
||||
fn is_plausible_ident_continue(ch: char) -> bool {
|
||||
ch == '_' || is_alnum(ch)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn chomp_part<F, G>(leading_is_good: F, rest_is_good: G, buffer: &[u8]) -> Result<&str, Progress>
|
||||
where
|
||||
|
@ -317,6 +321,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
if let Ok((next, _width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
// This would mean we have e.g.:
|
||||
// * identifier followed by a _
|
||||
// * an integer followed by an alphabetic char
|
||||
if is_plausible_ident_continue(next) {
|
||||
return Err(NoProgress);
|
||||
}
|
||||
}
|
||||
|
||||
if chomped == 0 {
|
||||
Err(NoProgress)
|
||||
} else {
|
||||
|
|
|
@ -535,6 +535,9 @@ pub enum EPattern<'a> {
|
|||
IndentStart(Position),
|
||||
IndentEnd(Position),
|
||||
AsIndentStart(Position),
|
||||
|
||||
RecordAccessorFunction(Position),
|
||||
TupleAccessorFunction(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -406,13 +406,13 @@ fn loc_ident_pattern_help<'a>(
|
|||
))
|
||||
}
|
||||
}
|
||||
Ident::RecordAccessorFunction(string) | Ident::TupleAccessorFunction(string) => Ok((
|
||||
Ident::RecordAccessorFunction(_string) => Err((
|
||||
MadeProgress,
|
||||
Loc {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Malformed(string),
|
||||
},
|
||||
state,
|
||||
EPattern::RecordAccessorFunction(loc_ident.region.start()),
|
||||
)),
|
||||
Ident::TupleAccessorFunction(_string) => Err((
|
||||
MadeProgress,
|
||||
EPattern::TupleAccessorFunction(loc_ident.region.start()),
|
||||
)),
|
||||
Ident::Malformed(malformed, problem) => {
|
||||
debug_assert!(!malformed.is_empty());
|
||||
|
|
|
@ -326,8 +326,12 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
|
||||
if state.bytes().starts_with(b"\"\"\"") {
|
||||
// ending the string; don't use the last newline
|
||||
segments
|
||||
.push(StrSegment::Plaintext(utf8(state.clone(), without_newline)?));
|
||||
if !without_newline.is_empty() {
|
||||
segments.push(StrSegment::Plaintext(utf8(
|
||||
state.clone(),
|
||||
without_newline,
|
||||
)?));
|
||||
}
|
||||
} else {
|
||||
segments
|
||||
.push(StrSegment::Plaintext(utf8(state.clone(), with_newline)?));
|
||||
|
|
|
@ -19,9 +19,9 @@ mod test_parse {
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::{self, Bump};
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::StrLiteral::*;
|
||||
use roc_parse::ast::StrSegment::*;
|
||||
use roc_parse::ast::{self, EscapedChar};
|
||||
use roc_parse::ast::{CommentOrNewline, StrLiteral::*};
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{Parser, SyntaxError};
|
||||
use roc_parse::state::State;
|
||||
|
@ -322,6 +322,22 @@ mod test_parse {
|
|||
assert_eq!(std::mem::size_of::<roc_parse::ast::Expr>(), 40);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_two_line_comment_with_crlf() {
|
||||
let src = "# foo\r\n# bar\r\n42";
|
||||
assert_parses_to(
|
||||
src,
|
||||
Expr::SpaceBefore(
|
||||
&Expr::Num("42"),
|
||||
&[
|
||||
CommentOrNewline::LineComment(" foo"),
|
||||
// We used to have a bug where there was an extra CommentOrNewline::Newline between these.
|
||||
CommentOrNewline::LineComment(" bar"),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// PARSE ERROR
|
||||
|
||||
// TODO this should be parse error, but isn't!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue