Merge branch 'main' into rust1_65

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>
This commit is contained in:
Anton-4 2023-01-17 18:14:30 +01:00 committed by GitHub
commit bbf35af8fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
179 changed files with 7089 additions and 3846 deletions

View file

@ -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"

View 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 {

View file

@ -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();
}

View file

@ -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,
};

View file

@ -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 {

View file

@ -535,6 +535,9 @@ pub enum EPattern<'a> {
IndentStart(Position),
IndentEnd(Position),
AsIndentStart(Position),
RecordAccessorFunction(Position),
TupleAccessorFunction(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -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());

View file

@ -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)?));

View file

@ -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!