@@ -47,12 +47,21 @@ It can include blank lines if [`MdxEsmParse`][crate::MdxEsmParse] passed in
4747-oncall (" whatsapp_clr" ).
4848
4949-include_lib (" markdown/include/markdown_const.hrl" ).
50+ -include_lib (" markdown/include/markdown_mdx.hrl" ).
5051-include_lib (" markdown/include/markdown_parser.hrl" ).
5152-include_lib (" markdown/include/markdown_vec.hrl" ).
5253
54+ -include_lib (" stdlib/include/assert.hrl" ).
55+
5356% % API
5457-export ([
55- start /1
58+ start /1 ,
59+ word /1 ,
60+ inside /1 ,
61+ line_start /1 ,
62+ continuation_start /1 ,
63+ blank_line_before /1 ,
64+ at_end /1
5665]).
5766
5867% %%=============================================================================
@@ -65,7 +74,7 @@ Start of MDX ESM.
6574```markdown
6675> | import a from 'b'
6776 ^
68- \ ```
77+ ```
6978""" .
7079-spec start (Tokenizer ) -> {Tokenizer , State } when Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
7180start (
@@ -99,3 +108,201 @@ start(
99108start (Tokenizer = # markdown_tokenizer {}) ->
100109 State = markdown_state :nok (),
101110 {Tokenizer , State }.
111+
112+ -doc """
113+ In keyword.
114+
115+ ```markdown
116+ > | import a from 'b'
117+ ^^^^^^
118+ ```
119+ """ .
120+ -spec word (Tokenizer ) -> {Tokenizer , State } when Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
121+ word (Tokenizer1 = # markdown_tokenizer {current = {some , Current }}) when Current >= $a andalso Current =< $z ->
122+ Tokenizer2 = markdown_tokenizer :consume (Tokenizer1 ),
123+ State = markdown_state :next (mdx_esm_word ),
124+ {Tokenizer2 , State };
125+ word (
126+ Tokenizer1 = # markdown_tokenizer {
127+ current = Current ,
128+ parse_state = # markdown_parse_state {bytes = Bytes },
129+ point = # markdown_point {offset = Index },
130+ tokenize_state = TokenizeState1 = # markdown_tokenize_state {start = Start }
131+ }
132+ ) ->
133+ Slice = markdown_slice :from_indices (Bytes , Start , Index ),
134+ SliceBytes = markdown_slice :as_binary (Slice ),
135+ case (SliceBytes =:= <<" export" >> orelse SliceBytes =:= <<" import" >>) andalso Current =:= {some , $\s } of
136+ true ->
137+ Tokenizer2 = Tokenizer1 # markdown_tokenizer {concrete = true },
138+ EventsLen = markdown_vec :size (Tokenizer2 # markdown_tokenizer .events ),
139+ TokenizeState2 = TokenizeState1 # markdown_tokenize_state {start = EventsLen - 1 },
140+ Tokenizer3 = Tokenizer2 # markdown_tokenizer {tokenize_state = TokenizeState2 },
141+ Tokenizer4 = markdown_tokenizer :consume (Tokenizer3 ),
142+ State = markdown_state :next (mdx_esm_inside ),
143+ {Tokenizer4 , State };
144+ false ->
145+ TokenizeState2 = TokenizeState1 # markdown_tokenize_state {start = 0 },
146+ Tokenizer2 = Tokenizer1 # markdown_tokenizer {tokenize_state = TokenizeState2 },
147+ State = markdown_state :nok (),
148+ {Tokenizer2 , State }
149+ end .
150+
151+ -doc """
152+ In data.
153+
154+ ```markdown
155+ > | import a from 'b'
156+ ^
157+ ```
158+ """ .
159+ -spec inside (Tokenizer ) -> {Tokenizer , State } when Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
160+ inside (Tokenizer1 = # markdown_tokenizer {current = Current }) when Current =:= none orelse Current =:= {some , $\n } ->
161+ Tokenizer2 = markdown_tokenizer :exit (Tokenizer1 , mdx_esm_data ),
162+ State = markdown_state :retry (mdx_esm_line_start ),
163+ {Tokenizer2 , State };
164+ inside (Tokenizer1 = # markdown_tokenizer {}) ->
165+ Tokenizer2 = markdown_tokenizer :consume (Tokenizer1 ),
166+ State = markdown_state :next (mdx_esm_inside ),
167+ {Tokenizer2 , State }.
168+
169+ -doc """
170+ At start of line.
171+
172+ ```markdown
173+ | import a from 'b'
174+ > | export {a}
175+ ^
176+ ```
177+ """ .
178+ -spec line_start (Tokenizer ) -> {Tokenizer , State } when Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
179+ line_start (Tokenizer1 = # markdown_tokenizer {current = none }) ->
180+ State = markdown_state :retry (mdx_esm_at_end ),
181+ {Tokenizer1 , State };
182+ line_start (Tokenizer1 = # markdown_tokenizer {current = {some , $\n }}) ->
183+ OkState = markdown_state :next (mdx_esm_at_end ),
184+ NokState = markdown_state :next (mdx_esm_continuation_start ),
185+ Tokenizer2 = markdown_tokenizer :check (Tokenizer1 , OkState , NokState ),
186+ State = markdown_state :retry (mdx_esm_blank_line_before ),
187+ {Tokenizer2 , State };
188+ line_start (Tokenizer1 = # markdown_tokenizer {}) ->
189+ Tokenizer2 = markdown_tokenizer :enter (Tokenizer1 , mdx_esm_data ),
190+ Tokenizer3 = markdown_tokenizer :consume (Tokenizer2 ),
191+ State = markdown_state :next (mdx_esm_inside ),
192+ {Tokenizer3 , State }.
193+
194+ -doc """
195+ At start of line that continues.
196+
197+ ```markdown
198+ | import a from 'b'
199+ > | export {a}
200+ ^
201+ ```
202+ """ .
203+ -spec continuation_start (Tokenizer ) -> {Tokenizer , State } when
204+ Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
205+ continuation_start (Tokenizer1 = # markdown_tokenizer {}) ->
206+ Tokenizer2 = markdown_tokenizer :enter (Tokenizer1 , line_ending ),
207+ Tokenizer3 = markdown_tokenizer :consume (Tokenizer2 ),
208+ Tokenizer4 = markdown_tokenizer :exit (Tokenizer3 , line_ending ),
209+ State = markdown_state :next (mdx_esm_line_start ),
210+ {Tokenizer4 , State }.
211+
212+ -doc """
213+ At start of a potentially blank line.
214+
215+ ```markdown
216+ | import a from 'b'
217+ > | export {a}
218+ ^
219+ ```
220+ """ .
221+ -spec blank_line_before (Tokenizer ) -> {Tokenizer , State } when
222+ Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
223+ blank_line_before (Tokenizer1 = # markdown_tokenizer {}) ->
224+ Tokenizer2 = markdown_tokenizer :enter (Tokenizer1 , line_ending ),
225+ Tokenizer3 = markdown_tokenizer :consume (Tokenizer2 ),
226+ Tokenizer4 = markdown_tokenizer :exit (Tokenizer3 , line_ending ),
227+ State = markdown_state :next (blank_line_start ),
228+ {Tokenizer4 , State }.
229+
230+ -doc """
231+ At end of line (blank or eof).
232+
233+ ```markdown
234+ > | import a from 'b'
235+ ^
236+ ```
237+ """ .
238+ -spec at_end (Tokenizer ) -> {Tokenizer , State } when Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
239+ at_end (Tokenizer1 = # markdown_tokenizer {}) ->
240+ {Tokenizer2 , Result } = parse_esm (Tokenizer1 ),
241+ case Result of
242+ ok ->
243+ Tokenizer3 = Tokenizer2 # markdown_tokenizer {concrete = false },
244+ Tokenizer4 = markdown_tokenizer :exit (Tokenizer3 , mdx_esm ),
245+ State = markdown_state :ok (),
246+ {Tokenizer4 , State };
247+ _ ->
248+ {Tokenizer2 , Result }
249+ end .
250+
251+ % %%-----------------------------------------------------------------------------
252+ % %% Internal functions
253+ % %%-----------------------------------------------------------------------------
254+
255+ % % @private
256+ -doc """
257+ Parse ESM with a given function.
258+ """ .
259+ -spec parse_esm (Tokenizer ) -> {Tokenizer , State } when Tokenizer :: markdown_tokenizer :t (), State :: markdown_state :t ().
260+ parse_esm (
261+ Tokenizer1 = # markdown_tokenizer {
262+ current = Current ,
263+ events = Events ,
264+ parse_state = # markdown_parse_state {
265+ bytes = Bytes , location = OptionLocation , options = # markdown_parse_options {mdx_esm_parse = {some , Parse }}
266+ },
267+ point = Point ,
268+ tokenize_state = # markdown_tokenize_state {start = Start }
269+ }
270+ ) when is_function (Parse , 1 ) ->
271+ % % Collect the body of the ESM and positional info for each run of it.
272+ CollectResult = markdown_mdx_collect :collect (Events , Bytes , Start , [mdx_esm_data , line_ending ], []),
273+ CollectValue = CollectResult # markdown_mdx_collect_result .value ,
274+ CollectStops = CollectResult # markdown_mdx_collect_result .stops ,
275+ % % Parse and handle what was signaled back.
276+ case Parse (CollectValue ) of
277+ # markdown_mdx_signal {inner = ok } ->
278+ State = markdown_state :ok (),
279+ {Tokenizer1 , State };
280+ # markdown_mdx_signal {inner = {error , Message , Relative , Source , RuleId }} ->
281+ % % BEGIN: assertions
282+ ? assertMatch ({some , _ }, OptionLocation , " expected location index if aware mdx is on" ),
283+ % % END: assertions
284+ {some , Location } = OptionLocation ,
285+ OptionRelativePoint = markdown_location :relative_to_point (Location , CollectStops , Relative ),
286+ % % BEGIN: assertions
287+ ? assertMatch ({some , _ }, OptionRelativePoint , " expected non-empty string" ),
288+ % % END: assertions
289+ {some , RelativePoint } = OptionRelativePoint ,
290+ Place = markdown_place :point (RelativePoint ),
291+ State = markdown_state :error (markdown_message :new ({some , Place }, Message , Source , RuleId )),
292+ {Tokenizer1 , State };
293+ # markdown_mdx_signal {inner = {eof , Message , Source , RuleId }} ->
294+ case Current of
295+ none ->
296+ Place = markdown_place :point (markdown_point :to_unist (Point )),
297+ State = markdown_state :error (markdown_message :new ({some , Place }, Message , Source , RuleId )),
298+ {Tokenizer1 , State };
299+ {some , _ } ->
300+ TokenizeState1 = Tokenizer1 # markdown_tokenizer .tokenize_state ,
301+ TokenizeState2 = TokenizeState1 # markdown_tokenize_state {
302+ mdx_last_parse_error = {some , {Message , Source , RuleId }}
303+ },
304+ Tokenizer2 = Tokenizer1 # markdown_tokenizer {tokenize_state = TokenizeState2 },
305+ State = markdown_state :retry (mdx_esm_continuation_start ),
306+ {Tokenizer2 , State }
307+ end
308+ end .
0 commit comments