Skip to main content

run/engine/
go.rs

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        // Try cache for inline/stdin payloads
131        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        // For cacheable code, use go build + run instead of go run
170        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}