Skip to content

Commit 8d78bc2

Browse files
committed
All: fix read/print of \\, and \n
1 parent f3c3903 commit 8d78bc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+162
-97
lines changed

awk/reader.awk

+4-14
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
function reader_read_string(token, v, r)
22
{
33
token = substr(token, 1, length(token) - 1)
4-
while (match(token, /\\["n\\]?/, r)) {
5-
switch (r[0]) {
6-
case "\\":
7-
return "!\"Invalid escape character '" substr(token, RSTART, 2) "'."
8-
case "\\n":
9-
v = v substr(token, 1, RSTART - 1) "\n"
10-
break
11-
default:
12-
v = v substr(token, 1, RSTART - 1) substr(r[0], 2, 1)
13-
break
14-
}
15-
token = substr(token, RSTART + RLENGTH)
16-
}
17-
return v token
4+
gsub(/\\\\/, "\\", token)
5+
gsub(/\\"/, "\"", token)
6+
gsub(/\\n/, "\n", token)
7+
return token
188
}
199

2010
function reader_read_atom(token)

bash/printer.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ _raw_string_pr_str () {
4242
r=":${s:2}"
4343
elif [ "${print_readably}" == "yes" ]; then
4444
s="${s//\\/\\\\}"
45-
r="\"${s//\"/\\\"}\""
45+
s="${s//\"/\\\"}"
46+
r="\"${s//$'\n'/\\n}\""
4647
else
4748
r="${s}"
4849
fi

bash/reader.sh

+2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ READ_ATOM () {
1313
case "${token}" in
1414
[0-9]*) _number "${token}" ;;
1515
\"*) token="${token:1:-1}"
16+
token="${token//\\\\/\\}"
1617
token="${token//\\\"/\"}"
18+
token="${token//\\n/$'\n'}"
1719
_string "${token}" ;;
1820
:*) _keyword "${token:1}" ;;
1921
nil) r="${__nil}" ;;

c/reader.c

+10-30
Original file line numberDiff line numberDiff line change
@@ -78,36 +78,12 @@ Reader *tokenize(char *line) {
7878
}
7979

8080

81-
// From http://creativeandcritical.net/str-replace-c/ - Laird Shaw
8281
char *replace_str(const char *str, const char *old, const char *new)
8382
{
84-
char *ret, *r;
85-
const char *p, *q;
86-
size_t oldlen = strlen(old);
87-
size_t count, retlen, newlen = strlen(new);
88-
89-
if (oldlen != newlen) {
90-
for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen)
91-
count++;
92-
/* this is undefined if p - str > PTRDIFF_MAX */
93-
retlen = p - str + strlen(p) + count * (newlen - oldlen);
94-
} else
95-
retlen = strlen(str);
96-
97-
if ((ret = malloc(retlen + 1)) == NULL)
98-
return NULL;
99-
100-
for (r = ret, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) {
101-
/* this is undefined if q - p > PTRDIFF_MAX */
102-
ptrdiff_t l = q - p;
103-
memcpy(r, p, l);
104-
r += l;
105-
memcpy(r, new, newlen);
106-
r += newlen;
107-
}
108-
strcpy(r, p);
109-
110-
return ret;
83+
GRegex *reg = g_regex_new (old, 0, 0, NULL);
84+
char *str_tmp = g_regex_replace_literal(reg, str, -1, 0, new, 0, NULL);
85+
free(reg);
86+
return str_tmp;
11187
}
11288

11389

@@ -142,8 +118,12 @@ MalVal *read_atom(Reader *reader) {
142118
atom = &mal_false;
143119
} else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) {
144120
//g_print("read_atom string: %s\n", token);
145-
char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\"", "\"");
146-
atom = malval_new_string(str_tmp);
121+
char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\\\"", "\"");
122+
char *str_tmp2 = replace_str(str_tmp, "\\\\n", "\n");
123+
free(str_tmp);
124+
char *str_tmp3 = replace_str(str_tmp2, "\\\\\\\\", "\\");
125+
free(str_tmp2);
126+
atom = malval_new_string(str_tmp3);
147127
} else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) {
148128
//g_print("read_atom keyword\n");
149129
atom = malval_new_keyword(g_match_info_fetch(matchInfo, 7));

coffee/reader.coffee

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ read_atom = (rdr) ->
2626
token.slice(1, token.length-1)
2727
.replace(/\\"/g, '"')
2828
.replace(/\\n/g, "\n")
29+
.replace(/\\\\/g, "\\")
2930
else if token[0] == ':' then types._keyword(token[1..])
3031
else if token == "nil" then null
3132
else if token == "true" then true

crystal/reader.cr

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class Reader
8282
when token == "false" then false
8383
when token == "nil" then nil
8484
when token[0] == '"' then token[1..-2].gsub(/\\"/, "\"")
85+
.gsub(/\\n/, "\n")
86+
.gsub(/\\\\/, "\\")
8587
when token[0] == ':' then "\u029e#{token[1..-1]}"
8688
else Mal::Symbol.new token
8789
end

cs/reader.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public static MalVal read_atom(Reader rdr) {
7272
string str = match.Groups[6].Value;
7373
str = str.Substring(1, str.Length-2)
7474
.Replace("\\\"", "\"")
75-
.Replace("\\n", "\n");
75+
.Replace("\\n", "\n")
76+
.Replace("\\\\", "\\");
7677
return new Mal.types.MalString(str);
7778
} else if (match.Groups[7].Value != String.Empty) {
7879
return new Mal.types.MalString("\u029e" + match.Groups[7].Value);

elixir/lib/mal/reader.ex

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ defmodule Mal.Reader do
8787
token
8888
|> String.slice(1..-2)
8989
|> String.replace("\\\"", "\"")
90+
|> String.replace("\\n", "\n")
91+
|> String.replace("\\\\", "\\")
9092

9193
integer?(token) ->
9294
Integer.parse(token)

es6/reader.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ function read_atom (reader) {
3232
} else if (token[0] === "\"") {
3333
return token.slice(1,token.length-1)
3434
.replace(/\\"/g, '"')
35-
.replace(/\\n/g, "\n"); // string
35+
.replace(/\\n/g, "\n")
36+
.replace(/\\\\/g, "\\"); // string
3637
} else if (token[0] === ":") {
3738
return _keyword(token.slice(1));
3839
} else if (token === "nil") {

factor/mal/printer/printer.factor

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ M: string (pr-str)
1616
[
1717
"\\" "\\\\" replace
1818
"\"" "\\\"" replace
19+
"\n" "\\n" replace
1920
"\"" dup surround
2021
] when ;
2122
M: array (pr-str) '[ _ (pr-str) ] map " " join "(" ")" surround ;

factor/mal/reader/reader.factor

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ DEFER: read-form
1010

1111
: (read-atom) ( str -- maltype )
1212
{
13-
{ [ dup first CHAR: " = ] [ rest but-last "\\\"" "\"" replace ] }
13+
{ [ dup first CHAR: " = ] [ rest but-last "\\\"" "\"" replace
14+
"\\n" "\n" replace
15+
"\\\\" "\\" replace ] }
1416
{ [ dup first CHAR: : = ] [ rest <malkeyword> ] }
1517
{ [ dup "false" = ] [ drop f ] }
1618
{ [ dup "true" = ] [ drop t ] }

go/src/reader/reader.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ func read_atom(rdr Reader) (MalType, error) {
6868
} else if (*token)[0] == '"' {
6969
str := (*token)[1 : len(*token)-1]
7070
return strings.Replace(
71-
strings.Replace(str, `\"`, `"`, -1),
72-
`\n`, "\n", -1), nil
71+
strings.Replace(
72+
strings.Replace(str, `\"`, `"`, -1),
73+
`\n`, "\n", -1),
74+
`\\`, "\\", -1), nil
7375
} else if (*token)[0] == ':' {
7476
return NewKeyword((*token)[1:len(*token)])
7577
} else if *token == "nil" {

guile/printer.scm

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
(string-sub
3838
(string-sub s "\\\\" "\\\\")
3939
"\"" "\\\"")
40-
"\n" "\\\n"))
40+
"\n" "\\n"))
4141
(define (%pr_str o) (pr_str o readable?))
4242
(match obj
4343
((? box?) (%pr_str (unbox obj)))

guile/reader.scm

+4-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@
8080
(define (read_atom reader)
8181
(define (->str s)
8282
(string-sub
83-
(string-sub s "\\\\\"" "\"")
84-
"\\\\\n" "\n"))
83+
(string-sub
84+
(string-sub s "\\\\\"" "\"")
85+
"\\\\n" "\n")
86+
"\\\\\\\\" "\\"))
8587
(let ((token (reader 'next)))
8688
(cond
8789
((string-match "^-?[0-9][0-9.]*$" token)

js/reader.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ function read_atom (reader) {
3434
} else if (token[0] === "\"") {
3535
return token.slice(1,token.length-1)
3636
.replace(/\\"/g, '"')
37-
.replace(/\\n/g, "\n"); // string
37+
.replace(/\\n/g, "\n")
38+
.replace(/\\\\/g, "\\"); // string
3839
} else if (token[0] === ":") {
3940
return types._keyword(token.slice(1));
4041
} else if (token === "nil") {

julia/reader.jl

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ function read_atom(rdr)
3939
float(token)
4040
elseif ismatch(r"^\".*\"$", token)
4141
replace(
42-
replace(token[2:end-1],
43-
"\\\"", "\""),
44-
"\\n", "\n")
42+
replace(
43+
replace(token[2:end-1],
44+
"\\\"", "\""),
45+
"\\n", "\n"),
46+
"\\\\", "\\")
4547
elseif token[1] == ':'
4648
"\u029e$(token[2:end])"
4749
elseif token == "nil"

kotlin/src/mal/printer.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ fun pr_str(malType: MalType, print_readably: Boolean = false): String =
77
":" + malType.value.substring(1)
88
} else if (malType is MalString) {
99
if (print_readably) {
10-
"\"" + malType.value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""
10+
"\"" + malType.value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") + "\""
1111
} else malType.value
1212
} else if (malType is MalConstant) {
1313
malType.value

kotlin/src/mal/reader.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ fun read_atom(reader: Reader): MalType {
139139
} else if (groups[4]?.value != null) {
140140
FALSE
141141
} else if (groups[5]?.value != null) {
142-
MalString((groups[5]?.value as String).replace("\\n", "\n").replace("\\\"", "\""))
142+
MalString((groups[5]?.value as String).replace("\\n", "\n").replace("\\\"", "\"").replace("\\\\", "\\"))
143143
} else if (groups[6]?.value != null) {
144144
MalKeyword(groups[6]?.value as String)
145145
} else if (groups[7]?.value != null) {

lua/reader.lua

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function M.read_atom(rdr)
4747
local sval = string.sub(token,2,string.len(token)-1)
4848
sval = string.gsub(sval, '\\"', '"')
4949
sval = string.gsub(sval, '\\n', '\n')
50+
sval = string.gsub(sval, '\\\\', '\\')
5051
return sval
5152
elseif string.sub(token,1,1) == ':' then
5253
return "\177" .. string.sub(token,2)

make/printer.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ symbol_pr_str = $($(1)_value)
3232

3333
keyword_pr_str = $(COLON)$(patsubst $(__keyword)%,%,$(call str_decode,$($(1)_value)))
3434

35-
string_pr_str = $(if $(filter $(__keyword)%,$(call str_decode,$($(1)_value))),$(COLON)$(patsubst $(__keyword)%,%,$(call str_decode,$($(1)_value))),$(if $(2),"$(subst $(DQUOTE),$(ESC_DQUOTE),$(subst $(SLASH),$(SLASH)$(SLASH),$(call str_decode,$($(1)_value))))",$(call str_decode,$($(1)_value))))
35+
string_pr_str = $(if $(filter $(__keyword)%,$(call str_decode,$($(1)_value))),$(COLON)$(patsubst $(__keyword)%,%,$(call str_decode,$($(1)_value))),$(if $(2),"$(subst $(NEWLINE),$(ESC_N),$(subst $(DQUOTE),$(ESC_DQUOTE),$(subst $(SLASH),$(SLASH)$(SLASH),$(call str_decode,$($(1)_value)))))",$(call str_decode,$($(1)_value))))
3636

3737
function_pr_str = <$(if $(word 6,$(value $(1)_value)),$(wordlist 1,5,$(value $(1)_value))...,$(value $(1)_value))>
3838

make/reader.mk

+12-1
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,29 @@ $(foreach ch,$(word 1,$($(1))),\
2727
))
2828
endef
2929

30+
# $(_NL) is used here instead of $(NEWLINE) because $(strip) removes
31+
# $(NEWLINE). str_encode will just pass through $(_NL) so str_decode
32+
# later will restore a correct newline
3033
define READ_STRING
3134
$(foreach ch,$(word 1,$($(1))),\
3235
$(if $(ch),\
3336
$(if $(and $(filter \,$(ch)),$(filter $(DQUOTE),$(word 2,$($(1))))),\
3437
$(eval $(1) := $(wordlist 3,$(words $($(1))),$($(1))))\
3538
$(and $(READER_DEBUG),$(info READ_STRING ch: \$(word 1,$($(1))) | $($(1))))\
3639
$(DQUOTE) $(strip $(call READ_STRING,$(1))),\
40+
$(if $(and $(filter \,$(ch)),$(filter n,$(word 2,$($(1))))),\
41+
$(eval $(1) := $(wordlist 3,$(words $($(1))),$($(1))))\
42+
$(and $(READER_DEBUG),$(info READ_STRING ch: \$(word 1,$($(1))) | $($(1))))\
43+
$(_NL) $(strip $(call READ_STRING,$(1))),\
44+
$(if $(and $(filter \,$(ch)),$(filter \,$(word 2,$($(1))))),\
45+
$(eval $(1) := $(wordlist 3,$(words $($(1))),$($(1))))\
46+
$(and $(READER_DEBUG),$(info READ_STRING ch: \$(word 1,$($(1))) | $($(1))))\
47+
\ $(strip $(call READ_STRING,$(1))),\
3748
$(if $(filter $(DQUOTE),$(ch)),\
3849
,\
3950
$(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
4051
$(and $(READER_DEBUG),$(info READ_STRING ch: $(ch) | $($(1))))\
41-
$(ch) $(strip $(call READ_STRING,$(1))))),))
52+
$(ch) $(strip $(call READ_STRING,$(1))))))),))
4253
endef
4354

4455
define READ_SYMBOL

make/util.mk

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RBRACKET := ]
2020
DQUOTE := "# "
2121
SLASH := $(strip \ )
2222
ESC_DQUOTE := $(SLASH)$(DQUOTE)
23+
ESC_N := $(SLASH)n
2324
SQUOTE := '# '
2425
QQUOTE := `# `
2526
SPACE :=

miniMAL/reader.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
["if", ["=", ["`", "\""], ["get", "token", 0]],
4040
[".",
4141
[".",
42-
["slice", "token", 1, ["-", ["count", "token"], 1]],
43-
["`", "replace"], ["RegExp", ["`", "\\\\\""], ["`", "g"]], ["`", "\""]],
44-
["`", "replace"], ["RegExp", ["`", "\\\\n"], ["`", "g"]], ["`", "\n"]],
42+
[".",
43+
["slice", "token", 1, ["-", ["count", "token"], 1]],
44+
["`", "replace"], ["RegExp", ["`", "\\\\\""], ["`", "g"]], ["`", "\""]],
45+
["`", "replace"], ["RegExp", ["`", "\\\\n"], ["`", "g"]], ["`", "\n"]],
46+
["`", "replace"], ["RegExp", ["`", "\\\\\\\\"], ["`", "g"]], ["`", "\\"]],
4547
["if", ["=", ["`", ":"], ["get", "token", 0]],
4648
["keyword", ["slice", "token", 1]],
4749
["if", ["=", ["`", "nil"], "token"],

nim/printer.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import strutils, sequtils, tables, types
33
proc str_handle(x: string, pr = true): string =
44
if x.len > 0 and x[0] == '\xff':
55
result = ":" & x[1 .. x.high]
6-
elif pr: result = "\"" & x.replace("\\", "\\\\").replace("\"", "\\\"") & "\""
6+
elif pr: result = "\"" & x.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") & "\""
77
else: result = x
88

99
proc pr_str*(m: MalType, pr = true): string =

nim/reader.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ proc read_hash_map(r: var Reader): MalType =
6161
proc read_atom(r: var Reader): MalType =
6262
let t = r.next
6363
if t.match(intRE): number t.parseInt
64-
elif t[0] == '"': str t[1 .. <t.high].replace("\\\"", "\"")
64+
elif t[0] == '"': str t[1 .. <t.high].replace("\\\"", "\"").replace("\\n", "\n").replace("\\\\", "\\")
6565
elif t[0] == ':': keyword t[1 .. t.high]
6666
elif t == "nil": nilObj
6767
elif t == "true": trueObj

perl/reader.pm

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ sub read_atom {
3535
my $str = substr $token, 1, -1;
3636
$str =~ s/\\"/"/g;
3737
$str =~ s/\\n/\n/g;
38+
$str =~ s/\\\\/\\/g;
3839
return String->new($str)
3940
}
4041
when(/^:/) { return _keyword(substr($token,1)) }

php/printer.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function _pr_str($obj, $print_readably=True) {
2626
if (strpos($obj, chr(0x7f)) === 0) {
2727
return ":".substr($obj,1);
2828
} elseif ($print_readably) {
29-
$obj = preg_replace('/"/', '\\"', preg_replace('/\\\\/', '\\\\\\\\', $obj));
29+
$obj = preg_replace('/\n/', '\\n', preg_replace('/"/', '\\"', preg_replace('/\\\\/', '\\\\\\\\', $obj)));
3030
return '"' . $obj . '"';
3131
} else {
3232
return $obj;

php/reader.php

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ function read_atom($reader) {
3939
} elseif ($token[0] === "\"") {
4040
$str = substr($token, 1, -1);
4141
$str = preg_replace('/\\\\"/', '"', $str);
42+
$str = preg_replace('/\\\\n/', "\n", $str);
43+
$str = preg_replace('/\\\\\\\\/', "\\", $str);
4244
return $str;
4345
} elseif ($token[0] === ":") {
4446
return _keyword(substr($token,1));

process/guide.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,17 @@ and each step will give progressively more bang for the buck.
365365

366366
* Add support for the other basic data type to your reader and printer
367367
functions: string, nil, true, and false. These become mandatory at
368-
step 4. When a string is read, a slash followed by a doublequote is
369-
translated into a plain doublequote character and a slash followed by
370-
"n" is translated into a newline. To properly print a string (for
371-
step 4 string functions), the `pr_str` function needs another
372-
parameter called `print_readably`. When `print_readably` is true,
373-
doublequotes and newlines are translated into their printed
374-
representations (the reverse of the reader). The `PRINT` function in
375-
the main program should call `pr_str` with print_readably set to
376-
true.
368+
step 4. When a string is read, the following transformations are
369+
applied: a backslash followed by a doublequote is translated into
370+
a plain doublequote character, a backslash followed by "n" is
371+
translated into a newline, and a backslash followed by another
372+
backslash is translated into a single backslash. To properly print
373+
a string (for step 4 string functions), the `pr_str` function needs
374+
another parameter called `print_readably`. When `print_readably` is
375+
true, doublequotes, newlines, and backslashes are translated into
376+
their printed representations (the reverse of the reader). The
377+
`PRINT` function in the main program should call `pr_str` with
378+
print_readably set to true.
377379

378380
* Add support for the other mal types: keyword, vector, hash-map, and
379381
atom.

ps/printer.ps

39 Bytes
Binary file not shown.

ps/reader.ps

47 Bytes
Binary file not shown.

python/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ WORKDIR /mal
2222
##########################################################
2323

2424
# Nothing additional needed for python
25+
RUN apt-get -y install python3

0 commit comments

Comments
 (0)