aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/colorful/colorful.tm2
-rw-r--r--examples/commands/commands.tm2
-rw-r--r--examples/http-server/http-server.tm12
-rw-r--r--examples/ini/ini.tm18
-rw-r--r--examples/log/log.tm16
-rw-r--r--examples/pthreads/pthreads.tm2
-rw-r--r--examples/tomo-install/tomo-install.tm2
-rw-r--r--examples/tomodeps/tomodeps.tm12
-rw-r--r--examples/wrap/wrap.tm10
-rw-r--r--src/environment.c4
-rw-r--r--src/parse.c61
-rw-r--r--src/stdlib/text.c37
-rw-r--r--test/lang.tm2
-rw-r--r--test/text.tm32
14 files changed, 97 insertions, 115 deletions
diff --git a/examples/colorful/colorful.tm b/examples/colorful/colorful.tm
index 30f3fc12..9a8bbbba 100644
--- a/examples/colorful/colorful.tm
+++ b/examples/colorful/colorful.tm
@@ -5,7 +5,7 @@ HELP := "
Usage: colorful [args...] [--by-line] [--files files...]
"
-CSI := "$\033["
+CSI := "\033["
use patterns
diff --git a/examples/commands/commands.tm b/examples/commands/commands.tm
index ace7deb5..d72398b9 100644
--- a/examples/commands/commands.tm
+++ b/examples/commands/commands.tm
@@ -28,7 +28,7 @@ struct ProgramResult(stdout:[Byte], stderr:[Byte], exit_type:ExitType)
if status == 0
if text := Text.from_bytes(r.stdout)
if trim_newline
- text = text.without_suffix(\n)
+ text = text.without_suffix("\n")
return text
else return none
return none
diff --git a/examples/http-server/http-server.tm b/examples/http-server/http-server.tm
index 3d72c1e8..5b73d1af 100644
--- a/examples/http-server/http-server.tm
+++ b/examples/http-server/http-server.tm
@@ -85,14 +85,14 @@ struct HTTPRequest(method:Text, path:Text, version:Text, headers:[Text], body:Te
struct HTTPResponse(body:Text, status=200, content_type="text/plain", headers:{Text=Text}={})
func bytes(r:HTTPResponse -> [Byte])
body_bytes := r.body.bytes()
- extra_headers := (++: "$k: $v$(\r\n)" for k,v in r.headers) or ""
+ extra_headers := (++: "$k: $v\r\n" for k,v in r.headers) or ""
return "
- HTTP/1.1 $(r.status) OK$\r
- Content-Length: $(body_bytes.length + 2)$\r
- Content-Type: $(r.content_type)$\r
- Connection: close$\r
+ HTTP/1.1 $(r.status) OK\r
+ Content-Length: $(body_bytes.length + 2)\r
+ Content-Type: $(r.content_type)\r
+ Connection: close\r
$extra_headers
- $\r$\n
+ \r\n
".bytes() ++ body_bytes
func _content_type(file:Path -> Text)
diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm
index c24cb4b9..4dc27725 100644
--- a/examples/ini/ini.tm
+++ b/examples/ini/ini.tm
@@ -10,23 +10,23 @@ _HELP := "
"
func parse_ini(path:Path -> {Text={Text=Text}})
- text := path.read() or exit("Could not read INI file: $\[31;1]$(path)$\[]")
+ text := path.read() or exit("Could not read INI file: \[31;1]$(path)\[]")
sections : @{Text=@{Text=Text}}
current_section : @{Text=Text}
# Line wraps:
- text = text.replace_pattern($Pat/\{1 nl}{0+space}/, " ")
+ text = text.replace_pattern($Pat/\\{1 nl}{0+space}/, " ")
for line in text.lines()
line = line.trim()
skip if line.starts_with(";") or line.starts_with("#")
if line.matches_pattern($Pat/[?]/)
- section_name := line.replace($Pat/[?]/, "\1").trim().lower()
+ section_name := line.replace($Pat/[?]/, "@1").trim().lower()
current_section = @{}
sections[section_name] = current_section
else if line.matches_pattern($Pat/{..}={..}/)
- key := line.replace_pattern($Pat/{..}={..}/, "\1").trim().lower()
- value := line.replace_pattern($Pat/{..}={..}/, "\2").trim()
+ key := line.replace_pattern($Pat/{..}={..}/, "@1").trim().lower()
+ value := line.replace_pattern($Pat/{..}={..}/, "@2").trim()
current_section[key] = value
return {k=v[] for k,v in sections[]}
@@ -46,8 +46,8 @@ func main(path:Path, key:Text?)
section := keys[1].lower()
section_data := data[section] or exit("
- Invalid section name: $\[31;1]$section$\[]
- Valid names: $\[1]$(", ".join([k.quoted() for k in data.keys]))$\[]
+ Invalid section name: \[31;1]$section\[]
+ Valid names: \[1]$(", ".join([k.quoted() for k in data.keys]))\[]
")
if keys.length < 2 or keys[2] == '*'
say("$section_data")
@@ -55,7 +55,7 @@ func main(path:Path, key:Text?)
section_key := keys[2].lower()
value := section_data[section_key] or exit("
- Invalid key: $\[31;1]$section_key$\[]
- Valid keys: $\[1]$(", ".join([s.quoted() for s in section_data.keys]))$\[]
+ Invalid key: \[31;1]$section_key\[]
+ Valid keys: \[1]$(", ".join([s.quoted() for s in section_data.keys]))\[]
")
say(value)
diff --git a/examples/log/log.tm b/examples/log/log.tm
index 9c3396e7..4b7893fd 100644
--- a/examples/log/log.tm
+++ b/examples/log/log.tm
@@ -16,24 +16,24 @@ func _timestamp(->Text)
return c_str.as_text()
func info(text:Text, newline=yes)
- say("$\[2]⚫ $text$\[]", newline)
+ say("\[2]⚫ $text\[]", newline)
for file in logfiles
- file.append("$(_timestamp()) [info] $text$\n")
+ file.append("$(_timestamp()) [info] $text\n")
func debug(text:Text, newline=yes)
- say("$\[32]🟢 $text$\[]", newline)
+ say("\[32]🟢 $text\[]", newline)
for file in logfiles
- file.append("$(_timestamp()) [debug] $text$\n")
+ file.append("$(_timestamp()) [debug] $text\n")
func warn(text:Text, newline=yes)
- say("$\[33;1]🟡 $text$\[]", newline)
+ say("\[33;1]🟡 $text\[]", newline)
for file in logfiles
- file.append("$(_timestamp()) [warn] $text$\n")
+ file.append("$(_timestamp()) [warn] $text\n")
func error(text:Text, newline=yes)
- say("$\[31;1]🔴 $text$\[]", newline)
+ say("\[31;1]🔴 $text\[]", newline)
for file in logfiles
- file.append("$(_timestamp()) [error] $text$\n")
+ file.append("$(_timestamp()) [error] $text\n")
func add_logfile(file:Path)
logfiles.add(file)
diff --git a/examples/pthreads/pthreads.tm b/examples/pthreads/pthreads.tm
index 7f720d5a..fee7ce5d 100644
--- a/examples/pthreads/pthreads.tm
+++ b/examples/pthreads/pthreads.tm
@@ -91,7 +91,7 @@ func main()
say_mutex := pthread_mutex_t.new()
announce := func(speaker:Text, text:Text)
do say_mutex.lock()
- say("$\033[2m[$speaker]$\033[m $text")
+ say("\[2][$speaker]\[] $text")
say_mutex.unlock()
worker := pthread_t.new(func()
diff --git a/examples/tomo-install/tomo-install.tm b/examples/tomo-install/tomo-install.tm
index c7d752ee..d915b993 100644
--- a/examples/tomo-install/tomo-install.tm
+++ b/examples/tomo-install/tomo-install.tm
@@ -80,5 +80,5 @@ func main(paths:[Path])
fi
).get_output()!)
- say("$\[1]Installed $url!$\[]")
+ say("\[1]Installed $url!\[]")
diff --git a/examples/tomodeps/tomodeps.tm b/examples/tomodeps/tomodeps.tm
index 1e6be615..5513290c 100644
--- a/examples/tomodeps/tomodeps.tm
+++ b/examples/tomodeps/tomodeps.tm
@@ -20,10 +20,10 @@ func _get_file_dependencies(file:Path -> |Dependency|)
if lines := file.by_line()
for line in lines
if line.matches_pattern($Pat/use {..}.tm/)
- file_import := Path.from_text(line.replace_pattern($Pat/use {..}/, "\1")).resolved(relative_to=file)
+ file_import := Path.from_text(line.replace_pattern($Pat/use {..}/, "@1")).resolved(relative_to=file)
deps.add(Dependency.File(file_import))
else if line.matches_pattern($Pat/use {id}/)
- module_name := line.replace_pattern($Pat/use {..}/, "\1")
+ module_name := line.replace_pattern($Pat/use {..}/, "@1")
deps.add(Dependency.Module(module_name))
return deps[]
@@ -64,17 +64,17 @@ func get_dependency_graph(dep:Dependency -> {Dependency=|Dependency|})
func _printable_name(dep:Dependency -> Text)
when dep is Module(module)
- return "$(\x1b)[34;1m$module$(\x1b)[m"
+ return "\[34;1]$module\[]"
is File(f)
f = f.relative_to((.))
if f.exists()
return Text(f)
else
- return "$(\x1b)[31;1m$(f) (not found)$(\x1b)[m"
+ return "\[31;1]$f (not found)\[]"
func _draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|}, already_printed:@|Dependency|, prefix="", is_last=yes)
if already_printed.has(dep)
- say(prefix ++ (if is_last then "└── " else "├── ") ++ _printable_name(dep) ++ " $\x1b[2m(recursive)$\x1b[m")
+ say(prefix ++ (if is_last then "└── " else "├── ") ++ _printable_name(dep) ++ " \[2](recursive)\[]")
return
say(prefix ++ (if is_last then "└── " else "├── ") ++ _printable_name(dep))
@@ -112,6 +112,6 @@ func main(files:[Text])
dependencies := get_dependency_graph(Module(arg))
draw_tree(Module(arg), dependencies)
else
- say("$\x1b[2mSkipping $arg$\x1b[m")
+ say("\[2]Skipping $arg\[]")
skip
diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm
index 1a29701f..bae01739 100644
--- a/examples/wrap/wrap.tm
+++ b/examples/wrap/wrap.tm
@@ -11,15 +11,15 @@ HELP := "
--hyphen='-': The text to use for hyphenation
"
-UNICODE_HYPHEN := \{hyphen}
+UNICODE_HYPHEN := "\{hyphen}"
func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN -> Text)
if preserve_paragraphs
paragraphs := text.split($/{2+ nl}/)
if paragraphs.length > 1
- return \n\n.join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs])
+ return "\n\n".join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs])
- return text.replace($/$(hyphen)$(\n)/, "")
+ return text.replace("$(hyphen)\n", "")
func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text)
if width <= 0
@@ -69,7 +69,7 @@ func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text)
if line != ""
lines.insert(line)
- return \n.join(lines)
+ return "\n".join(lines)
func _can_fit_word(line:Text, letters:[Text], width:Int -> Bool; inline)
if line == ""
@@ -99,4 +99,4 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN
wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen)
)
- out.write(\n\n.join(wrapped_paragraphs[]) ++ \n)
+ out.write("\n\n".join(wrapped_paragraphs[]) ++ "\n")
diff --git a/src/environment.c b/src/environment.c
index 21d3db9b..8084758e 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -327,7 +327,7 @@ env_t *global_env(void)
{"at", "Text$cluster", "func(text:Text, index:Int -> Text)"},
{"by_line", "Text$by_line", "func(text:Text -> func(->Text?))"},
{"by_split", "Text$by_split", "func(text:Text, delimiter='' -> func(->Text?))"},
- {"by_split_any", "Text$by_split_any", "func(text:Text, delimiters=\" $\\t\\r\\n\" -> func(->Text?))"},
+ {"by_split_any", "Text$by_split_any", "func(text:Text, delimiters=' \\t\\r\\n' -> func(->Text?))"},
{"bytes", "Text$utf8_bytes", "func(text:Text -> [Byte])"},
{"caseless_equals", "Text$equal_ignoring_case", "func(a,b:Text, language='C' -> Bool)"},
{"codepoint_names", "Text$codepoint_names", "func(text:Text -> [Text])"},
@@ -351,7 +351,7 @@ env_t *global_env(void)
{"right_pad", "Text$right_pad", "func(text:Text, count:Int, pad=' ', language='C' -> Text)"},
{"slice", "Text$slice", "func(text:Text, from=1, to=-1 -> Text)"},
{"split", "Text$split", "func(text:Text, delimiter='' -> [Text])"},
- {"split_any", "Text$split_any", "func(text:Text, delimiters=\" $\\t\\r\\n\" -> [Text])"},
+ {"split_any", "Text$split_any", "func(text:Text, delimiters=' \\t\\r\\n' -> [Text])"},
{"starts_with", "Text$starts_with", "func(text,prefix:Text -> Bool)"},
{"title", "Text$title", "func(text:Text, language='C' -> Text)"},
{"to", "Text$to", "func(text:Text, last:Int -> Text)"},
diff --git a/src/parse.c b/src/parse.c
index c31bea64..b9a695b9 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -147,7 +147,7 @@ static PARSER(parse_var);
static PARSER(parse_when);
static PARSER(parse_while);
static PARSER(parse_deserialize);
-static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open_quote, char close_quote, char open_interp);
+static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open_quote, char close_quote, char open_interp, bool allow_escapes);
//
// Print a parse error and exit (or use the on_err longjmp)
@@ -255,6 +255,24 @@ static const char *unescape(parse_ctx_t *ctx, const char **out) {
char name[len+1];
memcpy(name, &escape[2], len);
name[len] = '\0';
+
+ if (name[0] == 'U') {
+ for (char *p = &name[1]; *p; p++) {
+ if (!isxdigit(*p)) goto look_up_unicode_name;
+ }
+ // Unicode codepoints by hex
+ char *endptr = NULL;
+ long codepoint = strtol(name+1, &endptr, 16);
+ uint32_t ustr[2] = {codepoint, 0};
+ size_t bufsize = 8;
+ uint8_t buf[bufsize];
+ (void)u32_to_u8(ustr, bufsize, buf, &bufsize);
+ *endpos = escape + 3 + len;
+ return GC_strndup((char*)buf, bufsize);
+ }
+
+ look_up_unicode_name:;
+
uint32_t codepoint = unicode_name_character(name);
if (codepoint == UNINAME_INVALID)
parser_err(ctx, escape, escape + 3 + len,
@@ -265,16 +283,6 @@ static const char *unescape(parse_ctx_t *ctx, const char **out) {
(void)u32_to_u8(&codepoint, 1, (uint8_t*)str, &u8_len);
str[u8_len] = '\0';
return str;
- } else if (escape[1] == 'U' && escape[2]) {
- // Unicode codepoints by hex
- char *endptr = NULL;
- long codepoint = strtol(escape+2, &endptr, 16);
- uint32_t ustr[2] = {codepoint, 0};
- size_t bufsize = 8;
- uint8_t buf[bufsize];
- (void)u32_to_u8(ustr, bufsize, buf, &bufsize);
- *endpos = endptr;
- return GC_strndup((char*)buf, bufsize);
} else if (escape[1] == 'x' && escape[2] && escape[3]) {
// ASCII 2-digit hex
char buf[] = {escape[2], escape[3], 0};
@@ -1187,7 +1195,7 @@ PARSER(parse_bool) {
return NULL;
}
-ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open_quote, char close_quote, char open_interp)
+ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open_quote, char close_quote, char open_interp, bool allow_escapes)
{
const char *pos = *out_pos;
int64_t starting_indent = get_indent(ctx, pos);
@@ -1203,7 +1211,7 @@ ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open
if (chunk) {
ast_t *literal = NewAST(ctx->file, chunk_start, pos, TextLiteral, .cord=chunk);
chunks = new(ast_list_t, .ast=literal, .next=chunks);
- chunk = NULL;
+ chunk = CORD_EMPTY;
}
++pos;
ast_t *interp;
@@ -1212,6 +1220,9 @@ ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open
interp = expect(ctx, interp_start, &pos, parse_term_no_suffix, "I expected an interpolation term here");
chunks = new(ast_list_t, .ast=interp, .next=chunks);
chunk_start = pos;
+ } else if (allow_escapes && *pos == '\\') {
+ const char *c = unescape(ctx, &pos);
+ chunk = CORD_cat(chunk, c);
} else if (!leading_newline && *pos == open_quote && closing[(int)open_quote]) { // Nested pair begin
if (get_indent(ctx, pos) == starting_indent) {
++depth;
@@ -1266,35 +1277,22 @@ PARSER(parse_text) {
const char *start = pos;
const char *lang = NULL;
- // Escape sequence, e.g. \r\n
- if (*pos == '\\') {
- CORD cord = CORD_EMPTY;
- do {
- const char *c = unescape(ctx, &pos);
- cord = CORD_cat(cord, c);
- // cord = CORD_cat_char(cord, c);
- } while (*pos == '\\');
- return NewAST(ctx->file, start, pos, TextLiteral, .cord=cord);
- }
-
char open_quote, close_quote, open_interp = '$';
if (match(&pos, "\"")) { // Double quote
open_quote = '"', close_quote = '"', open_interp = '$';
} else if (match(&pos, "`")) { // Backtick
open_quote = '`', close_quote = '`', open_interp = '$';
} else if (match(&pos, "'")) { // Single quote
- open_quote = '\'', close_quote = '\'', open_interp = '\x03';
+ open_quote = '\'', close_quote = '\'', open_interp = '$';
} else if (match(&pos, "$")) { // Customized strings
lang = get_id(&pos);
// $"..." or $@"...."
static const char *interp_chars = "~!@#$%^&*+=\\?";
- if (match(&pos, "$")) { // Disable interpolation with $
+ if (match(&pos, "$")) { // Disable interpolation with $$
open_interp = '\x03';
} else if (strchr(interp_chars, *pos)) {
open_interp = *pos;
++pos;
- } else if (*pos == '(') {
- open_interp = '@'; // For shell commands
}
static const char *quote_chars = "\"'`|/;([{<";
if (!strchr(quote_chars, *pos))
@@ -1306,7 +1304,8 @@ PARSER(parse_text) {
return NULL;
}
- ast_list_t *chunks = _parse_text_helper(ctx, &pos, open_quote, close_quote, open_interp);
+ bool allow_escapes = (open_quote != '`');
+ ast_list_t *chunks = _parse_text_helper(ctx, &pos, open_quote, close_quote, open_interp, allow_escapes);
return NewAST(ctx->file, start, pos, TextJoin, .lang=lang, .children=chunks);
}
@@ -2277,7 +2276,7 @@ PARSER(parse_inline_c) {
if (!match(&pos, "("))
parser_err(ctx, start, pos, "I expected a '(' here");
chunks = new(ast_list_t, .ast=NewAST(ctx->file, pos, pos, TextLiteral, "({"),
- .next=_parse_text_helper(ctx, &pos, '(', ')', '@'));
+ .next=_parse_text_helper(ctx, &pos, '(', ')', '@', false));
if (type) {
REVERSE_LIST(chunks);
chunks = new(ast_list_t, .ast=NewAST(ctx->file, pos, pos, TextLiteral, "; })"), .next=chunks);
@@ -2286,7 +2285,7 @@ PARSER(parse_inline_c) {
} else {
if (!match(&pos, "{"))
parser_err(ctx, start, pos, "I expected a '{' here");
- chunks = _parse_text_helper(ctx, &pos, '{', '}', '@');
+ chunks = _parse_text_helper(ctx, &pos, '{', '}', '@', false);
}
return NewAST(ctx->file, start, pos, InlineCCode, .chunks=chunks, .type_ast=type);
diff --git a/src/stdlib/text.c b/src/stdlib/text.c
index 621de942..b3e9cebb 100644
--- a/src/stdlib/text.c
+++ b/src/stdlib/text.c
@@ -1352,12 +1352,9 @@ public Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark)
int32_t quote_char = Text$get_grapheme(quotation_mark, 0);
#define add_escaped(str) ({ if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[34;1m")); \
- if (!just_escaped) ret = concat2_assuming_safe(ret, Text("$")); \
ret = concat2_assuming_safe(ret, Text("\\" str)); \
- just_escaped = true; \
if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[0;35m")); })
TextIter_t state = NEW_TEXT_ITER_STATE(text);
- bool just_escaped = false;
// TODO: optimize for spans of non-escaped text
for (int64_t i = 0; i < text.length; i++) {
int32_t g = Text$get_grapheme_fast(&state, i);
@@ -1371,21 +1368,11 @@ public Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark)
case '\t': add_escaped("t"); break;
case '\v': add_escaped("v"); break;
case '\\': {
- if (just_escaped) {
- add_escaped("\\");
- } else {
- ret = concat2_assuming_safe(ret, Text("\\"));
- just_escaped = false;
- }
+ add_escaped("\\");
break;
}
case '$': {
- if (quote_char == '\'') {
- ret = concat2_assuming_safe(ret, Text("$"));
- just_escaped = false;
- } else {
- add_escaped("$");
- }
+ add_escaped("$");
break;
}
case '\x00' ... '\x06': case '\x0E' ... '\x1A':
@@ -1397,7 +1384,6 @@ public Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark)
ret = concat2_assuming_safe(ret, Text$from_strn(tmp, 2));
if (colorize)
ret = concat2_assuming_safe(ret, Text("\x1b[0;35m"));
- just_escaped = true;
break;
}
default: {
@@ -1405,7 +1391,6 @@ public Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark)
ret = concat2_assuming_safe(ret, quotation_mark);
} else {
ret = concat2_assuming_safe(ret, Text$slice(text, I(i+1), I(i+1)));
- just_escaped = false;
}
break;
}
@@ -1427,14 +1412,12 @@ public Text_t Text$as_text(const void *vtext, bool colorize, const TypeInfo_t *i
Text_t text = *(Text_t*)vtext;
// Figure out the best quotation mark to use:
- bool has_dollar = false, has_double_quote = false, has_backtick = false,
+ bool has_double_quote = false, has_backtick = false,
has_single_quote = false, needs_escapes = false;
TextIter_t state = NEW_TEXT_ITER_STATE(text);
for (int64_t i = 0; i < text.length; i++) {
int32_t g = Text$get_grapheme_fast(&state, i);
- if (g == '$') {
- has_dollar = true;
- } else if (g == '"') {
+ if (g == '"') {
has_double_quote = true;
} else if (g == '`') {
has_backtick = true;
@@ -1444,15 +1427,15 @@ public Text_t Text$as_text(const void *vtext, bool colorize, const TypeInfo_t *i
}
Text_t quote;
- // If there's dollar signs and/or double quotes in the string, it would
- // be nice to avoid needing to escape them by using single quotes, but
- // only if we don't have single quotes or need to escape anything else
- // (because single quotes don't have interpolation):
- if ((has_dollar || has_double_quote) && !has_single_quote && !needs_escapes)
+ // If there's double quotes in the string, it would be nice to avoid
+ // needing to escape them by using single quotes, but only if we don't have
+ // single quotes or need to escape anything else (because single quotes
+ // don't have interpolation):
+ if (has_double_quote && !has_single_quote)
quote = Text("'");
// If there is a double quote, but no backtick, we can save a bit of
// escaping by using backtick instead of double quote:
- else if (has_double_quote && !has_backtick)
+ else if (has_double_quote && has_single_quote && !has_backtick && !needs_escapes)
quote = Text("`");
// Otherwise fall back to double quotes as the default quoting style:
else
diff --git a/test/lang.tm b/test/lang.tm
index 081438ed..21b70f96 100644
--- a/test/lang.tm
+++ b/test/lang.tm
@@ -47,7 +47,7 @@ func main()
= $HTML"<p>Hello I &lt;3 hax!</p>"
>> Text(html)
- = '$HTML"Hello I &lt;3 hax!"'
+ = '\$HTML"Hello I &lt;3 hax!"'
>> b := Bold("Some <text> with junk")
>> $HTML"Your text: $b"
diff --git a/test/text.tm b/test/text.tm
index 1cabbdea..812ecd3f 100644
--- a/test/text.tm
+++ b/test/text.tm
@@ -34,22 +34,22 @@ func main()
>> str[9]
= "é"
- >> \UE9
+ >> "\{UE9}"
= "é"
- >> \U65\U301
+ >> "\{U65}\{U301}"
= "é"
- >> \{Penguin}.codepoint_names()
+ >> "\{Penguin}".codepoint_names()
= ["PENGUIN"]
- >> \[31;1]
- = "$\e[31;1m"
+ >> "\[31;1]"
+ = "\e[31;1m"
- >> \UE9 == \U65\U301
+ >> "\{UE9}" == "\{U65}\{U301}"
= yes
- amelie := "Am$(\UE9)lie"
+ amelie := "Am\{UE9}lie"
>> amelie.split()
= ["A", "m", "é", "l", "i", "e"]
>> amelie.utf32_codepoints()
@@ -61,7 +61,7 @@ func main()
>> Text.from_bytes([Byte(0xFF)])
= none
- amelie2 := "Am$(\U65\U301)lie"
+ amelie2 := "Am\{U65}\{U301}lie"
>> amelie2.split()
= ["A", "m", "é", "l", "i", "e"]
>> amelie2.utf32_codepoints()
@@ -98,20 +98,20 @@ func main()
line one
line two
"
- = "line one$\nline two"
+ = "line one\nline two"
say("Interpolation tests:")
>> "A $(1+2)"
= "A 3"
- >> 'A $(1+2)'
- = 'A $(1+2)'
+ >> "A \$(1+2)"
+ = "A \$(1+2)"
>> `A $(1+2)`
= "A 3"
>> $"A $(1+2)"
= "A 3"
>> $$"A $(1+2)"
- = 'A $(1+2)'
+ = "A \$(1+2)"
>> $="A =(1+2)"
= "A 3"
>> ${one {nested} two $(1+2)}
@@ -127,13 +127,13 @@ func main()
>> c == Text.from_bytes(c.bytes())!
= yes
- >> "one$(\n)two$(\n)three".lines()
+ >> "one\ntwo\nthree".lines()
= ["one", "two", "three"]
- >> "one$(\n)two$(\n)three$(\n)".lines()
+ >> "one\ntwo\nthree\n".lines()
= ["one", "two", "three"]
- >> "one$(\n)two$(\n)three$(\n\n)".lines()
+ >> "one\ntwo\nthree\n\n".lines()
= ["one", "two", "three", ""]
- >> "one$(\r\n)two$(\r\n)three$(\r\n)".lines()
+ >> "one\r\ntwo\r\nthree\r\n".lines()
= ["one", "two", "three"]
>> "".lines()
= []