1use std::collections::BTreeSet;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::process::{Command, Stdio};
5use std::time::Instant;
6
7use anyhow::{Context, Result};
8use tempfile::{Builder, TempDir};
9
10use super::{
11 ExecutionOutcome, ExecutionPayload, LanguageEngine, LanguageSession, cache_store,
12 execution_timeout, hash_source, run_version_command, try_cached_execution, wait_with_timeout,
13};
14
15pub struct GoEngine {
16 executable: Option<PathBuf>,
17}
18
19impl Default for GoEngine {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl GoEngine {
26 pub fn new() -> Self {
27 Self {
28 executable: resolve_go_binary(),
29 }
30 }
31
32 fn ensure_executable(&self) -> Result<&Path> {
33 self.executable.as_deref().ok_or_else(|| {
34 anyhow::anyhow!(
35 "Go support requires the `go` executable. Install it from https://go.dev/dl/ and ensure it is on your PATH."
36 )
37 })
38 }
39
40 fn write_temp_source(&self, code: &str) -> Result<(tempfile::TempDir, PathBuf)> {
41 let dir = Builder::new()
42 .prefix("run-go")
43 .tempdir()
44 .context("failed to create temporary directory for go source")?;
45 let path = dir.path().join("main.go");
46 let mut contents = code.to_string();
47 if !contents.ends_with('\n') {
48 contents.push('\n');
49 }
50 std::fs::write(&path, contents).with_context(|| {
51 format!("failed to write temporary Go source to {}", path.display())
52 })?;
53 Ok((dir, path))
54 }
55
56 fn execute_with_path(
57 &self,
58 binary: &Path,
59 source: &Path,
60 args: &[String],
61 ) -> Result<std::process::Output> {
62 let mut cmd = Command::new(binary);
63 cmd.arg("run")
64 .stdout(Stdio::piped())
65 .stderr(Stdio::piped())
66 .env("GO111MODULE", "off");
67 cmd.stdin(Stdio::inherit());
68
69 if let Some(parent) = source.parent() {
70 cmd.current_dir(parent);
71 if let Some(file_name) = source.file_name() {
72 cmd.arg(file_name);
73 } else {
74 cmd.arg(source);
75 }
76 cmd.args(args);
77 } else {
78 cmd.arg(source).args(args);
79 }
80 let child = cmd.spawn().with_context(|| {
81 format!(
82 "failed to invoke {} to run {}",
83 binary.display(),
84 source.display()
85 )
86 })?;
87 wait_with_timeout(child, execution_timeout())
88 }
89}
90
91impl LanguageEngine for GoEngine {
92 fn id(&self) -> &'static str {
93 "go"
94 }
95
96 fn display_name(&self) -> &'static str {
97 "Go"
98 }
99
100 fn aliases(&self) -> &[&'static str] {
101 &["golang"]
102 }
103
104 fn supports_sessions(&self) -> bool {
105 true
106 }
107
108 fn validate(&self) -> Result<()> {
109 let binary = self.ensure_executable()?;
110 let mut cmd = Command::new(binary);
111 cmd.arg("version")
112 .stdout(Stdio::null())
113 .stderr(Stdio::null());
114 cmd.status()
115 .with_context(|| format!("failed to invoke {}", binary.display()))?
116 .success()
117 .then_some(())
118 .ok_or_else(|| anyhow::anyhow!("{} is not executable", binary.display()))
119 }
120
121 fn toolchain_version(&self) -> Result<Option<String>> {
122 let binary = self.ensure_executable()?;
123 let mut cmd = Command::new(binary);
124 cmd.arg("version");
125 let context = format!("{}", binary.display());
126 run_version_command(cmd, &context)
127 }
128
129 fn execute(&self, payload: &ExecutionPayload) -> Result<ExecutionOutcome> {
130 let args = payload.args();
132
133 if let Some(code) = match payload {
134 ExecutionPayload::Inline { code, .. } | ExecutionPayload::Stdin { code, .. } => {
135 Some(code.as_str())
136 }
137 _ => None,
138 } {
139 let src_hash = hash_source(code);
140 if let Some(output) = try_cached_execution(src_hash) {
141 let start = Instant::now();
142 return Ok(ExecutionOutcome {
143 language: self.id().to_string(),
144 exit_code: output.status.code(),
145 stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
146 stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
147 duration: start.elapsed(),
148 });
149 }
150 }
151
152 let binary = self.ensure_executable()?;
153 let start = Instant::now();
154
155 let (temp_dir, source_path, cache_key) = match payload {
156 ExecutionPayload::Inline { code, .. } => {
157 let h = hash_source(code);
158 let (dir, path) = self.write_temp_source(code)?;
159 (Some(dir), path, Some(h))
160 }
161 ExecutionPayload::Stdin { code, .. } => {
162 let h = hash_source(code);
163 let (dir, path) = self.write_temp_source(code)?;
164 (Some(dir), path, Some(h))
165 }
166 ExecutionPayload::File { path, .. } => (None, path.clone(), None),
167 };
168
169 if let Some(h) = cache_key {
171 let dir = source_path.parent().unwrap_or(std::path::Path::new("."));
172 let bin_path = dir.join("run_go_binary");
173 let mut build_cmd = Command::new(binary);
174 build_cmd
175 .arg("build")
176 .arg("-o")
177 .arg(&bin_path)
178 .env("GO111MODULE", "off")
179 .stdout(Stdio::piped())
180 .stderr(Stdio::piped());
181 if let Some(file_name) = source_path.file_name() {
182 build_cmd.current_dir(dir).arg(file_name);
183 } else {
184 build_cmd.arg(&source_path);
185 }
186
187 let build_output = build_cmd.output().with_context(|| {
188 format!("failed to invoke {} to build Go source", binary.display())
189 })?;
190
191 if !build_output.status.success() {
192 return Ok(ExecutionOutcome {
193 language: self.id().to_string(),
194 exit_code: build_output.status.code(),
195 stdout: String::from_utf8_lossy(&build_output.stdout).into_owned(),
196 stderr: String::from_utf8_lossy(&build_output.stderr).into_owned(),
197 duration: start.elapsed(),
198 });
199 }
200
201 cache_store(h, &bin_path);
202
203 let mut run_cmd = Command::new(&bin_path);
204 run_cmd
205 .args(args)
206 .stdout(Stdio::piped())
207 .stderr(Stdio::piped())
208 .stdin(Stdio::inherit());
209 let child = run_cmd.spawn().with_context(|| {
210 format!(
211 "failed to execute compiled Go binary {}",
212 bin_path.display()
213 )
214 })?;
215 let output = wait_with_timeout(child, execution_timeout())?;
216
217 drop(temp_dir);
218 return Ok(ExecutionOutcome {
219 language: self.id().to_string(),
220 exit_code: output.status.code(),
221 stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
222 stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
223 duration: start.elapsed(),
224 });
225 }
226
227 let output = self.execute_with_path(binary, &source_path, args)?;
228 drop(temp_dir);
229
230 Ok(ExecutionOutcome {
231 language: self.id().to_string(),
232 exit_code: output.status.code(),
233 stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
234 stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
235 duration: start.elapsed(),
236 })
237 }
238
239 fn start_session(&self) -> Result<Box<dyn LanguageSession>> {
240 let binary = self.ensure_executable()?.to_path_buf();
241 let session = GoSession::new(binary)?;
242 Ok(Box::new(session))
243 }
244}
245
246fn resolve_go_binary() -> Option<PathBuf> {
247 which::which("go").ok()
248}
249
250fn import_is_used_in_code(import: &str, code: &str) -> bool {
251 let import_trimmed = import.trim().trim_matches('"');
252 let package_name = import_trimmed.rsplit('/').next().unwrap_or(import_trimmed);
253 let pattern = format!("{}.", package_name);
254 code.contains(&pattern)
255}
256
257const SESSION_MAIN_FILE: &str = "main.go";
258
259struct GoSession {
260 go_binary: PathBuf,
261 workspace: TempDir,
262 imports: BTreeSet<String>,
263 items: Vec<String>,
264 statements: Vec<String>,
265 last_stdout: String,
266 last_stderr: String,
267}
268
269enum GoSnippetKind {
270 Import(Option<String>),
271 Item,
272 Statement,
273}
274
275impl GoSession {
276 fn new(go_binary: PathBuf) -> Result<Self> {
277 let workspace = TempDir::new().context("failed to create Go session workspace")?;
278 let mut imports = BTreeSet::new();
279 imports.insert("\"fmt\"".to_string());
280 let session = Self {
281 go_binary,
282 workspace,
283 imports,
284 items: Vec::new(),
285 statements: Vec::new(),
286 last_stdout: String::new(),
287 last_stderr: String::new(),
288 };
289 session.persist_source()?;
290 Ok(session)
291 }
292
293 fn language_id(&self) -> &str {
294 "go"
295 }
296
297 fn source_path(&self) -> PathBuf {
298 self.workspace.path().join(SESSION_MAIN_FILE)
299 }
300
301 fn persist_source(&self) -> Result<()> {
302 let source = self.render_source();
303 fs::write(self.source_path(), source)
304 .with_context(|| "failed to write Go session source".to_string())
305 }
306
307 fn render_source(&self) -> String {
308 let mut source = String::from("package main\n\n");
309
310 if !self.imports.is_empty() {
311 source.push_str("import (\n");
312 for import in &self.imports {
313 source.push('\t');
314 source.push_str(import);
315 source.push('\n');
316 }
317 source.push_str(")\n\n");
318 }
319
320 source.push_str(concat!(
321 "func __print(value interface{}) {\n",
322 "\tif s, ok := value.(string); ok {\n",
323 "\t\tfmt.Println(s)\n",
324 "\t\treturn\n",
325 "\t}\n",
326 "\tfmt.Printf(\"%#v\\n\", value)\n",
327 "}\n\n",
328 ));
329
330 for item in &self.items {
331 source.push_str(item);
332 if !item.ends_with('\n') {
333 source.push('\n');
334 }
335 source.push('\n');
336 }
337
338 source.push_str("func main() {\n");
339 if self.statements.is_empty() {
340 source.push_str("\t// session body\n");
341 } else {
342 for snippet in &self.statements {
343 for line in snippet.lines() {
344 source.push('\t');
345 source.push_str(line);
346 source.push('\n');
347 }
348 }
349 }
350 source.push_str("}\n");
351
352 source
353 }
354
355 fn run_program(&self) -> Result<std::process::Output> {
356 let mut cmd = Command::new(&self.go_binary);
357 cmd.arg("run")
358 .arg(SESSION_MAIN_FILE)
359 .env("GO111MODULE", "off")
360 .stdout(Stdio::piped())
361 .stderr(Stdio::piped())
362 .current_dir(self.workspace.path());
363 cmd.output().with_context(|| {
364 format!(
365 "failed to execute {} for Go session",
366 self.go_binary.display()
367 )
368 })
369 }
370
371 fn run_standalone_program(&self, code: &str) -> Result<ExecutionOutcome> {
372 let start = Instant::now();
373 let standalone_path = self.workspace.path().join("standalone.go");
374
375 let source = if has_package_declaration(code) {
376 let mut snippet = code.to_string();
377 if !snippet.ends_with('\n') {
378 snippet.push('\n');
379 }
380 snippet
381 } else {
382 let mut source = String::from("package main\n\n");
383
384 let used_imports: Vec<_> = self
385 .imports
386 .iter()
387 .filter(|import| import_is_used_in_code(import, code))
388 .cloned()
389 .collect();
390
391 if !used_imports.is_empty() {
392 source.push_str("import (\n");
393 for import in &used_imports {
394 source.push('\t');
395 source.push_str(import);
396 source.push('\n');
397 }
398 source.push_str(")\n\n");
399 }
400
401 source.push_str(code);
402 if !code.ends_with('\n') {
403 source.push('\n');
404 }
405 source
406 };
407
408 fs::write(&standalone_path, source)
409 .with_context(|| "failed to write Go standalone source".to_string())?;
410
411 let mut cmd = Command::new(&self.go_binary);
412 cmd.arg("run")
413 .arg("standalone.go")
414 .env("GO111MODULE", "off")
415 .stdout(Stdio::piped())
416 .stderr(Stdio::piped())
417 .current_dir(self.workspace.path());
418
419 let output = cmd.output().with_context(|| {
420 format!(
421 "failed to execute {} for Go standalone program",
422 self.go_binary.display()
423 )
424 })?;
425
426 let outcome = ExecutionOutcome {
427 language: self.language_id().to_string(),
428 exit_code: output.status.code(),
429 stdout: Self::normalize_output(&output.stdout),
430 stderr: Self::normalize_output(&output.stderr),
431 duration: start.elapsed(),
432 };
433
434 let _ = fs::remove_file(&standalone_path);
435
436 Ok(outcome)
437 }
438
439 fn add_import(&mut self, spec: &str) -> GoSnippetKind {
440 let added = self.imports.insert(spec.to_string());
441 if added {
442 GoSnippetKind::Import(Some(spec.to_string()))
443 } else {
444 GoSnippetKind::Import(None)
445 }
446 }
447
448 fn add_item(&mut self, code: &str) -> GoSnippetKind {
449 let mut snippet = code.to_string();
450 if !snippet.ends_with('\n') {
451 snippet.push('\n');
452 }
453 self.items.push(snippet);
454 GoSnippetKind::Item
455 }
456
457 fn add_statement(&mut self, code: &str) -> GoSnippetKind {
458 let snippet = sanitize_statement(code);
459 self.statements.push(snippet);
460 GoSnippetKind::Statement
461 }
462
463 fn add_expression(&mut self, code: &str) -> GoSnippetKind {
464 let wrapped = wrap_expression(code);
465 self.statements.push(wrapped);
466 GoSnippetKind::Statement
467 }
468
469 fn rollback(&mut self, kind: GoSnippetKind) -> Result<()> {
470 match kind {
471 GoSnippetKind::Import(Some(spec)) => {
472 self.imports.remove(&spec);
473 }
474 GoSnippetKind::Import(None) => {}
475 GoSnippetKind::Item => {
476 self.items.pop();
477 }
478 GoSnippetKind::Statement => {
479 self.statements.pop();
480 }
481 }
482 self.persist_source()
483 }
484
485 fn normalize_output(bytes: &[u8]) -> String {
486 String::from_utf8_lossy(bytes)
487 .replace("\r\n", "\n")
488 .replace('\r', "")
489 }
490
491 fn diff_outputs(previous: &str, current: &str) -> String {
492 if let Some(suffix) = current.strip_prefix(previous) {
493 suffix.to_string()
494 } else {
495 current.to_string()
496 }
497 }
498
499 fn run_insertion(&mut self, kind: GoSnippetKind) -> Result<(ExecutionOutcome, bool)> {
500 match kind {
501 GoSnippetKind::Import(None) => Ok((
502 ExecutionOutcome {
503 language: self.language_id().to_string(),
504 exit_code: None,
505 stdout: String::new(),
506 stderr: String::new(),
507 duration: Default::default(),
508 },
509 true,
510 )),
511 other_kind => {
512 self.persist_source()?;
513 let start = Instant::now();
514 let output = self.run_program()?;
515
516 let stdout_full = Self::normalize_output(&output.stdout);
517 let stderr_full = Self::normalize_output(&output.stderr);
518
519 let stdout = Self::diff_outputs(&self.last_stdout, &stdout_full);
520 let stderr = Self::diff_outputs(&self.last_stderr, &stderr_full);
521 let duration = start.elapsed();
522
523 if output.status.success() {
524 self.last_stdout = stdout_full;
525 self.last_stderr = stderr_full;
526 let outcome = ExecutionOutcome {
527 language: self.language_id().to_string(),
528 exit_code: output.status.code(),
529 stdout,
530 stderr,
531 duration,
532 };
533 return Ok((outcome, true));
534 }
535
536 if matches!(&other_kind, GoSnippetKind::Import(Some(_)))
537 && stderr_full.contains("imported and not used")
538 {
539 return Ok((
540 ExecutionOutcome {
541 language: self.language_id().to_string(),
542 exit_code: None,
543 stdout: String::new(),
544 stderr: String::new(),
545 duration,
546 },
547 true,
548 ));
549 }
550
551 self.rollback(other_kind)?;
552 let outcome = ExecutionOutcome {
553 language: self.language_id().to_string(),
554 exit_code: output.status.code(),
555 stdout,
556 stderr,
557 duration,
558 };
559 Ok((outcome, false))
560 }
561 }
562 }
563
564 fn run_import(&mut self, spec: &str) -> Result<(ExecutionOutcome, bool)> {
565 let kind = self.add_import(spec);
566 self.run_insertion(kind)
567 }
568
569 fn run_item(&mut self, code: &str) -> Result<(ExecutionOutcome, bool)> {
570 let kind = self.add_item(code);
571 self.run_insertion(kind)
572 }
573
574 fn run_statement(&mut self, code: &str) -> Result<(ExecutionOutcome, bool)> {
575 let kind = self.add_statement(code);
576 self.run_insertion(kind)
577 }
578
579 fn run_expression(&mut self, code: &str) -> Result<(ExecutionOutcome, bool)> {
580 let kind = self.add_expression(code);
581 self.run_insertion(kind)
582 }
583}
584
585impl LanguageSession for GoSession {
586 fn language_id(&self) -> &str {
587 GoSession::language_id(self)
588 }
589
590 fn eval(&mut self, code: &str) -> Result<ExecutionOutcome> {
591 let trimmed = code.trim();
592 if trimmed.is_empty() {
593 return Ok(ExecutionOutcome {
594 language: self.language_id().to_string(),
595 exit_code: None,
596 stdout: String::new(),
597 stderr: String::new(),
598 duration: Instant::now().elapsed(),
599 });
600 }
601
602 if trimmed.starts_with("package ") && !trimmed.contains('\n') {
603 return Ok(ExecutionOutcome {
604 language: self.language_id().to_string(),
605 exit_code: None,
606 stdout: String::new(),
607 stderr: String::new(),
608 duration: Instant::now().elapsed(),
609 });
610 }
611
612 if contains_main_definition(trimmed) {
613 let outcome = self.run_standalone_program(code)?;
614 return Ok(outcome);
615 }
616
617 if let Some(import) = parse_import_spec(trimmed) {
618 let (outcome, _) = self.run_import(&import)?;
619 return Ok(outcome);
620 }
621
622 if is_item_snippet(trimmed) {
623 let (outcome, _) = self.run_item(code)?;
624 return Ok(outcome);
625 }
626
627 if should_treat_as_expression(trimmed) {
628 let (outcome, success) = self.run_expression(trimmed)?;
629 if success {
630 return Ok(outcome);
631 }
632 }
633
634 let (outcome, _) = self.run_statement(code)?;
635 Ok(outcome)
636 }
637
638 fn shutdown(&mut self) -> Result<()> {
639 Ok(())
640 }
641}
642
643fn parse_import_spec(code: &str) -> Option<String> {
644 let trimmed = code.trim_start();
645 if !trimmed.starts_with("import ") {
646 return None;
647 }
648 let rest = trimmed.trim_start_matches("import").trim();
649 if rest.is_empty() || rest.starts_with('(') {
650 return None;
651 }
652 Some(rest.to_string())
653}
654
655fn is_item_snippet(code: &str) -> bool {
656 let trimmed = code.trim_start();
657 if trimmed.is_empty() {
658 return false;
659 }
660 const KEYWORDS: [&str; 6] = ["type", "const", "var", "func", "package", "import"];
661 KEYWORDS.iter().any(|kw| {
662 trimmed.starts_with(kw)
663 && trimmed
664 .chars()
665 .nth(kw.len())
666 .map(|ch| ch.is_whitespace() || ch == '(')
667 .unwrap_or(true)
668 })
669}
670
671fn should_treat_as_expression(code: &str) -> bool {
672 let trimmed = code.trim();
673 if trimmed.is_empty() {
674 return false;
675 }
676 if trimmed.contains('\n') {
677 return false;
678 }
679 if trimmed.ends_with(';') {
680 return false;
681 }
682 if trimmed.contains(":=") {
683 return false;
684 }
685 if trimmed.contains('=') && !trimmed.contains("==") {
686 return false;
687 }
688 const RESERVED: [&str; 8] = [
689 "if ", "for ", "switch ", "select ", "return ", "go ", "defer ", "var ",
690 ];
691 if RESERVED.iter().any(|kw| trimmed.starts_with(kw)) {
692 return false;
693 }
694 true
695}
696
697fn wrap_expression(code: &str) -> String {
698 format!("__print({});\n", code)
699}
700
701fn sanitize_statement(code: &str) -> String {
702 let mut snippet = code.to_string();
703 if !snippet.ends_with('\n') {
704 snippet.push('\n');
705 }
706
707 let trimmed = code.trim();
708 if trimmed.is_empty() || trimmed.contains('\n') {
709 return snippet;
710 }
711
712 let mut identifiers: Vec<String> = Vec::new();
713
714 if let Some(idx) = trimmed.find(" :=") {
715 let lhs = &trimmed[..idx];
716 identifiers = lhs
717 .split(',')
718 .map(|part| part.trim())
719 .filter(|name| !name.is_empty() && *name != "_")
720 .map(|name| name.to_string())
721 .collect();
722 } else if let Some(idx) = trimmed.find(':') {
723 if trimmed[idx..].starts_with(":=") {
724 let lhs = &trimmed[..idx];
725 identifiers = lhs
726 .split(',')
727 .map(|part| part.trim())
728 .filter(|name| !name.is_empty() && *name != "_")
729 .map(|name| name.to_string())
730 .collect();
731 }
732 } else if let Some(stripped) = trimmed.strip_prefix("var ") {
733 let rest = stripped.trim();
734 if !rest.starts_with('(') {
735 let names_part = rest.split('=').next().unwrap_or(rest).trim();
736 identifiers = names_part
737 .split(',')
738 .filter_map(|segment| {
739 let token = segment.split_whitespace().next().unwrap_or("");
740 if token.is_empty() || token == "_" {
741 None
742 } else {
743 Some(token.to_string())
744 }
745 })
746 .collect();
747 }
748 } else if let Some(stripped) = trimmed.strip_prefix("const ") {
749 let rest = stripped.trim();
750 if !rest.starts_with('(') {
751 let names_part = rest.split('=').next().unwrap_or(rest).trim();
752 identifiers = names_part
753 .split(',')
754 .filter_map(|segment| {
755 let token = segment.split_whitespace().next().unwrap_or("");
756 if token.is_empty() || token == "_" {
757 None
758 } else {
759 Some(token.to_string())
760 }
761 })
762 .collect();
763 }
764 }
765
766 if identifiers.is_empty() {
767 return snippet;
768 }
769
770 for name in identifiers {
771 snippet.push_str("_ = ");
772 snippet.push_str(&name);
773 snippet.push('\n');
774 }
775
776 snippet
777}
778
779fn has_package_declaration(code: &str) -> bool {
780 code.lines()
781 .any(|line| line.trim_start().starts_with("package "))
782}
783
784fn contains_main_definition(code: &str) -> bool {
785 let bytes = code.as_bytes();
786 let len = bytes.len();
787 let mut i = 0;
788 let mut in_line_comment = false;
789 let mut in_block_comment = false;
790 let mut in_string = false;
791 let mut string_delim = b'"';
792 let mut in_char = false;
793
794 while i < len {
795 let b = bytes[i];
796
797 if in_line_comment {
798 if b == b'\n' {
799 in_line_comment = false;
800 }
801 i += 1;
802 continue;
803 }
804
805 if in_block_comment {
806 if b == b'*' && i + 1 < len && bytes[i + 1] == b'/' {
807 in_block_comment = false;
808 i += 2;
809 continue;
810 }
811 i += 1;
812 continue;
813 }
814
815 if in_string {
816 if b == b'\\' {
817 i = (i + 2).min(len);
818 continue;
819 }
820 if b == string_delim {
821 in_string = false;
822 }
823 i += 1;
824 continue;
825 }
826
827 if in_char {
828 if b == b'\\' {
829 i = (i + 2).min(len);
830 continue;
831 }
832 if b == b'\'' {
833 in_char = false;
834 }
835 i += 1;
836 continue;
837 }
838
839 match b {
840 b'/' if i + 1 < len && bytes[i + 1] == b'/' => {
841 in_line_comment = true;
842 i += 2;
843 continue;
844 }
845 b'/' if i + 1 < len && bytes[i + 1] == b'*' => {
846 in_block_comment = true;
847 i += 2;
848 continue;
849 }
850 b'"' | b'`' => {
851 in_string = true;
852 string_delim = b;
853 i += 1;
854 continue;
855 }
856 b'\'' => {
857 in_char = true;
858 i += 1;
859 continue;
860 }
861 b'f' if i + 4 <= len && &bytes[i..i + 4] == b"func" => {
862 if i > 0 {
863 let prev = bytes[i - 1];
864 if prev.is_ascii_alphanumeric() || prev == b'_' {
865 i += 1;
866 continue;
867 }
868 }
869
870 let mut j = i + 4;
871 while j < len && bytes[j].is_ascii_whitespace() {
872 j += 1;
873 }
874
875 if j + 4 > len || &bytes[j..j + 4] != b"main" {
876 i += 1;
877 continue;
878 }
879
880 let after = j + 4;
881 if after < len {
882 let ch = bytes[after];
883 if ch.is_ascii_alphanumeric() || ch == b'_' {
884 i += 1;
885 continue;
886 }
887 }
888
889 let mut k = after;
890 while k < len && bytes[k].is_ascii_whitespace() {
891 k += 1;
892 }
893 if k < len && bytes[k] == b'(' {
894 return true;
895 }
896 }
897 _ => {}
898 }
899
900 i += 1;
901 }
902
903 false
904}