mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 04:08:19 +00:00
Merge pull request #7497 from smores56/new-interpolation-syntax
Move to new interpolation syntax
This commit is contained in:
commit
528d1d2b69
66 changed files with 630 additions and 596 deletions
|
@ -278,14 +278,14 @@ import pf.Stdin
|
|||
main =
|
||||
Stdout.line! "What's your name?"
|
||||
name = Stdin.line!
|
||||
Stdout.line! "Hi $(name)!""#;
|
||||
Stdout.line! "Hi ${name}!""#;
|
||||
|
||||
const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "platform/main.roc" }
|
||||
|
||||
main =
|
||||
Stdout.line! "What's your name?"
|
||||
name = Stdin.line!
|
||||
Stdout.line! "Hi $(name)!"
|
||||
Stdout.line! "Hi ${name}!"
|
||||
"#;
|
||||
|
||||
fn setup_test_file(dir: &Path, file_name: &str, contents: &str) -> PathBuf {
|
||||
|
|
|
@ -10,7 +10,7 @@ show = \list ->
|
|||
|> List.map(Num.to_str)
|
||||
|> Str.join_with(", ")
|
||||
|
||||
"[$(content)]"
|
||||
"[${content}]"
|
||||
|
||||
sort_by : List a, (a -> Num *) -> List a
|
||||
sort_by = \list, to_comparable ->
|
||||
|
|
|
@ -25,7 +25,7 @@ show_rb_tree = \tree, show_key, show_value ->
|
|||
s_l = node_in_parens(left, show_key, show_value)
|
||||
s_r = node_in_parens(right, show_key, show_value)
|
||||
|
||||
"Node $(s_color) $(s_key) $(s_value) $(s_l) $(s_r)"
|
||||
"Node ${s_color} ${s_key} ${s_value} ${s_l} ${s_r}"
|
||||
|
||||
node_in_parens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
|
||||
node_in_parens = \tree, show_key, show_value ->
|
||||
|
@ -36,7 +36,7 @@ node_in_parens = \tree, show_key, show_value ->
|
|||
Node(_, _, _, _, _) ->
|
||||
inner = show_rb_tree(tree, show_key, show_value)
|
||||
|
||||
"($(inner))"
|
||||
"(${inner})"
|
||||
|
||||
show_color : NodeColor -> Str
|
||||
show_color = \color ->
|
||||
|
|
|
@ -8,7 +8,7 @@ snapshot_kind: text
|
|||
|
||||
The get_user function expects 1 argument, but it got 2 instead:
|
||||
|
||||
12│ $(Api.get_user(1, 2))
|
||||
12│ ${Api.get_user(1, 2)}
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Are there any missing commas? Or missing parentheses?
|
||||
|
@ -18,7 +18,7 @@ Are there any missing commas? Or missing parentheses?
|
|||
|
||||
This value is not a function, but it was given 1 argument:
|
||||
|
||||
13│ $(Api.base_url(1))
|
||||
13│ ${Api.base_url(1)}
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Are there any missing commas? Or missing parentheses?
|
||||
|
@ -28,7 +28,7 @@ Are there any missing commas? Or missing parentheses?
|
|||
|
||||
The get_post_comment function expects 2 arguments, but it got only 1:
|
||||
|
||||
16│ $(Api.get_post_comment(1))
|
||||
16│ ${Api.get_post_comment(1)}
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Roc does not allow functions to be partially applied. Use a closure to
|
||||
|
|
|
@ -11,7 +11,7 @@ fn_annotated_as_value definition:
|
|||
|
||||
3│ fn_annotated_as_value : Str
|
||||
4│> fn_annotated_as_value = \post_id, comment_id ->
|
||||
5│> "/posts/$(post_id)/comments/$(Num.to_str(comment_id))"
|
||||
5│> "/posts/${post_id}/comments/${Num.to_str(comment_id)}"
|
||||
|
||||
The body is an anonymous function of type:
|
||||
|
||||
|
@ -28,7 +28,7 @@ Something is off with the body of the missing_arg definition:
|
|||
|
||||
7│ missing_arg : Str -> Str
|
||||
8│> missing_arg = \post_id, _ ->
|
||||
9│> "/posts/$(post_id)/comments"
|
||||
9│> "/posts/${post_id}/comments"
|
||||
|
||||
The body is an anonymous function of type:
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ snapshot_kind: text
|
|||
|
||||
This argument to this string interpolation has an unexpected type:
|
||||
|
||||
10│ "$(Api.get_post)"
|
||||
10│ "${Api.get_post}"
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The argument is an anonymous function of type:
|
||||
|
|
|
@ -12,7 +12,7 @@ main =
|
|||
_: Task.ok(Dict.single("a", "b")),
|
||||
}!
|
||||
|
||||
Stdout.line!("For multiple tasks: $(Inspect.to_str(multiple_in))")
|
||||
Stdout.line!("For multiple tasks: ${Inspect.to_str(multiple_in)}")
|
||||
|
||||
sequential : Task a err, Task b err, (a, b -> c) -> Task c err
|
||||
sequential = \first_task, second_task, mapper ->
|
||||
|
|
|
@ -15,8 +15,8 @@ main! = \{} ->
|
|||
validate! : U32 => Result {} U32
|
||||
validate! = \x ->
|
||||
if Num.is_even(x) then
|
||||
Effect.put_line!("✅ $(Num.to_str(x))")
|
||||
Effect.put_line!("✅ ${Num.to_str(x)}")
|
||||
Ok({})
|
||||
else
|
||||
Effect.put_line!("$(Num.to_str(x)) is not even! ABORT!")
|
||||
Effect.put_line!("${Num.to_str(x)} is not even! ABORT!")
|
||||
Err(x)
|
||||
|
|
|
@ -7,7 +7,7 @@ main! = \{} ->
|
|||
first = ask!("What's your first name?")
|
||||
last = ask!("What's your last name?")
|
||||
|
||||
Effect.put_line!("\nHi, $(first) $(last)!\n")
|
||||
Effect.put_line!("\nHi, ${first} ${last}!\n")
|
||||
|
||||
when Str.to_u8(ask!("How old are you?")) is
|
||||
Err(InvalidNumStr) ->
|
||||
|
@ -17,7 +17,7 @@ main! = \{} ->
|
|||
Effect.put_line!("\nNice! You can vote!")
|
||||
|
||||
Ok(age) ->
|
||||
Effect.put_line!("\nYou'll be able to vote in $(Num.to_str((18 - age))) years")
|
||||
Effect.put_line!("\nYou'll be able to vote in ${Num.to_str(18 - age)} years")
|
||||
|
||||
Effect.put_line!("\nBye! 👋")
|
||||
|
||||
|
|
|
@ -15,5 +15,5 @@ main! = \{} ->
|
|||
else
|
||||
{}
|
||||
|
||||
Effect.put_line!("You entered: $(line)")
|
||||
Effect.put_line!("You entered: ${line}")
|
||||
Effect.put_line!("It is known")
|
||||
|
|
|
@ -18,5 +18,5 @@ main! = \{} ->
|
|||
get_line!: Effect.get_line!,
|
||||
}
|
||||
|
||||
Effect.put_line!("not_effectful: $(not_effectful.get_line!({}))")
|
||||
Effect.put_line!("effectful: $(effectful.get_line!({}))")
|
||||
Effect.put_line!("not_effectful: ${not_effectful.get_line!({})}")
|
||||
Effect.put_line!("effectful: ${effectful.get_line!({})}")
|
||||
|
|
|
@ -7,6 +7,6 @@ main! = \{} ->
|
|||
logged!("hello", \{} -> Effect.put_line!("Hello, World!"))
|
||||
|
||||
logged! = \name, fx! ->
|
||||
Effect.put_line!("Before $(name)")
|
||||
Effect.put_line!("Before ${name}")
|
||||
fx!({})
|
||||
Effect.put_line!("After $(name)")
|
||||
Effect.put_line!("After ${name}")
|
||||
|
|
|
@ -56,7 +56,7 @@ to_str = \{ scopes, stack, state, vars } ->
|
|||
stack_str = Str.join_with(List.map(stack, to_str_data), " ")
|
||||
vars_str = Str.join_with(List.map(vars, to_str_data), " ")
|
||||
|
||||
"\n============\nDepth: $(depth)\nState: $(state_str)\nStack: [$(stack_str)]\nVars: [$(vars_str)]\n============\n"
|
||||
"\n============\nDepth: ${depth}\nState: ${state_str}\nStack: [${stack_str}]\nVars: [${vars_str}]\n============\n"
|
||||
|
||||
with! : Str, (Context => a) => a
|
||||
with! = \path, callback! ->
|
||||
|
|
|
@ -21,7 +21,7 @@ main! = \filename ->
|
|||
{}
|
||||
|
||||
Err(StringErr(e)) ->
|
||||
Stdout.line!("Ran into problem:\n$(e)\n")
|
||||
Stdout.line!("Ran into problem:\n${e}\n")
|
||||
|
||||
interpret_file! : Str => Result {} [StringErr Str]
|
||||
interpret_file! = \filename ->
|
||||
|
@ -44,7 +44,7 @@ interpret_file! = \filename ->
|
|||
Err(StringErr("Ran into an invalid boolean that was neither false (0) or true (-1)"))
|
||||
|
||||
Err(InvalidChar(char)) ->
|
||||
Err(StringErr("Ran into an invalid character with ascii code: $(char)"))
|
||||
Err(StringErr("Ran into an invalid character with ascii code: ${char}"))
|
||||
|
||||
Err(MaxInputNumber) ->
|
||||
Err(StringErr("Like the original false compiler, the max input number is 320,000"))
|
||||
|
|
|
@ -7,4 +7,4 @@ app [main] {
|
|||
import json.JsonParser
|
||||
import csv.Csv
|
||||
|
||||
main = "Hello, World! $(JsonParser.example) $(Csv.example)"
|
||||
main = "Hello, World! ${JsonParser.example} ${Csv.example}"
|
||||
|
|
|
@ -7,4 +7,4 @@ app [main] {
|
|||
import one.One
|
||||
import two.Two
|
||||
|
||||
main = "$(One.example) | $(Two.example)"
|
||||
main = "${One.example} | ${Two.example}"
|
||||
|
|
|
@ -2,4 +2,4 @@ module [example]
|
|||
|
||||
import two.Two
|
||||
|
||||
example = "[One imports Two: $(Two.example)]"
|
||||
example = "[One imports Two: ${Two.example}]"
|
||||
|
|
|
@ -2,4 +2,4 @@ module [example]
|
|||
|
||||
import one.One
|
||||
|
||||
example = "[Zero imports One: $(One.example)]"
|
||||
example = "[Zero imports One: ${One.example}]"
|
||||
|
|
|
@ -14,18 +14,18 @@ module { app_id, protocol } -> [
|
|||
## value def referencing params
|
||||
base_url : Str
|
||||
base_url =
|
||||
protocol("api.example.com/$(app_id)")
|
||||
protocol("api.example.com/${app_id}")
|
||||
|
||||
## function def referencing params
|
||||
get_user : U32 -> Str
|
||||
get_user = \user_id ->
|
||||
# purposefully not using baseUrl to test top-level fn referencing param
|
||||
protocol("api.example.com/$(app_id)/users/$(Num.to_str(user_id))")
|
||||
protocol("api.example.com/${app_id}/users/${Num.to_str(user_id)}")
|
||||
|
||||
## function def referencing top-level value
|
||||
get_post : U32 -> Str
|
||||
get_post = \post_id ->
|
||||
"$(base_url)/posts/$(Num.to_str(post_id))"
|
||||
"${base_url}/posts/${Num.to_str(post_id)}"
|
||||
|
||||
## function def passing top-level function
|
||||
get_posts : List U32 -> List Str
|
||||
|
@ -35,13 +35,13 @@ get_posts = \ids ->
|
|||
## function def calling top-level function
|
||||
get_post_comments : U32 -> Str
|
||||
get_post_comments = \post_id ->
|
||||
"$(get_post(post_id))/comments"
|
||||
"${get_post(post_id)}/comments"
|
||||
|
||||
## function def passing nested function
|
||||
get_companies : List U32 -> List Str
|
||||
get_companies = \ids ->
|
||||
get_company = \id ->
|
||||
protocol("api.example.com/$(app_id)/companies/$(Num.to_str(id))")
|
||||
protocol("api.example.com/${app_id}/companies/${Num.to_str(id)}")
|
||||
|
||||
List.map(ids, get_company)
|
||||
|
||||
|
@ -59,11 +59,11 @@ get_post_aliased =
|
|||
get_user_safe : U32 -> Str
|
||||
get_user_safe =
|
||||
if Str.starts_with(app_id, "prod_") then
|
||||
\id -> "$(get_user(id))?safe=true"
|
||||
\id -> "${get_user(id)}?safe=true"
|
||||
else
|
||||
get_user
|
||||
|
||||
## two-argument function
|
||||
get_post_comment : U32, U32 -> Str
|
||||
get_post_comment = \post_id, comment_id ->
|
||||
"$(get_post(post_id))/comments/$(Num.to_str(comment_id))"
|
||||
"${get_post(post_id)}/comments/${Num.to_str(comment_id)}"
|
||||
|
|
|
@ -2,8 +2,8 @@ module { app_id } -> [fn_annotated_as_value, missing_arg]
|
|||
|
||||
fn_annotated_as_value : Str
|
||||
fn_annotated_as_value = \post_id, comment_id ->
|
||||
"/posts/$(post_id)/comments/$(Num.to_str(comment_id))"
|
||||
"/posts/${post_id}/comments/${Num.to_str(comment_id)}"
|
||||
|
||||
missing_arg : Str -> Str
|
||||
missing_arg = \post_id, _ ->
|
||||
"/posts/$(post_id)/comments"
|
||||
"/posts/${post_id}/comments"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module []
|
||||
|
||||
https = \url -> "https://$(url)"
|
||||
https = \url -> "https://${url}"
|
||||
|
||||
expect
|
||||
import Api { app_id: "one", protocol: https }
|
||||
|
|
|
@ -4,4 +4,4 @@ menu = \name ->
|
|||
indirect(name)
|
||||
|
||||
indirect = \name ->
|
||||
echo("Hi, $(name)!")
|
||||
echo("Hi, ${name}!")
|
||||
|
|
|
@ -6,8 +6,8 @@ import Api { app_id: "one", protocol: https } as App1
|
|||
import Api { app_id: "two", protocol: http } as App2
|
||||
import Api { app_id: "prod_1", protocol: http } as Prod
|
||||
|
||||
https = \url -> "https://$(url)"
|
||||
http = \url -> "http://$(url)"
|
||||
https = \url -> "https://${url}"
|
||||
http = \url -> "http://${url}"
|
||||
|
||||
users_app1 =
|
||||
# pass top-level fn in a module with params
|
||||
|
@ -27,33 +27,33 @@ main =
|
|||
List.map([1, 2, 3], App3.get_user)
|
||||
|
||||
"""
|
||||
App1.baseUrl: $(App1.base_url)
|
||||
App2.baseUrl: $(App2.base_url)
|
||||
App3.baseUrl: $(App3.base_url)
|
||||
App1.getUser 1: $(App1.get_user(1))
|
||||
App2.getUser 2: $(App2.get_user(2))
|
||||
App3.getUser 3: $(App3.get_user(3))
|
||||
App1.getPost 1: $(App1.get_post(1))
|
||||
App2.getPost 2: $(App2.get_post(2))
|
||||
App3.getPost 3: $(App3.get_post(3))
|
||||
App1.getPosts [1, 2]: $(Inspect.to_str(App1.get_posts([1, 2])))
|
||||
App2.getPosts [3, 4]: $(Inspect.to_str(App2.get_posts([3, 4])))
|
||||
App2.getPosts [5, 6]: $(Inspect.to_str(App2.get_posts([5, 6])))
|
||||
App1.getPostComments 1: $(App1.get_post_comments(1))
|
||||
App2.getPostComments 2: $(App2.get_post_comments(2))
|
||||
App2.getPostComments 3: $(App2.get_post_comments(3))
|
||||
App1.getCompanies [1, 2]: $(Inspect.to_str(App1.get_companies([1, 2])))
|
||||
App2.getCompanies [3, 4]: $(Inspect.to_str(App2.get_companies([3, 4])))
|
||||
App2.getCompanies [5, 6]: $(Inspect.to_str(App2.get_companies([5, 6])))
|
||||
App1.getPostAliased 1: $(App1.get_post_aliased(1))
|
||||
App2.getPostAliased 2: $(App2.get_post_aliased(2))
|
||||
App3.getPostAliased 3: $(App3.get_post_aliased(3))
|
||||
App1.baseUrlAliased: $(App1.base_url_aliased)
|
||||
App2.baseUrlAliased: $(App2.base_url_aliased)
|
||||
App3.baseUrlAliased: $(App3.base_url_aliased)
|
||||
App1.getUserSafe 1: $(App1.get_user_safe(1))
|
||||
Prod.getUserSafe 2: $(Prod.get_user_safe(2))
|
||||
usersApp1: $(Inspect.to_str(users_app1))
|
||||
getUserApp3Nested 3: $(get_user_app3_nested(3))
|
||||
usersApp3Passed: $(Inspect.to_str(users_app3_passed))
|
||||
App1.baseUrl: ${App1.base_url}
|
||||
App2.baseUrl: ${App2.base_url}
|
||||
App3.baseUrl: ${App3.base_url}
|
||||
App1.getUser 1: ${App1.get_user(1)}
|
||||
App2.getUser 2: ${App2.get_user(2)}
|
||||
App3.getUser 3: ${App3.get_user(3)}
|
||||
App1.getPost 1: ${App1.get_post(1)}
|
||||
App2.getPost 2: ${App2.get_post(2)}
|
||||
App3.getPost 3: ${App3.get_post(3)}
|
||||
App1.getPosts [1, 2]: ${Inspect.to_str(App1.get_posts([1, 2]))}
|
||||
App2.getPosts [3, 4]: ${Inspect.to_str(App2.get_posts([3, 4]))}
|
||||
App2.getPosts [5, 6]: ${Inspect.to_str(App2.get_posts([5, 6]))}
|
||||
App1.getPostComments 1: ${App1.get_post_comments(1)}
|
||||
App2.getPostComments 2: ${App2.get_post_comments(2)}
|
||||
App2.getPostComments 3: ${App2.get_post_comments(3)}
|
||||
App1.getCompanies [1, 2]: ${Inspect.to_str(App1.get_companies([1, 2]))}
|
||||
App2.getCompanies [3, 4]: ${Inspect.to_str(App2.get_companies([3, 4]))}
|
||||
App2.getCompanies [5, 6]: ${Inspect.to_str(App2.get_companies([5, 6]))}
|
||||
App1.getPostAliased 1: ${App1.get_post_aliased(1)}
|
||||
App2.getPostAliased 2: ${App2.get_post_aliased(2)}
|
||||
App3.getPostAliased 3: ${App3.get_post_aliased(3)}
|
||||
App1.baseUrlAliased: ${App1.base_url_aliased}
|
||||
App2.baseUrlAliased: ${App2.base_url_aliased}
|
||||
App3.baseUrlAliased: ${App3.base_url_aliased}
|
||||
App1.getUserSafe 1: ${App1.get_user_safe(1)}
|
||||
Prod.getUserSafe 2: ${Prod.get_user_safe(2)}
|
||||
usersApp1: ${Inspect.to_str(users_app1)}
|
||||
getUserApp3Nested 3: ${get_user_app3_nested(3)}
|
||||
usersApp3Passed: ${Inspect.to_str(users_app3_passed)}
|
||||
"""
|
||||
|
|
|
@ -4,14 +4,14 @@ app [main] {
|
|||
|
||||
import Api { app_id: "one", protocol: https }
|
||||
|
||||
https = \url -> "https://$(url)"
|
||||
https = \url -> "https://${url}"
|
||||
|
||||
main =
|
||||
"""
|
||||
# too many args
|
||||
$(Api.get_user(1, 2))
|
||||
$(Api.base_url(1))
|
||||
${Api.get_user(1, 2)}
|
||||
${Api.base_url(1)}
|
||||
|
||||
# too few args
|
||||
$(Api.get_post_comment(1))
|
||||
${Api.get_post_comment(1)}
|
||||
"""
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module { stdout! } -> [log!]
|
||||
|
||||
log! = \msg, level -> stdout!("$(level):$(msg)")
|
||||
log! = \msg, level -> stdout!("${level}:${msg}")
|
||||
|
|
|
@ -4,7 +4,7 @@ app [main] {
|
|||
|
||||
import Api { app_id: "one", protocol: https }
|
||||
|
||||
https = \url -> "https://$(url)"
|
||||
https = \url -> "https://${url}"
|
||||
|
||||
main =
|
||||
"$(Api.get_post)"
|
||||
"${Api.get_post}"
|
||||
|
|
|
@ -11,4 +11,4 @@ import foo.Foo
|
|||
|
||||
main_for_host : Str
|
||||
main_for_host =
|
||||
"$(main) $(Foo.foo)"
|
||||
"${main} ${Foo.foo}"
|
||||
|
|
|
@ -887,7 +887,7 @@ increase_size = \@Dict({ data, max_bucket_capacity, max_load_factor, shifts }) -
|
|||
},
|
||||
)
|
||||
else
|
||||
crash("Dict hit limit of $(Num.to_str(max_bucket_count)) elements. Unable to grow more.")
|
||||
crash("Dict hit limit of ${Num.to_str(max_bucket_count)} elements. Unable to grow more.")
|
||||
|
||||
alloc_buckets_from_shift : U8, F32 -> (List Bucket, U64)
|
||||
alloc_buckets_from_shift = \shifts, max_load_factor ->
|
||||
|
|
|
@ -1485,7 +1485,7 @@ for_each! = \list, func! ->
|
|||
## List.for_each_try!(files_to_delete, \path ->
|
||||
## File.delete!(path)?
|
||||
##
|
||||
## Stdout.line!("$(path) deleted")
|
||||
## Stdout.line!("${path} deleted")
|
||||
## )
|
||||
## ```
|
||||
for_each_try! : List a, (a => Result {} err) => Result {} err
|
||||
|
@ -1527,14 +1527,15 @@ walk! = \list, state, func! ->
|
|||
## If the function returns `Err`, the iteration stops and the error is returned.
|
||||
##
|
||||
## ```
|
||||
## names = try List.walk_try!(
|
||||
## names =
|
||||
## List.walk_try!(
|
||||
## ["First", "Middle", "Last"],
|
||||
## [],
|
||||
## \accumulator, which ->
|
||||
## try Stdout.write! ("$(which) name: ")
|
||||
## name = try Stdin.line! ({})
|
||||
## Ok (List.append accumulator name),
|
||||
## )
|
||||
## Stdout.write!("${which} name: ")?
|
||||
## name = Stdin.line!({})?
|
||||
## Ok(List.append(accumulator, name)),
|
||||
## )?
|
||||
## ```
|
||||
##
|
||||
## This is the same as [walk_try], except that the step function can have effects.
|
||||
|
|
|
@ -124,11 +124,12 @@ on_err = \result, transform ->
|
|||
## Like [on_err], but it allows the transformation function to produce effects.
|
||||
##
|
||||
## ```roc
|
||||
## Result.on_err(Err("missing user"), (\msg ->
|
||||
## Stdout.line!("ERROR: $(msg)")?
|
||||
##
|
||||
## Err(msg)
|
||||
## ))
|
||||
## Result.on_err(
|
||||
## Err("missing user"),
|
||||
## \msg ->
|
||||
## Stdout.line!("ERROR: ${msg}")?
|
||||
## Err(msg),
|
||||
## )
|
||||
## ```
|
||||
on_err! : Result a err, (err => Result a other_err) => Result a other_err
|
||||
on_err! = \result, transform! ->
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
## ```
|
||||
## name = "Sam"
|
||||
##
|
||||
## "Hi, my name is $(name)!"
|
||||
## "Hi, my name is ${name}!"
|
||||
## ```
|
||||
##
|
||||
## This will evaluate to the string `"Hi, my name is Sam!"`
|
||||
|
@ -44,7 +44,7 @@
|
|||
## ```
|
||||
## colors = ["red", "green", "blue"]
|
||||
##
|
||||
## "The colors are $(colors |> Str.join_with(", "))!"
|
||||
## "The colors are ${colors |> Str.join_with(", ")}!"
|
||||
## ```
|
||||
##
|
||||
## Interpolation can be used in multiline strings, but the part inside the parentheses must still be on one line.
|
||||
|
@ -800,7 +800,7 @@ replace_first : Str, Str, Str -> Str
|
|||
replace_first = \haystack, needle, flower ->
|
||||
when split_first(haystack, needle) is
|
||||
Ok({ before, after }) ->
|
||||
"$(before)$(flower)$(after)"
|
||||
"${before}${flower}${after}"
|
||||
|
||||
Err(NotFound) -> haystack
|
||||
|
||||
|
@ -818,7 +818,7 @@ replace_last : Str, Str, Str -> Str
|
|||
replace_last = \haystack, needle, flower ->
|
||||
when split_last(haystack, needle) is
|
||||
Ok({ before, after }) ->
|
||||
"$(before)$(flower)$(after)"
|
||||
"${before}${flower}${after}"
|
||||
|
||||
Err(NotFound) -> haystack
|
||||
|
||||
|
|
|
@ -2157,10 +2157,10 @@ mod test_can {
|
|||
// // This should NOT be string interpolation, because of the \\
|
||||
// indoc!(
|
||||
// r#"
|
||||
// "abcd\$(efg)hij"
|
||||
// "abcd\${efg}hij"
|
||||
// "#
|
||||
// ),
|
||||
// Str(r"abcd$(efg)hij".into()),
|
||||
// Str(r"abcd${efg}hij".into()),
|
||||
// );
|
||||
// }
|
||||
|
||||
|
|
|
@ -911,8 +911,8 @@ fn format_str_segment(seg: &StrSegment, buf: &mut Buf) {
|
|||
buf.push(escaped.to_parsed_char());
|
||||
}
|
||||
Interpolated(loc_expr) => {
|
||||
buf.push_str("$(");
|
||||
// e.g. (name) in "Hi, $(name)!"
|
||||
buf.push_str("${");
|
||||
// e.g. {name} in "Hi, ${name}!"
|
||||
let min_indent = buf.cur_line_indent() + INDENT;
|
||||
loc_expr.value.format_with_options(
|
||||
buf,
|
||||
|
@ -921,7 +921,7 @@ fn format_str_segment(seg: &StrSegment, buf: &mut Buf) {
|
|||
min_indent,
|
||||
);
|
||||
buf.indent(min_indent);
|
||||
buf.push(')');
|
||||
buf.push('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5864,7 +5864,7 @@ mod test_reporting {
|
|||
r#"
|
||||
greeting = "Privet"
|
||||
|
||||
if Bool.true then 1 else "$(greeting), World!"
|
||||
if Bool.true then 1 else "${greeting}, World!"
|
||||
"#,
|
||||
),
|
||||
@r#"
|
||||
|
@ -5872,7 +5872,7 @@ mod test_reporting {
|
|||
|
||||
This `if` has an `else` branch with a different type from its `then` branch:
|
||||
|
||||
6│ if Bool.true then 1 else "$(greeting), World!"
|
||||
6│ if Bool.true then 1 else "${greeting}, World!"
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The `else` branch is a string of type:
|
||||
|
@ -15052,7 +15052,7 @@ All branches in an `if` must have the same type!
|
|||
u64_nums = parse_items_with Str.to_u64
|
||||
u8_nums = parse_items_with Str.to_u8
|
||||
|
||||
"$(Inspect.to_str u64_nums) $(Inspect.to_str u8_nums)"
|
||||
"${Inspect.to_str(u64_nums)} ${Inspect.to_str(u8_nums)}"
|
||||
"#
|
||||
),
|
||||
@"" // no errors
|
||||
|
@ -15304,7 +15304,7 @@ All branches in an `if` must have the same type!
|
|||
get_cheer = \msg ->
|
||||
name = Effect.get_line! {}
|
||||
|
||||
"$(msg), $(name)!"
|
||||
"${msg}, ${name}!"
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
|
@ -15340,7 +15340,7 @@ All branches in an `if` must have the same type!
|
|||
|
||||
trim : Str -> Str
|
||||
trim = \msg ->
|
||||
Effect.put_line! "Trimming $(msg)"
|
||||
Effect.put_line!("Trimming ${msg}")
|
||||
Str.trim msg
|
||||
"#
|
||||
),
|
||||
|
@ -15349,7 +15349,7 @@ All branches in an `if` must have the same type!
|
|||
|
||||
This call to `Effect.put_line!` might produce an effect:
|
||||
|
||||
10│ Effect.put_line! "Trimming $(msg)"
|
||||
10│ Effect.put_line!("Trimming ${msg}")
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
However, the type of the enclosing function requires that it's pure:
|
||||
|
@ -15736,7 +15736,7 @@ All branches in an `if` must have the same type!
|
|||
(get, put) = (Effect.get_line!, Effect.put_line!)
|
||||
|
||||
name = get {}
|
||||
put "Hi, $(name)"
|
||||
put "Hi, ${name}"
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
|
@ -15808,7 +15808,7 @@ All branches in an `if` must have the same type!
|
|||
Tag get put = Tag Effect.get_line! Effect.put_line!
|
||||
|
||||
name = get {}
|
||||
put "Hi, $(name)"
|
||||
put "Hi, ${name}"
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
|
|
|
@ -1574,7 +1574,7 @@ fn module_params_checks() {
|
|||
r#"
|
||||
module { key } -> [url]
|
||||
|
||||
url = "example.com/$(key)"
|
||||
url = "example.com/${key}"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
@ -1605,7 +1605,7 @@ fn module_params_optional() {
|
|||
r#"
|
||||
module { key, exp ? "default" } -> [url]
|
||||
|
||||
url = "example.com/$(key)?exp=$(exp)"
|
||||
url = "example.com/${key}?exp=${exp}"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
@ -1636,7 +1636,7 @@ fn module_params_typecheck_fail() {
|
|||
r#"
|
||||
module { key } -> [url]
|
||||
|
||||
url = "example.com/$(key)"
|
||||
url = "example.com/${key}"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
@ -1687,7 +1687,7 @@ fn module_params_missing_fields() {
|
|||
r#"
|
||||
module { key } -> [url]
|
||||
|
||||
url = "example.com/$(key)"
|
||||
url = "example.com/${key}"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
@ -1740,7 +1740,7 @@ fn module_params_extra_fields() {
|
|||
r#"
|
||||
module { key } -> [url]
|
||||
|
||||
url = "example.com/$(key)"
|
||||
url = "example.com/${key}"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
@ -1839,7 +1839,7 @@ fn module_params_missing() {
|
|||
r#"
|
||||
module { key, exp } -> [url]
|
||||
|
||||
url = "example.com/$(key)?exp=$(Num.to_str exp)"
|
||||
url = "example.com/${key}?exp=${Num.to_str(exp)}"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
@ -2169,7 +2169,7 @@ fn roc_package_depends_on_other_package() {
|
|||
r#"
|
||||
module [say]
|
||||
|
||||
say = \msg -> "$(msg), world!"
|
||||
say = \msg -> "${msg}, world!"
|
||||
"#
|
||||
),
|
||||
),
|
||||
|
|
|
@ -75,7 +75,7 @@ pub enum CalledVia {
|
|||
UnaryOp(UnaryOp),
|
||||
|
||||
/// This call is the result of desugaring string interpolation,
|
||||
/// e.g. "$(first) $(last)" is transformed into Str.concat (Str.concat first " ") last.
|
||||
/// e.g. "${first} ${last}" is transformed into `Str.concat(Str.concat(first, " "))` last.
|
||||
StringInterpolation,
|
||||
|
||||
/// This call is the result of desugaring a map2-based Record Builder field. e.g.
|
||||
|
|
|
@ -425,16 +425,18 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
}
|
||||
}
|
||||
}
|
||||
b'(' if preceded_by_dollar && !is_single_quote => {
|
||||
b'(' | b'{' if preceded_by_dollar && !is_single_quote => {
|
||||
let old_style_interpolation_block = one_byte == b'(';
|
||||
|
||||
// We're about to begin string interpolation!
|
||||
//
|
||||
// End the previous segment so we can begin a new one.
|
||||
// Retroactively end it right before the `$` char we parsed.
|
||||
// (We can't use end_segment! here because it ends it right after
|
||||
// the just-parsed character, which here would be '(' rather than '$')
|
||||
// the just-parsed character, which here would be '{' rather than '$')
|
||||
// Don't push anything if the string would be empty.
|
||||
if segment_parsed_bytes > 2 {
|
||||
// exclude the 2 chars we just parsed, namely '$' and '('
|
||||
// exclude the 2 chars we just parsed, namely '$' and '{'
|
||||
let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 2)];
|
||||
|
||||
match std::str::from_utf8(string_bytes) {
|
||||
|
@ -452,19 +454,27 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
}
|
||||
}
|
||||
|
||||
// Advance past the `$(`
|
||||
// Advance past the `${`
|
||||
state.advance_mut(2);
|
||||
|
||||
let original_byte_count = state.bytes().len();
|
||||
|
||||
// Parse an arbitrary expression, followed by ')'
|
||||
// Parse an arbitrary expression, followed by '}' or ')'
|
||||
let terminating_char = if old_style_interpolation_block {
|
||||
b')'
|
||||
} else {
|
||||
b'}'
|
||||
};
|
||||
let (_progress, (mut loc_expr, sp), new_state) = and(
|
||||
specialize_err_ref(
|
||||
EString::Format,
|
||||
loc(allocated(reset_min_indent(expr::expr_help())))
|
||||
.trace("str_interpolation"),
|
||||
),
|
||||
skip_second(space0_e(EString::FormatEnd), byte(b')', EString::FormatEnd)),
|
||||
skip_second(
|
||||
space0_e(EString::FormatEnd),
|
||||
byte(terminating_char, EString::FormatEnd),
|
||||
),
|
||||
)
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
|
@ -488,8 +498,8 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
}
|
||||
}
|
||||
|
||||
// iff the '$' is followed by '(', this is string interpolation.
|
||||
// We'll check for the '(' on the next iteration of the loop.
|
||||
// iff the '$' is followed by '{', this is string interpolation.
|
||||
// We'll check for the '{' on the next iteration of the loop.
|
||||
preceded_by_dollar = one_byte == b'$';
|
||||
}
|
||||
|
||||
|
|
|
@ -160,17 +160,17 @@ mod test_parse {
|
|||
|
||||
#[test]
|
||||
fn escaped_interpolation() {
|
||||
assert_segments(r#""Hi, \$(name)!""#, |arena| {
|
||||
assert_segments(r#""Hi, \${name}!""#, |arena| {
|
||||
bumpalo::vec![in arena;
|
||||
Plaintext("Hi, "),
|
||||
EscapedChar(EscapedChar::Dollar),
|
||||
Plaintext("(name)!"),
|
||||
Plaintext("{name}!"),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_middle() {
|
||||
fn string_with_old_interpolation_still_works_for_now() {
|
||||
assert_segments(r#""Hi, $(name)!""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
|
@ -185,9 +185,31 @@ mod test_parse {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_mixed_new_and_old_interpolation_braces_fails() {
|
||||
assert_parsing_fails(r#""${foo)""#, SyntaxError::Unexpected(Region::zero()));
|
||||
assert_parsing_fails(r#""$(foo}""#, SyntaxError::Unexpected(Region::zero()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_middle() {
|
||||
assert_segments(r#""Hi, ${name}!""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
});
|
||||
|
||||
bumpalo::vec![in arena;
|
||||
Plaintext("Hi, "),
|
||||
Interpolated(Loc::new(7, 11, expr)),
|
||||
Plaintext("!")
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_front() {
|
||||
assert_segments(r#""$(name), hi!""#, |arena| {
|
||||
assert_segments(r#""${name}, hi!""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
@ -232,7 +254,7 @@ mod test_parse {
|
|||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_back() {
|
||||
assert_segments(r#""Hello $(name)""#, |arena| {
|
||||
assert_segments(r#""Hello ${name}""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
@ -247,7 +269,7 @@ mod test_parse {
|
|||
|
||||
#[test]
|
||||
fn string_with_multiple_interpolations() {
|
||||
assert_segments(r#""Hi, $(name)! How is $(project) going?""#, |arena| {
|
||||
assert_segments(r#""Hi, ${name}! How is ${project} going?""#, |arena| {
|
||||
let expr1 = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
@ -271,7 +293,7 @@ mod test_parse {
|
|||
#[test]
|
||||
fn string_with_non_interpolation_dollar_signs() {
|
||||
assert_segments(
|
||||
r#""$a Hi, $(name)! $b How is $(project) going? $c""#,
|
||||
r#""$a Hi, ${name}! $b How is ${project} going? $c""#,
|
||||
|arena| {
|
||||
let expr1 = arena.alloc(Var {
|
||||
module_name: "",
|
||||
|
|
|
@ -324,7 +324,7 @@ mod solve_expr {
|
|||
r#"
|
||||
what_it_is = "great"
|
||||
|
||||
"type inference is $(what_it_is)!"
|
||||
"type inference is ${what_it_is}!"
|
||||
"#
|
||||
),
|
||||
"Str",
|
||||
|
@ -338,7 +338,7 @@ mod solve_expr {
|
|||
r#"
|
||||
what_it_is = "great"
|
||||
|
||||
str = "type inference is $(what_it_is)!"
|
||||
str = "type inference is ${what_it_is}!"
|
||||
|
||||
what_it_is
|
||||
"#
|
||||
|
@ -354,7 +354,7 @@ mod solve_expr {
|
|||
r#"
|
||||
rec = { what_it_is: "great" }
|
||||
|
||||
str = "type inference is $(rec.what_it_is)!"
|
||||
str = "type inference is ${rec.what_it_is}!"
|
||||
|
||||
rec
|
||||
"#
|
||||
|
@ -4751,7 +4751,7 @@ mod solve_expr {
|
|||
r#"
|
||||
set_roc_email : _ -> { name: Str, email: Str }_
|
||||
set_roc_email = \person ->
|
||||
{ person & email: "$(person.name)@roclang.com" }
|
||||
{ person & email: "${person.name}@roclang.com" }
|
||||
set_roc_email
|
||||
"#
|
||||
),
|
||||
|
|
|
@ -330,7 +330,7 @@ fn list_map_try_ok() {
|
|||
List.map_try [1, 2, 3] \num ->
|
||||
str = Num.to_str (num * 2)
|
||||
|
||||
Ok "$(str)!"
|
||||
Ok "${str}!"
|
||||
"#,
|
||||
// Result Str [] is unwrapped to just Str
|
||||
RocList::<RocStr>::from_slice(&[
|
||||
|
@ -3870,10 +3870,10 @@ fn issue_3571_lowlevel_call_function_with_bool_lambda_set() {
|
|||
List.concat state mapped_vals
|
||||
|
||||
add2 : Str -> Str
|
||||
add2 = \x -> "added $(x)"
|
||||
add2 = \x -> "added ${x}"
|
||||
|
||||
mul2 : Str -> Str
|
||||
mul2 = \x -> "multiplied $(x)"
|
||||
mul2 = \x -> "multiplied ${x}"
|
||||
|
||||
foo = [add2, mul2]
|
||||
bar = ["1", "2", "3", "4"]
|
||||
|
|
|
@ -3120,7 +3120,7 @@ fn recursively_build_effect() {
|
|||
hi = "Hello"
|
||||
name = "World"
|
||||
|
||||
"$(hi), $(name)!"
|
||||
"${hi}, ${name}!"
|
||||
|
||||
main =
|
||||
when nest_help 4 is
|
||||
|
@ -3876,8 +3876,8 @@ fn compose_recursive_lambda_set_productive_toplevel() {
|
|||
compose = \f, g -> \x -> g (f x)
|
||||
|
||||
identity = \x -> x
|
||||
exclaim = \s -> "$(s)!"
|
||||
whisper = \s -> "($(s))"
|
||||
exclaim = \s -> "${s}!"
|
||||
whisper = \s -> "(${s})"
|
||||
|
||||
main =
|
||||
res: Str -> Str
|
||||
|
@ -3899,8 +3899,8 @@ fn compose_recursive_lambda_set_productive_nested() {
|
|||
compose = \f, g -> \x -> g (f x)
|
||||
|
||||
identity = \x -> x
|
||||
exclaim = \s -> "$(s)!"
|
||||
whisper = \s -> "($(s))"
|
||||
exclaim = \s -> "${s}!"
|
||||
whisper = \s -> "(${s})"
|
||||
|
||||
res: Str -> Str
|
||||
res = List.walk [ exclaim, whisper ] identity compose
|
||||
|
@ -3921,8 +3921,8 @@ fn compose_recursive_lambda_set_productive_inferred() {
|
|||
compose = \f, g -> \x -> g (f x)
|
||||
|
||||
identity = \x -> x
|
||||
exclaim = \s -> "$(s)!"
|
||||
whisper = \s -> "($(s))"
|
||||
exclaim = \s -> "${s}!"
|
||||
whisper = \s -> "(${s})"
|
||||
|
||||
res = List.walk [ exclaim, whisper ] identity compose
|
||||
res "hello"
|
||||
|
@ -3947,8 +3947,8 @@ fn compose_recursive_lambda_set_productive_nullable_wrapped() {
|
|||
else \x -> f (g x)
|
||||
|
||||
identity = \x -> x
|
||||
exclame = \s -> "$(s)!"
|
||||
whisper = \s -> "($(s))"
|
||||
exclame = \s -> "${s}!"
|
||||
whisper = \s -> "(${s})"
|
||||
|
||||
main =
|
||||
res: Str -> Str
|
||||
|
@ -4475,7 +4475,7 @@ fn reset_recursive_type_wraps_in_named_type() {
|
|||
Cons x xs ->
|
||||
str_x = f x
|
||||
str_xs = print_linked_list xs f
|
||||
"Cons $(str_x) ($(str_xs))"
|
||||
"Cons ${str_x} (${str_xs})"
|
||||
"#
|
||||
),
|
||||
RocStr::from("Cons 2 (Cons 3 (Cons 4 (Nil)))"),
|
||||
|
|
|
@ -37,7 +37,7 @@ fn early_return_nested_ifs() {
|
|||
else
|
||||
third
|
||||
|
||||
"$(first), $(second)"
|
||||
"${first}, ${second}"
|
||||
|
||||
main : List Str
|
||||
main = List.map [1, 2, 3] display_n
|
||||
|
@ -76,7 +76,7 @@ fn early_return_nested_whens() {
|
|||
_ ->
|
||||
third
|
||||
|
||||
"$(first), $(second)"
|
||||
"${first}, ${second}"
|
||||
|
||||
main : List Str
|
||||
main = List.map [1, 2, 3] display_n
|
||||
|
|
|
@ -1759,7 +1759,7 @@ fn lambda_capture_niches_with_other_lambda_capture() {
|
|||
when val is
|
||||
_ -> ""
|
||||
|
||||
capture2 = \val -> \{} -> "$(val)"
|
||||
capture2 = \val -> \{} -> "${val}"
|
||||
|
||||
x : [A, B, C]
|
||||
x = A
|
||||
|
@ -2072,7 +2072,7 @@ fn polymorphic_expression_unification() {
|
|||
]
|
||||
parse_function : Str -> RenderTree
|
||||
parse_function = \name ->
|
||||
last = Indent [Text ".trace(\"$(name)\")" ]
|
||||
last = Indent [Text ".trace(\"${name}\")" ]
|
||||
Indent [last]
|
||||
|
||||
values = parse_function "interface_header"
|
||||
|
@ -2636,7 +2636,7 @@ fn recursively_build_effect() {
|
|||
hi = "Hello"
|
||||
name = "World"
|
||||
|
||||
"$(hi), $(name)!"
|
||||
"${hi}, ${name}!"
|
||||
|
||||
main =
|
||||
when nest_help 4 is
|
||||
|
@ -2956,8 +2956,8 @@ fn compose_recursive_lambda_set_productive_nullable_wrapped() {
|
|||
else \x -> f (g x)
|
||||
|
||||
identity = \x -> x
|
||||
exclaim = \s -> "$(s)!"
|
||||
whisper = \s -> "($(s))"
|
||||
exclaim = \s -> "${s}!"
|
||||
whisper = \s -> "(${s})"
|
||||
|
||||
main =
|
||||
res: Str -> Str
|
||||
|
@ -3291,7 +3291,7 @@ fn dbg_nested_expr() {
|
|||
fn dbg_inside_string() {
|
||||
indoc!(
|
||||
r#"
|
||||
"Hello $(dbg "world")!"
|
||||
"Hello ${dbg "world"}!"
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
@ -3690,7 +3690,7 @@ fn dec_refcount_for_usage_after_early_return_in_if() {
|
|||
else
|
||||
third
|
||||
|
||||
"$(first), $(second)"
|
||||
"${first}, ${second}"
|
||||
|
||||
display_n 3
|
||||
"#
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
"$(g)" : q
|
||||
"${g}" : q
|
||||
f
|
|
@ -1,2 +1,2 @@
|
|||
"""$(g)""":q
|
||||
"""${g}""":q
|
||||
f
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
"""
|
||||
"$(i
|
||||
"${i
|
||||
"""
|
||||
""")"
|
||||
"""}"
|
|
@ -1 +1 @@
|
|||
"""""""$(i"""""")"
|
||||
"""""""${i""""""}"
|
|
@ -1,8 +1,8 @@
|
|||
"""
|
||||
$({
|
||||
${{
|
||||
}
|
||||
i)
|
||||
$({
|
||||
i}
|
||||
${{
|
||||
}
|
||||
i)
|
||||
i}
|
||||
"""
|
|
@ -1,4 +1,4 @@
|
|||
"""$({
|
||||
}i)
|
||||
$({
|
||||
}i)"""
|
||||
"""${{
|
||||
}i}
|
||||
${{
|
||||
}i}"""
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
"$(S #
|
||||
)"
|
||||
"${S #
|
||||
}"
|
|
@ -1,2 +1,2 @@
|
|||
"$((S#
|
||||
))"
|
||||
"${(S#
|
||||
)}"
|
||||
|
|
|
@ -3,5 +3,5 @@ main =
|
|||
|> List.dropFirst 1
|
||||
|> List.mapTry? Str.toU8
|
||||
|> List.sum
|
||||
|> \total -> "Sum of numbers: $(Num.to_str total)"
|
||||
|> \total -> "Sum of numbers: ${Num.to_str total}"
|
||||
|> Str.toUpper
|
||||
|
|
|
@ -6575,13 +6575,13 @@ mod test_fmt {
|
|||
expr_formats_to(
|
||||
indoc!(
|
||||
"
|
||||
x = \"foo:\u{200B} $(bar).\"
|
||||
x = \"foo:\u{200B} ${bar}.\"
|
||||
x
|
||||
"
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
x = "foo:\u(200b) $(bar)."
|
||||
x = "foo:\u(200b) ${bar}."
|
||||
x
|
||||
"#
|
||||
),
|
||||
|
@ -6595,7 +6595,7 @@ mod test_fmt {
|
|||
"
|
||||
x =
|
||||
\"\"\"
|
||||
foo:\u{200B} $(bar).
|
||||
foo:\u{200B} ${bar}.
|
||||
\"\"\"
|
||||
x
|
||||
"
|
||||
|
@ -6604,7 +6604,7 @@ mod test_fmt {
|
|||
r#"
|
||||
x =
|
||||
"""
|
||||
foo:\u(200b) $(bar).
|
||||
foo:\u(200b) ${bar}.
|
||||
"""
|
||||
x
|
||||
"#
|
||||
|
|
|
@ -1036,7 +1036,7 @@ mod test_snapshots {
|
|||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_middle() {
|
||||
assert_segments(r#""Hi, $(name)!""#, |arena| {
|
||||
assert_segments(r#""Hi, ${name}!""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
@ -1052,7 +1052,7 @@ mod test_snapshots {
|
|||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_front() {
|
||||
assert_segments(r#""$(name), hi!""#, |arena| {
|
||||
assert_segments(r#""${name}, hi!""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
@ -1067,7 +1067,7 @@ mod test_snapshots {
|
|||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_back() {
|
||||
assert_segments(r#""Hello $(name)""#, |arena| {
|
||||
assert_segments(r#""Hello ${name}""#, |arena| {
|
||||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
@ -1082,7 +1082,7 @@ mod test_snapshots {
|
|||
|
||||
#[test]
|
||||
fn string_with_multiple_interpolations() {
|
||||
assert_segments(r#""Hi, $(name)! How is $(project) going?""#, |arena| {
|
||||
assert_segments(r#""Hi, ${name}! How is ${project} going?""#, |arena| {
|
||||
let expr1 = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
|
|
|
@ -48,7 +48,7 @@ shape = \@Types(types), id ->
|
|||
Err(OutOfBounds) ->
|
||||
id_str = Num.to_str(type_id_to_u64(id))
|
||||
|
||||
crash("TypeId #$(id_str) was not found in Types. This should never happen, and means there was a bug in `roc glue`. If you have time, please open an issue at <https://github.com/roc-lang/roc/issues>")
|
||||
crash("TypeId #${id_str} was not found in Types. This should never happen, and means there was a bug in `roc glue`. If you have time, please open an issue at <https://github.com/roc-lang/roc/issues>")
|
||||
|
||||
alignment : Types, TypeId -> U32
|
||||
alignment = \@Types(types), id ->
|
||||
|
@ -57,7 +57,7 @@ alignment = \@Types(types), id ->
|
|||
Err(OutOfBounds) ->
|
||||
id_str = Num.to_str(type_id_to_u64(id))
|
||||
|
||||
crash("TypeId #$(id_str) was not found in Types. This should never happen, and means there was a bug in `roc glue`. If you have time, please open an issue at <https://github.com/roc-lang/roc/issues>")
|
||||
crash("TypeId #${id_str} was not found in Types. This should never happen, and means there was a bug in `roc glue`. If you have time, please open an issue at <https://github.com/roc-lang/roc/issues>")
|
||||
|
||||
size : Types, TypeId -> U32
|
||||
size = \@Types(types), id ->
|
||||
|
@ -66,4 +66,4 @@ size = \@Types(types), id ->
|
|||
Err(OutOfBounds) ->
|
||||
id_str = Num.to_str(type_id_to_u64(id))
|
||||
|
||||
crash("TypeId #$(id_str) was not found in Types. This should never happen, and means there was a bug in `roc glue`. If you have time, please open an issue at <https://github.com/roc-lang/roc/issues>")
|
||||
crash("TypeId #${id_str} was not found in Types. This should never happen, and means there was a bug in `roc glue`. If you have time, please open an issue at <https://github.com/roc-lang/roc/issues>")
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -755,14 +755,14 @@ fn type_problem_unary_operator() {
|
|||
#[test]
|
||||
fn type_problem_string_interpolation() {
|
||||
expect_failure(
|
||||
"\"This is not a string -> $(1)\"",
|
||||
"\"This is not a string -> ${1}\"",
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
This argument to this string interpolation has an unexpected type:
|
||||
|
||||
4│ "This is not a string -> $(1)"
|
||||
4│ "This is not a string -> ${1}"
|
||||
^
|
||||
|
||||
The argument is a number of type:
|
||||
|
@ -833,14 +833,14 @@ fn list_get_negative_index() {
|
|||
#[test]
|
||||
fn invalid_string_interpolation() {
|
||||
expect_failure(
|
||||
"\"$(123)\"",
|
||||
"\"${123}\"",
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
This argument to this string interpolation has an unexpected type:
|
||||
|
||||
4│ "$(123)"
|
||||
4│ "${123}"
|
||||
^^^
|
||||
|
||||
The argument is a number of type:
|
||||
|
@ -1537,7 +1537,7 @@ fn interpolation_with_nested_strings() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
"foo $(Str.join_with ["a", "b", "c"] ", ") bar"
|
||||
"foo ${Str.join_with ["a", "b", "c"] ", "} bar"
|
||||
"#
|
||||
),
|
||||
r#""foo a, b, c bar" : Str"#,
|
||||
|
@ -1549,7 +1549,7 @@ fn interpolation_with_num_to_str() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
"foo $(Num.to_str Num.max_i8) bar"
|
||||
"foo ${Num.to_str Num.max_i8} bar"
|
||||
"#
|
||||
),
|
||||
r#""foo 127 bar" : Str"#,
|
||||
|
@ -1561,7 +1561,7 @@ fn interpolation_with_operator_desugaring() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
"foo $(Num.to_str (1 + 2)) bar"
|
||||
"foo ${Num.to_str (1 + 2)} bar"
|
||||
"#
|
||||
),
|
||||
r#""foo 3 bar" : Str"#,
|
||||
|
@ -1576,7 +1576,7 @@ fn interpolation_with_nested_interpolation() {
|
|||
expect_failure(
|
||||
indoc!(
|
||||
r#"
|
||||
"foo $(Str.join_with ["a$(Num.to_str 5)", "b"] "c")"
|
||||
"foo ${Str.join_with ["a${Num.to_str 5}", "b"] "c"}"
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -1585,7 +1585,7 @@ fn interpolation_with_nested_interpolation() {
|
|||
|
||||
This string interpolation is invalid:
|
||||
|
||||
4│ "foo $(Str.join_with ["a$(Num.to_str 5)", "b"] "c")"
|
||||
4│ "foo ${Str.join_with ["a${Num.to_str 5}", "b"] "c"}"
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
String interpolations cannot contain newlines or other interpolations.
|
||||
|
|
|
@ -1098,7 +1098,7 @@ fn to_str_report<'a>(
|
|||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
alloc.concat([
|
||||
alloc.reflow(r"You could change it to something like "),
|
||||
alloc.parser_suggestion("\"The count is $(count)\""),
|
||||
alloc.parser_suggestion("\"The count is ${count}\""),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
]);
|
||||
|
@ -1176,7 +1176,7 @@ fn to_str_report<'a>(
|
|||
alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("I am part way through parsing this single-quote literal, "),
|
||||
alloc.reflow("but I encountered a string interpolation like \"$(this)\","),
|
||||
alloc.reflow("but I encountered a string interpolation like \"${this}\","),
|
||||
alloc.reflow("which is not allowed in single-quote literals."),
|
||||
]),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
|
|
|
@ -341,7 +341,7 @@ fn joinpoint_with_closure() {
|
|||
cat_sound = make_sound Cat
|
||||
dog_sound = make_sound Dog
|
||||
goose_sound = make_sound Goose
|
||||
"Cat: $(cat_sound), Dog: $(dog_sound), Goose: $(goose_sound)"
|
||||
"Cat: ${cat_sound}, Dog: ${dog_sound}, Goose: ${goose_sound}"
|
||||
|
||||
test
|
||||
)
|
||||
|
@ -370,7 +370,7 @@ fn joinpoint_with_reuse() {
|
|||
Cons x xs ->
|
||||
str_x = f x
|
||||
str_xs = print_linked_list xs f
|
||||
"Cons $(str_x) ($(str_xs))"
|
||||
"Cons ${str_x} (${str_xs})"
|
||||
|
||||
test =
|
||||
new_list = map_linked_list (Cons 1 (Cons 2 (Cons 3 Nil))) (\x -> x + 1)
|
||||
|
@ -457,7 +457,7 @@ fn tree_rebalance() {
|
|||
s_l = node_in_parens left show_key show_value
|
||||
s_r = node_in_parens right show_key show_value
|
||||
|
||||
"Node $(s_color) $(s_key) $(s_value) $(s_l) $(s_r)"
|
||||
"Node ${s_color} ${s_key} ${s_value} ${s_l} ${s_r}"
|
||||
|
||||
node_in_parens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
|
||||
node_in_parens = \tree, show_key, show_value ->
|
||||
|
@ -468,7 +468,7 @@ fn tree_rebalance() {
|
|||
Node _ _ _ _ _ ->
|
||||
inner = show_rb_tree tree show_key show_value
|
||||
|
||||
"($(inner))"
|
||||
"(${inner})"
|
||||
|
||||
show_color : NodeColor -> Str
|
||||
show_color = \color ->
|
||||
|
@ -516,7 +516,7 @@ fn joinpoint_nullpointer() {
|
|||
Nil -> "Nil"
|
||||
Cons x xs ->
|
||||
str_xs = print_linked_list xs
|
||||
"Cons $(x) ($(str_xs))"
|
||||
"Cons ${x} (${str_xs})"
|
||||
|
||||
linked_list_head : LinkedList Str -> LinkedList Str
|
||||
linked_list_head = \linked_list ->
|
||||
|
@ -528,7 +528,7 @@ fn joinpoint_nullpointer() {
|
|||
test =
|
||||
cons = print_linked_list (linked_list_head (Cons "foo" Nil))
|
||||
nil = print_linked_list (linked_list_head (Nil))
|
||||
"$(cons) - $(nil)"
|
||||
"${cons} - ${nil}"
|
||||
|
||||
test
|
||||
)
|
||||
|
|
|
@ -37,7 +37,7 @@ view =
|
|||
Newline,
|
||||
Desc([Ident("user"), Kw("="), Ident("Http.get!"), Ident("url"), Ident("Json.utf8")], "<p>This fetches the contents of the URL and decodes them as <a href=\"https://www.json.org\">JSON</a>.</p><p>If the shape of the JSON isn't compatible with the type of <code>user</code> (based on type inference), this will give a decoding error immediately.</p><p>As with all the other function calls involving the <code>!</code> operator, if there's an error, nothing else in <code>storeEmail</code> will be run, and <code>handleErr</code> will run.</p>"),
|
||||
Newline,
|
||||
Desc([Ident("dest"), Kw("="), Ident("Path.fromStr"), StrInterpolation("\"", "user.name", ".txt\"")], "<p>The <code>\$(user.name)</code> in this string literal will be replaced with the value stored in the <code>user</code> record's <code>name</code> field. This is <a href=\"/tutorial#string-interpolation\">string interpolation</a>.</p><p>Note that this function call doesn't involve the <code>!</code> operator. That's because <code>Path.fromStr</code> doesn't involve any Tasks, so there's no need to use <code>!</code> to wait for it to finish.</p>"),
|
||||
Desc([Ident("dest"), Kw("="), Ident("Path.fromStr"), StrInterpolation("\"", "user.name", ".txt\"")], "<p>The <code>\${user.name}</code> in this string literal will be replaced with the value stored in the <code>user</code> record's <code>name</code> field. This is <a href=\"/tutorial#string-interpolation\">string interpolation</a>.</p><p>Note that this function call doesn't involve the <code>!</code> operator. That's because <code>Path.fromStr</code> doesn't involve any Tasks, so there's no need to use <code>!</code> to wait for it to finish.</p>"),
|
||||
Newline,
|
||||
Desc([Ident("File.writeUtf8!"), Ident("dest"), Ident("user.email")], "<p>This writes <code>user.email</code> to the file, encoded as <a href=\"https://en.wikipedia.org/wiki/UTF-8\">UTF-8</a>.</p><p>Since <code>File.writeUtf8</code> doesn't produce any information on success, we don't bother using <code>=</code> like we did on the other lines.</p>"),
|
||||
Newline,
|
||||
|
@ -90,7 +90,7 @@ tokens_to_str = \tokens ->
|
|||
# Don't put spaces after opening parens or before closing parens
|
||||
args_with_commas =
|
||||
args
|
||||
|> List.map(\ident -> "<span class=\"ident\">$(ident)</span>")
|
||||
|> List.map(\ident -> "<span class=\"ident\">${ident}</span>")
|
||||
|> Str.join_with("<span class=\"literal\">,</span> ")
|
||||
|
||||
buf_with_space
|
||||
|
@ -99,22 +99,22 @@ tokens_to_str = \tokens ->
|
|||
|> Str.concat("<span class=\"kw\"> -></span>")
|
||||
|
||||
Kw(str) ->
|
||||
Str.concat(buf_with_space, "<span class=\"kw\">$(str)</span>")
|
||||
Str.concat(buf_with_space, "<span class=\"kw\">${str}</span>")
|
||||
|
||||
Num(str) | Str(str) | Literal(str) -> # We may render these differently in the future
|
||||
Str.concat(buf_with_space, "<span class=\"literal\">$(str)</span>")
|
||||
Str.concat(buf_with_space, "<span class=\"literal\">${str}</span>")
|
||||
|
||||
Comment(str) ->
|
||||
Str.concat(buf_with_space, "<span class=\"comment\"># $(str)</span>")
|
||||
Str.concat(buf_with_space, "<span class=\"comment\"># ${str}</span>")
|
||||
|
||||
Ident(str) ->
|
||||
Str.concat(buf_with_space, ident_to_html(str))
|
||||
|
||||
StrInterpolation(before, interp, after) ->
|
||||
buf_with_space
|
||||
|> Str.concat((if Str.is_empty(before) then "" else "<span class=\"literal\">$(before)</span>"))
|
||||
|> Str.concat("<span class=\"kw\">\$(</span>$(ident_to_html(interp))<span class=\"kw\">)</span>")
|
||||
|> Str.concat((if Str.is_empty(after) then "" else "<span class=\"literal\">$(after)</span>")))
|
||||
|> Str.concat((if Str.is_empty(before) then "" else "<span class=\"literal\">${before}</span>"))
|
||||
|> Str.concat("<span class=\"kw\">\${</span>${ident_to_html(interp)}<span class=\"kw\">}</span>")
|
||||
|> Str.concat((if Str.is_empty(after) then "" else "<span class=\"literal\">${after}</span>")))
|
||||
|
||||
ident_to_html : Str -> Str
|
||||
ident_to_html = \str ->
|
||||
|
@ -122,18 +122,18 @@ ident_to_html = \str ->
|
|||
len = Str.count_utf8_bytes(ident)
|
||||
without_suffix = ident |> Str.replace_last("!", "")
|
||||
|
||||
ident_html = "<span class=\"ident\">$(without_suffix)</span>"
|
||||
ident_html = "<span class=\"ident\">${without_suffix}</span>"
|
||||
html =
|
||||
# If removing a trailing "!" changed the length, then there must have been a trailing "!"
|
||||
if len > Str.count_utf8_bytes(without_suffix) then
|
||||
"$(ident_html)<span class=\"kw\">!</span>"
|
||||
"${ident_html}<span class=\"kw\">!</span>"
|
||||
else
|
||||
ident_html
|
||||
|
||||
if Str.is_empty(accum) then
|
||||
html
|
||||
else
|
||||
"$(accum)<span class=\"kw\">.</span>$(html)")
|
||||
"${accum}<span class=\"kw\">.</span>${html}")
|
||||
|
||||
sections_to_str : List Section -> Str
|
||||
sections_to_str = \sections ->
|
||||
|
@ -178,5 +178,5 @@ radio = \index, label_html, desc_html ->
|
|||
checked_html = if index == 0 then " checked" else ""
|
||||
|
||||
"""
|
||||
<input class="interactive-radio" type="radio" name="r" id="r$(Num.to_str(index))" $(checked_html)><label for="r$(Num.to_str(index))" title="Tap to learn about this syntax">$(label_html)</label><span class="interactive-desc" role="presentation"><button class="close-desc">X</button>$(desc_html)</span>
|
||||
<input class="interactive-radio" type="radio" name="r" id="r${Num.to_str(index)}" ${checked_html}><label for="r${Num.to_str(index)}" title="Tap to learn about this syntax">${label_html}</label><span class="interactive-desc" role="presentation"><button class="close-desc">X</button>${desc_html}</span>
|
||||
"""
|
||||
|
|
|
@ -9,7 +9,7 @@ Roc's syntax isn't trivial, but there also isn't much of it to learn. It's desig
|
|||
- `user.email` always accesses the `email` field of a record named `user`. <span class="nowrap">(Roc has</span> no inheritance, subclassing, or proxying.)
|
||||
- `Email.isValid` always refers to something named `isValid` exported by a module named `Email`. (Module names are always capitalized, and variables/constants never are.) Modules are always defined statically and can't be modified at runtime; there's no [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) to consider either.
|
||||
- `x = doSomething y z` always declares a new constant `x` (Roc has [no mutable variables, reassignment, or shadowing](/functional)) to be whatever the `doSomething` function returns when passed the arguments `y` and `z`. (Function calls in Roc don't need parentheses or commas.)
|
||||
- `"Name: $(Str.trim name)"` uses *string interpolation* syntax: a dollar sign inside a string literal, followed by an expression in parentheses.
|
||||
- `"Name: ${Str.trim(name)}"` uses *string interpolation* syntax: a dollar sign inside a string literal, followed by an expression in parentheses.
|
||||
|
||||
Roc also ships with a source code formatter that helps you maintain a consistent style with little effort. The `roc format` command neatly formats your source code according to a common style, and it's designed with the time-saving feature of having no configuration options. This feature saves teams all the time they would otherwise spend debating which stylistic tweaks to settle on!
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ A benefit of this design is that it makes Roc code easier to rearrange without c
|
|||
|
||||
<pre><samp class="code-snippet">func <span class="kw">=</span> <span class="kw">\</span>arg <span class="kw">-></span>
|
||||
greeting <span class="kw">=</span> <span class="string">"Hello"</span>
|
||||
welcome <span class="kw">=</span> <span class="kw">\</span>name <span class="kw">-></span> <span class="string">"</span><span class="kw">$(</span>greeting<span class="kw">)</span><span class="string">, </span><span class="kw">$(</span>name<span class="kw">)</span><span class="string">!"</span>
|
||||
welcome <span class="kw">=</span> <span class="kw">\</span>name <span class="kw">-></span> <span class="string">"</span><span class="kw">${</span>greeting<span class="kw">}</span><span class="string">, </span><span class="kw">${</span>name<span class="kw">}</span><span class="string">!"</span>
|
||||
|
||||
<span class="comment"># …</span>
|
||||
|
||||
|
@ -82,7 +82,7 @@ Suppose I decide to extract the `welcome` function to the top level, so I can re
|
|||
|
||||
<span class="comment"># …</span>
|
||||
|
||||
welcome <span class="kw">=</span> <span class="kw">\</span>prefix<span class="punctuation section">,</span> name <span class="kw">-></span> <span class="string">"</span><span class="kw">$(</span>prefix<span class="kw">)</span><span class="string">, </span><span class="kw">$(</span>name<span class="kw">)</span><span class="string">!"</span></samp></pre>
|
||||
welcome <span class="kw">=</span> <span class="kw">\</span>prefix<span class="punctuation section">,</span> name <span class="kw">-></span> <span class="string">"</span><span class="kw">${</span>prefix<span class="kw">}</span><span class="string">, </span><span class="kw">${</span>name<span class="kw">}</span><span class="string">!"</span></samp></pre>
|
||||
|
||||
Even without knowing the rest of `func`, we can be confident this change will not alter the code's behavior.
|
||||
|
||||
|
@ -90,7 +90,7 @@ In contrast, suppose Roc allowed reassignment. Then it's possible something in t
|
|||
|
||||
<pre><samp class="code-snippet">func <span class="kw">=</span> <span class="kw">\</span>arg <span class="kw">-></span>
|
||||
greeting <span class="kw">=</span> <span class="string">"Hello"</span>
|
||||
welcome <span class="kw">=</span> <span class="kw">\</span>name <span class="kw">-></span> <span class="string">"</span><span class="kw">$(</span>greeting<span class="kw">)</span><span class="string">, </span><span class="kw">$(</span>name<span class="kw">)</span><span class="string">!"</span>
|
||||
welcome <span class="kw">=</span> <span class="kw">\</span>name <span class="kw">-></span> <span class="string">"</span><span class="kw">${</span>greeting<span class="kw">}</span><span class="string">, </span><span class="kw">${</span>name<span class="kw">}</span><span class="string">!"</span>
|
||||
|
||||
<span class="comment"># …</span>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<p id="homepage-tagline">A fast, friendly, functional language.</p>
|
||||
<pre id="first-code-sample"><samp class="code-snippet">credits <span class="kw">=</span> List<span class="punctuation section">.</span>map songs <span class="kw">\</span>song <span class="kw">-></span>
|
||||
<span class="string">"Performed by </span><span class="kw">$(</span>song<span class="punctuation section">.</span>artist<span class="kw">)</span><span class="string">"</span></samp></pre>
|
||||
<span class="string">"Performed by </span><span class="kw">${</span>song<span class="punctuation section">.</span>artist<span class="kw">}</span><span class="string">"</span></samp></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -109,16 +109,16 @@ Note that in Roc, we don't need parentheses or commas to call functions. We don'
|
|||
|
||||
That said, just like in the arithmetic example above, we can use parentheses to specify how nested function calls should work. For example, we could write this:
|
||||
|
||||
<pre><samp><span class="repl-prompt">Str.concat "Birds: " (Num.toStr 42)</span>
|
||||
<pre><samp><span class="repl-prompt">Str.concat "Birds: " (Num.to_str 42)</span>
|
||||
|
||||
<span class="literal">"Birds: 42"</span> <span class="colon">:</span> Str
|
||||
</samp></pre>
|
||||
|
||||
This calls `Num.toStr` on the number `42`, which converts it into the string `"42"`, and then passes that string as the second argument to `Str.concat`.
|
||||
This calls `Num.to_str` on the number `42`, which converts it into the string `"42"`, and then passes that string as the second argument to `Str.concat`.
|
||||
|
||||
The parentheses are important here to specify how the function calls nest. Try removing them, and see what happens:
|
||||
|
||||
<pre><samp><span class="repl-prompt">Str.concat "Birds: " Num.toStr 42</span>
|
||||
<pre><samp><span class="repl-prompt">Str.concat "Birds: " Num.to_str 42</span>
|
||||
|
||||
<span class="repl-err"><error></span>
|
||||
</samp></pre>
|
||||
|
@ -126,12 +126,12 @@ The parentheses are important here to specify how the function calls nest. Try r
|
|||
The error tells us that we've given `Str.concat` too many arguments. Indeed we have! We've passed it three arguments:
|
||||
|
||||
1. The string `"Birds"`
|
||||
2. The function `Num.toStr`
|
||||
2. The function `Num.to_str`
|
||||
3. The number `42`
|
||||
|
||||
That's not what we intended to do. Putting parentheses around the `Num.toStr 42` call clarifies that we want it to be evaluated as its own expression, rather than being two arguments to `Str.concat`.
|
||||
That's not what we intended to do. Putting parentheses around the `Num.to_str 42` call clarifies that we want it to be evaluated as its own expression, rather than being two arguments to `Str.concat`.
|
||||
|
||||
Both the `Str.concat` function and the `Num.toStr` function have a dot in their names. In `Str.concat`, `Str` is the name of a _module_, and `concat` is the name of a function inside that module. Similarly, `Num` is a module, and `toStr` is a function inside that module.
|
||||
Both the `Str.concat` function and the `Num.to_str` function have a dot in their names. In `Str.concat`, `Str` is the name of a _module_, and `concat` is the name of a function inside that module. Similarly, `Num` is a module, and `to_str` is a function inside that module.
|
||||
|
||||
We'll get into more depth about modules later, but for now you can think of a module as a named collection of functions. Eventually we'll discuss how to use them for more than that.
|
||||
|
||||
|
@ -139,7 +139,7 @@ We'll get into more depth about modules later, but for now you can think of a mo
|
|||
|
||||
An alternative syntax for `Str.concat` is _string interpolation_, which looks like this:
|
||||
|
||||
<pre><samp class="repl-prompt"><span class="literal">"<span class="str-esc">$(</span><span class="str-interp">greeting</span><span class="str-esc">)</span> there, <span class="str-esc">$(</span><span class="str-interp">audience</span><span class="str-esc">)</span>."</span></samp></pre>
|
||||
<pre><samp class="repl-prompt"><span class="literal">"<span class="str-esc">${</span><span class="str-interp">greeting</span><span class="str-esc">}</span> there, <span class="str-esc">${</span><span class="str-interp">audience</span><span class="str-esc">}</span>."</span></samp></pre>
|
||||
|
||||
This is syntax sugar for calling `Str.concat` several times, like so:
|
||||
|
||||
|
@ -149,7 +149,7 @@ Str.concat greeting (Str.concat " there, " (Str.concat audience "."))
|
|||
|
||||
You can put entire single-line expressions inside the parentheses in string interpolation. For example:
|
||||
|
||||
<pre><samp class="repl-prompt"><span class="literal">"Two plus three is: <span class="str-esc">$(</span><span class="str-interp">Num.toStr (2 + 3)</span><span class="str-esc">)</span>"</span></samp></pre>
|
||||
<pre><samp class="repl-prompt"><span class="literal">"Two plus three is: <span class="str-esc">${</span><span class="str-interp">Num.to_str(2 + 3)</span><span class="str-esc">}</span>"</span></samp></pre>
|
||||
|
||||
By the way, there are many other ways to put strings together! Check out the [documentation](https://www.roc-lang.org/builtins/Str) for the `Str` module for more.
|
||||
|
||||
|
@ -187,10 +187,10 @@ birds = 3
|
|||
|
||||
iguanas = 2
|
||||
|
||||
total = Num.toStr (birds + iguanas)
|
||||
total = Num.to_str (birds + iguanas)
|
||||
|
||||
main! = \_args ->
|
||||
Stdout.line! "There are $(total) animals."
|
||||
Stdout.line! "There are ${total} animals."
|
||||
```
|
||||
|
||||
Now run `roc main.roc` again. This time the "Downloading ..." message won't appear; the file has been cached from last time, and won't need to be downloaded again.
|
||||
|
@ -204,9 +204,9 @@ You should see this:
|
|||
A definition names an expression.
|
||||
|
||||
- The first two defs assign the names `birds` and `iguanas` to the expressions `3` and `2`.
|
||||
- The next def assigns the name `total` to the expression `Num.toStr (birds + iguanas)`.
|
||||
- The next def assigns the name `total` to the expression `Num.to_str (birds + iguanas)`.
|
||||
|
||||
Once we have a def, we can use its name in other expressions. For example, the `total` expression refers to `birds` and `iguanas`, and `Stdout.line! "There are $(total) animals."` refers to `total`.
|
||||
Once we have a def, we can use its name in other expressions. For example, the `total` expression refers to `birds` and `iguanas`, and `Stdout.line! "There are ${total} animals."` refers to `total`.
|
||||
|
||||
You can name a def using any combination of letters and numbers, but they have to start with a lowercase letter.
|
||||
|
||||
|
@ -219,7 +219,7 @@ birds = 2
|
|||
|
||||
### [Defining Functions](#defining-functions) {#defining-functions}
|
||||
|
||||
So far we've called functions like `Num.toStr`, `Str.concat`, and `Stdout.line`. Next let's try defining a function of our own.
|
||||
So far we've called functions like `Num.to_str`, `Str.concat`, and `Stdout.line`. Next let's try defining a function of our own.
|
||||
|
||||
```roc
|
||||
birds = 3
|
||||
|
@ -229,13 +229,13 @@ iguanas = 2
|
|||
total = add_and_stringify birds iguanas
|
||||
|
||||
main! = \_args ->
|
||||
Stdout.line! "There are $(total) animals."
|
||||
Stdout.line! "There are ${total} animals."
|
||||
|
||||
add_and_stringify = \num1, num2 ->
|
||||
Num.toStr (num1 + num2)
|
||||
Num.to_str (num1 + num2)
|
||||
```
|
||||
|
||||
This new `add_and_stringify` function we've defined accepts two numbers, adds them, calls `Num.toStr` on the result, and returns that.
|
||||
This new `add_and_stringify` function we've defined accepts two numbers, adds them, calls `Num.to_str` on the result, and returns that.
|
||||
|
||||
The `\num1, num2 ->` syntax defines a function's arguments, and the expression after the `->` is the body of the function. Whenever a function gets called, its body expression gets evaluated and returned.
|
||||
|
||||
|
@ -250,13 +250,13 @@ add_and_stringify = \num1, num2 ->
|
|||
if sum == 0 then
|
||||
""
|
||||
else
|
||||
Num.toStr sum
|
||||
Num.to_str sum
|
||||
```
|
||||
|
||||
We did two things here:
|
||||
|
||||
- We introduced a _local def_ named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `add_and_stringify`, it's _local_ to that scope and can't be accessed outside that function.
|
||||
- We added an `if`\-`then`\-`else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`.
|
||||
- We added an `if`\-`then`\-`else` conditional to return either `""` or `Num.to_str sum` depending on whether `sum == 0`.
|
||||
|
||||
Every `if` must be accompanied by both `then` and also `else`. Having an `if` without an `else` is an error, because `if` is an expression, and all expressions must evaluate to a value. If there were ever an `if` without an `else`, that would be an expression that might not evaluate to a value!
|
||||
|
||||
|
@ -273,7 +273,7 @@ add_and_stringify = \num1, num2 ->
|
|||
else if sum < 0 then
|
||||
"negative"
|
||||
else
|
||||
Num.toStr sum
|
||||
Num.to_str sum
|
||||
```
|
||||
|
||||
Note that `else if` is not a separate language keyword! It's just an `if`/`else` where the `else` branch contains another `if`/`else`. This is easier to see with different indentation:
|
||||
|
@ -288,7 +288,7 @@ add_and_stringify = \num1, num2 ->
|
|||
if sum < 0 then
|
||||
"negative"
|
||||
else
|
||||
Num.toStr sum
|
||||
Num.to_str sum
|
||||
```
|
||||
|
||||
This differently-indented version is equivalent to writing `else if sum < 0 then` on the same line, although the convention is to use the original version's style.
|
||||
|
@ -325,7 +325,7 @@ Currently our `add_and_stringify` function takes two arguments. We can instead m
|
|||
total = add_and_stringify { birds: 5, iguanas: 7 }
|
||||
|
||||
add_and_stringify = \counts ->
|
||||
Num.toStr (counts.birds + counts.iguanas)
|
||||
Num.to_str (counts.birds + counts.iguanas)
|
||||
```
|
||||
|
||||
The function now takes a _record_, which is a group of named values. Records are not [objects](<https://en.wikipedia.org/wiki/Object_(computer_science)>); they don't have methods or inheritance, they just store information.
|
||||
|
@ -349,7 +349,7 @@ total = add_and_stringify { birds: 5, iguanas: 7 }
|
|||
total_with_note = add_and_stringify { birds: 4, iguanas: 3, note: "Whee!" }
|
||||
|
||||
add_and_stringify = \counts ->
|
||||
Num.toStr (counts.birds + counts.iguanas)
|
||||
Num.to_str (counts.birds + counts.iguanas)
|
||||
```
|
||||
|
||||
This works because `add_and_stringify` only uses `counts.birds` and `counts.iguanas`. If we were to use `counts.note` inside `add_and_stringify`, then we would get an error because `total` is calling `add_and_stringify` passing a record that doesn't have a `note` field.
|
||||
|
@ -383,14 +383,14 @@ We can use _destructuring_ to avoid naming a record in a function argument, inst
|
|||
|
||||
```roc
|
||||
add_and_stringify = \{ birds, iguanas } ->
|
||||
Num.toStr (birds + iguanas)
|
||||
Num.to_str (birds + iguanas)
|
||||
```
|
||||
|
||||
Here, we've _destructured_ the record to create a `birds` def that's assigned to its `birds` field, and an `iguanas` def that's assigned to its `iguanas` field. We can customize this if we like:
|
||||
|
||||
```roc
|
||||
add_and_stringify = \{ birds, iguanas: lizards } ->
|
||||
Num.toStr (birds + lizards)
|
||||
Num.to_str (birds + lizards)
|
||||
```
|
||||
|
||||
In this version, we created a `lizards` def that's assigned to the record's `iguanas` field. (We could also do something similar with the `birds` field if we like.)
|
||||
|
@ -810,7 +810,7 @@ Here's how calling `List.get` can look in practice:
|
|||
|
||||
```roc
|
||||
when List.get ["a", "b", "c"] index is
|
||||
Ok str -> "I got this string: $(str)"
|
||||
Ok str -> "I got this string: ${str}"
|
||||
Err OutOfBounds -> "That index was out of bounds, sorry!"
|
||||
```
|
||||
|
||||
|
@ -1017,7 +1017,7 @@ Sometimes you may want to document the type of a definition. For example, you mi
|
|||
```roc
|
||||
# Takes a first_name string and a last_name string, and returns a string
|
||||
full_name = \first_name, last_name ->
|
||||
"$(first_name) $(last_name)"
|
||||
"${first_name} ${last_name}"
|
||||
```
|
||||
|
||||
Comments can be valuable documentation, but they can also get out of date and become misleading. If someone changes this function and forgets to update the comment, it will no longer be accurate.
|
||||
|
@ -1029,7 +1029,7 @@ Here's another way to document this function's type, which doesn't have that pro
|
|||
```roc
|
||||
full_name : Str, Str -> Str
|
||||
full_name = \first_name, last_name ->
|
||||
"$(first_name) $(last_name)"
|
||||
"${first_name} ${last_name}"
|
||||
```
|
||||
|
||||
The `full_name :` line is a _type annotation_. It's a strictly optional piece of metadata we can add above a def to describe its type. Unlike a comment, the Roc compiler will check type annotations for accuracy. If the annotation ever doesn't fit with the implementation, we'll get a compile-time error.
|
||||
|
@ -1410,12 +1410,12 @@ You can write automated tests for your Roc code like so:
|
|||
|
||||
```roc
|
||||
pluralize = \singular, plural, count ->
|
||||
count_str = Num.toStr count
|
||||
count_str = Num.to_str count
|
||||
|
||||
if count == 1 then
|
||||
"$(count_str) $(singular)"
|
||||
"${count_str} ${singular}"
|
||||
else
|
||||
"$(count_str) $(plural)"
|
||||
"${count_str} ${plural}"
|
||||
|
||||
expect pluralize "cactus" "cacti" 1 == "1 cactus"
|
||||
|
||||
|
@ -1439,14 +1439,14 @@ Expects do not have to be at the top level:
|
|||
|
||||
```roc
|
||||
pluralize = \singular, plural, count ->
|
||||
count_str = Num.toStr count
|
||||
count_str = Num.to_str count
|
||||
|
||||
if count == 1 then
|
||||
"$(count_str) $(singular)"
|
||||
"${count_str} ${singular}"
|
||||
else
|
||||
expect count > 0
|
||||
|
||||
"$(count_str) $(plural)"
|
||||
"${count_str} ${plural}"
|
||||
```
|
||||
|
||||
This `expect` will fail if you call `pluralize` passing a count of 0.
|
||||
|
@ -1625,7 +1625,7 @@ There are two types of functions in roc, "pure" and "effectful". Consider these
|
|||
```roc
|
||||
with_extension : Str -> Str
|
||||
with_extension = \filename ->
|
||||
"$(filename).roc"
|
||||
"${filename}.roc"
|
||||
|
||||
read_file! : Str => Str
|
||||
read_file! = \path ->
|
||||
|
@ -1690,7 +1690,7 @@ import pf.Stdin
|
|||
main! = \_args ->
|
||||
try Stdout.line! "Type in something and press Enter:"
|
||||
input = try Stdin.line! {}
|
||||
try Stdout.line! "Your input was: $(input)"
|
||||
try Stdout.line! "Your input was: ${input}"
|
||||
|
||||
Ok {}
|
||||
```
|
||||
|
@ -1766,7 +1766,7 @@ main! : List Arg => Result {} [Exit I32 Str]
|
|||
main! = \_args ->
|
||||
try Stdout.line! "Type in something and press Enter:"
|
||||
input = try Stdin.line! {}
|
||||
try Stdout.line! "Your input was: $(input)"
|
||||
try Stdout.line! "Your input was: ${input}"
|
||||
|
||||
Ok {}
|
||||
```
|
||||
|
@ -1798,7 +1798,7 @@ my_function! : {} => Result {} [EndOfFile, StdinErr _, StdoutErr _]
|
|||
my_function! = \{} ->
|
||||
try Stdout.line! "Type in something and press Enter:"
|
||||
input = try Stdin.line! {}
|
||||
try Stdout.line! "Your input was: $(input)"
|
||||
try Stdout.line! "Your input was: ${input}"
|
||||
|
||||
Ok {}
|
||||
```
|
||||
|
@ -1841,7 +1841,7 @@ import pf.Stdin
|
|||
main! = \_args ->
|
||||
try Stdout.line! "Type in something and press Enter:"
|
||||
input = try Stdin.line! {}
|
||||
try Stdout.line! "Your input was: $(input)"
|
||||
try Stdout.line! "Your input was: ${input}"
|
||||
Ok {}
|
||||
```
|
||||
|
||||
|
@ -1856,7 +1856,7 @@ main! = \_args ->
|
|||
_ = Stdout.line! "Type in something and press Enter:"
|
||||
when Stdin.line! {} is
|
||||
Ok input ->
|
||||
_ = Stdout.line! "Your input was: $(input)"
|
||||
_ = Stdout.line! "Your input was: ${input}"
|
||||
Ok {}
|
||||
Err _ ->
|
||||
Ok {}
|
||||
|
@ -1870,7 +1870,7 @@ Although it's rare, it is possible that either of the `Stdout.line!` operations
|
|||
main! = \_args ->
|
||||
try Stdout.line! "Type something and press Enter."
|
||||
input = try Stdin.line! {}
|
||||
try Stdout.line! "You entered: $(input)"
|
||||
try Stdout.line! "You entered: ${input}"
|
||||
Ok {}
|
||||
```
|
||||
|
||||
|
@ -1897,7 +1897,7 @@ main! = \_args ->
|
|||
|> Result.mapErr UnableToReadInput
|
||||
|> try
|
||||
|
||||
Stdout.line! "You entered: $(input)"
|
||||
Stdout.line! "You entered: ${input}"
|
||||
|> Result.mapErr UnableToPrintInput
|
||||
|> try
|
||||
|
||||
|
@ -1920,14 +1920,14 @@ This code is doing three things:
|
|||
|
||||
See the [Error Handling example](https://www.roc-lang.org/examples/ErrorHandling/README.html) for a more detailed explanation of error handling in a larger program.
|
||||
|
||||
### [Displaying Roc values with `Inspect.toStr`](#inspect) {#inspect}
|
||||
### [Displaying Roc values with `Inspect.to_str`](#inspect) {#inspect}
|
||||
|
||||
The [`Inspect.toStr`](https://www.roc-lang.org/builtins/Inspect#toStr) function returns a `Str` representation of any Roc value using its [`Inspect` ability](/abilities#inspect-ability). It's useful for things like debugging and logging (although [`dbg`](https://www.roc-lang.org/tutorial#debugging) is often nicer for debugging in particular), but its output is almost never something that should be shown to end users! In this case we're just using it for our own learning, but it would be better to run a `when` on `e` and display a more helpful message.
|
||||
The [`Inspect.to_str`](https://www.roc-lang.org/builtins/Inspect#to_str) function returns a `Str` representation of any Roc value using its [`Inspect` ability](/abilities#inspect-ability). It's useful for things like debugging and logging (although [`dbg`](https://www.roc-lang.org/tutorial#debugging) is often nicer for debugging in particular), but its output is almost never something that should be shown to end users! In this case we're just using it for our own learning, but it would be better to run a `when` on `e` and display a more helpful message.
|
||||
|
||||
```roc
|
||||
when err is
|
||||
StdoutErr e -> Exit 1 "Error writing to stdout: $(Inspect.toStr e)"
|
||||
StdinErr e -> Exit 2 "Error writing to stdin: $(Inspect.toStr e)"
|
||||
StdoutErr e -> Exit 1 "Error writing to stdout: ${Inspect.to_str e}"
|
||||
StdinErr e -> Exit 2 "Error writing to stdin: ${Inspect.to_str e}"
|
||||
```
|
||||
|
||||
### [The early `return` keyword](#the-early-return-keyword) {#the-early-return-keyword}
|
||||
|
@ -1988,7 +1988,7 @@ Let's say I write a function which takes a record with a `first_name` and `last_
|
|||
|
||||
```roc
|
||||
full_name = \user ->
|
||||
"$(user.first_name) $(user.last_name)"
|
||||
"${user.first_name} ${user.last_name}"
|
||||
```
|
||||
|
||||
I can pass this function a record that has more fields than just `first_name` and `last_name`, as long as it has _at least_ both of those fields (and both of them are strings). So any of these calls would work:
|
||||
|
@ -2007,14 +2007,14 @@ If we add a type annotation to this `full_name` function, we can choose to have
|
|||
# Closed record
|
||||
full_name : { first_name : Str, last_name : Str } -> Str
|
||||
full_name = \user ->
|
||||
"$(user.first_name) $(user.last_name)"
|
||||
"${user.first_name} ${user.last_name}"
|
||||
```
|
||||
|
||||
```roc
|
||||
# Open record (because of the `*`)
|
||||
full_name : { first_name : Str, last_name : Str }* -> Str
|
||||
full_name = \user ->
|
||||
"$(user.first_name) $(user.last_name)"
|
||||
"${user.first_name} ${user.last_name}"
|
||||
```
|
||||
|
||||
The `*` in the type `{ first_name : Str, last_name : Str }*` is what makes it an open record type. This `*` is the _wildcard type_ we saw earlier with empty lists. (An empty list has the type `List *`, in contrast to something like `List Str` which is a list of strings.)
|
||||
|
@ -2030,7 +2030,7 @@ The type variable can also be a named type variable, like so:
|
|||
```roc
|
||||
add_https : { url : Str }a -> { url : Str }a
|
||||
add_https = \record ->
|
||||
{ record & url: "https://$(record.url)" }
|
||||
{ record & url: "https://${record.url}" }
|
||||
```
|
||||
|
||||
This function uses _constrained records_ in its type. The annotation is saying:
|
||||
|
|
|
@ -64,11 +64,11 @@ get_page_info = \page_path_str ->
|
|||
Str.split_on(page_path_str, "/")
|
||||
|> List.take_last(2)
|
||||
|> List.first # we use the folder for name for the page title, e.g. Json from examples/Json/README.html
|
||||
|> unwrap_or_crash("This List.first should never fail. pagePathStr ($(page_path_str)) did not contain any `/`.")
|
||||
|> unwrap_or_crash("This List.first should never fail. page_path_str (${page_path_str}) did not contain any `/`.")
|
||||
|> (\page_title ->
|
||||
{ title: "$(page_title) | Roc", description: "$(page_title) example in the Roc programming language." })
|
||||
{ title: "${page_title} | Roc", description: "${page_title} example in the Roc programming language." })
|
||||
else
|
||||
crash("Web page $(page_path_str) did not have a title and description specified in the pageData Dict. Please add one.")
|
||||
crash("Web page ${page_path_str} did not have a title and description specified in the pageData Dict. Please add one.")
|
||||
|
||||
unwrap_or_crash : Result a b, Str -> a where b implements Inspect
|
||||
unwrap_or_crash = \result, error_msg ->
|
||||
|
@ -77,7 +77,7 @@ unwrap_or_crash = \result, error_msg ->
|
|||
val
|
||||
|
||||
Err(err) ->
|
||||
crash("$(Inspect.to_str(err)): $(error_msg)")
|
||||
crash("${Inspect.to_str(err)}: ${error_msg}")
|
||||
|
||||
transform : Str, Str -> Str
|
||||
transform = \page_path_str, html_content ->
|
||||
|
|
|
@ -40,7 +40,7 @@ const repl = {
|
|||
},
|
||||
{
|
||||
match: (input) => input.replace(/ /g, "").match(/^name="/i),
|
||||
show: '<p>This created a new <a href="https://www.roc-lang.org/tutorial#defs">definition</a>—<code>name</code> is now defined to be equal to the <a href="/tutorial#strings-and-numbers">string</a> you entered.</p><p>Try using this definition by entering <code>"Hi, \$(name)!"</code></p>',
|
||||
show: '<p>This created a new <a href="https://www.roc-lang.org/tutorial#defs">definition</a>—<code>name</code> is now defined to be equal to the <a href="/tutorial#strings-and-numbers">string</a> you entered.</p><p>Try using this definition by entering <code>"Hi, \${name}!"</code></p>',
|
||||
},
|
||||
{
|
||||
match: (input) => input.match(/^"[^\$]+\$\(name\)/i),
|
||||
|
@ -388,9 +388,9 @@ function updateHistoryEntry(index, ok, outputText) {
|
|||
bounds.top >= 0 &&
|
||||
bounds.left >= 0 &&
|
||||
bounds.bottom <=
|
||||
(window.innerHeight || document.documentElement.clientHeight) &&
|
||||
(window.innerHeight || document.documentElement.clientHeight) &&
|
||||
bounds.right <=
|
||||
(window.innerWidth || document.documentElement.clientWidth);
|
||||
(window.innerWidth || document.documentElement.clientWidth);
|
||||
|
||||
if (!isInView) {
|
||||
repl.elemSourceInput.scrollIntoView({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue