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

source-academy / js-slang / 19987249164

06 Dec 2025 10:32AM UTC coverage: 76.655%. Remained the same
19987249164

Pull #1833

github

web-flow
Merge 9398ce873 into fed61abb1
Pull Request #1833: November 2025 maintenance

3501 of 4761 branches covered (73.53%)

Branch coverage included in aggregate %.

7085 of 9049 relevant lines covered (78.3%)

195806.76 hits per line

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

75.0
/src/stdlib/parser.ts
1
import type es from 'estree'
2

3
import { parse as sourceParse } from '../parser/parser'
4
import { SourceParser } from '../parser/source'
5
import { libraryParserLanguage } from '../parser/source/syntax'
6
import type { Context, ContiguousArrayElements, Node, NodeTypeToNode, Value } from '../types'
7
import { getSourceVariableDeclaration } from '../utils/ast/helpers'
8
import { isDeclaration } from '../utils/ast/typeGuards'
9
import { oneLine } from '../utils/formatters'
10
import { vector_to_list, type List } from './list'
11

12
class ParseError extends Error {
13
  constructor(message: string) {
14
    super(message)
3✔
15
    this.name = 'ParseError'
3✔
16
  }
17
}
18

19
function unreachable() {
20
  console.error(oneLine`
×
21
    UNREACHABLE CODE REACHED!
22
    Please file an issue at
23
    https://github.com/source-academy/js-slang/issues
24
    if you see this.
25
  `)
26
}
27

28
// sequences of expressions of length 1
29
// can be represented by the element itself,
30
// instead of constructing a sequence
31

32
function makeSequenceIfNeeded(exs: Node[]): List {
33
  return exs.length === 1
99✔
34
    ? transform(exs[0])
35
    : vector_to_list(['sequence', vector_to_list(exs.map(transform))])
36
}
37

38
function makeBlockIfNeeded(exs: Node[]): List {
39
  return hasDeclarationAtToplevel(exs)
30✔
40
    ? vector_to_list(['block', makeSequenceIfNeeded(exs)])
41
    : makeSequenceIfNeeded(exs)
42
}
43

44
// checks if sequence has declaration at toplevel
45
// (outside of any block)
46
function hasDeclarationAtToplevel(exs: Node[]) {
47
  return exs.some(isDeclaration)
30✔
48
}
49

50
type ParseTransformer<T extends Node> = (node: T) => List
51
type ASTTransformers = {
52
  [K in Node['type']]?: ParseTransformer<NodeTypeToNode<K>>
53
}
54

55
const transformers: ASTTransformers = {
70✔
56
  ArrayExpression: ({ elements }) =>
6✔
57
    vector_to_list([
58
      'array_expression',
59
      vector_to_list((elements as ContiguousArrayElements).map(transform))
60
    ]),
61
  ArrowFunctionExpression: node =>
13✔
62
    vector_to_list([
63
      'lambda_expression',
64
      vector_to_list(node.params.map(transform)),
65
      node.body.type === 'BlockStatement'
13✔
66
        ? // body.body: strip away one layer of block:
67
          // The body of a function is the statement
68
          // inside the curly braces.
69
          makeBlockIfNeeded(node.body.body)
70
        : vector_to_list(['return_statement', transform(node.body)])
71
    ]),
72
  AssignmentExpression: node => {
73
    if (node.left.type === 'Identifier') {
24✔
74
      return vector_to_list(['assignment', transform(node.left), transform(node.right)])
16✔
75
    } else if (node.left.type === 'MemberExpression') {
8!
76
      return vector_to_list(['object_assignment', transform(node.left), transform(node.right)])
8✔
77
    } else {
78
      unreachable()
×
79
      throw new ParseError('Invalid assignment')
×
80
    }
81
  },
82
  BinaryExpression: node =>
44✔
83
    vector_to_list([
84
      'binary_operator_combination',
85
      node.operator,
86
      transform(node.left),
87
      transform(node.right)
88
    ]),
89
  BlockStatement: ({ body }) => makeBlockIfNeeded(body),
20✔
90
  BreakStatement: () => vector_to_list(['break_statement']),
4✔
91
  CallExpression: ({ callee, arguments: args }) =>
18✔
92
    vector_to_list(['application', transform(callee), vector_to_list(args.map(transform))]),
93
  ClassDeclaration: node => {
94
    return vector_to_list([
×
95
      'class_declaration',
96
      vector_to_list([
97
        'name',
98
        node.id?.name,
99
        !node.superClass ? null : transform(node.superClass),
×
100
        node.body.body.map(transform)
101
      ])
102
    ])
103
  },
104
  ConditionalExpression: node =>
3✔
105
    vector_to_list([
106
      'conditional_expression',
107
      transform(node.test),
108
      transform(node.consequent),
109
      transform(node.alternate)
110
    ]),
111
  ContinueStatement: () => vector_to_list(['continue_statement']),
4✔
112
  ExportDefaultDeclaration: node =>
4✔
113
    vector_to_list(['export_default_declaration', transform(node.declaration)]),
114
  ExportNamedDeclaration: ({ declaration, specifiers }) =>
8✔
115
    vector_to_list([
116
      'export_named_declaration',
117
      declaration ? transform(declaration) : specifiers.map(transform)
8✔
118
    ]),
119
  ExportSpecifier: node => vector_to_list(['name', node.exported.name]),
2✔
120
  ExpressionStatement: ({ expression }) => transform(expression),
75✔
121
  ForStatement: node =>
4✔
122
    vector_to_list([
123
      'for_loop',
124
      transform(node.init!),
125
      transform(node.test!),
126
      transform(node.update!),
127
      transform(node.body)
128
    ]),
129
  FunctionDeclaration: node =>
8✔
130
    vector_to_list([
131
      'function_declaration',
132
      transform(node.id!),
133
      vector_to_list(node.params.map(transform)),
134
      makeBlockIfNeeded(node.body.body)
135
    ]),
136
  FunctionExpression: ({ body: { body }, params }) =>
×
137
    vector_to_list([
138
      'lambda_expression',
139
      vector_to_list(params.map(transform)),
140
      makeBlockIfNeeded(body)
141
    ]),
142
  Identifier: ({ name }) => vector_to_list(['name', name]),
179✔
143
  IfStatement: node =>
8✔
144
    vector_to_list([
145
      'conditional_statement',
146
      transform(node.test),
147
      transform(node.consequent),
148
      node.alternate == null ? makeSequenceIfNeeded([]) : transform(node.alternate)
8✔
149
    ]),
150
  ImportDeclaration: node =>
3✔
151
    vector_to_list([
152
      'import_declaration',
153
      vector_to_list(node.specifiers.map(transform)),
154
      node.source.value
155
    ]),
156
  ImportDefaultSpecifier: () => vector_to_list(['default']),
2✔
157
  ImportSpecifier: node => vector_to_list(['name', node.imported.name]),
1✔
158
  Literal: ({ value }) => vector_to_list(['literal', value]),
159✔
159
  LogicalExpression: node =>
7✔
160
    vector_to_list([
161
      'logical_composition',
162
      node.operator,
163
      transform(node.left),
164
      transform(node.right)
165
    ]),
166
  MemberExpression: node => {
167
    // "computed" property of MemberExpression distinguishes
168
    // between dot access (not computed) and
169
    // a[...] (computed)
170
    // the key in dot access is meant as string, and
171
    // represented by a "property" node in parse result
172
    return vector_to_list([
19✔
173
      'object_access',
174
      transform(node.object),
175
      !node.computed && node.property.type === 'Identifier'
43✔
176
        ? vector_to_list(['property', node.property.name])
177
        : transform(node.property)
178
    ])
179
  },
180
  MethodDefinition: node =>
×
181
    vector_to_list([
182
      'method_definition',
183
      node.kind,
184
      node.static,
185
      transform(node.key),
186
      transform(node.value)
187
    ]),
188
  NewExpression: ({ callee, arguments: args }) =>
×
189
    vector_to_list(['new_expression', transform(callee), vector_to_list(args.map(transform))]),
190
  ObjectExpression: ({ properties }) =>
11✔
191
    vector_to_list(['object_expression', vector_to_list(properties.map(transform))]),
192
  Program: ({ body }) => makeSequenceIfNeeded(body),
68✔
193
  Property: node => {
194
    // identifiers before the ":" in literal objects are meant
195
    // as string, and represented by a "property" node in parse result
196
    return vector_to_list([
21✔
197
      'key_value_pair',
198
      node.key.type === 'Identifier'
21✔
199
        ? vector_to_list(['property', node.key.name])
200
        : transform(node.key),
201
      transform(node.value)
202
    ])
203
  },
204
  RestElement: ({ argument }) => vector_to_list(['rest_element', transform(argument)]),
1✔
205
  ReturnStatement: node => vector_to_list(['return_statement', transform(node.argument!)]),
13✔
206
  SpreadElement: ({ argument }) => vector_to_list(['spread_element', transform(argument)]),
1✔
207
  StatementSequence: ({ body }) => makeSequenceIfNeeded(body),
×
208
  Super: () => vector_to_list(['super_expression']),
×
209
  ThisExpression: () => vector_to_list(['this_expression']),
×
210
  ThrowStatement: ({ argument }) => vector_to_list(['throw_statement', transform(argument)]),
×
211
  TryStatement: node => {
212
    return vector_to_list([
×
213
      'try_statement',
214
      transform(node.block),
215
      !node.handler ? null : vector_to_list(['name', (node.handler.param as es.Identifier).name]),
×
216
      !node.handler ? null : transform(node.handler.body)
×
217
    ])
218
  },
219
  UnaryExpression: ({ operator, argument }) =>
5✔
220
    vector_to_list([
221
      'unary_operator_combination',
222
      operator === '-' ? '-unary' : operator,
5✔
223
      transform(argument)
224
    ]),
225
  VariableDeclaration: node => {
226
    const { id, init } = getSourceVariableDeclaration(node)
28✔
227

228
    if (node.kind === 'let') {
28✔
229
      return vector_to_list(['variable_declaration', transform(id), transform(init)])
15✔
230
    } else if (node.kind === 'const') {
13!
231
      return vector_to_list(['constant_declaration', transform(id), transform(init)])
13✔
232
    } else {
233
      unreachable()
×
234
      throw new ParseError('Invalid declaration kind')
×
235
    }
236
  },
237
  WhileStatement: ({ test, body }) =>
2✔
238
    vector_to_list(['while_loop', transform(test), transform(body)])
239
}
240

241
/**
242
 * Converts the given Node to a Source Value (which will be a list
243
 * consisting of a string description followed by that node's components)
244
 */
245
function transform(node: Node) {
246
  if (!(node.type in transformers)) {
765!
247
    unreachable()
×
248
    throw new ParseError('Cannot transform unknown type: ' + node.type)
×
249
  }
250

251
  const transformer = transformers[node.type] as ParseTransformer<Node>
765✔
252
  return transformer(node)
765✔
253
}
254

255
export function parse(x: string, context: Context): Value {
256
  context.chapter = libraryParserLanguage
71✔
257
  const program = sourceParse(x, context)
71✔
258
  if (context.errors.length > 0) {
71✔
259
    throw new ParseError(context.errors[0].explain())
3✔
260
  }
261

262
  if (program) {
68!
263
    return transform(program)
68✔
264
  } else {
265
    unreachable()
×
266
    throw new ParseError('Invalid parse')
×
267
  }
268
}
269

270
export function tokenize(x: string, context: Context): Value {
271
  const tokensArr = SourceParser.tokenize(x, context).map(tok => x.substring(tok.start, tok.end))
58✔
272
  return vector_to_list(tokensArr)
3✔
273
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc