@@ -23,8 +23,8 @@ create or replace package body ut_utils is
2323 gc_invalid_xml_char constant varchar2(50) := '[^_[:alnum:]\.-]';
2424 gc_full_valid_xml_name constant varchar2(50) := '^([[:alpha:]])([_[:alnum:]\.-])*$';
2525 gc_owner_hash constant integer(11) := dbms_utility.get_hash_value( ut_owner(), 0, power(2,31)-1);
26- gc_open_chars constant varchar2(4):= chr(91) || chr(123) || chr(40) || chr(60); -- [{( this has very specific purpose to not confuse lexer in IDE
27- gc_close_chars constant varchar2(4):= chr(93) || chr(125) || chr(41) || chr(62); -- ]})> this has very specific purpose to not confuse lexer in IDE
26+ gc_open_chars constant varchar2(4):= ' [{(<';
27+ gc_close_chars constant varchar2(4):= ' ]})>';
2828 gc_max_plsql_source_len constant integer := 32767;
2929
3030 function surround_with(a_value varchar2, a_quote_char varchar2) return varchar2 is
@@ -660,6 +660,115 @@ create or replace package body ut_utils is
660660 return l_result;
661661 end;
662662
663+ function scan_line(a_line in varchar2, a_in_ml_comment in out boolean) return varchar2 is
664+ -- Normal scan
665+ l_remaining varchar2(32767) := a_line;
666+ l_line varchar2(32767);
667+ l_ml_start binary_integer;
668+ l_comment_start binary_integer;
669+ l_text_start binary_integer;
670+ l_eq_text_start binary_integer;
671+ l_eq_end_char varchar2(1 char);
672+ l_pos binary_integer;
673+ l_end binary_integer;
674+ l_ml_end binary_integer;
675+ begin
676+ if a_in_ml_comment then
677+ l_ml_end := instr(l_remaining, '*/');
678+ if l_ml_end > 0 then
679+ a_in_ml_comment := false;
680+ l_remaining := substr(l_remaining, l_ml_end + 2);
681+ else
682+ return null;
683+ end if;
684+ end if;
685+
686+ loop
687+ exit when l_remaining is null;
688+ l_ml_start := instr(l_remaining, '/*');
689+ l_comment_start := instr(l_remaining, '--');
690+ l_text_start := instr(l_remaining, '''');
691+ -- q' always puts ' at l_text_start; just check the char immediately before it
692+ l_eq_text_start := case
693+ when l_text_start > 1 and substr(l_remaining, l_text_start - 1, 1) = 'q'
694+ then l_text_start - 1
695+ else 0
696+ end;
697+ -- Sentinel gc_max_plsql_source_len means "not present"; 32767 is beyond any VARCHAR2 position
698+ l_pos := least(
699+ case when l_ml_start > 0 then l_ml_start else gc_max_plsql_source_len end,
700+ case when l_comment_start > 0 then l_comment_start else gc_max_plsql_source_len end,
701+ case when l_text_start > 0 then l_text_start else gc_max_plsql_source_len end,
702+ case when l_eq_text_start > 0 then l_eq_text_start else gc_max_plsql_source_len end
703+ );
704+
705+ if l_pos = gc_max_plsql_source_len then
706+ l_line := l_line || l_remaining;
707+ exit;
708+ end if;
709+
710+ l_line := l_line || substr(l_remaining, 1, l_pos - 1);
711+ l_remaining := substr(l_remaining, l_pos);
712+ -- l_remaining now starts exactly at the token; all branch offsets below are relative to 1
713+ if l_pos = l_eq_text_start then
714+ -- q-quoted string: l_remaining starts at 'q', delimiter is at position 3
715+ l_eq_end_char := translate(substr(l_remaining, 3, 1), gc_open_chars, gc_close_chars);
716+ l_end := instr(l_remaining, l_eq_end_char || '''', 4);
717+ if l_end > 0 then
718+ l_line := l_line || substr(l_remaining, 1, l_end + 1);
719+ l_remaining := substr(l_remaining, l_end + 2);
720+ else
721+ l_line := l_line || l_remaining;
722+ exit;
723+ end if;
724+
725+ elsif l_pos = l_ml_start then
726+ -- Multi-line comment: l_remaining starts at '/*', so end search starts at 3
727+ l_ml_end := instr(l_remaining, '*/', 3);
728+ if l_ml_end > 0 then
729+ l_remaining := substr(l_remaining, l_ml_end + 2);
730+ else
731+ a_in_ml_comment := true;
732+ -- preserve trailing newline if present — it belongs to this line, not the comment
733+ if substr(l_remaining, -1) = chr(10) then
734+ l_line := l_line || chr(10);
735+ end if;
736+ return l_line;
737+ end if;
738+
739+ elsif l_pos = l_comment_start then
740+ -- Single-line comment: everything from here is comment, keep and stop
741+ l_line := l_line || l_remaining;
742+ return l_line;
743+
744+ else
745+ -- Regular string literal: l_remaining starts at the opening quote
746+ -- scan from position 2 to skip the opening quote
747+ l_end := 2;
748+ loop
749+ l_end := instr(l_remaining, '''', l_end);
750+ exit when l_end = 0;
751+ if substr(l_remaining, l_end, 2) = '''''' then
752+ l_end := l_end + 2; -- skip escaped quote pair
753+ else
754+ exit; -- real closing quote
755+ end if;
756+ end loop;
757+
758+ if l_end > 0 then
759+ l_line := l_line || substr(l_remaining, 1, l_end);
760+ l_remaining := substr(l_remaining, l_end + 1);
761+ else
762+ l_line := l_line || l_remaining;
763+ exit;
764+ end if;
765+
766+ end if;
767+ end loop;
768+
769+ return l_line;
770+ end;
771+
663772 function replace_multiline_comments(a_source dbms_preprocessor.source_lines_t)
664773 return dbms_preprocessor.source_lines_t
665774 is
@@ -668,13 +777,6 @@ create or replace package body ut_utils is
668777 l_remaining varchar2(32767);
669778 l_in_ml_comment boolean := false;
670779 l_ml_end binary_integer;
671- l_ml_start binary_integer;
672- l_comment_start binary_integer;
673- l_text_start binary_integer;
674- l_eq_text_start binary_integer;
675- l_eq_end_char varchar2(1 char);
676- l_pos binary_integer;
677- l_end binary_integer;
678780 l_has_ml_comment boolean := false;
679781 begin
680782 if a_source.count = 0 then
@@ -720,92 +822,10 @@ create or replace package body ut_utils is
720822
721823 -- Normal scan
722824 l_remaining := l_line;
723- l_line := null;
724-
725- <<scan_line>>
726- loop
727- exit when l_remaining is null or l_remaining = '';
728- l_ml_start := instr(l_remaining, '/*');
729- l_comment_start := instr(l_remaining, '--');
730- l_text_start := instr(l_remaining, '''');
731- -- q' always puts ' at l_text_start; just check the char immediately before it
732- l_eq_text_start := case
733- when l_text_start > 1 and substr(l_remaining, l_text_start - 1, 1) = 'q'
734- then l_text_start - 1
735- else 0
736- end;
737- -- Sentinel gc_max_plsql_source_len means "not present"; 32767 is beyond any VARCHAR2 position
738- l_pos := least(
739- case when l_ml_start > 0 then l_ml_start else gc_max_plsql_source_len end,
740- case when l_comment_start > 0 then l_comment_start else gc_max_plsql_source_len end,
741- case when l_text_start > 0 then l_text_start else gc_max_plsql_source_len end,
742- case when l_eq_text_start > 0 then l_eq_text_start else gc_max_plsql_source_len end
743- );
744-
745- if l_pos = gc_max_plsql_source_len then
746- l_line := l_line || l_remaining;
747- exit scan_line;
748- end if;
749-
750- l_line := l_line || substr(l_remaining, 1, l_pos - 1);
751- l_remaining := substr(l_remaining, l_pos);
752- -- l_remaining now starts exactly at the token; all branch offsets below are relative to 1
753- if l_pos = l_eq_text_start then
754- -- q-quoted string: l_remaining starts at 'q', delimiter is at position 3
755- l_eq_end_char := translate(substr(l_remaining, 3, 1), gc_open_chars, gc_close_chars);
756- l_end := instr(l_remaining, l_eq_end_char || '''', 4);
757- if l_end > 0 then
758- l_line := l_line || substr(l_remaining, 1, l_end + 1);
759- l_remaining := substr(l_remaining, l_end + 2);
760- else
761- l_line := l_line || l_remaining;
762- exit scan_line;
763- end if;
764-
765- elsif l_pos = l_ml_start then
766- -- Multi-line comment: l_remaining starts at '/*', so end search starts at 3
767- l_ml_end := instr(l_remaining, '*/', 3);
768- if l_ml_end > 0 then
769- l_remaining := substr(l_remaining, l_ml_end + 2);
770- else
771- l_in_ml_comment := true;
772- -- preserve trailing newline if present — it belongs to this line, not the comment
773- if substr(l_remaining, -1) = chr(10) then
774- l_line := l_line || chr(10);
775- end if;
776- exit scan_line;
777- end if;
778-
779- elsif l_pos = l_comment_start then
780- -- Single-line comment: everything from here is comment, keep and stop
781- l_line := l_line || l_remaining;
782- exit scan_line;
783-
784- else
785- -- Regular string literal: l_remaining starts at the opening quote
786- -- scan from position 2 to skip the opening quote
787- l_end := 2;
788- loop
789- l_end := instr(l_remaining, '''', l_end);
790- exit when l_end = 0;
791- if substr(l_remaining, l_end, 2) = '''''' then
792- l_end := l_end + 2; -- skip escaped quote pair
793- else
794- exit; -- real closing quote
795- end if;
796- end loop;
797-
798- if l_end > 0 then
799- l_line := l_line || substr(l_remaining, 1, l_end);
800- l_remaining := substr(l_remaining, l_end + 1);
801- else
802- l_line := l_line || l_remaining;
803- exit scan_line;
804- end if;
805-
806- end if;
807- end loop scan_line;
808-
825+ l_line := scan_line(l_remaining, l_in_ml_comment);
826+ if l_line is null then
827+ l_line := '';
828+ end if;
809829 l_result(i) := l_line;
810830 end loop;
811831
0 commit comments