1+ import { Environment } from 'js-slang/dist/types' ;
12import { Node } from 'konva/types/Node' ;
3+ import { cloneDeep } from 'lodash' ;
24
35import { Value } from './components/values/Value' ;
46import { Config } from './EnvVisualizerConfig' ;
57import {
68 Data ,
79 EmptyObject ,
810 Env ,
11+ EnvTree ,
12+ EnvTreeNode ,
913 FnTypes ,
1014 PrimitiveTypes ,
1115 ReferenceType
1216} from './EnvVisualizerTypes' ;
1317
18+ // TODO: can make use of lodash
1419/** checks if `x` is an object */
1520export function isObject ( x : any ) : x is object {
1621 return x === Object ( x ) ;
@@ -21,6 +26,21 @@ export function isEmptyObject(object: Object): object is EmptyObject {
2126 return Object . keys ( object ) . length === 0 ;
2227}
2328
29+ /** checks if `object` is `Environment` */
30+ export function isEnvironment ( object : Object ) : object is Environment {
31+ return 'head' in object && 'tail' in object && 'name' in object ;
32+ }
33+
34+ /** checks if `object` is `EnvTreeNode` */
35+ export function isEnvTreeNode ( object : Object ) : object is EnvTreeNode {
36+ return 'parent' in object && 'children' in object ;
37+ }
38+
39+ /** checks if `object` is `EnvTree` */
40+ export function isEnvTree ( object : Object ) : object is EnvTree {
41+ return 'root' in object ;
42+ }
43+
2444/** checks if `env` is empty (that is, head of env is an empty object) */
2545export function isEmptyEnvironment ( env : Env ) : env is Env & { head : EmptyObject } {
2646 return env === null || isEmptyObject ( env . head ) ;
@@ -93,7 +113,11 @@ export function getTextWidth(
93113) : number {
94114 const canvas = document . createElement ( 'canvas' ) ;
95115 const context = canvas . getContext ( '2d' ) ;
96- if ( ! context ) return 0 ;
116+
117+ if ( ! context || ! text ) {
118+ return 0 ;
119+ }
120+
97121 context . font = font ;
98122 const longestLine = text
99123 . split ( '\n' )
@@ -186,3 +210,42 @@ export function getNonEmptyEnv(environment: Env): Env {
186210 return environment ;
187211 }
188212}
213+
214+ /**
215+ * Given any objects, this function will find the underlying `Environment` objects
216+ * and perform copying of property descriptors from source frames to destination frames.
217+ * Property descriptors are important for us to distinguish between constants and variables.
218+ */
219+ export function copyOwnPropertyDescriptors ( source : any , destination : any ) {
220+ // TODO: use lodash cloneDeepWith customizer?
221+ if ( isFunction ( source ) || isPrimitiveData ( source ) ) {
222+ return ;
223+ }
224+ if ( isEnvTree ( source ) && isEnvTree ( destination ) ) {
225+ copyOwnPropertyDescriptors ( source . root , destination . root ) ;
226+ } else if ( isEnvTreeNode ( source ) && isEnvTreeNode ( destination ) ) {
227+ // recurse only on children and environment
228+ copyOwnPropertyDescriptors ( source . children , destination . children ) ;
229+ copyOwnPropertyDescriptors ( source . environment , destination . environment ) ;
230+ } else if ( isArray ( source ) && isArray ( destination ) ) {
231+ // recurse on array items
232+ source . forEach ( ( item , i ) => copyOwnPropertyDescriptors ( item , destination [ i ] ) ) ;
233+ } else if ( isEnvironment ( source ) && isEnvironment ( destination ) ) {
234+ // copy descriptors from source frame to destination frame
235+ Object . defineProperties ( destination . head , Object . getOwnPropertyDescriptors ( source . head ) ) ;
236+ // recurse on tail
237+ copyOwnPropertyDescriptors ( source . tail , destination . tail ) ;
238+ }
239+ }
240+
241+ /**
242+ * creates a deep clone of `EnvTree`
243+ *
244+ * TODO: move this function to EnvTree class
245+ * so we can invoke like so: environmentTree.deepCopy()
246+ */
247+ export function deepCopyTree ( value : EnvTree ) : EnvTree {
248+ const clone = cloneDeep ( value ) ;
249+ copyOwnPropertyDescriptors ( value , clone ) ;
250+ return clone ;
251+ }
0 commit comments