Skip to main content

jj_core/
symbol_util.rs

1// Copyright 2020-2026 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Symbol and string formatting and parsing utilities for our DSL.
16
17use std::ascii;
18
19/// Escapes special characters in the input.
20pub fn escape_string(unescaped: &str) -> String {
21    let mut escaped = String::with_capacity(unescaped.len());
22    escape_string_to_buf(&mut escaped, unescaped);
23    escaped
24}
25
26/// Formats a string by quoting and escaping it.
27pub fn format_string(unescaped: &str) -> String {
28    let mut escaped = String::with_capacity(unescaped.len() + 2);
29    escaped.push('"');
30    escape_string_to_buf(&mut escaped, unescaped);
31    escaped.push('"');
32    escaped
33}
34
35fn escape_string_to_buf(escaped: &mut String, unescaped: &str) {
36    for c in unescaped.chars() {
37        match c {
38            '"' => escaped.push_str(r#"\""#),
39            '\\' => escaped.push_str(r#"\\"#),
40            '\t' => escaped.push_str(r#"\t"#),
41            '\r' => escaped.push_str(r#"\r"#),
42            '\n' => escaped.push_str(r#"\n"#),
43            '\0' => escaped.push_str(r#"\0"#),
44            c if c.is_ascii_control() => {
45                for b in ascii::escape_default(c as u8) {
46                    escaped.push(b as char);
47                }
48            }
49            c => escaped.push(c),
50        }
51    }
52}
53
54/// Parses an escape sequence into a character.
55pub fn unescape_char(escaped: &str) -> char {
56    assert!(escaped.starts_with('\\'));
57    match &escaped[1..] {
58        "\"" => '"',
59        "\\" => '\\',
60        "t" => '\t',
61        "r" => '\r',
62        "n" => '\n',
63        "0" => '\0',
64        "e" => '\x1b',
65        hex if hex.starts_with('x') => {
66            char::from(u8::from_str_radix(&hex[1..], 16).expect("hex characters"))
67        }
68        char => panic!("invalid escape: \\{char:?}"),
69    }
70}