jsonette/parser/utils.rs
1/*
2 * Copyright (c) 2026 DevEtte.
3 *
4 * This project is dual-licensed under both the MIT License and the
5 * Apache License, Version 2.0 (the "License"). You may not use this
6 * file except in compliance with one of these licenses.
7 *
8 * You may obtain a copy of the Licenses at:
9 * - MIT: https://opensource.org
10 * - Apache 2.0: http://apache.org
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19//! Internal parser utilities, including mapping line/column errors back to byte offsets.
20
21/// Helper to convert a 1-indexed line and column to a 0-indexed byte offset in the input string.
22///
23/// # Arguments
24///
25/// * `input` - The raw JSON string slice.
26/// * `line` - The 1-indexed line number where the error occurred.
27/// * `col` - The 1-indexed column number where the error occurred.
28///
29/// # Returns
30///
31/// The 0-indexed byte offset of the character in the input string. If `line` is `0`, `0` is returned.
32/// If `line` or `col` exceeds the boundaries of the string, the length of the string is returned.
33pub fn line_col_to_byte_offset(input: &str, line: usize, col: usize) -> usize {
34 if line == 0 {
35 return 0;
36 }
37 let mut current_line = 1;
38 let mut line_start_offset = 0;
39 let bytes = input.as_bytes();
40
41 for (offset, &b) in bytes.iter().enumerate() {
42 if current_line == line {
43 let target_offset = line_start_offset + (col.saturating_sub(1));
44 return target_offset.min(input.len());
45 }
46 if b == b'\n' {
47 current_line += 1;
48 line_start_offset = offset + 1;
49 }
50 }
51
52 if current_line == line {
53 let target_offset = line_start_offset + (col.saturating_sub(1));
54 return target_offset.min(input.len());
55 }
56
57 input.len()
58}