Skip to content

Separate program values from their representations #1651

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

Merged
merged 65 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
51144f9
Prepare scheme files for new parser
s-kybound Mar 6, 2024
c5429c0
update JS version for js-slang
s-kybound Mar 6, 2024
f778f67
Merge branch 'source-academy/master'
s-kybound Mar 6, 2024
d341b96
proper formatting of files
s-kybound Mar 6, 2024
0e43676
fix separate program environments across REPL eval calls
s-kybound Mar 7, 2024
18cb104
remove logger messages from interpreter
s-kybound Mar 7, 2024
28b2e8f
Enable variadic continuations for future
s-kybound Mar 11, 2024
af27846
Remove Infinity and NaN representation from Scheme
s-kybound Mar 11, 2024
fd62e06
Change scm-slang to follow forked version
s-kybound Mar 11, 2024
9f9d57a
Merge branch 'origin/master'
s-kybound Mar 12, 2024
cfa44bd
update scm-slang to newest parser
s-kybound Mar 18, 2024
d07e19b
Merge branch 'master' into master
martin-henz Mar 19, 2024
9347f34
resolve linting problems
s-kybound Mar 19, 2024
60fac47
Merge remote-tracking branch 'refs/remotes/origin/master'
s-kybound Mar 19, 2024
3f1d450
add test cases to verify proper chapter validation, decoded represent…
s-kybound Mar 19, 2024
d750e57
Merge branch 'master' into master
Mar 20, 2024
a6987ec
Merge branch 'master' into master
Mar 20, 2024
0d45e9d
update scm-slang
s-kybound Mar 20, 2024
42e184e
Move scheme-specific tests to scm-slang
s-kybound Mar 20, 2024
eae3212
Merge remote-tracking branch 'origin/master'
s-kybound Mar 20, 2024
9a912cf
make scheme test names more obvious
s-kybound Mar 20, 2024
467279d
Revert "Move scheme-specific tests to scm-slang"
s-kybound Mar 21, 2024
2959c97
move scm-slang to dedicated alt-lang folder
s-kybound Mar 21, 2024
a9fefb5
remove duplicate code between scm-slang and js-slang
s-kybound Mar 21, 2024
7089e07
ignore alt langs coverage
s-kybound Mar 21, 2024
90d0818
Merge branch 'source-academy:master' into master
s-kybound Mar 22, 2024
ae3eeef
Merge branch 'source-academy:master' into master
s-kybound Apr 1, 2024
8ecf274
update scm-slang
s-kybound Apr 1, 2024
7068f4f
Merge remote-tracking branch 'refs/remotes/origin/master'
s-kybound Apr 1, 2024
cbd5d84
Merge branch 'source-academy:master' into master
s-kybound Apr 3, 2024
1b402b8
start to add mapping functions for data
s-kybound Apr 4, 2024
631a565
Merge branch 'master' of https://github.com/s-kybound/js-slang
s-kybound Apr 4, 2024
90de75a
update python and scheme-slang
s-kybound Apr 4, 2024
40205e4
destructively change data types in js-slang, especially since they ar…
s-kybound Apr 4, 2024
1436d4a
prevent js-slang from testing alternate languages - they should manag…
s-kybound Apr 8, 2024
1d01e90
add mapping and language-specific representations for the result type
s-kybound Apr 8, 2024
106bb8c
change the command-line REPL to use representations if necessary
s-kybound Apr 8, 2024
6fe24fb
Merge branch 'source/master'
s-kybound Apr 8, 2024
7b4a904
add tests for mapper back into coverage pattern
s-kybound Apr 8, 2024
029b5b3
fix arrays being treated as pairs in scheme
s-kybound Apr 8, 2024
35cea1e
add test for mapper
s-kybound Apr 8, 2024
1fbe3c5
undo accidental deletion of scheme parser tests
s-kybound Apr 8, 2024
dba59cd
fix typo in repl, make undefined check explicit
s-kybound Apr 8, 2024
833b1a7
test every version of scheme parser
s-kybound Apr 8, 2024
14c48d7
resolved issue that caused js-slang to ignore tests
s-kybound Apr 8, 2024
a75c1e4
Merge branch 'master' into master
martin-henz Apr 8, 2024
d995495
add tests for scheme mapper
s-kybound Apr 8, 2024
74a87a5
Merge branch 'master' of https://github.com/s-kybound/js-slang
s-kybound Apr 8, 2024
5d3789c
Merge branch 'source-academy:master' into master
s-kybound Apr 8, 2024
666a1a2
Merge remote-tracking branch 'source/master' into master
s-kybound Apr 9, 2024
4ed528a
Merge branch 'source/master'
s-kybound Apr 9, 2024
1390759
Add name and parameter data to builtin functions
s-kybound Apr 9, 2024
e7ebf71
Repair representation of closures and builtin functions
s-kybound Apr 9, 2024
47f06d6
update scm-slang
s-kybound Apr 9, 2024
64cffb2
update scm-slang
s-kybound Apr 9, 2024
575f49c
update scm-slang
s-kybound Apr 9, 2024
625323e
Merge branch 'master' into master
martin-henz Apr 9, 2024
f92d3ea
bump scm-slang
s-kybound Apr 9, 2024
d72020f
Merge branch 'master' of https://github.com/s-kybound/js-slang
s-kybound Apr 9, 2024
84146df
Merge branch 'master' into master
martin-henz Apr 9, 2024
61ed680
Merge branch 'master' into master
martin-henz Apr 10, 2024
283345a
Merge branch 'source'
s-kybound Apr 11, 2024
90e6c56
add dummy prelude for scheme
s-kybound Apr 11, 2024
2fad18b
Merge branch 'master' of https://github.com/s-kybound/js-slang
s-kybound Apr 11, 2024
1de9d9e
Merge branch 'master' into master
martin-henz Apr 11, 2024
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"testRegex": "/__tests__/.*\\.ts$",
"testPathIgnorePatterns": [
"/dist/",
"/src/alt-langs/scheme/scm-slang",
".*benchmark.*",
"/__tests__/(.*/)?utils\\.ts"
],
Expand All @@ -114,7 +115,7 @@
"/node_modules/",
"/src/typings/",
"/src/utils/testing.ts",
"/src/alt-langs",
"/src/alt-langs/scheme/scm-slang",
"/src/py-slang/"
],
"reporters": [
Expand Down
32 changes: 32 additions & 0 deletions src/alt-langs/__tests__/mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { mockContext } from "../../mocks/context";
import { Chapter, Finished } from "../../types";
import { mapResult } from "../mapper";

test("given source, mapper should do nothing (no mapping needed)", () => {
const context = mockContext();
const result = {
status: "finished",
context: context,
value: 5,
} as Finished;
const mapper = mapResult(context);
expect(mapper(result)).toEqual(result);
})

test("given scheme, mapper should map result to scheme representation", () => {
const context = mockContext(Chapter.SCHEME_1);
const result = {
status: "finished",
context: context,
value: [1, 2, 3, 4, 5],
} as Finished;
const mapper = mapResult(context);
expect(mapper(result)).toEqual({
status: "finished",
context: context,
value: [1, 2, 3, 4, 5],
representation: {
representation: "#(1 2 3 4 5)",
},
});
})
43 changes: 43 additions & 0 deletions src/alt-langs/mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* A generic mapper for all languages.
* If required, maps the final result produced by js-slang to
* the required representation for the language.
*/

import { Context, Result } from ".."
import { Chapter } from "../types"
import { mapErrorToScheme, mapResultToScheme } from "./scheme/scheme-mapper"

/**
* A representation of a value in a language.
* This is used to represent the final value produced by js-slang.
* It is separate from the actual value of the result.
*/
export class Representation {
constructor(public representation: string) {}
toString() {
return this.representation
}
}

export function mapResult(context: Context): (x: Result) => Result {
switch (context.chapter) {
case Chapter.SCHEME_1:
case Chapter.SCHEME_2:
case Chapter.SCHEME_3:
case Chapter.SCHEME_4:
case Chapter.FULL_SCHEME:
return x => {
if (x.status === 'finished') {
return mapResultToScheme(x)
} else if (x.status === "error") {
context.errors = context.errors.map(mapErrorToScheme)
}
return x
}
default:
// normally js-slang.
// there is no need for a mapper in this case.
return x => x
}
}
4 changes: 2 additions & 2 deletions src/alt-langs/scheme/__tests__/scheme-encode-decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UnassignedVariable } from '../../../errors/errors'
import { decode, encode } from '../scm-slang/src'
import { cons, set$45$cdr$33$ } from '../scm-slang/src/stdlib/base'
import { dummyExpression } from '../../../utils/ast/dummyAstCreator'
import { decodeError, decodeValue } from '../../../parser/scheme'
import { mapErrorToScheme, decodeValue } from '../scheme-mapper'

describe('Scheme encoder and decoder', () => {
it('encoder and decoder are proper inverses of one another', () => {
Expand Down Expand Up @@ -62,6 +62,6 @@ describe('Scheme encoder and decoder', () => {
const token = `😀`
const dummyNode: Node = dummyExpression()
const error = new UnassignedVariable(encode(token), dummyNode)
expect(decodeError(error).elaborate()).toContain(`😀`)
expect(mapErrorToScheme(error).elaborate()).toContain(`😀`)
})
})
58 changes: 58 additions & 0 deletions src/alt-langs/scheme/__tests__/scheme-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { schemeVisualise } from "../scheme-mapper"
import { make_number } from "../scm-slang/src/stdlib/core-math"
import { circular$45$list, cons, cons$42$, list } from "../scm-slang/src/stdlib/base"

test("schemeVisualise: should visualise null properly", () => {
expect(schemeVisualise(null).toString()).toEqual("()")
})

test("schemeVisualise: should visualise undefined properly", () => {
expect(schemeVisualise(undefined).toString()).toEqual("undefined")
})

test("schemeVisualise: should visualise strings properly", () => {
expect(schemeVisualise("hello").toString()).toEqual("\"hello\"")
})

test("schemeVisualise: should visualise scheme numbers properly", () => {
expect(schemeVisualise(make_number("1i")).toString()).toEqual("0+1i")
})

test("schemeVisualise: should visualise booleans properly", () => {
expect(schemeVisualise(true).toString()).toEqual("#t")
expect(schemeVisualise(false).toString()).toEqual("#f")
})

test("schemeVisualise: should visualise circular lists properly", () => {
const circularList = circular$45$list(1, 2, 3)
//expect(schemeVisualise(circularList).toString()).toEqual("#0=(1 2 3 . #0#)")
//for now, this will do
expect(schemeVisualise(circularList).toString()).toEqual("(circular list)")
})

test("schemeVisualise: should visualise dotted lists properly", () => {
const dottedList = cons$42$(1, 2, 3)
expect(schemeVisualise(dottedList).toString()).toEqual("(1 2 . 3)")
})

test("schemeVisualise: should visualise proper lists properly", () => {
const properList = list(1, 2, 3, 4)
expect(schemeVisualise(properList).toString()).toEqual("(1 2 3 4)")
})

test("schemeVisualise: should visualise vectors properly", () => {
const vector = [1, 2, 3, 4]
expect(schemeVisualise(vector).toString()).toEqual("#(1 2 3 4)")
})

test("schemeVisualise: should visualise pairs properly", () => {
const pair = cons(1, 2)
expect(schemeVisualise(pair).toString()).toEqual("(1 . 2)")
})

test("schemeVisualise: vectors and pairs should be distinct", () => {
const maybe_pair = [1, 2]
expect(schemeVisualise(maybe_pair).toString()).toEqual("#(1 2)")
})

export { schemeVisualise }
192 changes: 192 additions & 0 deletions src/alt-langs/scheme/scheme-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { ArrowFunctionExpression, Identifier, RestElement } from "estree"
import Closure from "../../cse-machine/closure"
import { decode, estreeDecode } from "./scm-slang/src"
import { boolean$63$, car, cdr, circular$45$list$63$, cons, dotted$45$list$63$, last$45$pair, list$45$tail, null$63$, number$63$, pair$63$, proper$45$list$63$, set$45$cdr$33$, vector$63$ } from "./scm-slang/src/stdlib/source-scheme-library"
import { ErrorType, Result, SourceError } from "../../types"
import { List, Pair } from "../../stdlib/list"
import { Representation } from "../mapper"

export function mapResultToScheme(res: Result): Result {
if (res.status === "finished" || res.status === "suspended-non-det") {
return {
...res,
value: decodeValue(res.value),
representation: showSchemeData(res.value)
}
}
return res
}

// Given an error, decode its message if and
// only if an encoded value may exist in it.
export function mapErrorToScheme(error: SourceError): SourceError {
if (error.type === ErrorType.SYNTAX) {
// Syntax errors are not encoded.
return error
}
const newExplain = decodeString(error.explain())
const newElaborate = decodeString(error.elaborate())
return {
...error,
explain: () => newExplain,
elaborate: () => newElaborate
}
}

export function showSchemeData(data: any): Representation {
return schemeVisualise(decodeValue(data))
}

function decodeString(str: string): string {
return str.replace(/\$scheme_[\w$]+|\$\d+\$/g, match => {
return decode(match)
})
}

// Given any value, change the representation of it to
// the required scheme representation.
export function schemeVisualise(x: any): Representation {
// hack: builtins are represented using an object with a toString method
// and minArgsNeeded.
// so to detect these, we use a function that checks for these
function isBuiltinFunction(x: any): boolean {
return x.minArgsNeeded !== undefined && x.toString !== undefined
}
function stringify(x: any): string {
if (null$63$(x)) {
return '()'
} else if (x === undefined) {
return 'undefined'
} else if (typeof x === 'string') {
return `"${x}"`
} else if (number$63$(x)) {
return x.toString()
} else if (boolean$63$(x)) {
return x ? '#t' : '#f'
} else if (x instanceof Closure) {
const node = x.originalNode
const parameters = node.params.map(
(param: Identifier | RestElement) => param.type === "Identifier"
? param.name
: ". " + (param.argument as Identifier).name)
.join(' ')
.trim()
return `#<procedure (${parameters})>`
} else if (isBuiltinFunction(x) || typeof x === 'function') {
function decodeParams(params: string[]): string {
// if parameter starts with ... then it is a rest parameter
const convertedparams = params
.map(param => {
if (param.startsWith('...')) {
return `. ${param.slice(3)}`
}
return param
})
.map(decodeString)
return convertedparams.join(' ')
}
// take the name and parameter out of the defined function name
const name = decodeString(x.funName)
const parameters = decodeParams(x.funParameters)
return `#<builtin-procedure ${name} (${parameters})>`
} else if (circular$45$list$63$(x)) {
return '(circular list)'
} else if (dotted$45$list$63$(x) && pair$63$(x)) {
let string = '('
let current = x
while (pair$63$(current)) {
string += `${schemeVisualise(car(current))} `
current = cdr(current)
}
return string.trim() + ` . ${schemeVisualise(current)})`
} else if (proper$45$list$63$(x)) {
let string = '('
let current = x
while (current !== null) {
string += `${schemeVisualise(car(current))} `
current = cdr(current)
}
return string.trim() + ')'
} else if (vector$63$(x)) {
let string = '#('
for (let i = 0; i < x.length; i++) {
string += `${schemeVisualise(x[i])} `
}
return string.trim() + ')'
} else {
return x.toString()
}
}

// return an object with a toString method that returns the stringified version of x
return new Representation(stringify(x))
}

// Given any value, decode it if and
// only if an encoded value may exist in it.
// this function is used to accurately display
// values in the REPL.
export function decodeValue(x: any): any {
// helper version of list_tail that assumes non-null return value
function list_tail(xs: List, i: number): List {
if (i === 0) {
return xs
} else {
return list_tail(list$45$tail(xs), i - 1)
}
}

if (circular$45$list$63$(x)) {
// May contain encoded strings.
let circular_pair_index = -1
const all_pairs: Pair<any, any>[] = []

// iterate through all pairs in the list until we find the circular pair
let current = x
while (current !== null) {
if (all_pairs.includes(current)) {
circular_pair_index = all_pairs.indexOf(current)
break
}
all_pairs.push(current)
current = cdr(current)
}

// assemble a new list using the elements in all_pairs
let new_list = null
for (let i = all_pairs.length - 1; i >= 0; i--) {
new_list = cons(decodeValue(car(all_pairs[i])), new_list)
}

// finally we can set the last cdr of the new list to the circular-pair itself

const circular_pair = list_tail(new_list, circular_pair_index)
set$45$cdr$33$(last$45$pair(new_list), circular_pair)
return new_list
} else if (pair$63$(x)) {
// May contain encoded strings.
return cons(decodeValue(car(x)), decodeValue(cdr(x)))
} else if (vector$63$(x)) {
// May contain encoded strings.
return x.map(decodeValue)
} else if (x instanceof Closure) {
const newNode = estreeDecode(x.originalNode) as ArrowFunctionExpression

// not a big fan of mutation, but we assert we will never need the original node again anyway
x.node = newNode
x.originalNode = newNode
return x
} else if (typeof x === 'function') {
// copy x to avoid modifying the original object
const newX = { ...x }
const newString = decodeString(x.toString())
// change the toString method to return the decoded string
newX.toString = () => newString
return newX
} else {
// string, number, boolean, null, undefined
// no need to decode.
return x
}
}

Loading