1#![no_std]
77#![doc(test(
78 no_crate_inject,
79 attr(allow(
80 dead_code,
81 unused_variables,
82 clippy::undocumented_unsafe_blocks,
83 clippy::unused_trait_names,
84 ))
85))]
86#![forbid(unsafe_code)]
87#![warn(
88 missing_debug_implementations,
90 missing_docs,
91 clippy::alloc_instead_of_core,
92 clippy::exhaustive_enums,
93 clippy::exhaustive_structs,
94 clippy::impl_trait_in_params,
95 clippy::std_instead_of_alloc,
96 clippy::std_instead_of_core,
97 )]
99#![allow(clippy::inline_always)]
100
101extern crate alloc;
102extern crate std;
103
104#[cfg(test)]
105#[path = "gen/tests/assert_impl.rs"]
106mod assert_impl;
107#[cfg(test)]
108#[path = "gen/tests/track_size.rs"]
109mod track_size;
110
111mod error;
112
113use alloc::{borrow::Cow, boxed::Box, string::String, vec, vec::Vec};
114use core::{mem, ops::Range, str};
115use std::collections::HashMap;
116
117use smallvec::SmallVec;
118
119pub use self::error::Error;
120use self::error::{ErrorKind, InternalResult, Result};
121
122#[allow(clippy::missing_panics_doc)]
124pub fn parse(text: &str) -> Result<Dockerfile<'_>> {
125 #[cold]
126 fn error(
127 p: &ParseIter<'_>,
128 e: ErrorKind<'_>,
129 instructions: &mut Vec<Instruction<'_>>,
130 stages: &mut Vec<Range<usize>>,
131 ) -> Error {
132 *instructions = vec![];
134 *stages = vec![];
135 e.into_error(p)
136 }
137
138 let mut p = ParseIter::new(text)?;
139 let mut s = p.s;
140
141 let mut instructions = Vec::with_capacity((p.text.len() / 60).min(1024));
142 let mut stages = Vec::with_capacity(1);
143 let mut named_stages = 0;
144 let mut current_stage = None;
145 while let Some((&b, s_next)) = s.split_first() {
146 let instruction = parse_instruction(&mut p, &mut s, b, s_next)
147 .map_err(|e| error(&p, e, &mut instructions, &mut stages))?;
148 match instruction {
149 Instruction::From(from) => {
150 named_stages += from.as_.is_some() as usize;
151 let new_stage = instructions.len();
152 if let Some(prev_stage) = current_stage.replace(new_stage) {
153 stages.push(prev_stage..new_stage);
154 }
155 instructions.push(Instruction::From(from));
156 }
157 arg @ Instruction::Arg(..) => instructions.push(arg),
158 instruction => {
159 if current_stage.is_none() {
160 return Err(error(
161 &p,
162 error::expected("FROM", instruction.instruction_span().start),
163 &mut instructions,
164 &mut stages,
165 ));
166 }
167 instructions.push(instruction);
168 }
169 }
170 consume_comments_and_whitespaces(&mut s, p.escape_byte);
171 }
172 if let Some(current_stage) = current_stage {
173 stages.push(current_stage..instructions.len());
174 }
175
176 if stages.is_empty() {
177 return Err(error(&p, error::no_stage(), &mut instructions, &mut stages));
179 }
180 let mut stages_by_name = HashMap::<Cow<'_, str>, usize>::with_capacity(named_stages);
184 for (i, stage) in stages.iter().enumerate() {
185 let Instruction::From(from) = &instructions[stage.start] else { unreachable!() };
186 if let Some((_as, name)) = &from.as_ {
187 if let Some(&first_occurrence) = stages_by_name.get(&name.value) {
188 drop(stages_by_name);
189 let second_start = name.span.start;
190 let Instruction::From(from) = &mut instructions[stages[first_occurrence].start]
191 else {
192 unreachable!()
193 };
194 let name = mem::take(&mut from.as_.as_mut().unwrap().1.value);
195 return Err(error(
196 &p,
197 error::duplicate_name(name, second_start),
198 &mut instructions,
199 &mut stages,
200 ));
201 }
202 stages_by_name.insert(name.value.clone(), i);
203 }
204 }
205
206 Ok(Dockerfile { parser_directives: p.parser_directives, instructions, stages, stages_by_name })
207}
208
209pub fn parse_iter(text: &str) -> Result<ParseIter<'_>> {
219 ParseIter::new(text)
220}
221
222#[derive(Debug)]
224#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
225#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
226pub struct Dockerfile<'a> {
227 pub parser_directives: ParserDirectives<'a>,
229 pub instructions: Vec<Instruction<'a>>,
231 #[cfg_attr(feature = "serde", serde(skip))]
232 stages: Vec<Range<usize>>,
233 #[cfg_attr(feature = "serde", serde(skip))]
234 stages_by_name: HashMap<Cow<'a, str>, usize>,
235}
236impl<'a> Dockerfile<'a> {
237 #[allow(clippy::missing_panics_doc)] #[must_use]
240 pub fn global_args<'b>(&'b self) -> impl ExactSizeIterator<Item = &'b ArgInstruction<'a>> {
241 self.instructions[..self.stages.first().unwrap().start].iter().map(|arg| {
242 let Instruction::Arg(arg) = arg else { unreachable!() };
243 arg
244 })
245 }
246 #[must_use]
248 pub fn stage<'b>(&'b self, name: &str) -> Option<Stage<'a, 'b>> {
249 let i = *self.stages_by_name.get(name)?;
250 let stage = &self.stages[i];
251 let Instruction::From(from) = &self.instructions[stage.start] else { unreachable!() };
252 Some(Stage { from, instructions: &self.instructions[stage.start + 1..stage.end] })
253 }
254 #[must_use]
256 pub fn stages<'b>(&'b self) -> impl ExactSizeIterator<Item = Stage<'a, 'b>> {
257 self.stages.iter().map(move |stage| {
258 let Instruction::From(from) = &self.instructions[stage.start] else { unreachable!() };
259 Stage { from, instructions: &self.instructions[stage.start + 1..stage.end] }
260 })
261 }
262}
263#[derive(Debug)]
265#[non_exhaustive]
266pub struct Stage<'a, 'b> {
267 pub from: &'b FromInstruction<'a>,
269 pub instructions: &'b [Instruction<'a>],
271}
272
273#[derive(Debug)]
277#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
278#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
279#[non_exhaustive]
280pub struct ParserDirectives<'a> {
281 pub syntax: Option<ParserDirective<&'a str>>,
285 pub escape: Option<ParserDirective<char>>,
289 pub check: Option<ParserDirective<&'a str>>,
293}
294#[derive(Debug)]
296#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
297#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
298pub struct ParserDirective<T> {
299 start: usize,
304 pub value: Spanned<T>,
309}
310impl<T> ParserDirective<T> {
311 #[must_use]
316 pub fn span(&self) -> Span {
317 self.start..self.value.span.end
318 }
319}
320
321#[derive(Debug)]
323#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
324#[cfg_attr(feature = "serde", serde(tag = "kind"))]
325#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
326#[non_exhaustive]
327pub enum Instruction<'a> {
329 Add(AddInstruction<'a>),
331 Arg(ArgInstruction<'a>),
333 Cmd(CmdInstruction<'a>),
335 Copy(CopyInstruction<'a>),
337 Entrypoint(EntrypointInstruction<'a>),
339 Env(EnvInstruction<'a>),
341 Expose(ExposeInstruction<'a>),
343 From(FromInstruction<'a>),
345 Healthcheck(HealthcheckInstruction<'a>),
347 Label(LabelInstruction<'a>),
349 Maintainer(MaintainerInstruction<'a>),
351 Onbuild(OnbuildInstruction<'a>),
353 Run(RunInstruction<'a>),
355 Shell(ShellInstruction<'a>),
357 Stopsignal(StopsignalInstruction<'a>),
359 User(UserInstruction<'a>),
361 Volume(VolumeInstruction<'a>),
363 Workdir(WorkdirInstruction<'a>),
365}
366impl Instruction<'_> {
367 fn instruction_span(&self) -> Span {
368 match self {
369 Instruction::Add(instruction) => instruction.add.span.clone(),
370 Instruction::Arg(instruction) => instruction.arg.span.clone(),
371 Instruction::Cmd(instruction) => instruction.cmd.span.clone(),
372 Instruction::Copy(instruction) => instruction.copy.span.clone(),
373 Instruction::Entrypoint(instruction) => instruction.entrypoint.span.clone(),
374 Instruction::Env(instruction) => instruction.env.span.clone(),
375 Instruction::Expose(instruction) => instruction.expose.span.clone(),
376 Instruction::From(instruction) => instruction.from.span.clone(),
377 Instruction::Healthcheck(instruction) => instruction.healthcheck.span.clone(),
378 Instruction::Label(instruction) => instruction.label.span.clone(),
379 Instruction::Maintainer(instruction) => instruction.maintainer.span.clone(),
380 Instruction::Onbuild(instruction) => instruction.onbuild.span.clone(),
381 Instruction::Run(instruction) => instruction.run.span.clone(),
382 Instruction::Shell(instruction) => instruction.shell.span.clone(),
383 Instruction::Stopsignal(instruction) => instruction.stopsignal.span.clone(),
384 Instruction::User(instruction) => instruction.user.span.clone(),
385 Instruction::Volume(instruction) => instruction.volume.span.clone(),
386 Instruction::Workdir(instruction) => instruction.workdir.span.clone(),
387 }
388 }
389}
390#[derive(Debug)]
394#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
395#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
396#[non_exhaustive]
397pub struct AddInstruction<'a> {
398 pub add: Keyword,
403 pub options: SmallVec<[Flag<'a>; 1]>,
408 pub src: SmallVec<[Source<'a>; 1]>,
414 pub dest: UnescapedString<'a>,
419}
420#[derive(Debug)]
424#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
425#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
426#[non_exhaustive]
427pub struct ArgInstruction<'a> {
428 pub arg: Keyword,
433 pub arguments: UnescapedString<'a>,
439}
440#[derive(Debug)]
444#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
445#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
446#[non_exhaustive]
447pub struct CmdInstruction<'a> {
448 pub cmd: Keyword,
453 pub arguments: Command<'a>,
458}
459#[derive(Debug)]
463#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
464#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
465#[non_exhaustive]
466pub struct CopyInstruction<'a> {
467 pub copy: Keyword,
472 pub options: SmallVec<[Flag<'a>; 1]>,
477 pub src: SmallVec<[Source<'a>; 1]>,
483 pub dest: UnescapedString<'a>,
488}
489#[derive(Debug)]
492#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
493#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
494#[non_exhaustive]
495pub enum Source<'a> {
496 Path(UnescapedString<'a>),
498 HereDoc(HereDoc<'a>),
500}
501#[derive(Debug)]
505#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
506#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
507#[non_exhaustive]
508pub struct EntrypointInstruction<'a> {
509 pub entrypoint: Keyword,
514 pub arguments: Command<'a>,
519}
520#[derive(Debug)]
524#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
525#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
526#[non_exhaustive]
527pub struct EnvInstruction<'a> {
528 pub env: Keyword,
533 pub arguments: UnescapedString<'a>,
539}
540#[derive(Debug)]
544#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
545#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
546#[non_exhaustive]
547pub struct ExposeInstruction<'a> {
548 pub expose: Keyword,
553 pub arguments: SmallVec<[UnescapedString<'a>; 1]>,
558}
559#[derive(Debug)]
563#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
564#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
565#[non_exhaustive]
566pub struct FromInstruction<'a> {
567 pub from: Keyword,
572 pub options: Vec<Flag<'a>>,
577 pub image: UnescapedString<'a>,
582 pub as_: Option<(Keyword, UnescapedString<'a>)>,
587}
588#[derive(Debug)]
592#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
593#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
594#[non_exhaustive]
595pub struct HealthcheckInstruction<'a> {
596 pub healthcheck: Keyword,
601 pub options: Vec<Flag<'a>>,
606 pub arguments: HealthcheckArguments<'a>,
611}
612#[derive(Debug)]
614#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
615#[cfg_attr(feature = "serde", serde(tag = "kind"))]
616#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
617#[non_exhaustive]
618pub enum HealthcheckArguments<'a> {
619 #[non_exhaustive]
621 Cmd {
622 cmd: Keyword,
627 arguments: Command<'a>,
632 },
633 #[non_exhaustive]
635 None {
636 none: Keyword,
641 },
642}
643#[derive(Debug)]
647#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
648#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
649#[non_exhaustive]
650pub struct LabelInstruction<'a> {
651 pub label: Keyword,
656 pub arguments: UnescapedString<'a>,
662}
663#[derive(Debug)]
667#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
668#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
669#[non_exhaustive]
670pub struct MaintainerInstruction<'a> {
671 pub maintainer: Keyword,
676 pub name: UnescapedString<'a>,
681}
682#[derive(Debug)]
686#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
687#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
688#[non_exhaustive]
689pub struct OnbuildInstruction<'a> {
690 pub onbuild: Keyword,
695 pub instruction: Box<Instruction<'a>>,
700}
701#[derive(Debug)]
705#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
706#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
707#[non_exhaustive]
708pub struct RunInstruction<'a> {
709 pub run: Keyword,
714 pub options: SmallVec<[Flag<'a>; 1]>,
719 pub arguments: Command<'a>,
724 pub here_docs: Vec<HereDoc<'a>>,
732}
733#[derive(Debug)]
737#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
738#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
739#[non_exhaustive]
740pub struct ShellInstruction<'a> {
741 pub shell: Keyword,
746 pub arguments: SmallVec<[UnescapedString<'a>; 4]>,
753}
754#[derive(Debug)]
758#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
759#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
760#[non_exhaustive]
761pub struct StopsignalInstruction<'a> {
762 pub stopsignal: Keyword,
767 pub arguments: UnescapedString<'a>,
772}
773#[derive(Debug)]
777#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
778#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
779#[non_exhaustive]
780pub struct UserInstruction<'a> {
781 pub user: Keyword,
786 pub arguments: UnescapedString<'a>,
791}
792#[derive(Debug)]
796#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
797#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
798#[non_exhaustive]
799pub struct VolumeInstruction<'a> {
800 pub volume: Keyword,
805 pub arguments: JsonOrStringArray<'a, 1>,
810}
811#[derive(Debug)]
815#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
816#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
817#[non_exhaustive]
818pub struct WorkdirInstruction<'a> {
819 pub workdir: Keyword,
824 pub arguments: UnescapedString<'a>,
829}
830
831#[derive(Debug)]
833#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
834#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
835#[non_exhaustive]
836pub struct Keyword {
837 #[allow(missing_docs)]
838 pub span: Span,
839}
840
841#[derive(Debug)]
843#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
844#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
845pub struct Flag<'a> {
846 flag_start: usize,
851 pub name: UnescapedString<'a>,
856 pub value: Option<UnescapedString<'a>>,
861}
862impl Flag<'_> {
863 #[must_use]
868 pub fn flag_span(&self) -> Span {
869 self.flag_start..self.name.span.end
870 }
871 #[must_use]
876 pub fn span(&self) -> Span {
877 match &self.value {
878 Some(v) => self.flag_start..v.span.end,
879 None => self.flag_span(),
880 }
881 }
882}
883
884#[derive(Debug, PartialEq)]
886#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
887#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
888#[non_exhaustive]
889pub struct UnescapedString<'a> {
890 #[allow(missing_docs)]
891 pub span: Span,
892 #[allow(missing_docs)]
893 pub value: Cow<'a, str>,
894}
895impl UnescapedString<'_> {
896 #[inline]
897 fn trim_end(&mut self) {
898 match &mut self.value {
900 Cow::Borrowed(v) => {
901 while let Some(&b) = v.as_bytes().last() {
902 if TABLE[b as usize] & (WHITESPACE | POSSIBLE_LINE) == 0 {
903 break;
904 }
905 *v = &v[..v.len() - 1];
906 self.span.end -= 1;
907 }
908 }
909 Cow::Owned(v) => {
910 while let Some(&b) = v.as_bytes().last() {
911 if TABLE[b as usize] & (WHITESPACE | POSSIBLE_LINE) == 0 {
912 break;
913 }
914 v.pop();
915 self.span.end -= 1;
916 }
917 }
918 }
919 }
920}
921
922#[derive(Debug)]
929#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
930#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
931#[non_exhaustive]
932pub enum Command<'a> {
933 Exec(Spanned<SmallVec<[UnescapedString<'a>; 1]>>),
936 Shell(Spanned<&'a str>),
938}
939
940#[derive(Debug)]
945#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
946#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
947#[allow(clippy::exhaustive_enums)]
948pub enum JsonOrStringArray<'a, const N: usize> {
949 Json(Spanned<SmallVec<[UnescapedString<'a>; N]>>),
951 String(SmallVec<[UnescapedString<'a>; N]>),
953}
954
955#[derive(Debug)]
957#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
958#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
959#[non_exhaustive]
960pub struct HereDoc<'a> {
961 #[allow(missing_docs)]
962 pub span: Span,
963 pub expand: bool,
965 #[allow(missing_docs)]
966 pub value: Cow<'a, str>,
967}
968
969#[derive(Debug)]
971#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
972#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
973#[allow(clippy::exhaustive_structs)]
974pub struct Spanned<T> {
975 #[allow(missing_docs)]
976 pub span: Span,
977 #[allow(missing_docs)]
978 pub value: T,
979}
980
981#[allow(missing_docs)]
982pub type Span = Range<usize>;
983
984#[allow(missing_debug_implementations)]
991#[must_use = "iterators are lazy and do nothing unless consumed"]
992pub struct ParseIter<'a> {
993 text: &'a str,
994 s: &'a [u8],
995 escape_byte: u8,
996 has_stage: bool,
997 in_onbuild: bool,
998 parser_directives: ParserDirectives<'a>,
999}
1000impl<'a> ParseIter<'a> {
1001 fn new(mut text: &'a str) -> Result<Self> {
1002 if text.as_bytes().starts_with(UTF8_BOM) {
1004 text = &text[UTF8_BOM.len()..];
1005 }
1006 let mut p = Self {
1007 text,
1008 s: text.as_bytes(),
1009 escape_byte: DEFAULT_ESCAPE_BYTE,
1010 has_stage: false,
1011 in_onbuild: false,
1012 parser_directives: ParserDirectives {
1013 syntax: None,
1015 escape: None,
1016 check: None,
1017 },
1018 };
1019
1020 parse_parser_directives(&mut p).map_err(|e| e.into_error(&p))?;
1021
1022 consume_comments_and_whitespaces(&mut p.s, p.escape_byte);
1026 Ok(p)
1027 }
1028}
1029impl<'a> Iterator for ParseIter<'a> {
1030 type Item = Result<Instruction<'a>>;
1031 #[inline]
1032 fn next(&mut self) -> Option<Self::Item> {
1033 #[cold]
1034 fn error(p: &mut ParseIter<'_>, e: ErrorKind<'_>) -> Error {
1035 let e = e.into_error(p);
1036 p.s = &[];
1038 p.has_stage = true;
1039 e
1040 }
1041
1042 let p = self;
1043 let mut s = p.s;
1044 if let Some((&b, s_next)) = s.split_first() {
1045 let instruction = match parse_instruction(p, &mut s, b, s_next) {
1046 Ok(i) => i,
1047 Err(e) => return Some(Err(error(p, e))),
1048 };
1049 match &instruction {
1050 Instruction::From(..) => {
1051 p.has_stage = true;
1052 }
1053 Instruction::Arg(..) => {}
1054 instruction => {
1055 if !p.has_stage {
1056 return Some(Err(error(
1057 p,
1058 error::expected("FROM", instruction.instruction_span().start),
1059 )));
1060 }
1061 }
1062 }
1063 consume_comments_and_whitespaces(&mut s, p.escape_byte);
1064 p.s = s;
1065 return Some(Ok(instruction));
1066 }
1067 if !p.has_stage {
1068 return Some(Err(error(p, error::no_stage())));
1070 }
1071 None
1072 }
1073}
1074
1075impl core::iter::FusedIterator for ParseIter<'_> {}
1076
1077const DEFAULT_ESCAPE_BYTE: u8 = b'\\';
1078
1079fn parse_parser_directives(p: &mut ParseIter<'_>) -> InternalResult<'static, ()> {
1080 while let Some((&b'#', s_next)) = p.s.split_first() {
1082 p.s = s_next;
1083 consume_whitespaces_no_line_continuation(&mut p.s);
1084 let directive_start = p.text.len() - p.s.len();
1085 if token(&mut p.s, b"SYNTAX") {
1086 consume_whitespaces_no_line_continuation(&mut p.s);
1087 if let Some((&b'=', s_next)) = p.s.split_first() {
1088 p.s = s_next;
1089 if p.parser_directives.syntax.is_some() {
1090 p.parser_directives.syntax = None;
1092 p.parser_directives.escape = None;
1093 p.parser_directives.check = None;
1094 p.escape_byte = DEFAULT_ESCAPE_BYTE;
1095 consume_current_line_no_line_continuation(&mut p.s);
1096 break;
1097 }
1098 consume_whitespaces_no_line_continuation(&mut p.s);
1099 let value_start = p.text.len() - p.s.len();
1100 consume_until_whitespaces_or_line_no_line_continuation(&mut p.s);
1101 let end = p.text.len() - p.s.len();
1102 let value = trim_end(p.text, value_start, end);
1103 p.parser_directives.syntax = Some(ParserDirective {
1104 start: directive_start,
1105 value: Spanned { span: value_start..value_start + value.len(), value },
1106 });
1107 consume_current_line_no_line_continuation(&mut p.s);
1108 continue;
1109 }
1110 } else if token(&mut p.s, b"CHECK") {
1111 consume_whitespaces_no_line_continuation(&mut p.s);
1112 if let Some((&b'=', s_next)) = p.s.split_first() {
1113 p.s = s_next;
1114 if p.parser_directives.check.is_some() {
1115 p.parser_directives.syntax = None;
1117 p.parser_directives.escape = None;
1118 p.parser_directives.check = None;
1119 p.escape_byte = DEFAULT_ESCAPE_BYTE;
1120 consume_current_line_no_line_continuation(&mut p.s);
1121 break;
1122 }
1123 consume_whitespaces_no_line_continuation(&mut p.s);
1124 let value_start = p.text.len() - p.s.len();
1125 consume_until_whitespaces_or_line_no_line_continuation(&mut p.s);
1126 let end = p.text.len() - p.s.len();
1127 let value = trim_end(p.text, value_start, end);
1128 p.parser_directives.check = Some(ParserDirective {
1129 start: directive_start,
1130 value: Spanned { span: value_start..value_start + value.len(), value },
1131 });
1132 consume_current_line_no_line_continuation(&mut p.s);
1133 continue;
1134 }
1135 } else if token(&mut p.s, b"ESCAPE") {
1136 consume_whitespaces_no_line_continuation(&mut p.s);
1137 if let Some((&b'=', s_next)) = p.s.split_first() {
1138 p.s = s_next;
1139 if p.parser_directives.escape.is_some() {
1140 p.parser_directives.syntax = None;
1142 p.parser_directives.escape = None;
1143 p.parser_directives.check = None;
1144 p.escape_byte = DEFAULT_ESCAPE_BYTE;
1145 consume_current_line_no_line_continuation(&mut p.s);
1146 break;
1147 }
1148 consume_whitespaces_no_line_continuation(&mut p.s);
1149 let value_start = p.text.len() - p.s.len();
1150 consume_until_whitespaces_or_line_no_line_continuation(&mut p.s);
1151 let end = p.text.len() - p.s.len();
1152 let value = trim_end(p.text, value_start, end);
1153 match value {
1154 "`" => p.escape_byte = b'`',
1155 "\\" => {}
1156 _ => return Err(error::invalid_escape(value_start)),
1157 }
1158 p.parser_directives.escape = Some(ParserDirective {
1159 start: directive_start,
1160 value: Spanned {
1161 span: value_start..value_start + value.len(),
1162 value: p.escape_byte as char,
1163 },
1164 });
1165 consume_current_line_no_line_continuation(&mut p.s);
1166 continue;
1167 }
1168 }
1169 consume_current_line_no_line_continuation(&mut p.s);
1170 break;
1171 }
1172 Ok(())
1173}
1174
1175#[inline]
1176fn parse_instruction<'a>(
1177 p: &mut ParseIter<'a>,
1178 s: &mut &'a [u8],
1179 b: u8,
1180 s_next: &'a [u8],
1181) -> InternalResult<'a, Instruction<'a>> {
1182 let instruction_start = p.text.len() - s.len();
1183 *s = s_next;
1184 match b & TO_UPPER8 {
1186 b'A' => {
1187 if token(s, &b"ARG"[1..]) {
1188 let instruction_span = instruction_start..p.text.len() - s.len();
1189 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1190 return parse_arg(p, s, Keyword { span: instruction_span });
1191 }
1192 } else if token(s, &b"ADD"[1..]) {
1193 let instruction_span = instruction_start..p.text.len() - s.len();
1194 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1195 let add = Keyword { span: instruction_span };
1196 let (options, src, dest) = parse_add_or_copy(p, s, &add)?;
1197 return Ok(Instruction::Add(AddInstruction { add, options, src, dest }));
1198 }
1199 } else if token_slow(s, &b"ARG"[1..], p.escape_byte) {
1200 let instruction_span = instruction_start..p.text.len() - s.len();
1201 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1202 return parse_arg(p, s, Keyword { span: instruction_span });
1203 }
1204 } else if token_slow(s, &b"ADD"[1..], p.escape_byte) {
1205 let instruction_span = instruction_start..p.text.len() - s.len();
1206 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1207 let add = Keyword { span: instruction_span };
1208 let (options, src, dest) = parse_add_or_copy(p, s, &add)?;
1209 return Ok(Instruction::Add(AddInstruction { add, options, src, dest }));
1210 }
1211 }
1212 }
1213 b'C' => {
1214 if token(s, &b"COPY"[1..]) {
1215 let instruction_span = instruction_start..p.text.len() - s.len();
1216 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1217 let copy = Keyword { span: instruction_span };
1218 let (options, src, dest) = parse_add_or_copy(p, s, ©)?;
1219 return Ok(Instruction::Copy(CopyInstruction { copy, options, src, dest }));
1220 }
1221 } else if token(s, &b"CMD"[1..]) {
1222 let instruction_span = instruction_start..p.text.len() - s.len();
1223 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1224 return parse_cmd(p, s, Keyword { span: instruction_span });
1225 }
1226 } else if token_slow(s, &b"COPY"[1..], p.escape_byte) {
1227 let instruction_span = instruction_start..p.text.len() - s.len();
1228 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1229 let copy = Keyword { span: instruction_span };
1230 let (options, src, dest) = parse_add_or_copy(p, s, ©)?;
1231 return Ok(Instruction::Copy(CopyInstruction { copy, options, src, dest }));
1232 }
1233 } else if token_slow(s, &b"CMD"[1..], p.escape_byte) {
1234 let instruction_span = instruction_start..p.text.len() - s.len();
1235 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1236 return parse_cmd(p, s, Keyword { span: instruction_span });
1237 }
1238 }
1239 }
1240 b'E' => {
1241 if token(s, &b"ENV"[1..]) {
1242 let instruction_span = instruction_start..p.text.len() - s.len();
1243 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1244 return parse_env(p, s, Keyword { span: instruction_span });
1245 }
1246 } else if token(s, &b"EXPOSE"[1..]) {
1247 let instruction_span = instruction_start..p.text.len() - s.len();
1248 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1249 return parse_expose(p, s, Keyword { span: instruction_span });
1250 }
1251 } else if token(s, &b"ENTRYPOINT"[1..]) {
1252 let instruction_span = instruction_start..p.text.len() - s.len();
1253 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1254 return parse_entrypoint(p, s, Keyword { span: instruction_span });
1255 }
1256 } else if token_slow(s, &b"ENV"[1..], p.escape_byte) {
1257 let instruction_span = instruction_start..p.text.len() - s.len();
1258 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1259 return parse_env(p, s, Keyword { span: instruction_span });
1260 }
1261 } else if token_slow(s, &b"EXPOSE"[1..], p.escape_byte) {
1262 let instruction_span = instruction_start..p.text.len() - s.len();
1263 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1264 return parse_expose(p, s, Keyword { span: instruction_span });
1265 }
1266 } else if token_slow(s, &b"ENTRYPOINT"[1..], p.escape_byte) {
1267 let instruction_span = instruction_start..p.text.len() - s.len();
1268 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1269 return parse_entrypoint(p, s, Keyword { span: instruction_span });
1270 }
1271 }
1272 }
1273 b'F' => {
1274 cold_path();
1275 if token(s, &b"FROM"[1..]) || token_slow(s, &b"FROM"[1..], p.escape_byte) {
1276 let instruction_span = instruction_start..p.text.len() - s.len();
1277 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1278 return parse_from(p, s, Keyword { span: instruction_span });
1279 }
1280 }
1281 }
1282 b'H' => {
1283 cold_path();
1284 if token(s, &b"HEALTHCHECK"[1..]) || token_slow(s, &b"HEALTHCHECK"[1..], p.escape_byte)
1285 {
1286 let instruction_span = instruction_start..p.text.len() - s.len();
1287 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1288 return parse_healthcheck(p, s, Keyword { span: instruction_span });
1289 }
1290 }
1291 }
1292 b'L' => {
1293 cold_path();
1294 if token(s, &b"LABEL"[1..]) || token_slow(s, &b"LABEL"[1..], p.escape_byte) {
1295 let instruction_span = instruction_start..p.text.len() - s.len();
1296 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1297 return parse_label(p, s, Keyword { span: instruction_span });
1298 }
1299 }
1300 }
1301 b'M' => {
1302 cold_path();
1303 if token(s, &b"MAINTAINER"[1..]) || token_slow(s, &b"MAINTAINER"[1..], p.escape_byte) {
1304 let instruction_span = instruction_start..p.text.len() - s.len();
1305 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1306 return parse_maintainer(p, s, Keyword { span: instruction_span });
1307 }
1308 }
1309 }
1310 b'O' => {
1311 cold_path();
1312 if token(s, &b"ONBUILD"[1..]) || token_slow(s, &b"ONBUILD"[1..], p.escape_byte) {
1313 let instruction_span = instruction_start..p.text.len() - s.len();
1314 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1315 return parse_onbuild(p, s, Keyword { span: instruction_span });
1316 }
1317 }
1318 }
1319 b'R' => {
1320 if token(s, &b"RUN"[1..]) || token_slow(s, &b"RUN"[1..], p.escape_byte) {
1321 let instruction_span = instruction_start..p.text.len() - s.len();
1322 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1323 return parse_run(p, s, Keyword { span: instruction_span });
1324 }
1325 }
1326 }
1327 b'S' => {
1328 cold_path();
1329 if token(s, &b"SHELL"[1..]) {
1330 let instruction_span = instruction_start..p.text.len() - s.len();
1331 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1332 return parse_shell(p, s, Keyword { span: instruction_span });
1333 }
1334 } else if token(s, &b"STOPSIGNAL"[1..]) {
1335 let instruction_span = instruction_start..p.text.len() - s.len();
1336 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1337 return parse_stopsignal(p, s, Keyword { span: instruction_span });
1338 }
1339 } else if token_slow(s, &b"SHELL"[1..], p.escape_byte) {
1340 let instruction_span = instruction_start..p.text.len() - s.len();
1341 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1342 return parse_shell(p, s, Keyword { span: instruction_span });
1343 }
1344 } else if token_slow(s, &b"STOPSIGNAL"[1..], p.escape_byte) {
1345 let instruction_span = instruction_start..p.text.len() - s.len();
1346 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1347 return parse_stopsignal(p, s, Keyword { span: instruction_span });
1348 }
1349 }
1350 }
1351 b'U' => {
1352 cold_path();
1353 if token(s, &b"USER"[1..]) || token_slow(s, &b"USER"[1..], p.escape_byte) {
1354 let instruction_span = instruction_start..p.text.len() - s.len();
1355 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1356 return parse_user(p, s, Keyword { span: instruction_span });
1357 }
1358 }
1359 }
1360 b'V' => {
1361 cold_path();
1362 if token(s, &b"VOLUME"[1..]) || token_slow(s, &b"VOLUME"[1..], p.escape_byte) {
1363 let instruction_span = instruction_start..p.text.len() - s.len();
1364 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1365 return parse_volume(p, s, Keyword { span: instruction_span });
1366 }
1367 }
1368 }
1369 b'W' => {
1370 cold_path();
1371 if token(s, &b"WORKDIR"[1..]) || token_slow(s, &b"WORKDIR"[1..], p.escape_byte) {
1372 let instruction_span = instruction_start..p.text.len() - s.len();
1373 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1374 return parse_workdir(p, s, Keyword { span: instruction_span });
1375 }
1376 }
1377 }
1378 _ => {}
1379 }
1380 Err(error::unknown_instruction(instruction_start))
1381}
1382
1383#[inline]
1384fn parse_arg<'a>(
1385 p: &mut ParseIter<'a>,
1386 s: &mut &'a [u8],
1387 instruction: Keyword,
1388) -> InternalResult<'static, Instruction<'a>> {
1389 debug_assert!(token_slow(
1390 &mut p.text[instruction.span.clone()].as_bytes(),
1391 b"ARG",
1392 p.escape_byte,
1393 ));
1394 let mut arguments = collect_until_line_consume_newline(s, p.text, p.escape_byte);
1395 arguments.trim_end();
1396 if arguments.value.is_empty() {
1397 return Err(error::at_least_one_argument(instruction.span.start));
1398 }
1399 Ok(Instruction::Arg(ArgInstruction { arg: instruction, arguments }))
1400}
1401
1402#[inline]
1403fn parse_add_or_copy<'a>(
1404 p: &mut ParseIter<'a>,
1405 s: &mut &'a [u8],
1406 instruction: &Keyword,
1407) -> InternalResult<'a, (SmallVec<[Flag<'a>; 1]>, SmallVec<[Source<'a>; 1]>, UnescapedString<'a>)> {
1408 debug_assert!(
1409 token_slow(&mut p.text[instruction.span.clone()].as_bytes(), b"ADD", p.escape_byte,)
1410 || token_slow(&mut p.text[instruction.span.clone()].as_bytes(), b"COPY", p.escape_byte,)
1411 );
1412 let options = parse_options(s, p.text, p.escape_byte);
1413 if is_maybe_json(s) {
1414 let mut tmp = *s;
1415 if let Ok(((src, dest), _array_span)) = parse_json_array::<(
1416 SmallVec<[Source<'_>; 1]>,
1417 Option<_>,
1418 )>(&mut tmp, p.text, p.escape_byte)
1419 {
1420 if let Some((&b, s_next)) = tmp.split_first() {
1421 let consumed = consume_newline(b, s, s_next);
1422 debug_assert!(consumed);
1423 } else {
1424 *s = &[];
1425 }
1426 if src.is_empty() {
1427 return Err(error::at_least_two_arguments(instruction.span.start));
1428 }
1429 return Ok((options, src, dest.unwrap()));
1430 }
1431 }
1432 let (mut src, dest) = collect_space_separated_consume_line::<(
1433 SmallVec<[Source<'_>; 1]>,
1434 Option<_>,
1435 )>(s, p.text, p.escape_byte);
1436 if src.is_empty() {
1437 return Err(error::at_least_two_arguments(instruction.span.start));
1438 }
1439 for src in &mut src {
1440 let Source::Path(path) = src else { unreachable!() };
1441 let full = mem::take(&mut path.value);
1442 let mut tmp = full.as_bytes();
1443 if let Some(tmp_next) = tmp.strip_prefix(b"<<") {
1444 if let Some((delim, strip_tab, expand)) =
1445 collect_here_doc_delim(&mut tmp, tmp_next, &full)?
1446 {
1447 let delim_start = 2 + usize::from(strip_tab);
1448 debug_assert!(
1450 tmp.is_empty()
1451 && (matches!(delim, Cow::Owned(..))
1452 || full.as_bytes()[delim_start..] == *delim)
1453 );
1454 let delim = match delim {
1455 Cow::Borrowed(_) => match full {
1456 Cow::Borrowed(v) => Cow::Borrowed(&v.as_bytes()[delim_start..]),
1457 Cow::Owned(v) => {
1458 let mut v = v.into_bytes();
1459 drop(v.drain(..delim_start));
1460 Cow::Owned(v)
1461 }
1462 },
1463 Cow::Owned(v) => Cow::Owned(v),
1464 };
1465 let (here_doc, span) = collect_here_doc(s, p.text, delim, strip_tab)?;
1466 *src = Source::HereDoc(HereDoc { span, expand, value: here_doc });
1467 continue;
1468 }
1469 }
1470 path.value = full;
1471 }
1472 Ok((options, src, dest.unwrap()))
1473}
1474
1475#[allow(clippy::unnecessary_wraps)]
1476#[inline]
1477fn parse_cmd<'a>(
1478 p: &mut ParseIter<'a>,
1479 s: &mut &'a [u8],
1480 instruction: Keyword,
1481) -> InternalResult<'static, Instruction<'a>> {
1482 debug_assert!(token_slow(
1483 &mut p.text[instruction.span.clone()].as_bytes(),
1484 b"CMD",
1485 p.escape_byte,
1486 ));
1487 if is_maybe_json(s) {
1488 let mut tmp = *s;
1489 if let Ok((arguments, array_span)) =
1490 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1491 {
1492 if let Some((&b, s_next)) = tmp.split_first() {
1493 let consumed = consume_newline(b, s, s_next);
1494 debug_assert!(consumed);
1495 } else {
1496 *s = &[];
1497 }
1498 return Ok(Instruction::Cmd(CmdInstruction {
1501 cmd: instruction,
1502 arguments: Command::Exec(Spanned { span: array_span, value: arguments }),
1503 }));
1504 }
1505 }
1506 let arguments_start = p.text.len() - s.len();
1507 consume_current_line(s, p.escape_byte);
1508 let end = p.text.len() - s.len();
1509 let arguments = trim_end(p.text, arguments_start, end);
1510 Ok(Instruction::Cmd(CmdInstruction {
1511 cmd: instruction,
1512 arguments: Command::Shell(Spanned {
1513 span: arguments_start..arguments_start + arguments.len(),
1514 value: arguments,
1515 }),
1516 }))
1517}
1518
1519#[inline]
1520fn parse_env<'a>(
1521 p: &mut ParseIter<'a>,
1522 s: &mut &'a [u8],
1523 instruction: Keyword,
1524) -> InternalResult<'static, Instruction<'a>> {
1525 debug_assert!(token_slow(
1526 &mut p.text[instruction.span.clone()].as_bytes(),
1527 b"ENV",
1528 p.escape_byte,
1529 ));
1530 let mut arguments = collect_until_line_consume_newline(s, p.text, p.escape_byte);
1531 arguments.trim_end();
1532 if arguments.value.is_empty() {
1533 return Err(error::at_least_one_argument(instruction.span.start));
1534 }
1535 Ok(Instruction::Env(EnvInstruction { env: instruction, arguments }))
1536}
1537
1538#[inline]
1539fn parse_expose<'a>(
1540 p: &mut ParseIter<'a>,
1541 s: &mut &'a [u8],
1542 instruction: Keyword,
1543) -> InternalResult<'static, Instruction<'a>> {
1544 debug_assert!(token_slow(
1545 &mut p.text[instruction.span.clone()].as_bytes(),
1546 b"EXPOSE",
1547 p.escape_byte,
1548 ));
1549 let arguments: SmallVec<[_; 1]> =
1550 collect_space_separated_consume_line(s, p.text, p.escape_byte);
1551 if arguments.is_empty() {
1552 return Err(error::at_least_one_argument(instruction.span.start));
1553 }
1554 Ok(Instruction::Expose(ExposeInstruction { expose: instruction, arguments }))
1555}
1556
1557#[inline]
1558fn parse_entrypoint<'a>(
1559 p: &mut ParseIter<'a>,
1560 s: &mut &'a [u8],
1561 instruction: Keyword,
1562) -> InternalResult<'static, Instruction<'a>> {
1563 debug_assert!(token_slow(
1564 &mut p.text[instruction.span.clone()].as_bytes(),
1565 b"ENTRYPOINT",
1566 p.escape_byte,
1567 ));
1568 if is_maybe_json(s) {
1569 let mut tmp = *s;
1570 if let Ok((arguments, array_span)) =
1571 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1572 {
1573 if let Some((&b, s_next)) = tmp.split_first() {
1574 let consumed = consume_newline(b, s, s_next);
1575 debug_assert!(consumed);
1576 } else {
1577 *s = &[];
1578 }
1579 if arguments.is_empty() {
1580 return Err(error::at_least_one_argument(instruction.span.start));
1581 }
1582 return Ok(Instruction::Entrypoint(EntrypointInstruction {
1583 entrypoint: instruction,
1584 arguments: Command::Exec(Spanned { span: array_span, value: arguments }),
1585 }));
1586 }
1587 }
1588 let arguments_start = p.text.len() - s.len();
1589 consume_current_line(s, p.escape_byte);
1590 let end = p.text.len() - s.len();
1591 let arguments = trim_end(p.text, arguments_start, end);
1592 if arguments.is_empty() {
1593 return Err(error::at_least_one_argument(instruction.span.start));
1594 }
1595 Ok(Instruction::Entrypoint(EntrypointInstruction {
1596 entrypoint: instruction,
1597 arguments: Command::Shell(Spanned {
1598 span: arguments_start..arguments_start + arguments.len(),
1599 value: arguments,
1600 }),
1601 }))
1602}
1603
1604#[inline]
1605fn parse_from<'a>(
1606 p: &mut ParseIter<'a>,
1607 s: &mut &'a [u8],
1608 instruction: Keyword,
1609) -> InternalResult<'static, Instruction<'a>> {
1610 debug_assert!(token_slow(
1611 &mut p.text[instruction.span.clone()].as_bytes(),
1612 b"FROM",
1613 p.escape_byte,
1614 ));
1615 let options = parse_options(s, p.text, p.escape_byte);
1616 let image = collect_non_whitespace(s, p.text, p.escape_byte);
1619 if image.value.is_empty() {
1620 return Err(error::at_least_one_argument(instruction.span.start));
1621 }
1622 let mut as_ = None;
1623 if consume_whitespaces(s, p.escape_byte) {
1624 let as_start = p.text.len() - s.len();
1625 if token(s, b"AS") || token_slow(s, b"AS", p.escape_byte) {
1626 let as_span = as_start..p.text.len() - s.len();
1627 if !consume_whitespaces(s, p.escape_byte) {
1628 return Err(error::expected("AS", as_start));
1629 }
1630 let name = collect_non_whitespace(s, p.text, p.escape_byte);
1631 consume_whitespaces(s, p.escape_byte);
1632 if !is_line_end(s.first()) {
1633 return Err(error::expected("newline or eof", p.text.len() - s.len()));
1634 }
1635 as_ = Some((Keyword { span: as_span }, name));
1636 } else if !is_line_end(s.first()) {
1637 return Err(error::expected("AS", as_start));
1638 }
1639 }
1640 Ok(Instruction::From(FromInstruction { from: instruction, options, image, as_ }))
1641}
1642
1643#[inline]
1644fn parse_healthcheck<'a>(
1645 p: &mut ParseIter<'a>,
1646 s: &mut &'a [u8],
1647 instruction: Keyword,
1648) -> InternalResult<'static, Instruction<'a>> {
1649 debug_assert!(token_slow(
1650 &mut p.text[instruction.span.clone()].as_bytes(),
1651 b"HEALTHCHECK",
1652 p.escape_byte,
1653 ));
1654 let options = parse_options(s, p.text, p.escape_byte);
1655 let Some((&b, s_next)) = s.split_first() else {
1656 return Err(error::expected("CMD or NONE", p.text.len() - s.len()));
1657 };
1658 let cmd_or_none_start = p.text.len() - s.len();
1659 match b & TO_UPPER8 {
1660 b'C' => {
1661 *s = s_next;
1662 if token(s, &b"CMD"[1..]) || token_slow(s, &b"CMD"[1..], p.escape_byte) {
1663 let cmd_span = cmd_or_none_start..p.text.len() - s.len();
1664 let cmd_keyword = Keyword { span: cmd_span };
1665 if consume_whitespaces_or_is_empty_line(s, p.escape_byte) {
1666 if is_maybe_json(s) {
1667 let mut tmp = *s;
1668 if let Ok((arguments, array_span)) =
1669 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1670 {
1671 if let Some((&b, s_next)) = tmp.split_first() {
1672 let consumed = consume_newline(b, s, s_next);
1673 debug_assert!(consumed);
1674 } else {
1675 *s = &[];
1676 }
1677 if arguments.is_empty() {
1678 return Err(error::at_least_one_argument(instruction.span.start));
1679 }
1680 return Ok(Instruction::Healthcheck(HealthcheckInstruction {
1681 healthcheck: instruction,
1682 options,
1683 arguments: HealthcheckArguments::Cmd {
1684 cmd: cmd_keyword,
1685 arguments: Command::Exec(Spanned {
1686 span: array_span,
1687 value: arguments,
1688 }),
1689 },
1690 }));
1691 }
1692 }
1693 let arguments_start = p.text.len() - s.len();
1694 consume_current_line(s, p.escape_byte);
1695 let end = p.text.len() - s.len();
1696 let arguments = trim_end(p.text, arguments_start, end);
1697 return Ok(Instruction::Healthcheck(HealthcheckInstruction {
1698 healthcheck: instruction,
1699 options,
1700 arguments: HealthcheckArguments::Cmd {
1701 cmd: cmd_keyword,
1702 arguments: Command::Shell(Spanned {
1703 span: arguments_start..arguments_start + arguments.len(),
1704 value: arguments,
1705 }),
1706 },
1707 }));
1708 }
1709 }
1710 }
1711 b'N' => {
1712 *s = s_next;
1713 if token(s, &b"NONE"[1..]) || token_slow(s, &b"NONE"[1..], p.escape_byte) {
1714 let none_span = cmd_or_none_start..p.text.len() - s.len();
1715 consume_whitespaces(s, p.escape_byte);
1716 if !is_line_end(s.first()) {
1717 return Err(error::other(
1718 "HEALTHCHECK NONE does not accept arguments",
1719 p.text.len() - s.len(),
1720 ));
1721 }
1722 let none_keyword = Keyword { span: none_span };
1724 return Ok(Instruction::Healthcheck(HealthcheckInstruction {
1725 healthcheck: instruction,
1726 options,
1727 arguments: HealthcheckArguments::None { none: none_keyword },
1728 }));
1729 }
1730 }
1731 _ => {}
1732 }
1733 Err(error::expected("CMD or NONE", p.text.len() - s.len()))
1734}
1735
1736#[inline]
1737fn parse_label<'a>(
1738 p: &mut ParseIter<'a>,
1739 s: &mut &'a [u8],
1740 instruction: Keyword,
1741) -> InternalResult<'static, Instruction<'a>> {
1742 debug_assert!(token_slow(
1743 &mut p.text[instruction.span.clone()].as_bytes(),
1744 b"LABEL",
1745 p.escape_byte,
1746 ));
1747 let mut arguments = collect_until_line_consume_newline(s, p.text, p.escape_byte);
1748 arguments.trim_end();
1749 if arguments.value.is_empty() {
1750 return Err(error::at_least_one_argument(instruction.span.start));
1751 }
1752 Ok(Instruction::Label(LabelInstruction { label: instruction, arguments }))
1753}
1754
1755#[cold]
1756fn parse_maintainer<'a>(
1757 p: &mut ParseIter<'a>,
1758 s: &mut &'a [u8],
1759 instruction: Keyword,
1760) -> InternalResult<'static, Instruction<'a>> {
1761 debug_assert!(token_slow(
1762 &mut p.text[instruction.span.clone()].as_bytes(),
1763 b"MAINTAINER",
1764 p.escape_byte,
1765 ));
1766 let mut name = collect_until_line_consume_newline(s, p.text, p.escape_byte);
1767 name.trim_end();
1768 if name.value.is_empty() {
1769 return Err(error::exactly_one_argument(instruction.span.start));
1770 }
1771 Ok(Instruction::Maintainer(MaintainerInstruction { maintainer: instruction, name }))
1772}
1773
1774#[inline]
1775fn parse_onbuild<'a>(
1776 p: &mut ParseIter<'a>,
1777 s: &mut &'a [u8],
1778 instruction: Keyword,
1779) -> InternalResult<'a, Instruction<'a>> {
1780 debug_assert!(token_slow(
1781 &mut p.text[instruction.span.clone()].as_bytes(),
1782 b"ONBUILD",
1783 p.escape_byte,
1784 ));
1785 if p.in_onbuild {
1787 return Err(error::other("ONBUILD ONBUILD is not allowed", instruction.span.start));
1788 }
1789 p.in_onbuild = true;
1790 let Some((&b, s_next)) = s.split_first() else {
1791 return Err(error::expected("instruction after ONBUILD", instruction.span.start));
1792 };
1793 let inner_instruction = parse_instruction(p, s, b, s_next)?;
1816 p.in_onbuild = false;
1817 Ok(Instruction::Onbuild(OnbuildInstruction {
1818 onbuild: instruction,
1819 instruction: Box::new(inner_instruction),
1820 }))
1821}
1822
1823#[inline]
1824fn parse_run<'a>(
1825 p: &mut ParseIter<'a>,
1826 s: &mut &'a [u8],
1827 instruction: Keyword,
1828) -> InternalResult<'a, Instruction<'a>> {
1829 debug_assert!(token_slow(
1830 &mut p.text[instruction.span.clone()].as_bytes(),
1831 b"RUN",
1832 p.escape_byte,
1833 ));
1834 let options = parse_options(s, p.text, p.escape_byte);
1835 if is_maybe_json(s) {
1836 let mut tmp = *s;
1837 if let Ok((arguments, array_span)) =
1838 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1839 {
1840 if let Some((&b, s_next)) = tmp.split_first() {
1841 let consumed = consume_newline(b, s, s_next);
1842 debug_assert!(consumed);
1843 } else {
1844 *s = &[];
1845 }
1846 if arguments.is_empty() {
1847 return Err(error::at_least_one_argument(instruction.span.start));
1848 }
1849 return Ok(Instruction::Run(RunInstruction {
1850 run: instruction,
1851 options,
1852 arguments: Command::Exec(Spanned { span: array_span, value: arguments }),
1853 here_docs: vec![],
1855 }));
1856 }
1857 }
1858
1859 if s.len() >= 5 {
1862 if let Some(s_next) = s.strip_prefix(b"<<") {
1864 if let Some((delim, strip_tab, expand)) = collect_here_doc_delim(s, s_next, p.text)? {
1865 let arguments_start = p.text.len() - s.len();
1867 consume_current_line(s, p.escape_byte);
1868 let end = p.text.len() - s.len();
1869 let arguments = trim_end(p.text, arguments_start, end);
1870 let (here_doc, span) = collect_here_doc(s, p.text, delim, strip_tab)?;
1871 let here_doc = HereDoc { span, expand, value: here_doc };
1872 return Ok(Instruction::Run(RunInstruction {
1873 run: instruction,
1874 options,
1875 arguments: Command::Shell(Spanned {
1876 span: arguments_start..arguments_start + arguments.len(),
1877 value: arguments,
1878 }),
1879 here_docs: vec![here_doc],
1881 }));
1882 }
1883 }
1884 }
1885
1886 let arguments_start = p.text.len() - s.len();
1887 consume_current_line(s, p.escape_byte);
1888 let end = p.text.len() - s.len();
1889 let arguments = trim_end(p.text, arguments_start, end);
1890 Ok(Instruction::Run(RunInstruction {
1891 run: instruction,
1892 options,
1893 arguments: Command::Shell(Spanned {
1894 span: arguments_start..arguments_start + arguments.len(),
1895 value: arguments,
1896 }),
1897 here_docs: vec![],
1898 }))
1899}
1900
1901#[inline]
1902fn parse_shell<'a>(
1903 p: &mut ParseIter<'a>,
1904 s: &mut &'a [u8],
1905 instruction: Keyword,
1906) -> InternalResult<'static, Instruction<'a>> {
1907 debug_assert!(token_slow(
1908 &mut p.text[instruction.span.clone()].as_bytes(),
1909 b"SHELL",
1910 p.escape_byte,
1911 ));
1912 if !is_maybe_json(s) {
1913 return Err(error::expected("JSON array", p.text.len() - s.len()));
1914 }
1915 let (arguments, _array_span) =
1916 parse_json_array::<SmallVec<[_; 4]>>(s, p.text, p.escape_byte).map_err(error::json)?;
1917 if let Some((&b, s_next)) = s.split_first() {
1918 let consumed = consume_newline(b, s, s_next);
1919 debug_assert!(consumed);
1920 }
1921 if arguments.is_empty() {
1922 return Err(error::at_least_one_argument(instruction.span.start));
1923 }
1924 Ok(Instruction::Shell(ShellInstruction { shell: instruction, arguments }))
1925}
1926
1927#[inline]
1928fn parse_stopsignal<'a>(
1929 p: &mut ParseIter<'a>,
1930 s: &mut &'a [u8],
1931 instruction: Keyword,
1932) -> InternalResult<'static, Instruction<'a>> {
1933 debug_assert!(token_slow(
1934 &mut p.text[instruction.span.clone()].as_bytes(),
1935 b"STOPSIGNAL",
1936 p.escape_byte,
1937 ));
1938 let mut arguments = collect_until_line_consume_newline(s, p.text, p.escape_byte);
1940 arguments.trim_end();
1941 if arguments.value.is_empty() {
1942 return Err(error::exactly_one_argument(instruction.span.start));
1943 }
1944 Ok(Instruction::Stopsignal(StopsignalInstruction { stopsignal: instruction, arguments }))
1945}
1946
1947#[inline]
1948fn parse_user<'a>(
1949 p: &mut ParseIter<'a>,
1950 s: &mut &'a [u8],
1951 instruction: Keyword,
1952) -> InternalResult<'static, Instruction<'a>> {
1953 debug_assert!(token_slow(
1954 &mut p.text[instruction.span.clone()].as_bytes(),
1955 b"USER",
1956 p.escape_byte,
1957 ));
1958 let mut arguments = collect_until_line_consume_newline(s, p.text, p.escape_byte);
1960 arguments.trim_end();
1961 if arguments.value.is_empty() {
1962 return Err(error::exactly_one_argument(instruction.span.start));
1963 }
1964 Ok(Instruction::User(UserInstruction { user: instruction, arguments }))
1965}
1966
1967#[inline]
1968fn parse_volume<'a>(
1969 p: &mut ParseIter<'a>,
1970 s: &mut &'a [u8],
1971 instruction: Keyword,
1972) -> InternalResult<'static, Instruction<'a>> {
1973 debug_assert!(token_slow(
1974 &mut p.text[instruction.span.clone()].as_bytes(),
1975 b"VOLUME",
1976 p.escape_byte,
1977 ));
1978 if is_maybe_json(s) {
1979 let mut tmp = *s;
1980 if let Ok((arguments, array_span)) = parse_json_array(&mut tmp, p.text, p.escape_byte) {
1981 if let Some((&b, s_next)) = tmp.split_first() {
1982 let consumed = consume_newline(b, s, s_next);
1983 debug_assert!(consumed);
1984 } else {
1985 *s = &[];
1986 }
1987 return Ok(Instruction::Volume(VolumeInstruction {
1989 volume: instruction,
1990 arguments: JsonOrStringArray::Json(Spanned { span: array_span, value: arguments }),
1991 }));
1992 }
1993 }
1994 let arguments: SmallVec<[_; 1]> =
1995 collect_space_separated_consume_line(s, p.text, p.escape_byte);
1996 if arguments.is_empty() {
1997 return Err(error::at_least_one_argument(instruction.span.start));
1999 }
2000 Ok(Instruction::Volume(VolumeInstruction {
2001 volume: instruction,
2002 arguments: JsonOrStringArray::String(arguments),
2003 }))
2004}
2005
2006#[inline]
2007fn parse_workdir<'a>(
2008 p: &mut ParseIter<'a>,
2009 s: &mut &'a [u8],
2010 instruction: Keyword,
2011) -> InternalResult<'static, Instruction<'a>> {
2012 debug_assert!(token_slow(
2013 &mut p.text[instruction.span.clone()].as_bytes(),
2014 b"WORKDIR",
2015 p.escape_byte,
2016 ));
2017 let mut arguments = collect_until_line_consume_newline(s, p.text, p.escape_byte);
2019 arguments.trim_end();
2020 if arguments.value.is_empty() {
2021 return Err(error::exactly_one_argument(instruction.span.start));
2022 }
2023 Ok(Instruction::Workdir(WorkdirInstruction { workdir: instruction, arguments }))
2024}
2025
2026const POSSIBLE_LINE: u8 = 1 << 0;
2031const SPACE: u8 = 1 << 1;
2033const WHITESPACE: u8 = 1 << 2;
2037const COMMENT: u8 = 1 << 3;
2039const DOUBLE_QUOTE: u8 = 1 << 4;
2041const POSSIBLE_ESCAPE: u8 = 1 << 5;
2043const EQ: u8 = 1 << 6;
2045const CONTROL: u8 = 1 << 7;
2047
2048static TABLE: [u8; 256] = {
2049 let mut table = [0; 256];
2050 let mut i = 0;
2051 loop {
2052 let mut v = 0;
2053 if i < 0x20 {
2054 v |= CONTROL;
2055 }
2056 match i {
2057 b' ' | b'\t' => v |= WHITESPACE | SPACE,
2058 b'\x0B' | b'\x0C' => v |= WHITESPACE,
2059 b'\r' => v |= WHITESPACE | POSSIBLE_LINE,
2060 b'\n' => v |= POSSIBLE_LINE,
2061 b'#' => v |= COMMENT,
2062 b'"' => v |= DOUBLE_QUOTE,
2063 b'\\' | b'`' => v |= POSSIBLE_ESCAPE,
2064 b'=' => v |= EQ,
2065 _ => {}
2066 }
2067 table[i as usize] = v;
2068 if i == u8::MAX {
2069 break;
2070 }
2071 i += 1;
2072 }
2073 table
2074};
2075
2076#[rustfmt::skip]
2078static HEX_DECODE_TABLE: [u8; 256] = {
2079 const __: u8 = u8::MAX;
2080 [
2081 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, __, __, __, __, __, __, __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, ]
2099};
2100
2101const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
2102
2103trait Store<T>: Sized {
2104 fn new() -> Self;
2105 fn push(&mut self, val: T);
2106}
2107impl<T> Store<T> for Vec<T> {
2108 #[inline]
2109 fn new() -> Self {
2110 Self::new()
2111 }
2112 #[inline]
2113 fn push(&mut self, val: T) {
2114 self.push(val);
2115 }
2116}
2117impl<T, const N: usize> Store<T> for SmallVec<[T; N]> {
2118 #[inline]
2119 fn new() -> Self {
2120 Self::new()
2121 }
2122 #[inline]
2123 fn push(&mut self, val: T) {
2124 self.push(val);
2125 }
2126}
2127impl<'a, const N: usize> Store<UnescapedString<'a>>
2128 for (SmallVec<[Source<'a>; N]>, Option<UnescapedString<'a>>)
2129{
2130 #[inline]
2131 fn new() -> Self {
2132 (SmallVec::new(), None)
2133 }
2134 #[inline]
2135 fn push(&mut self, val: UnescapedString<'a>) {
2136 if let Some(val) = self.1.replace(val) {
2137 self.0.push(Source::Path(val));
2138 }
2139 }
2140}
2141
2142#[inline(always)]
2144#[cold]
2145fn cold_path() {}
2146
2147#[inline]
2149fn is_line_end(b: Option<&u8>) -> bool {
2150 matches!(b, Some(b'\n') | None)
2151}
2152
2153#[inline]
2154fn parse_options<'a, S: Store<Flag<'a>>>(s: &mut &[u8], start: &'a str, escape_byte: u8) -> S {
2155 let mut options = S::new();
2156 'outer: while let Some((&b'-', mut s_next)) = s.split_first() {
2157 loop {
2158 let Some((&b, s_next_next)) = s_next.split_first() else {
2159 break 'outer;
2160 };
2161 if b == b'-' {
2162 s_next = s_next_next;
2163 break;
2164 }
2165 if consume_line_continuation(&mut s_next, b, s_next_next, escape_byte) {
2166 continue;
2167 }
2168 break 'outer;
2169 }
2170 let flag_start = start.len() - s.len();
2171 *s = s_next;
2172 let name = collect_until::<{ WHITESPACE | POSSIBLE_LINE | EQ }>(s, start, escape_byte);
2173 let Some((&b'=', s_next)) = s.split_first() else {
2174 options.push(Flag { flag_start, name, value: None });
2175 consume_whitespaces(s, escape_byte);
2176 continue;
2177 };
2178 *s = s_next;
2179 let value = collect_non_whitespace(s, start, escape_byte);
2180 options.push(Flag { flag_start, name, value: Some(value) });
2181 consume_whitespaces(s, escape_byte);
2182 }
2183 options
2184}
2185
2186#[inline]
2187fn is_maybe_json(s: &[u8]) -> bool {
2188 s.first() == Some(&b'[') && s.get(1) != Some(&b'[')
2192}
2193fn parse_json_array<'a, S: Store<UnescapedString<'a>>>(
2194 s: &mut &[u8],
2195 start: &'a str,
2196 escape_byte: u8,
2197) -> Result<(S, Span), usize> {
2198 debug_assert!(is_maybe_json(s));
2199 let mut res = S::new();
2200 let array_start = start.len() - s.len();
2201 *s = &s[1..];
2202 consume_whitespaces(s, escape_byte);
2203 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2204 match b {
2205 b'"' => {
2206 *s = s_next;
2207 loop {
2208 let full_word_start = start.len() - s.len();
2209 let mut word_start = full_word_start;
2210 let mut buf = String::new();
2211 loop {
2212 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2213 if TABLE[b as usize] & (DOUBLE_QUOTE | POSSIBLE_ESCAPE | CONTROL) == 0 {
2214 *s = s_next;
2215 continue;
2216 }
2217 match b {
2218 b'"' => break,
2219 _ if b < 0x20 => return Err(array_start),
2220 _ => {}
2221 }
2222 let word_end = start.len() - s.len();
2223 if consume_line_continuation(s, b, s_next, escape_byte) {
2224 buf.push_str(&start[word_start..word_end]);
2226 word_start = start.len() - s.len();
2227 continue;
2228 }
2229 if b == b'\\' {
2230 let word_end = start.len() - s.len();
2232 buf.push_str(&start[word_start..word_end]);
2233 *s = s_next;
2234 if let Some((&b, s_next)) = s.split_first() {
2235 consume_line_continuation(s, b, s_next, escape_byte);
2236 }
2237 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2238 *s = s_next;
2239 let new = match b {
2240 b'"' | b'\\' | b'/' => b as char,
2241 b'b' => '\x08',
2242 b'f' => '\x0c',
2243 b'n' => '\n',
2244 b'r' => '\r',
2245 b't' => '\t',
2246 b'u' => parse_json_hex_escape(s, escape_byte, array_start)?,
2247 _ => return Err(array_start), };
2249 buf.push(new);
2250 word_start = start.len() - s.len();
2251 continue;
2252 }
2253 *s = s_next;
2254 }
2255 let word_end = start.len() - s.len();
2256 let value = if full_word_start == word_start {
2257 Cow::Borrowed(&start[word_start..word_end])
2259 } else {
2260 buf.push_str(&start[word_start..word_end]);
2261 Cow::Owned(buf)
2262 };
2263 res.push(UnescapedString { span: full_word_start..word_end, value });
2264 *s = &s[1..]; consume_whitespaces(s, escape_byte);
2266 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2267 match b {
2268 b',' => {
2269 *s = s_next;
2270 consume_whitespaces(s, escape_byte);
2271 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2272 if b == b'"' {
2273 *s = s_next;
2274 continue;
2275 }
2276 return Err(array_start);
2277 }
2278 b']' => {
2279 *s = s_next;
2280 break;
2281 }
2282 _ => return Err(array_start),
2283 }
2284 }
2285 }
2286 b']' => *s = s_next,
2287 _ => return Err(array_start),
2288 }
2289 let array_end = start.len() - s.len();
2290 consume_whitespaces(s, escape_byte);
2291 if !is_line_end(s.first()) {
2292 return Err(array_start);
2293 }
2294 Ok((res, array_start..array_end))
2295}
2296#[cold]
2298fn parse_json_hex_escape(
2299 s: &mut &[u8],
2300 escape_byte: u8,
2301 array_start: usize,
2302) -> Result<char, usize> {
2303 fn decode_hex_escape(s: &mut &[u8], escape_byte: u8, array_start: usize) -> Result<u16, usize> {
2304 if s.len() < 4 {
2305 return Err(array_start); }
2307
2308 let mut n = 0;
2309 for _ in 0..4 {
2310 if let Some((&b, s_next)) = s.split_first() {
2311 consume_line_continuation(s, b, s_next, escape_byte);
2312 }
2313 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2314 *s = s_next;
2315 match decode_hex_val(b) {
2316 None => return Err(array_start), Some(val) => {
2318 n = (n << 4) + val;
2319 }
2320 }
2321 }
2322 Ok(n)
2323 }
2324
2325 fn decode_hex_val(val: u8) -> Option<u16> {
2326 let n = HEX_DECODE_TABLE[val as usize] as u16;
2327 if n == u8::MAX as u16 { None } else { Some(n) }
2328 }
2329
2330 let c = match decode_hex_escape(s, escape_byte, array_start)? {
2331 _n @ 0xDC00..=0xDFFF => return Err(array_start), n1 @ 0xD800..=0xDBFF => {
2338 if let Some((&b, s_next)) = s.split_first() {
2339 consume_line_continuation(s, b, s_next, escape_byte);
2340 }
2341 let Some((&b'\\', s_next)) = s.split_first() else {
2342 return Err(array_start); };
2344 *s = s_next;
2345
2346 if let Some((&b, s_next)) = s.split_first() {
2347 consume_line_continuation(s, b, s_next, escape_byte);
2348 }
2349 let Some((&b'u', s_next)) = s.split_first() else {
2350 return Err(array_start); };
2352 *s = s_next;
2353
2354 let n2 = decode_hex_escape(s, escape_byte, array_start)?;
2355
2356 if n2 < 0xDC00 || n2 > 0xDFFF {
2357 return Err(array_start); }
2359
2360 let n = ((((n1 - 0xD800) as u32) << 10) | (n2 - 0xDC00) as u32) + 0x1_0000;
2361
2362 match char::from_u32(n) {
2363 Some(c) => c,
2364 None => return Err(array_start), }
2366 }
2367
2368 n => char::from_u32(n as u32).unwrap(),
2371 };
2372 Ok(c)
2373}
2374#[allow(clippy::needless_raw_string_hashes)]
2375#[test]
2376fn test_parse_json_array() {
2377 let t = r#"[]"#;
2379 let mut s = t.as_bytes();
2380 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[]);
2381 assert_eq!(s, b"");
2382 let t = r#"[ ]"#;
2383 let mut s = t.as_bytes();
2384 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[]);
2385 assert_eq!(s, b"");
2386 let t = r#"["abc"]"#;
2388 let mut s = t.as_bytes();
2389 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[UnescapedString {
2390 span: 2..5,
2391 value: "abc".into()
2392 }]);
2393 assert_eq!(s, b"");
2394 let t = "[\"ab\",\"c\" , \"de\" ] \n";
2396 let mut s = t.as_bytes();
2397 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[
2398 UnescapedString { span: 2..4, value: "ab".into() },
2399 UnescapedString { span: 7..8, value: "c".into() },
2400 UnescapedString { span: 14..16, value: "de".into() },
2401 ]);
2402 assert_eq!(s, b"\n");
2403 let t = "[\"a\\\"\\\\\\/\\b\\f\\n\\r\\tbc\\u12ab\\uAB12\\uD83C\\uDF95\\\n\\\\\nu\\\nD\\\n8\\\n3\\\nC\\\n\\\\\nu\\\nD\\\nF\\\n9\\\n5\\\n\"]";
2405 let mut s = t.as_bytes();
2406 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[UnescapedString {
2407 span: 2..83,
2408 value: "a\"\\/\x08\x0c\n\r\tbc\u{12ab}\u{AB12}\u{1F395}\u{1F395}".into()
2409 }]);
2410 assert_eq!(s, b"");
2411
2412 let t = r#"["]"#;
2414 let mut s = t.as_bytes();
2415 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2416 assert_eq!(s, br#""#);
2417 let t = r#"["a]"#;
2418 let mut s = t.as_bytes();
2419 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2420 assert_eq!(s, br#""#);
2421 let t = r#"['abc']"#;
2423 let mut s = t.as_bytes();
2424 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2425 assert_eq!(s, br#"'abc']"#);
2426 let t = r#"["abc",]"#;
2428 let mut s = t.as_bytes();
2429 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2430 assert_eq!(s, br#"]"#);
2431 let t = r#"["abc"d]"#;
2433 let mut s = t.as_bytes();
2434 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2435 assert_eq!(s, br#"d]"#);
2436 let t = r#"["abc"] c"#;
2438 let mut s = t.as_bytes();
2439 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2440 assert_eq!(s, br#"c"#);
2441 let t = "[\"ab\\c\"]";
2443 let mut s = t.as_bytes();
2444 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2445 assert_eq!(s, b"\"]");
2446 let t = "[\"\\uD83C\\uFFFF\"]";
2448 let mut s = t.as_bytes();
2449 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2450 assert_eq!(s, b"\"]");
2451 let t = "[\"a\nb\"]";
2453 let mut s = t.as_bytes();
2454 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2455 assert_eq!(s, b"\nb\"]");
2456 let t = "[\"a\x1Fb\"]";
2458 let mut s = t.as_bytes();
2459 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2460 assert_eq!(s, b"\x1Fb\"]");
2461 }
2463
2464#[inline]
2465fn collect_here_doc_delim<'a>(
2466 s: &mut &'a [u8],
2467 mut s_next: &'a [u8],
2468 start: &'a str,
2469) -> InternalResult<'static, Option<(Cow<'a, [u8]>, bool, bool)>> {
2470 let strip_tab = if let Some((&b'-', s_next_next)) = s_next.split_first() {
2471 s_next = s_next_next;
2472 true
2473 } else {
2474 false
2475 };
2476 let delim_start = start.len() - s_next.len();
2477 let mut current_start = delim_start;
2478 let mut expand = true;
2479 let mut quote = None;
2480 let mut buf = vec![];
2481 while let Some((&b, s_next_next)) = s_next.split_first() {
2482 match b {
2483 b'"' | b'\'' => {
2484 if let Some(q) = quote {
2485 if b == q {
2486 quote = None;
2487 let end = start.len() - s_next.len();
2488 buf.extend_from_slice(&start.as_bytes()[current_start..end]);
2489 current_start = start.len() - s_next_next.len();
2490 }
2491 } else {
2492 quote = Some(b);
2493 expand = false;
2494 let end = start.len() - s_next.len();
2495 buf.extend_from_slice(&start.as_bytes()[current_start..end]);
2496 current_start = start.len() - s_next_next.len();
2497 }
2498 }
2499 b'\\' => {
2500 let end = start.len() - s_next.len();
2502 buf.extend_from_slice(&start.as_bytes()[current_start..end]);
2503 current_start = start.len() - s_next_next.len();
2504 let Some((_, s_next_next)) = s_next_next.split_first() else {
2505 return Err(error::other("unterminated escape", start.len() - s_next.len()));
2506 };
2507 s_next = s_next_next;
2508 continue;
2509 }
2510 _ if quote.is_none() && TABLE[b as usize] & (WHITESPACE | POSSIBLE_LINE) != 0 => break,
2511 _ => {}
2512 }
2513 s_next = s_next_next;
2514 }
2515 if let Some(quote) = quote {
2516 return Err(error::expected_quote(quote, None, start.len() - s_next.len()));
2517 }
2518 let end = start.len() - s_next.len();
2519 let delim = if delim_start == current_start {
2520 Cow::Borrowed(&start.as_bytes()[delim_start..end])
2521 } else {
2522 buf.extend_from_slice(&start.as_bytes()[current_start..end]);
2523 Cow::Owned(buf)
2524 };
2525 if delim.is_empty() {
2526 return Ok(None);
2527 }
2528 *s = s_next;
2529 Ok(Some((delim, strip_tab, expand)))
2530}
2531#[inline]
2532fn collect_here_doc<'a>(
2533 s: &mut &[u8],
2534 start: &'a str,
2535 delim_cow: Cow<'a, [u8]>,
2536 strip_tab: bool,
2537) -> InternalResult<'a, (Cow<'a, str>, Span)> {
2538 let delim: &[u8] = &delim_cow;
2539 let here_doc_start = start.len() - s.len();
2540 let mut current_start = here_doc_start;
2541 let mut buf = String::new();
2542 let mut end;
2543 loop {
2544 if strip_tab {
2545 if let Some((&b'\t', mut s_next)) = s.split_first() {
2547 let end = start.len() - s.len();
2548 buf.push_str(&start[current_start..end]);
2549 while let Some((&b'\t', s_next_next)) = s_next.split_first() {
2550 s_next = s_next_next;
2551 }
2552 *s = s_next;
2553 current_start = start.len() - s.len();
2554 }
2555 }
2556 if s.len() < delim.len() {
2557 return Err(error::expected_here_doc_end(delim_cow, start.len() - s.len()));
2558 }
2559 if s.starts_with(delim) {
2560 let s_next = &s[delim.len()..];
2561 end = start.len() - s.len();
2562 if let Some((&b, s_next)) = s_next.split_first() {
2563 if consume_newline(b, s, s_next) {
2564 break;
2565 }
2566 } else {
2567 *s = s_next;
2568 break;
2569 }
2570 }
2571 consume_current_line_no_line_continuation(s);
2572 }
2573 let span = here_doc_start..end;
2574 if here_doc_start == current_start {
2575 Ok((Cow::Borrowed(&start[span.clone()]), span))
2576 } else {
2577 buf.push_str(&start[current_start..end]);
2578 Ok((Cow::Owned(buf), span))
2579 }
2580}
2581
2582#[inline]
2584fn collect_space_separated_consume_line<'a, S: Store<UnescapedString<'a>>>(
2585 s: &mut &'a [u8],
2586 start: &'a str,
2587 escape_byte: u8,
2588) -> S {
2589 let mut res = S::new();
2590 loop {
2591 let val = collect_non_whitespace(s, start, escape_byte);
2592 if !val.value.is_empty() {
2593 res.push(val);
2594 if consume_whitespaces(s, escape_byte) {
2595 continue;
2596 }
2597 }
2598 if let Some((&b, s_next)) = s.split_first() {
2599 let consumed = consume_newline(b, s, s_next);
2600 debug_assert!(consumed);
2601 }
2602 break;
2603 }
2604 res
2605}
2606#[inline]
2607fn collect_non_whitespace<'a>(
2608 s: &mut &[u8],
2609 start: &'a str,
2610 escape_byte: u8,
2611) -> UnescapedString<'a> {
2612 collect_until::<{ WHITESPACE | POSSIBLE_LINE }>(s, start, escape_byte)
2613}
2614#[inline]
2615fn collect_until<'a, const UNTIL_MASK: u8>(
2616 s: &mut &[u8],
2617 start: &'a str,
2618 escape_byte: u8,
2619) -> UnescapedString<'a> {
2620 let full_word_start = start.len() - s.len();
2621 let mut word_start = full_word_start;
2622 let mut buf = String::new();
2623 while let Some((&b, s_next)) = s.split_first() {
2624 let t = TABLE[b as usize];
2625 if t & (UNTIL_MASK | POSSIBLE_ESCAPE) != 0 {
2626 if t & UNTIL_MASK != 0 {
2627 break;
2628 }
2629 let word_end = start.len() - s.len();
2630 if consume_line_continuation(s, b, s_next, escape_byte) {
2631 buf.push_str(&start[word_start..word_end]);
2632 word_start = start.len() - s.len();
2633 continue;
2634 }
2635 }
2636 *s = s_next;
2637 }
2638 let word_end = start.len() - s.len();
2639 let value = if full_word_start == word_start {
2640 Cow::Borrowed(&start[word_start..word_end])
2642 } else {
2643 buf.push_str(&start[word_start..word_end]);
2644 Cow::Owned(buf)
2645 };
2646 UnescapedString { span: full_word_start..word_end, value }
2647}
2648#[inline]
2649fn collect_until_line_consume_newline<'a>(
2650 s: &mut &[u8],
2651 start: &'a str,
2652 escape_byte: u8,
2653) -> UnescapedString<'a> {
2654 let full_word_start = start.len() - s.len();
2655 let mut word_start = full_word_start;
2656 let mut buf = String::new();
2657 let word_end;
2658 loop {
2659 let Some((&b, s_next)) = s.split_first() else {
2660 word_end = start.len() - s.len();
2661 break;
2662 };
2663 let t = TABLE[b as usize];
2664 if t & (POSSIBLE_LINE | POSSIBLE_ESCAPE) != 0 {
2665 match b {
2666 b'\n' => {
2667 word_end = start.len() - s.len();
2668 *s = s_next;
2669 break;
2670 }
2671 b'\r' => {
2672 if s_next.first() == Some(&b'\n') {
2673 word_end = start.len() - s.len();
2674 *s = &s_next[1..];
2675 break;
2676 }
2677 }
2678 _ => {
2679 let word_end = start.len() - s.len();
2680 if consume_line_continuation(s, b, s_next, escape_byte) {
2681 buf.push_str(&start[word_start..word_end]);
2682 word_start = start.len() - s.len();
2683 continue;
2684 }
2685 }
2686 }
2687 }
2688 *s = s_next;
2689 }
2690 let value = if full_word_start == word_start {
2691 Cow::Borrowed(&start[word_start..word_end])
2693 } else {
2694 buf.push_str(&start[word_start..word_end]);
2695 Cow::Owned(buf)
2696 };
2697 UnescapedString { span: full_word_start..word_end, value }
2698}
2699
2700#[inline(always)]
2703fn consume_newline<'a>(b: u8, s: &mut &'a [u8], s_next: &'a [u8]) -> bool {
2704 match b {
2705 b'\n' => {
2706 *s = s_next;
2707 return true;
2708 }
2709 b'\r' => {
2710 if s_next.first() == Some(&b'\n') {
2711 *s = &s_next[1..];
2712 return true;
2713 }
2714 }
2715 _ => {}
2716 }
2717 false
2718}
2719
2720#[inline]
2722fn consume_line_continuation<'a>(
2723 s: &mut &'a [u8],
2724 b: u8,
2725 s_next: &'a [u8],
2726 escape_byte: u8,
2727) -> bool {
2728 #[inline]
2729 fn followup(s: &mut &[u8], _escape_byte: u8) {
2730 while let Some((&b, mut s_next)) = s.split_first() {
2731 let t = TABLE[b as usize];
2732 if t & (WHITESPACE | POSSIBLE_LINE | COMMENT) == 0 {
2733 break;
2734 }
2735 let mut b = b;
2736 if t & WHITESPACE != 0 {
2737 consume_whitespaces_no_line_continuation(&mut s_next);
2739 let Some((&b_, s_next_next)) = s_next.split_first() else { break };
2740 let t = TABLE[b_ as usize];
2741 if t & (COMMENT | POSSIBLE_LINE) == 0 {
2742 break;
2743 }
2744 b = b_;
2745 s_next = s_next_next;
2746 }
2747 *s = s_next;
2748 if b != b'\n' {
2751 consume_current_line_no_line_continuation(s);
2752 }
2753 }
2754 }
2755
2756 if b == escape_byte {
2757 cold_path();
2758 if let Some((&b, mut s_next)) = s_next.split_first() {
2759 if consume_newline(b, s, s_next) {
2760 followup(s, escape_byte);
2761 return true;
2762 }
2763 if TABLE[b as usize] & SPACE != 0 {
2766 cold_path();
2767 consume_whitespaces_no_line_continuation(&mut s_next);
2768 if let Some((&b, s_next)) = s_next.split_first() {
2769 if consume_newline(b, s, s_next) {
2770 followup(s, escape_byte);
2771 return true;
2772 }
2773 }
2774 }
2775 }
2776 }
2777 false
2778}
2779
2780#[inline]
2784fn consume_until_whitespaces_or_line_no_line_continuation(s: &mut &[u8]) -> bool {
2785 let start = *s;
2786 while let Some((&b, s_next)) = s.split_first() {
2787 if TABLE[b as usize] & (WHITESPACE | POSSIBLE_LINE) != 0 {
2788 break;
2789 }
2790 *s = s_next;
2791 }
2792 start.len() != s.len()
2793}
2794
2795#[inline]
2798fn consume_current_line_no_line_continuation(s: &mut &[u8]) {
2799 while let Some((&b, s_next)) = s.split_first() {
2800 if consume_newline(b, s, s_next) {
2801 break;
2802 }
2803 *s = s_next;
2804 }
2805}
2806#[inline]
2809fn consume_current_line(s: &mut &[u8], escape_byte: u8) {
2810 let mut has_whitespace_only = 0;
2811 while let Some((&b, s_next)) = s.split_first() {
2812 let t = TABLE[b as usize];
2813 if t & (POSSIBLE_LINE | COMMENT | POSSIBLE_ESCAPE) != 0 {
2814 if consume_newline(b, s, s_next) {
2815 break;
2816 }
2817 if has_whitespace_only != 0 && t & COMMENT != 0 {
2818 *s = s_next;
2819 consume_current_line_no_line_continuation(s);
2820 continue;
2821 }
2822 if consume_line_continuation(s, b, s_next, escape_byte) {
2823 has_whitespace_only = WHITESPACE;
2824 continue;
2825 }
2826 }
2827 has_whitespace_only &= t;
2828 *s = s_next;
2829 }
2830}
2831
2832#[inline]
2836fn consume_whitespaces_no_line_continuation(s: &mut &[u8]) -> bool {
2837 let start = *s;
2838 while let Some((&b, s_next)) = s.split_first() {
2839 if TABLE[b as usize] & WHITESPACE != 0 {
2840 *s = s_next;
2841 continue;
2842 }
2843 break;
2844 }
2845 start.len() != s.len()
2846}
2847#[inline]
2851fn consume_whitespaces(s: &mut &[u8], escape_byte: u8) -> bool {
2852 let mut has_space = false;
2853 while let Some((&b, s_next)) = s.split_first() {
2854 let t = TABLE[b as usize];
2855 if t & (WHITESPACE | POSSIBLE_ESCAPE) != 0 {
2856 if t & WHITESPACE != 0 {
2857 *s = s_next;
2858 has_space = true;
2859 continue;
2860 }
2861 if consume_line_continuation(s, b, s_next, escape_byte) {
2862 continue;
2863 }
2864 }
2865 break;
2866 }
2867 has_space
2868}
2869#[inline]
2873fn consume_whitespaces_or_is_empty_line(s: &mut &[u8], escape_byte: u8) -> bool {
2874 let mut has_space = false;
2875 loop {
2876 let Some((&b, s_next)) = s.split_first() else { return true };
2877 {
2878 let t = TABLE[b as usize];
2879 if t & (WHITESPACE | POSSIBLE_ESCAPE | POSSIBLE_LINE) != 0 {
2880 if t & WHITESPACE != 0 {
2881 *s = s_next;
2882 has_space = true;
2883 continue;
2884 }
2885 if b == b'\n' {
2886 return true;
2887 }
2888 if consume_line_continuation(s, b, s_next, escape_byte) {
2889 continue;
2890 }
2891 }
2892 break;
2893 }
2894 }
2895 has_space
2896}
2897#[inline]
2899fn consume_comments_and_whitespaces(s: &mut &[u8], escape_byte: u8) {
2900 while let Some((&b, s_next)) = s.split_first() {
2901 let t = TABLE[b as usize];
2902 if t & (WHITESPACE | POSSIBLE_LINE | COMMENT | POSSIBLE_ESCAPE) != 0 {
2903 if t & (WHITESPACE | POSSIBLE_LINE) != 0 {
2904 *s = s_next;
2905 continue;
2906 }
2907 if t & COMMENT != 0 {
2908 *s = s_next;
2909 consume_current_line_no_line_continuation(s);
2910 continue;
2911 }
2912 if consume_line_continuation(s, b, s_next, escape_byte) {
2913 continue;
2914 }
2915 }
2916 break;
2917 }
2918}
2919
2920#[inline]
2921#[track_caller]
2922fn trim_end(text: &str, start: usize, mut end: usize) -> &str {
2923 while start < end {
2924 let next_end = end - 1;
2925 if let Some(&b) = text.as_bytes().get(next_end) {
2926 if TABLE[b as usize] & (WHITESPACE | POSSIBLE_LINE) != 0 {
2927 end = next_end;
2928 continue;
2929 }
2930 }
2931 break;
2932 }
2933 &text[start..end]
2934}
2935
2936#[inline(always)]
2937fn token(s: &mut &[u8], token: &'static [u8]) -> bool {
2938 let matched = starts_with_ignore_ascii_case(s, token);
2939 if matched {
2940 *s = &s[token.len()..];
2941 true
2942 } else {
2943 false
2944 }
2945}
2946#[cold]
2947fn token_slow(s: &mut &[u8], mut token: &'static [u8], escape_byte: u8) -> bool {
2948 debug_assert!(!token.is_empty() && token.iter().all(|&n| n & TO_UPPER8 == n));
2949 if s.len() < token.len() {
2950 return false;
2951 }
2952 let mut tmp = *s;
2953 while let Some((&b, tmp_next)) = tmp.split_first() {
2954 if b & TO_UPPER8 == token[0] {
2955 tmp = tmp_next;
2956 token = &token[1..];
2957 if token.is_empty() {
2958 *s = tmp;
2959 return true;
2960 }
2961 continue;
2962 }
2963 if consume_line_continuation(&mut tmp, b, tmp_next, escape_byte) {
2964 continue;
2965 }
2966 break;
2967 }
2968 false
2969}
2970
2971const TO_UPPER8: u8 = 0xDF;
2972const TO_UPPER64: u64 = 0xDFDF_DFDF_DFDF_DFDF;
2973
2974#[inline(always)] fn starts_with_ignore_ascii_case(mut s: &[u8], mut needle: &'static [u8]) -> bool {
2976 debug_assert!(!needle.is_empty() && needle.iter().all(|&n| n & TO_UPPER8 == n));
2977 if s.len() < needle.len() {
2978 return false;
2979 }
2980 if needle.len() == 1 {
2981 return needle[0] == s[0] & TO_UPPER8;
2982 }
2983 if needle.len() >= 8 {
2984 loop {
2985 if u64::from_ne_bytes(needle[..8].try_into().unwrap())
2986 != u64::from_ne_bytes(s[..8].try_into().unwrap()) & TO_UPPER64
2987 {
2988 return false;
2989 }
2990 needle = &needle[8..];
2991 s = &s[8..];
2992 if needle.len() < 8 {
2993 if needle.is_empty() {
2994 return true;
2995 }
2996 break;
2997 }
2998 }
2999 }
3000 let s = {
3001 let mut buf = [0; 8];
3002 buf[..needle.len()].copy_from_slice(&s[..needle.len()]);
3003 u64::from_ne_bytes(buf)
3004 };
3005 let needle = {
3006 let mut buf = [0; 8];
3007 buf[..needle.len()].copy_from_slice(needle);
3008 u64::from_ne_bytes(buf)
3009 };
3010 needle == s & TO_UPPER64
3011}
3012#[test]
3013fn test_starts_with_ignore_ascii_case() {
3014 assert!(starts_with_ignore_ascii_case(b"ABC", b"ABC"));
3015 assert!(starts_with_ignore_ascii_case(b"abc", b"ABC"));
3016 assert!(starts_with_ignore_ascii_case(b"AbC", b"ABC"));
3017 assert!(!starts_with_ignore_ascii_case(b"ABB", b"ABC"));
3018 assert!(starts_with_ignore_ascii_case(b"ABCDEFGH", b"ABCDEFGH"));
3019 assert!(starts_with_ignore_ascii_case(b"abcdefgh", b"ABCDEFGH"));
3020 assert!(starts_with_ignore_ascii_case(b"AbCdEfGh", b"ABCDEFGH"));
3021 assert!(!starts_with_ignore_ascii_case(b"ABCDEFGc", b"ABCDEFGH"));
3022 assert!(starts_with_ignore_ascii_case(
3023 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
3024 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3025 ));
3026 assert!(starts_with_ignore_ascii_case(
3027 b"abcdefghijklmnopqrstuvwxyz",
3028 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3029 ));
3030 assert!(starts_with_ignore_ascii_case(
3031 b"aBcDeFgHiJkLmNoPqRsTuVwXyZ",
3032 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3033 ));
3034 assert!(!starts_with_ignore_ascii_case(
3035 b"aBcDeFgHiJkLmNoPqRsTuVwXyc",
3036 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3037 ));
3038}