1+ defmodule JsonRemedy.Layer3.ContextManager do
2+ @ moduledoc """
3+ Context management functions for Layer 3 syntax normalization.
4+
5+ Handles parsing state, expectation tracking, and context transitions
6+ during character-by-character processing.
7+ """
8+
9+ @ doc """
10+ Determine what to expect next based on current state.
11+ """
12+ @ spec determine_next_expecting ( map ( ) ) :: atom ( )
13+ def determine_next_expecting ( state ) do
14+ case List . first ( state . context_stack ) do
15+ :object -> :comma_or_end
16+ :array -> :comma_or_end
17+ _ -> :value
18+ end
19+ end
20+
21+ @ doc """
22+ Determine what to expect after closing a delimiter.
23+ """
24+ @ spec determine_expecting_after_close ( list ( ) ) :: atom ( )
25+ def determine_expecting_after_close ( stack ) do
26+ case List . first ( stack ) do
27+ :object -> :comma_or_end
28+ :array -> :comma_or_end
29+ _ -> :value
30+ end
31+ end
32+
33+ @ doc """
34+ Check if a position in the input is inside a string literal.
35+ Used to avoid applying repairs to string content.
36+ """
37+ @ spec inside_string? ( String . t ( ) , non_neg_integer ( ) ) :: boolean ( )
38+ def inside_string? ( input , position )
39+ when is_binary ( input ) and is_integer ( position ) and position >= 0 do
40+ check_string_context ( input , position , 0 , false , false , nil )
41+ end
42+
43+ # Handle invalid inputs gracefully
44+ def inside_string? ( nil , _position ) , do: false
45+ def inside_string? ( _input , position ) when not is_integer ( position ) , do: false
46+ def inside_string? ( _input , position ) when position < 0 , do: false
47+ def inside_string? ( input , _position ) when not is_binary ( input ) , do: false
48+
49+ # Helper function to check if position is inside a string
50+ defp check_string_context ( _input , position , current_pos , in_string , _escape_next , _quote )
51+ when current_pos >= position do
52+ in_string
53+ end
54+
55+ defp check_string_context ( input , position , current_pos , in_string , escape_next , quote ) do
56+ if current_pos >= String . length ( input ) do
57+ in_string
58+ else
59+ char = String . at ( input , current_pos )
60+
61+ cond do
62+ escape_next ->
63+ check_string_context ( input , position , current_pos + 1 , in_string , false , quote )
64+
65+ in_string && char == "\\ " ->
66+ check_string_context ( input , position , current_pos + 1 , in_string , true , quote )
67+
68+ in_string && char == quote ->
69+ check_string_context ( input , position , current_pos + 1 , false , false , nil )
70+
71+ in_string ->
72+ check_string_context ( input , position , current_pos + 1 , in_string , false , quote )
73+
74+ char == "\" " ->
75+ check_string_context ( input , position , current_pos + 1 , true , false , "\" " )
76+
77+ char == "'" ->
78+ check_string_context ( input , position , current_pos + 1 , true , false , "'" )
79+
80+ true ->
81+ check_string_context ( input , position , current_pos + 1 , false , false , nil )
82+ end
83+ end
84+ end
85+
86+ @ doc """
87+ Get position information for error reporting.
88+ """
89+ @ spec get_position_info ( String . t ( ) , non_neg_integer ( ) ) ::
90+ % { line: pos_integer ( ) , column: pos_integer ( ) , context: String . t ( ) }
91+ def get_position_info ( input , position )
92+ when is_binary ( input ) and is_integer ( position ) and position >= 0 do
93+ lines = String . split ( input , "\n " )
94+
95+ { line_num , column , _ } =
96+ Enum . reduce_while ( lines , { 1 , 1 , 0 } , fn line , { current_line , _col , char_count } ->
97+ # +1 for newline
98+ line_length = String . length ( line ) + 1
99+
100+ if char_count + line_length > position do
101+ column = position - char_count + 1
102+ { :halt , { current_line , column , char_count } }
103+ else
104+ { :cont , { current_line + 1 , 1 , char_count + line_length } }
105+ end
106+ end )
107+
108+ context_start = max ( 0 , position - 20 )
109+ context_end = min ( String . length ( input ) , position + 20 )
110+ context = String . slice ( input , context_start , context_end - context_start )
111+
112+ % {
113+ line: line_num ,
114+ column: column ,
115+ context: context
116+ }
117+ end
118+
119+ # Handle invalid inputs gracefully
120+ def get_position_info ( nil , _position ) do
121+ % { line: 1 , column: 1 , context: "" }
122+ end
123+
124+ def get_position_info ( _input , position ) when not is_integer ( position ) or position < 0 do
125+ % { line: 1 , column: 1 , context: "" }
126+ end
127+
128+ def get_position_info ( input , _position ) when not is_binary ( input ) do
129+ % { line: 1 , column: 1 , context: inspect ( input ) }
130+ end
131+ end
0 commit comments