@@ -71,6 +71,33 @@ fn text_literals_and_escapes() {
7171 assert!(err(r#""\x""#).contains("escape"));
7272}
7373
74+#[test]
75+fn single_quoted_text_literals() {
76+ // single quotes are an alternative delimiter with the same escapes
77+ assert_eq!(toks("'hello'"), vec![Tok::Text("hello".into())]);
78+ // an unescaped " is fine inside single quotes
79+ assert_eq!(toks(r#"'say "hi"'"#), vec![Tok::Text("say \"hi\"".into())]);
80+ // an unescaped ' is fine inside double quotes
81+ assert_eq!(toks("\"don't\""), vec![Tok::Text("don't".into())]);
82+ // \' escapes the single quote
83+ assert_eq!(toks(r"'don\'t'"), vec![Tok::Text("don't".into())]);
84+ // \" works in single-quoted literals too
85+ assert_eq!(toks(r#"'a\"b'"#), vec![Tok::Text("a\"b".into())]);
86+ // other escapes work the same
87+ assert_eq!(toks(r"'\n\t\r\\'"), vec![Tok::Text("\n\t\r\\".into())]);
88+ // empty
89+ assert_eq!(toks("''"), vec![Tok::Text("".into())]);
90+ // errors
91+ assert!(err("'unterminated").contains("unterminated"));
92+ assert!(err("'a\nb'").contains("newline"));
93+ assert!(err(r"'\x'").contains("escape"));
94+ // adjacent strings are two tokens (application), not one string
95+ assert_eq!(
96+ toks("'it' 's'"),
97+ vec![Tok::Text("it".into()), Tok::Text("s".into())]
98+ );
99+}
100+
74101#[test]
75102fn id_literals() {
76103 assert_eq!(toks("@wqzt"), vec![Tok::IdLit("wqzt".into())]);