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

source-academy / js-slang / 15235726114

25 May 2025 07:55AM UTC coverage: 77.048%. First build
15235726114

Pull #1742

github

web-flow
Merge d7783cf1e into 0be74e78c
Pull Request #1742: Rewrite: Stepper

3433 of 4826 branches covered (71.14%)

Branch coverage included in aggregate %.

1032 of 1260 new or added lines in 27 files covered. (81.9%)

10099 of 12737 relevant lines covered (79.29%)

140954.8 hits per line

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

84.4
/src/tracer/nodes/Program.ts
1
import { Comment, Program, SourceLocation } from 'estree'
2
import { StepperBaseNode } from '../interface'
3
import { convert } from '../generator'
61✔
4

5
import { redex } from '..'
61✔
6
import { assignMuTerms } from '../utils'
61✔
7
import { StepperVariableDeclaration } from './Statement/VariableDeclaration'
8
import { StepperStatement } from './Statement'
9
import { StepperFunctionDeclaration } from './Statement/FunctionDeclaration'
10
import { StepperExpression, StepperPattern, undefinedNode } from '.'
61✔
11

12
export class StepperProgram implements Program, StepperBaseNode {
61✔
13
  type: 'Program'
14
  sourceType: 'script' | 'module'
15
  body: StepperStatement[]
16
  comments?: Comment[] | undefined
17
  leadingComments?: Comment[] | undefined
18
  trailingComments?: Comment[] | undefined
19
  loc?: SourceLocation | null | undefined
20
  range?: [number, number] | undefined
21

22
  isContractible(): boolean {
NEW
23
    return false
×
24
  }
25

26
  isOneStepPossible(): boolean {
27
    return this.body.length === 0
3,162✔
28
      ? false // unlike BlockStatement
29
      : this.body[0].isOneStepPossible() ||
3,767✔
30
          this.body.length >= 2 ||
31
          (this.body.length == 1 &&
32
            (this.body[0].type == 'VariableDeclaration' ||
33
              this.body[0].type == 'FunctionDeclaration'))
34
  }
35

36
  contract(): StepperProgram {
NEW
37
    throw new Error('not implemented')
×
38
  }
39

40
  oneStep(): StepperProgram {
41
    // reduce the first statement
42
    if (this.body[0].isOneStepPossible()) {
3,043✔
43
      const firstStatementOneStep = this.body[0].oneStep()
2,867✔
44
      const afterSubstitutedScope = this.body.slice(1)
2,861✔
45
      if (firstStatementOneStep === undefinedNode) {
2,861✔
46
        return new StepperProgram([afterSubstitutedScope].flat())
2✔
47
      }
48
      return new StepperProgram(
2,859✔
49
        [firstStatementOneStep as StepperStatement, afterSubstitutedScope].flat()
50
      )
51
    }
52

53
    // If the first statement is constant declaration, gracefully handle it!
54
    if (this.body[0].type == 'VariableDeclaration') {
176✔
55
      const declarations = assignMuTerms(this.body[0].declarations) // for arrow function expression
91✔
56
      const afterSubstitutedScope = this.body
91✔
57
        .slice(1)
58
        .map(current =>
59
          declarations
187✔
60
            .filter(declarator => declarator.init)
187✔
61
            .reduce(
62
              (statement, declarator) =>
63
                statement.substitute(declarator.id, declarator.init!) as StepperStatement,
187✔
64
              current
65
            )
66
        ) as StepperStatement[]
67
      const substitutedProgram = new StepperProgram(afterSubstitutedScope)
91✔
68
      redex.preRedex = [this.body[0]]
91✔
69
      redex.postRedex = afterSubstitutedScope
91✔
70
      return substitutedProgram
91✔
71
    }
72

73
    // If the first statement is function declaration, also gracefully handle it!
74
    if (this.body[0].type == 'FunctionDeclaration') {
85✔
75
      const arrowFunction = (
76
        this.body[0] as StepperFunctionDeclaration
53✔
77
      ).getArrowFunctionExpression()
78
      const functionIdentifier = (this.body[0] as StepperFunctionDeclaration).id
53✔
79
      const afterSubstitutedScope = this.body
53✔
80
        .slice(1)
81
        .map(
82
          statement => statement.substitute(functionIdentifier, arrowFunction) as StepperStatement
121✔
83
        ) as StepperStatement[]
84
      const substitutedProgram = new StepperProgram(afterSubstitutedScope)
53✔
85
      redex.preRedex = [this.body[0]]
53✔
86
      redex.postRedex = afterSubstitutedScope
53✔
87
      return substitutedProgram
53✔
88
    }
89

90
    const firstValueStatement = this.body[0]
32✔
91
    // After this stage, the first statement is a value statement. Now, proceed until getting the second value statement.
92
    if (this.body.length >= 2 && this.body[1].isOneStepPossible()) {
32✔
93
      const secondStatementOneStep = this.body[1].oneStep()
10✔
94
      const afterSubstitutedScope = this.body.slice(2)
10✔
95
      if (secondStatementOneStep === undefinedNode) {
10✔
96
        return new StepperProgram([firstValueStatement, afterSubstitutedScope].flat())
1✔
97
      }
98
      return new StepperProgram(
9✔
99
        [
100
          firstValueStatement,
101
          secondStatementOneStep as StepperStatement,
102
          afterSubstitutedScope
103
        ].flat()
104
      )
105
    }
106

107
    // If the second statement is constant declaration, gracefully handle it!
108
    if (this.body.length >= 2 && this.body[1].type == 'VariableDeclaration') {
22✔
109
      const declarations = assignMuTerms(this.body[1].declarations)
6✔
110
      const afterSubstitutedScope = this.body
6✔
111
        .slice(2)
112
        .map(current =>
113
          declarations
11✔
114
            .filter(declarator => declarator.init)
11✔
115
            .reduce(
116
              (statement, declarator) =>
117
                statement.substitute(declarator.id, declarator.init!) as StepperStatement,
11✔
118
              current
119
            )
120
        ) as StepperStatement[]
121
      const substitutedProgram = new StepperProgram(
6✔
122
        [firstValueStatement, afterSubstitutedScope].flat()
123
      )
124
      redex.preRedex = [this.body[1]]
6✔
125
      redex.postRedex = declarations.map(x => x.id)
6✔
126
      return substitutedProgram
6✔
127
    }
128

129
    // If the second statement is function declaration, also gracefully handle it!
130
    if (this.body.length >= 2 && this.body[1].type == 'FunctionDeclaration') {
16✔
131
      const arrowFunction = (
132
        this.body[1] as StepperFunctionDeclaration
2✔
133
      ).getArrowFunctionExpression()
134
      const functionIdentifier = (this.body[1] as StepperFunctionDeclaration).id
2✔
135
      const afterSubstitutedScope = this.body
2✔
136
        .slice(2)
137
        .map(
138
          statement => statement.substitute(functionIdentifier, arrowFunction) as StepperStatement
5✔
139
        ) as StepperStatement[]
140
      const substitutedProgram = new StepperProgram(
2✔
141
        [firstValueStatement, afterSubstitutedScope].flat()
142
      )
143
      redex.preRedex = [this.body[1]]
2✔
144
      redex.postRedex = afterSubstitutedScope
2✔
145
      return substitutedProgram
2✔
146
    }
147

148
    this.body[0].contractEmpty() // update the contracted statement onto redex
14✔
149
    return new StepperProgram(this.body.slice(1))
14✔
150
  }
151

152
  static create(node: Program) {
153
    return new StepperProgram(
383✔
154
      node.body.map(ast => convert(ast) as StepperStatement),
878✔
155
      node.comments,
156
      node.leadingComments,
157
      node.trailingComments,
158
      node.loc,
159
      node.range
160
    )
161
  }
162

163
  constructor(
164
    body: StepperStatement[], // TODO: Add support for variable declaration
165
    comments?: Comment[] | undefined,
166
    leadingComments?: Comment[] | undefined,
167
    trailingComments?: Comment[] | undefined,
168
    loc?: SourceLocation | null | undefined,
169
    range?: [number, number] | undefined
170
  ) {
171
    this.type = 'Program'
4,444✔
172
    this.sourceType = 'module'
4,444✔
173
    this.body = body
4,444✔
174
    this.comments = comments
4,444✔
175
    this.leadingComments = leadingComments
4,444✔
176
    this.trailingComments = trailingComments
4,444✔
177
    this.loc = loc
4,444✔
178
    this.range = range
4,444✔
179
  }
180

181
  substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode {
182
    return new StepperProgram(
1,024✔
183
      this.body.map(statement => statement.substitute(id, value) as StepperStatement)
2,344✔
184
    )
185
  }
186

187
  scanAllDeclarationNames(): string[] {
NEW
188
    return this.body
×
NEW
189
      .filter(ast => ast.type === 'VariableDeclaration' || ast.type === 'FunctionDeclaration')
×
190
      .flatMap((ast: StepperVariableDeclaration | StepperFunctionDeclaration) => {
NEW
191
        if (ast.type === 'VariableDeclaration') {
×
NEW
192
          return ast.declarations.map(ast => ast.id.name)
×
193
        } else {
194
          // Function Declaration
NEW
195
          return [(ast as StepperFunctionDeclaration).id.name]
×
196
        }
197
      })
198
  }
199

200
  freeNames(): string[] {
NEW
201
    const names = new Set(this.body.flatMap(ast => ast.freeNames()))
×
NEW
202
    this.scanAllDeclarationNames().forEach(name => names.delete(name))
×
NEW
203
    return Array.from(names)
×
204
  }
205

206
  allNames(): string[] {
NEW
207
    return Array.from(new Set(this.body.flatMap(ast => ast.allNames())))
×
208
  }
209

210
  rename(before: string, after: string): StepperProgram {
NEW
211
    return new StepperProgram(
×
NEW
212
      this.body.map(statement => statement.rename(before, after) as StepperStatement)
×
213
    )
214
  }
215
}
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