Skip to content

Rewrite: Stepper #1742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 79 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
9e2f130
Feat: Implement contraction and redex for basic AST nodes
kavishsathia Feb 28, 2025
694cb98
Merge branch 'master' into rewrite/stepper
martin-henz Mar 4, 2025
d817339
Feat: Add Program and ExpressionStatement
kavishsathia Mar 8, 2025
c67c36f
Merge branch 'rewrite/stepper' of https://github.com/source-academy/j…
CATISNOTSODIUM Mar 8, 2025
0b93acd
add program, statements, and variable declaration handler
CATISNOTSODIUM Mar 8, 2025
e32d110
restructure project, add substitution
CATISNOTSODIUM Mar 8, 2025
cc125fa
(feat) block scope handling
CATISNOTSODIUM Mar 9, 2025
e86cf6a
(feat) adding freeName
CATISNOTSODIUM Mar 9, 2025
496160b
Merge branch 'master' into rewrite/stepper
martin-henz Mar 10, 2025
fe6ecbc
(feat) add alpha renaming
CATISNOTSODIUM Mar 10, 2025
00aa05f
Merge branch 'rewrite/stepper' of https://github.com/source-academy/j…
CATISNOTSODIUM Mar 10, 2025
873bfee
(fix) alpha renaming for multiple variables
CATISNOTSODIUM Mar 11, 2025
9210965
(fix) edit source runner for stepper
CATISNOTSODIUM Mar 14, 2025
34736ee
Feat: Add IfStatements
kavishsathia Mar 15, 2025
355a65a
Feat: Add ConditionalExpressions
kavishsathia Mar 15, 2025
819fae7
Feat: Add ArrowFunctionExpressions and FunctionApplications
kavishsathia Mar 15, 2025
b09fb3d
(feat) add explanation handler
CATISNOTSODIUM Mar 17, 2025
20961b3
Feat: ArrowFunctions with BlockStatement bodies
kavishsathia Mar 17, 2025
52ad7ef
Feat: Add MuTerms v1
kavishsathia Mar 17, 2025
89d4290
Feat: Add Alpha Renaming for Arrow Functions v1
kavishsathia Mar 18, 2025
003f193
(feat) add explanation handler
CATISNOTSODIUM Mar 18, 2025
00d8cfb
Merge branch 'rewrite/stepper' of https://github.com/source-academy/j…
CATISNOTSODIUM Mar 18, 2025
c907c83
Feat: Add Alpha-renaming for ArrowFunctionExpressions
kavishsathia Mar 20, 2025
c3aa835
Fix: Remove ReturnStatements
kavishsathia Mar 20, 2025
d556243
Feat: Add BlockExpressions
kavishsathia Mar 21, 2025
58bb457
Feat: Add FunctionDeclarations
kavishsathia Mar 25, 2025
5aac4dc
Fix: Modify ReturnStatement Contraction Logic
kavishsathia Mar 25, 2025
095f610
Fix: Modify ArrowFunctionExpression Substituter
kavishsathia Mar 25, 2025
d4ec978
Fix: Add lte and gte to BinaryExpressions
kavishsathia Mar 25, 2025
dc6ab86
Chore: Move stepperV2 to tracer
kavishsathia Mar 26, 2025
0f29f36
Feat: Add Support for Math Builtins
kavishsathia Mar 26, 2025
51a572f
Feat: Add Support for Misc Builtins
kavishsathia Mar 26, 2025
fad5740
Feat: Add LogicalExpressions with Short-circuiting
kavishsathia Mar 26, 2025
ef6daf3
fix: remove substitution scope usage
CATISNOTSODIUM Mar 27, 2025
0d02471
fix: handle undefined statements
CATISNOTSODIUM Mar 28, 2025
67fd0cf
feat: add test case
CATISNOTSODIUM Mar 28, 2025
f968894
feat: fix function declaration
CATISNOTSODIUM Mar 29, 2025
93edeff
fix: handle return statement
CATISNOTSODIUM Mar 29, 2025
a67de42
feat: add prelude constant
CATISNOTSODIUM Mar 29, 2025
b0e293e
feat: add predeclared functions with arity
CATISNOTSODIUM Mar 30, 2025
254ea29
ci: adding test cases
CATISNOTSODIUM Mar 31, 2025
8000189
fix: convert negated number to literal during initialization
CATISNOTSODIUM Mar 31, 2025
3d71c27
feat: add predeclared list functions
CATISNOTSODIUM Mar 31, 2025
0206491
feat: cover more explanations
CATISNOTSODIUM Mar 31, 2025
71e0cab
Feat: Add Support for Auxiliary Builtins
kavishsathia Apr 1, 2025
9c675a4
fix: function declaration with if else block
CATISNOTSODIUM Apr 2, 2025
b00da05
fix: edit source runner for new stepper
CATISNOTSODIUM Apr 5, 2025
d6b365b
fix: mu term declaration after substitution
CATISNOTSODIUM Apr 5, 2025
b393712
fix: add raw field to the result from math builtin
CATISNOTSODIUM Apr 5, 2025
d4b0fea
fix: Block-statement-empty-reduce
CATISNOTSODIUM Apr 5, 2025
108afbd
fix: is_function
CATISNOTSODIUM Apr 5, 2025
a677cba
fix: feature parity
CATISNOTSODIUM Apr 5, 2025
953a57c
fix: alpha renaming with multiple clashes
CATISNOTSODIUM Apr 5, 2025
c6de056
fix: #1714 steps appear as if capturing happens
CATISNOTSODIUM Apr 6, 2025
8f6d7b0
Feat: Add Error Handling during Runtime
kavishsathia Apr 7, 2025
e596e75
Feat: Add Support for NaN and Infinity
kavishsathia Apr 7, 2025
210a8b7
Fix: Preserve Line Number for Array, ArrowFunction, and BinaryExpress…
kavishsathia Apr 7, 2025
b03e8a4
Fix: Preserve Line Number for Conditional, FunctionApplication, Ident…
kavishsathia Apr 7, 2025
5da68e8
Fix: Preserve Line Number for UnaryExpressions
kavishsathia Apr 7, 2025
f7d1609
Fix: Preserve Line Number for Statements
kavishsathia Apr 7, 2025
a7691fb
Fix: Preserve Line Number for BlockExpressions
kavishsathia Apr 7, 2025
5b2fcf1
Feat: Add Errors for Binary, FunctionApplication, Logical, and UnaryE…
kavishsathia Apr 7, 2025
a28a8ac
Feat: Add Errors for ConditionalExpressions
kavishsathia Apr 7, 2025
1e8e18b
Chore: Code Reordering
kavishsathia Apr 7, 2025
20cb359
Fix: Resolve Issue with Negative Infinity
kavishsathia Apr 7, 2025
61d2bc5
feat: add more tests
CATISNOTSODIUM Apr 8, 2025
6a63b7a
fix: remove unnecessary renaming
CATISNOTSODIUM Apr 8, 2025
c009f97
chore: lint fix
CATISNOTSODIUM Apr 8, 2025
2b3a255
fix: explanation for function application
CATISNOTSODIUM Apr 14, 2025
e92a776
chore: eslint fix
CATISNOTSODIUM Apr 14, 2025
9231fe2
doc: update README.md
CATISNOTSODIUM Apr 14, 2025
1f40498
doc: update README.md
CATISNOTSODIUM May 2, 2025
47a6c1f
doc: update README.md
CATISNOTSODIUM May 5, 2025
22e69ca
fix: handle invalid argument
CATISNOTSODIUM May 6, 2025
ef10ca4
Merge remote-tracking branch 'origin/master' into rewrite/stepper
CATISNOTSODIUM May 7, 2025
ff54d24
fix: arity for list
CATISNOTSODIUM May 7, 2025
2de3fb1
fix: source runner
CATISNOTSODIUM May 7, 2025
39c0442
chore: format fix
CATISNOTSODIUM May 7, 2025
dda543b
chore: format fix
CATISNOTSODIUM May 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 3 additions & 21 deletions src/runner/sourceRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ import { RuntimeSourceError } from '../errors/runtimeSourceError'
import { TimeoutError } from '../errors/timeoutErrors'
import { isPotentialInfiniteLoop } from '../infiniteLoops/errors'
import { testForInfiniteLoop } from '../infiniteLoops/runtime'
import {
callee,
getEvaluationSteps,
getRedex,
type IStepperPropContents,
redexify
} from '../stepper/stepper'
import { sandboxedEval } from '../transpiler/evalContainer'
import { transpile } from '../transpiler/transpiler'
import { Variant } from '../types'
import { getSteps } from '../tracer/steppers'
import { toSourceError } from './errors'
import { resolvedErrorPromise } from './utils'
import type { Runner } from './types'
Expand All @@ -31,26 +25,14 @@ const runners = {
return CSEResultPromise(context, value)
},
substitution: (program, context, options) => {
const steps = getEvaluationSteps(program, context, options)
const steps = getSteps(program, context, options)
if (context.errors.length > 0) {
return resolvedErrorPromise
}

const redexedSteps = steps.map((step): IStepperPropContents => {
const redex = getRedex(step[0], step[1])
const redexed = redexify(step[0], step[1])
return {
code: redexed[0],
redex: redexed[1],
explanation: step[2],
function: callee(redex, context)
}
})

return Promise.resolve({
status: 'finished',
context,
value: redexedSteps
value: steps
})
},
native: async (program, context, options) => {
Expand Down
116 changes: 116 additions & 0 deletions src/tracer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Stepper Documentation
## Quickstart
First of all, make sure that you have already installed `js-slang` using `yarn`. There are many possible ways that you can work and test the code. One of my personal solution is using `yarn test`. During the development, you can edit the file from `../__test__/tracer_debug.ts` and run it with the following command:
```bash
yarn test -- tracer_debug.ts > testOutput.log
```
Note that the flag `--silence=false` is set in order to see the output from `console.log`. In order to fully test the stepper, you can execute the following command.
```bash
yarn test -- tracer_full.ts
```
## High-level implementation details
Our stepper is a program that reduces a piece of code to its simplest form until it can no longer be reduced. While the idea sounds abstract, the step-by-step reduction of code allows us to add explanations, further aiding student learning when they want to scrutinize how the code is evaluated. The method is formally defined as beta reduction.

### Expression stepper
In order to implement the program with such functionalities, we have to first parse the program string into certain structure that we can approach to, so-called Abstract Syntax Tree (AST). For instance, string `2 * 3` can be converted to AST as `BinExp[* Lit[2] Lit[3]]`, where `BinExp` is a binary expression node consisting of two literals `2` and `3` combined with operator `*`. Note that our programming language consists of various node types, it's difficult to implement a parser from scratch to cover all cases. Luckily, the library `acorn` helps us generate the AST from a piece of string which is very convenient.

Here comes the fun part. How are we supposed to evaluate `BinExp[* Lit[2] Lit[3]]`? Since we cannot do anything further, we just simply **contract** them to `Lit[6]` and we are done. However, considering the AST `BinExp[* Lit[2] BinExp[+ Lit[3] Lit[4]]]` generated from `2 * (3 + 4)`, if we contract this expression directly, we will get `2 * {OBJECT}` which is not computable since one of the operands is not a number. Hence, we have to contract `BinExp[+ Lit[3] Lit[4]]]` first before contracting the outer `BinExp`. Note that our stepper only contracts one node for each step. This is our main constraint that we use during the implementation.

![alt text](images/tracer-1.png)

A quick way of identifying whether this AST should be contract right away is making sure that the child nodes cannot proceed further. To implement this, we should have two methods `oneStep` and `contract` to perform this reduction. In addition, we also have another two methods `oneStepPossible` and `isContractible` to safeguard our AST before actually proceeding with our reduction procedure.

- `isContractible: StepperBaseNode => boolean` returns true if all of its children cannot be proceed further (`oneStepPossible` is false).
- `oneStepPossible: StepperBaseNode => boolean` returns true if the AST can be proceed further.
- `oneStep: StepperBaseNode => StepperBaseNode` either perform `contract` if the AST is contractible or performing `oneStep` on child nodes.
- `contract: StepperBaseNode => StepperBaseNode` contracts the AST.

These methods can be applied for all types of AST nodes, such as expressions, statements, and program. The details about `StepperBaseNode` are discussed in [this](#augmenting-functionalities) section.

### Statements
// TODO
### Encountering constant declarations
// TODO
### Functions and mu terms
// TODO
### Augmenting functionalities
Since our stepper takes AST (of any types) as an input and recursively navigate along the tree to find the next node to contract. There are many functionalities that we have to perform on each AST (such as contracting the AST as discussed in the previous section). Intuitively, we should add these functionalities as methods for each of the AST classes. Since the AST obtaining from the library is an `ESTree` interface, we have to implement our own concrete classes inherited from the ESTree AST Nodes. We also have to create our own convertor to convert the former ESTree into our own Stepper AST:

```typescript
// interface.ts
export interface StepperBaseNode {
type: string
isContractible(): boolean
isOneStepPossible(): boolean
contract(): StepperBaseNode
oneStep(): StepperBaseNode
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode
freeNames(): string[]
allNames(): string[]
rename(before: string, after: string): StepperBaseNode
}
```
Conversion from `es.BaseNode` (AST interface from `ESTree`) to `StepperBaseNode` is handled by function `convert` in `generator.ts`.

```typescript
// generator.ts
const nodeConverters: { [Key: string]: (node: any) => StepperBaseNode } = {
Literal: (node: es.SimpleLiteral) => StepperLiteral.create(node),
UnaryExpression: (node: es.UnaryExpression) => StepperUnaryExpression.create(node),
BinaryExpression: (node: es.BinaryExpression) => StepperBinaryExpression.create(node),
// ... (omitted)
}

export function convert(node: es.BaseNode): StepperBaseNode {
const converter = nodeConverters[node.type as keyof typeof nodeConverters]
return converter ? converter(node as any) : undefinedNode
// undefinedNode is a global variable with type StepperLiteral
}
```

### Entry point
The starting point of our stepper is at `steppers.ts` with the function `getSteps`. This function is responsible for triggering reduction until it cannot be proceed. The result from our evaluation is then stored in array `steps`. Here is the shorten version of `getSteps`.

```typescript
export function getSteps(node: StepperBaseNode) {
const steps = []
function evaluate(node) {
const isOneStepPossible = node.isOneStepPossible()
if (isOneStepPossible) {
const oldNode = node
const newNode = node.oneStep() // entry point
// read global state redex and add them to steps array
return evaluate(newNode)
} else {
return node
}
}
evaluate(node)
return steps
}

```

In order to keep track of the node that is reduced (i.e., the node highlighted in orange and green in the current SICP stepper), we use the global state redex to track all nodes that should be highlighted. This state is then updated dynamically during the contraction process.

```typescript
export let redex: { preRedex: StepperBaseNode[]; postRedex: StepperBaseNode[] } = {
preRedex: [],
postRedex: []
}
// How to use global state
redex.preRedex = [node]
const ret = someSortOfReduction(node)
redex.postRedex = [ret]
```

### Some important decisions
There are some design decisions that diverge from the original Source 1 and 2. Here are some changes we have made.
#### Builtin math functions
Calling math function with non number arguments is prohibited in stepper.
```ts
// Test Incorrect type of argument for math function
math_sin(true); // error!
```
#### Undeclared variables
This issue has not been properly resolved in this version.
Loading
Loading