• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / py-slang / 23358181246

20 Mar 2026 06:58PM UTC coverage: 42.048% (+7.1%) from 34.925%
23358181246

Pull #104

github

web-flow
Merge 77ac9acf7 into 8feccc8f9
Pull Request #104: feat: replace hand-written parser with Nearley grammar

402 of 1286 branches covered (31.26%)

Branch coverage included in aggregate %.

543 of 618 new or added lines in 22 files covered. (87.86%)

36 existing lines in 2 files now uncovered.

1409 of 3021 relevant lines covered (46.64%)

52.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.84
/src/parser/parser-adapter.ts
1
/**
2
 * Adapter for Nearley parser to match the interface of the old hand-written parser
3
 */
4

5
import nearley from "nearley";
3✔
6
import pythonLexer from "./lexer";
3✔
7
import grammar from "./python-grammar";
3✔
8
import { StmtNS } from "../ast-types";
9

10
/**
11
 * NearleyParser - Drop-in replacement for the old Parser class
12
 */
13
export class NearleyParser {
3✔
14
  private readonly source: string;
15

16
  constructor(source: string, _tokens?: unknown[]) {
17
    // Note: Nearley doesn't use pre-tokenized input in the same way
18
    // The lexer is integrated into the parser
19
    this.source = source;
269✔
20
  }
21

22
  /**
23
   * Parse the source code and return the AST
24
   */
25
  parse(): StmtNS.FileInput {
26
    // Create a new parser instance with our grammar
27
    const parser = new nearley.Parser(
269✔
28
      nearley.Grammar.fromCompiled({
29
        ...(grammar as unknown as nearley.CompiledRules),
30
        Lexer: pythonLexer as nearley.Lexer,
31
      }),
32
    );
33

34
    try {
269✔
35
      // Feed the source code to the parser
36
      parser.feed(this.source);
269✔
37

38
      // Check if we got results
39
      if (parser.results.length === 0) {
265✔
40
        throw new Error("Unexpected end of input - no parse results");
1✔
41
      }
42

43
      // Ambiguous grammar is a bug — fail loudly so tests catch it
44
      if (parser.results.length > 1) {
264!
NEW
45
        throw new Error(`Ambiguous grammar: ${parser.results.length} possible parses for input`);
×
46
      }
47

48
      // Return the first (or only) parse result
49
      return parser.results[0];
264✔
50
    } catch (error: unknown) {
51
      // Transform Nearley errors to match our error format
52
      const err = error as {
5✔
53
        token?: { value?: string; type?: string; line?: number; col?: number };
54
      };
55
      if (err.token) {
5✔
56
        const token = err.token;
4✔
57
        const line = token.line || 0;
4!
58
        const col = token.col || 0;
4!
59
        throw new ParseError(
4✔
60
          `Unexpected token: ${token.value || token.type} at line ${line}, column ${col}`,
4!
61
          line,
62
          col,
63
          this.source,
64
        );
65
      }
66
      throw error;
1✔
67
    }
68
  }
69
}
70

71
/**
72
 * Error class for parse errors
73
 */
74
export class ParseError extends SyntaxError {
3✔
75
  line: number;
76
  col: number;
77
  source: string;
78

79
  constructor(message: string, line: number, col: number, source: string) {
80
    super(message);
4✔
81
    this.name = "ParseError";
4✔
82
    this.line = line;
4✔
83
    this.col = col;
4✔
84
    this.source = source;
4✔
85
  }
86
}
87

88
/**
89
 * Convenience function to parse Python source code
90
 */
91
export function parse(source: string): StmtNS.FileInput {
3✔
92
  const parser = new NearleyParser(source);
212✔
93
  return parser.parse();
212✔
94
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc