diff --git a/base/exports.jl b/base/exports.jl index 162bfc9f31a38..2cc03891df509 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1100,6 +1100,8 @@ export # Macros @str, + @mstr, + @imstr, @I_str, @E_str, @B_str, diff --git a/base/string.jl b/base/string.jl index e7ceed755f1b6..21c4d2859d635 100644 --- a/base/string.jl +++ b/base/string.jl @@ -652,9 +652,53 @@ function interp_parse_bytes(s::String) interp_parse(s, unescape_string, writer) end +## multiline strings ## + +function multiline_lstrip(s::String) + if length(s) == 0 || !isspace(s[1]) + return s + end + lines = split(s, '\n') + + # trim leading,trailing whitespace + a,b = 1,length(lines) + if b == 1 return s end + if lstrip(lines[a]) == "" a += 1 end + if lstrip(lines[b]) == "" b -= 1 end + if a > b return s end + + # find prefix + first_line = lines[a] + n = 0 + for c in first_line + if isspace(c) + n += 1 + else + break + end + end + prefix = (n == 0) ? "" : first_line[1:n] + + # output string + prefix_len = length(prefix) + buf = memio(length(s) - (b-a+1)*prefix_len, false) + for i = a:b + line = lines[i] + if begins_with(line, prefix) + print(buf, line[prefix_len+1:end]) + else + print(buf, line) + end + if i != b print(buf, '\n') end + end + takebuf_string(buf) +end + ## core string macros ## macro str(s); interp_parse(s); end +macro mstr(s); multiline_lstrip(s); end +macro imstr(s); interp_parse(multiline_lstrip(s)); end macro I_str(s); interp_parse(s, x->unescape_chars(x,"\"")); end macro E_str(s); check_utf8(unescape_string(s)); end macro B_str(s); interp_parse_bytes(s); end diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 78354488e617a..95624b8eab293 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -800,16 +800,19 @@ (if (and (symbol? ex) (not (operator? ex)) (not (ts:space? s))) ;; custom prefixed string literals, x"s" => @x_str "s" - (let ((str (begin (take-token s) - (parse-string-literal s))) - (macname (symbol (string #\@ ex '_str)))) - (let ((nxt (peek-token s))) - (if (and (symbol? nxt) (not (operator? nxt)) - (not (ts:space? s))) - ;; string literal suffix, "s"x - (loop `(macrocall ,macname ,(car str) - ,(string (take-token s)))) - (loop `(macrocall ,macname ,(car str)))))) + (let* ((str (begin (take-token s) + (parse-string-literal s))) + (nxt (peek-token s)) + (macname (symbol (string #\@ ex '_str))) + (macstr (if (triplequote-string-literal? str) + `(macrocall @mstr ,(car str)) + (car str)))) + (if (and (symbol? nxt) (not (operator? nxt)) + (not (ts:space? s))) + ;; string literal suffix, "s"x + (loop `(macrocall ,macname ,macstr + ,(string (take-token s)))) + (loop `(macrocall ,macname ,macstr)))) ex)) (else ex)))))))) @@ -1312,7 +1315,7 @@ c)) (define (take-char p) - (begin (read-char p) p)) + (begin (read-char p) p)) ; reads a raw string literal with no processing. ; quote can be escaped with \, but the \ is left in place. @@ -1322,7 +1325,7 @@ (if (eqv? (peek-char p) #\") (if (eqv? (peek-char (take-char p)) #\") (parse-string-literal-3 (take-char p)) - (cons "" #f)) + (cons "" (cons #f #f))) (parse-string-literal-1 p)))) (define (parse-string-literal-1 p) @@ -1340,7 +1343,7 @@ (set! interpolate #t)) (write-char (not-eof-3 c) b))) (loop (read-char p))))) - (cons (io.tostring! b) interpolate))) + (cons (io.tostring! b) (cons interpolate #f)))) (define (parse-string-literal-3 p) (let ((b (open-output-string)) @@ -1366,7 +1369,10 @@ (set! interpolate #t)) (write-char (not-eof-3 c) b))) (loop (read-char p))))) - (cons (io.tostring! b) interpolate))) + (cons (io.tostring! b) (cons interpolate #t)))) + +(define (interpolate-string-literal? s) (cadr s)) +(define (triplequote-string-literal? s) (cddr s)) (define (not-eof-1 c) (if (eof-object? c) @@ -1542,12 +1548,16 @@ ((eqv? t #\") (take-token s) (let ((ps (parse-string-literal s))) - (if (cdr ps) - `(macrocall @str ,(car ps)) - (let ((str (unescape-string (car ps)))) - (if (not (string.isutf8 str)) - (error "invalid UTF-8 sequence")) - str)))) + (if (interpolate-string-literal? ps) + (if (triplequote-string-literal? ps) + `(macrocall @imstr ,(car ps)) + `(macrocall @str ,(car ps))) + (let ((str (unescape-string (car ps)))) + (if (not (string.isutf8 str)) + (error "invalid UTF-8 sequence")) + (if (triplequote-string-literal? ps) + `(macrocall @mstr ,str) + str))))) ;; macro call ((eqv? t #\@) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index b7388ef418070..598be522e0842 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2121,7 +2121,8 @@ So far only the second case can actually occur. (define (julia-expand-strs e) (cond ((not (pair? e)) e) - ((and (eq? (car e) 'macrocall) (eq? (cadr e) '@str)) + ((and (eq? (car e) 'macrocall) (or (eq? (cadr e) '@str) + (eq? (cadr e) '@mstr))) ;; expand macro (let ((form (apply invoke-julia-macro (cadr e) (cddr e)))) diff --git a/test/strings.jl b/test/strings.jl index 6d8ac87cca817..28dcae387c545 100644 --- a/test/strings.jl +++ b/test/strings.jl @@ -508,3 +508,22 @@ str = "s\u2200" @test """ab""c""" == "ab\"\"c" @test """ab"\"c""" == "ab\"\"c" @test """abc\"""" == "abc\"" +n = 3 +@test """$n""" == "$n" +@test E"""$n""" == E"$n" +@test """ + a + b + + c + """ == "a\nb\n\nc" +@test """x + a + """ == "x\n a\n " +@test """ + $n + """ == "3" +@test E""" + $n + """ == E"$n" +