-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlanguage.coffee
1298 lines (1057 loc) · 44.8 KB
/
language.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module = angular.module 'vislang'
module.factory 'interpreter', ($q, $http, $timeout, $rootScope) ->
schema_version = 2
eval_expression = (expression) -> eval "(#{expression})"
make_index_map = (objects, attribute) ->
result = {}
for obj in objects
result[obj[attribute]] = obj
result
clone_endpoint = (endpoint) ->
node:endpoint.node
nib:endpoint.nib
last = (list) -> list[list.length-1]
### EXCEPTION TYPES ###
class RuntimeException
constructor: (@message) ->
class Exit extends RuntimeException
constructor: -> @message = "Exit Signal"
class InputError extends RuntimeException
constructor: -> @message = "Cancelled execution due to lack of input"
class NotConnected extends RuntimeException
constructor: (@message="Something in the program is disconnected") ->
class NotImplemented extends RuntimeException
constructor: (@name) -> @message = "JavaScript \"#{@name}\" is not implemented"
class CodeSyntaxError extends RuntimeException
constructor: (@name, @exception) -> @message = "#{exception} in builtin \"#{@name}\": "
### Exception types that should never happen unless the IDE is borked ###
class UnboundLambdaException extends RuntimeException
### RUNTIME ###
class Thread
constructor: (@runtime, @id) ->
@traces = []
@state = @runtime.state
@graphics_element = @runtime.graphics_element
trace: (log) ->
@traces.push log
log: (message) ->
@runtime.log message, @id
addEventListener: ->
@runtime.addEventListener @, arguments...
setInterval: ->
@runtime.setInterval @, arguments...
class Runtime
constructor: ({@graphics_element, @definition}={}) ->
@log_messages = []
@event_handlers = []
@timers = []
@state = {}
@threads = []
cleanup: ->
for handler in @event_handlers
handler.element.removeEventListener handler.type, handler.handler
for timer in @timers
clearTimeout timer
setInterval: (thread, handler, output_index, delay) ->
#thread = @new_thread()
handle = => $rootScope.$apply =>
handler.call [], output_index, thread
timer = setInterval handle, delay
@timers.push timer
addEventListener: (thread, type, handler_subroutine, element, output_index=0) ->
#thread = @new_thread()
handler = (event) => $rootScope.$apply =>
handler_subroutine.call [event], output_index, thread
element.addEventListener type, handler
@event_handlers.push {element, handler, type}
log: (message, id=0) ->
formatted_message = "Thread #{id}: #{message}"
@log_messages.unshift formatted_message
console.log formatted_message
new_thread: ->
thread = new Thread @, @threads.length
@threads.push thread
thread
run: (nib) ->
thread = @new_thread()
@scope = make_scope
runtime: thread
inputs: @definition.user_inputs()
try
$timeout => execute runtime,
=> @definition.invoke @scope, nib # the magic
catch exception
if excepition instanceof InputError
runtime.log "Invalid JSON: #{exception.message}"
else
throw exception
### DEFINITION TYPES ###
class BaseType
toJSON: ->
type:@constructor.name
class Definition extends BaseType
constructor: ({@id, @text}={}) ->
@id ?= UUID()
all_definitions[@id] = @
fromJSON: -> @
initialize: -> @
toJSON: ->
_.extend super,
id:@id
text:@text
find_nib: (id) ->
for nib in @inputs.concat @outputs
return nib if nib.id is id
get_call_sinks: -> @inputs
get_value_sinks: -> []
find_uses: ->
graph for id, graph of all_definitions when graph instanceof Graph and graph.uses_definition @
make_scope = ({runtime, inputs, parent_scope}={inputs:[], parent_scope:null}) ->
child_scope = {}
runtime: runtime
inputs: inputs
input_values: {} # inputs of the graph
output_values: {} # outputs of functions
nodes: child_scope # makes sense for graphs
state: child_scope # makes sense for code
parent_scope: parent_scope
make_child_scope = (parent_scope, {inputs}={inputs:[]}) ->
scope = make_scope
runtime: parent_scope.runtime
inputs: inputs
parent_scope: parent_scope
scope
class Subroutine extends Definition
constructor: ({inputs, outputs, @stateful}={}) ->
super
@inputs = []
@outputs = []
@add_input data for data in (inputs or [])
@add_output data for data in (outputs or [])
user_inputs: ->
# TODO: these could be implemented to act as if they come from a Code node in a graph outside the called one
results = for input in @inputs
console.log "an input: #{input} #{input.text}"
do (input) -> ->
console.log "memoized #{input} #{input.text} with default value #{input.default_value}"
result = if input.default_value
input.default_value
else
input_name = if input.text then "\"#{input.text}\"" else "(nameless)"
prompt "Provide a JSON value for input #{input.index}: #{input_name}"
throw new Exit "cancelled execution" if result is null
try
window.JSON.parse result
catch exception
if exception instanceof SyntaxError
throw new InputError result
else
throw exception
results.push -> # for sequencer inputs
results
call: (inputs, output_index=0, runtime) ->
# This is how programs are meant to deal with subroutine literals
nib = @outputs[output_index]
wrapped_inputs = []
for input in inputs
do (input) ->
wrapped_inputs.push -> input
scope = make_scope
runtime: runtime
inputs: wrapped_inputs
@invoke scope, nib
delete_nib: (nib, group) ->
@[group] = _.without @[group], nib
for nib, index in @[group]
nib.index = index
###
delete_index = @[group].indexOf nib
@[group].splice delete_index, 1
for index in [delete_index...@[group].length]
@[group][index].index -= 1
###
add_nib: (group, the_class, data={}) ->
nib = new the_class _.extend data,
index:@[group].length
@[group].push nib
nib
delete_input: (nib) -> @delete_nib nib, 'inputs'
delete_output: (nib) -> @delete_nib nib, 'outputs'
add_input: (data={}) -> @add_nib 'inputs', Input, data
add_output: (data={}) -> @add_nib 'outputs', Output, data
toJSON: ->
_.extend super,
inputs:@inputs
outputs:@outputs
stateful:@stateful
# These return the nibs that should be used as sources or sinks when this object
# is used as a node in the grid.
get_node_sources: -> @inputs
get_node_sinks: -> @outputs
# These return nibs for when this object is used as the implementation of a node.
get_call_sinks: -> @add_stateful_nib sequencer_input_nib, @inputs
get_call_sources: -> @add_stateful_nib sequencer_output_nib, @outputs
add_stateful_nib: (nib, nibs) ->
unless @stateful
nibs
else
nibs.concat [nib]
evaluate: -> @
class Code extends Subroutine
constructor: ({@output_implementation}={}) -> super
toJSON: ->
_.extend super,
output_implementation:@output_implementation
invoke: (scope, output_nib, node={id:0}) ->
input_generators = scope.inputs
if @stateful
stateful_input = last input_generators
input_generators = input_generators[...-1]
ignore_if_disconnected stateful_input
meta =
runtime: scope.runtime
output_index: output_nib.index
inputs: input_generators
state: scope.nodes # Reusing this space as it is analogous.
# Code can use it as a scratch pad.
is_sequencer: output_nib is sequencer_output_nib
# Parse
try
output_function = @eval_code @output_implementation
catch exception
if exception instanceof SyntaxError or exception instanceof Error
throw new CodeSyntaxError @text, exception
else throw exception
throw new NotImplemented @text unless output_function
# Run
try
result = output_function meta, input_generators...
return result unless meta.is_sequencer
catch exception
if exception instanceof TypeError
throw new CodeSyntaxError @text, exception
else throw exception
get_content_id: ->
{
implementation:@output_implementation
type:'code'
@language
inputs:@inputs.length
outputs:@outputs.length
}
class CoffeeScript extends Code
eval_code: (code) -> eval window.CoffeeScript.compile code, bare:true if code
language: 'coffeescript'
class JavaScript extends Code
eval_code: eval_expression
language: 'javascript'
class Graph extends Subroutine
constructor: ->
super
@nodes = []
@connections = []
toJSON: ->
_.extend super,
nodes:@nodes
connections:@connections
get_name: -> @text
### RUNNING ###
invoke: (scope, output_nib) ->
@evaluate_connection scope, @, output_nib
evaluate_connection: (scope, to_node, to_nib) ->
### This helper will follow a connection and evaluate whatever it finds ###
connection = @find_connection 'to', to_node, to_nib
unless connection
connection_text = if to_node.implementation? then to_node.implementation.text else to_node.text
throw new NotConnected """Missing connection in "#{@text}" to node "#{connection_text}"."""
{node, nib, internal} = connection.from
scope.runtime.trace
scope: scope
graph: @
connection: connection
state: 'visiting'
if internal
if node is scope.lambda_node
result = scope.lambda_value_generators[nib.index]()
else
# TODO: this error doesn't need to exist. Make it impossible to make this kind of connection instead.
throw new UnboundLambdaException """Node '#{to_node.get_name()}' tried to receive input from Lambda '#{node.get_name()}' from outside its scope."""
else if node instanceof Graph
if nib.index not of scope.input_values
scope.input_values[nib.index] = scope.inputs[nib.index]()
result = scope.input_values[nib.index]
else
result = node.evaluate scope, nib
scope.runtime.trace
scope: scope
graph: @
connection: connection
state: 'evaluated'
value: result
return result
find_node: (id) ->
for node in @nodes
return node if node.id is id
find_connection: (direction, node, nib) ->
unless node? and nib?
console.log "what"
console.log "what"
### Use this to determine how nodes are connected ###
for connection in @connections
if connection[direction].node.id is node.id and connection[direction].nib.id is nib.id
return connection
undefined
delete_connections: (direction, node, nib) ->
@connections = _.reject @connections, (connection) ->
connection[direction].node is node and connection[direction].nib is nib
delete_node_connections: (node) ->
@connections = _.reject @connections, (connection) ->
connection.from.node is node or connection.to.node is node
delete_nodes: (nodes) ->
@connections = _.reject @connections, (connection) ->
connection.from.node in nodes or connection.to.node in nodes
@nodes = _.without @nodes, nodes...
export: ->
dependencies = @get_dependencies()
dependencies.schema_version = schema_version
dependencies
delete_input: (nib) ->
@delete_connections 'to', @, nib
super nib
delete_output: (nib) ->
@delete_connections 'from', @, nib
super nib
# these next four are only used by make_from right now
remove_node: (node) ->
@nodes = _.without @nodes, node
add_node: (node) ->
node.graph = @
@nodes.push node
remove_connection: (connection) ->
@connections = _.without @connections, connection
add_connection: (connection) ->
@connections.push connection
# -
uses_definition: (definition) ->
for node in @nodes
if node.implementation is definition
return true
definitions_used: ->
definitions = {}
for node in @nodes
implementation = node.implementation
definitions[implementation.id] = implementation
definitions
#get_content_id: ->
# TODO: cull out unreachable dependencies
#
#for
### probably outdated
get_dependencies: (dependencies={subroutines:{},builtins:{}}) ->
# TODO: UPDATE
dependencies.subroutines[@id] = @ if @id not of dependencies.subroutines
for id, node of @nodes
if node instanceof SubroutineApplication
child_dependencies = node.implementation.get_dependencies dependencies
_.extend dependencies.subroutines, child_dependencies.subroutines
_.extend dependencies.builtins, child_dependencies.builtins
else if node instanceof BuiltinApplication
dependencies.builtins[@id] = node.implementation
dependencies
subroutines_referenced: ->
# TODO: UPDATE
# TODO: turn this into a cleanup function
results = []
for output in @outputs
parent = output.get_connection()?.connection.output.parent
if parent
results.push parent.id if parent.type is 'function'
resuts = results.concat parent.subroutines_referenced()
return results
build_adjacency_list: ->
# clear prior data
for id, node of @nodes
node.adjacency_id = null
adjacency_list = []
# number and add self first
adjacency_list.push
node:@
connections:[]
@adjacency_id = 0
# number all the connected nodes in a predictable way, and add them to the list
input_queue = [].concat @outputs
while input_queue.length > 0
input = input_queue.shift()
node = input.get_node()
# NOTE: if node is a subroutine then we've reached ourselves again
if node instanceof Node and node.adjacency_id is null
item_count = adjacency_list.push
node:node
connections:[]
node.adjacency_id = item_count - 1 # length is offset by 1 from index
input_queue = input_queue.concat node.inputs
# record all the connections based on the consistent numbering scheme
for item in adjacency_list
nibs = if item.node instanceof Node then item.node.inputs else item.node.outputs
for input, input_index in nibs
node = input.parent
item.connections[input_index] = node.adjacency_id
adjacency_list
###
bust_node: (busting_node) ->
@remove_node busting_node
busting_scope = busting_node.implementation
# clone nodes
node_mapping = {}
for node in busting_scope.nodes
new_node = node.clone @
node_mapping[node.id] = new_node
# Points at the new clone of a node instead of the original
translate_endpoint = (endpoint) ->
node:node_mapping[endpoint.node.id]
nib:endpoint.nib
# clone internal connections
internal_connections = _.filter busting_scope.connections, (connection) ->
connection.from.node isnt busting_scope and connection.to.node isnt busting_scope
for connection in internal_connections
new Connection
graph:@
from:translate_endpoint connection.from
to:translate_endpoint connection.to
inbound_connections = _.filter @connections, (connection) ->
connection.to.node is busting_node
through_connections = []
for connection in inbound_connections
# find things that are connected to this nib and expand them in place of this connection
@remove_connection connection
nib = connection.to.nib
inner_connections = _.filter busting_scope.connections, (inner_connection) =>
inner_connection.from.nib is nib and inner_connection.from.node is busting_scope
for inner_connection in inner_connections
if inner_connection.to.node is busting_scope
through_connections.push
beginning_connection:connection
middle_connection:inner_connection
else
new Connection
graph:@
from:clone_endpoint connection.from
to:translate_endpoint inner_connection.to
for {beginning_connection, middle_connection} in through_connections
nib = middle_connection.to.nib
outer_connections = _.filter @connections, (outer_connection) =>
outer_connection.from.nib is nib and outer_connection.from.node is busting_node
for outer_connection in outer_connections
outer_connection.from = clone_endpoint beginning_connection.from
# Now that all the through-connections are handled, handle the normal outbound connections.
outbound_connections = _.filter @connections, (connection) =>
connection.from.node is busting_node
for connection in outbound_connections
nib = connection.from.nib
inner_connection = _.find busting_scope.connections, (connection) =>
connection.to.node is busting_scope and connection.to.nib is nib
if inner_connection
connection.from = translate_endpoint inner_connection.from
return _.values node_mapping
make_from: (old_graph, nodes) ->
### Build a subroutine out of nodes in another subroutine. ###
# move the nodes
for node in nodes
old_graph.remove_node node
@add_node node
# create a node for this in the old parent
new_node = new Call
graph:old_graph
position:V(0,0)
implementation:@
# classify the connections that touch these nodes
inbound_connections = []
outbound_connections = []
contained_connections = []
for connection in old_graph.connections
from_inside = connection.from.node in nodes
to_inside = connection.to.node in nodes
if from_inside and to_inside
contained_connections.push connection
else if from_inside
outbound_connections.push connection
else if to_inside
inbound_connections.push connection
# move the contained connections
for connection in contained_connections
old_graph.remove_connection connection
@add_connection connection
group_connections = (connections) ->
# Connections are grouped by their source, since one source can connect to multiple sinks.
# One sink cannot connect to multiple sources, so there is no need to group in the other direction.
groups = {}
for connection in connections
key = "#{connection.from.nib.id}-#{connection.from.node.id}"
groups[key] ?= []
groups[key].push connection
_.values groups
cross_threshhold = (connections, add_nib, direction, other_direction) =>
# We group connections from the same source to avoid creating extra nibs
for group in group_connections connections
new_nib = @[add_nib]()
for connection in group
# Create a new connection to the previous connection's target,
# which is now inside the new subroutine
data = graph:@
data[direction] = clone_endpoint connection[direction]
data[other_direction] =
node:@
nib:new_nib
new Connection data
# Modify the old connection to point to the newly created input
connection[direction] =
node:new_node
nib:new_nib
cross_threshhold inbound_connections, 'add_input', 'to', 'from'
cross_threshhold outbound_connections, 'add_output', 'from', 'to'
return new_node
###
find_strongly_connected_components: (collected={}, index=0)->
@index = index
index += 1
collected[@id] = @
for id,definition of @definitions_used()
unless definition.index
definition.find_strongly_connected_components collected, index
else
###
strongly_connected_components = null
tarjan = ->
index = 0
stack = []
indexed = {}
lowlink = {}
components = []
strongconnect = (graph) ->
indexed[graph.id] = index
lowlink[graph.id] = index
index += 1
# Traverse and index
stack.push graph
for id,dependency of graph.definitions_used()
if dependency instanceof Graph
if dependency.id not of indexed
strongconnect dependency
lowlink[graph.id] = Math.min lowlink[graph.id], lowlink[dependency.id]
else if dependency in stack
lowlink[graph.id] = Math.min lowlink[graph.id], indexed[dependency.id]
# See if we found a set
if lowlink[graph.id] is indexed[graph.id]
new_component = []
loop
node = stack.pop()
new_component.push node
if node.id is graph.id
if new_component.length > 1
components.push new_component
return
for id,definition of all_definitions
if definition instanceof Graph
if definition.id not in indexed
strongconnect definition
strongly_connected_components = components
components
class BoundLambda extends BaseType
constructor: ({@scope, @node}) ->
invoke: (output_nib, inputs, calling_scope) ->
### TODO - Re-evaluate this comment
Graph.evaluate_connection checks for these values in the scope,
which provide inputs from the calling graph to the implementing graph.
NOTE: it may not be possible to use a lambda within a lambda this way.
###
# Nodes inside a lambda value can reach nodes from the defining scope.
defining_scope = @scope
calling_scope.nodes.__proto__ = defining_scope.nodes
# Nodes inside a lambda can reach the lambda's input sources,
# in addition to the parent graph's.
angular.extend calling_scope,
# Inputs changes purpose. The 'inputs' that come in on this object
# are inputs to the function: that is, the implementation and the other input values.
# When we run the value in its defining scope, the graph inputs that are
# relevant are the defining graph's inputs, instead.
inputs: defining_scope.inputs
# This is how we get at the lambda's inputs.
lambda_value_generators: inputs
lambda_node: @node
# We now follow a connection from the node's output sinks in its defining graph.
@node.graph.evaluate_connection calling_scope, @node, output_nib
get_name: -> @node.get_name()
class Lambda extends Graph
constructor: ->
super
@implementation_input = new Input
text:'implementation'
id:@id
evaluate:(scope, node) ->
###
This is called on lambda values, which are kinda like subgraphs.
It returns a callable that is bound to the scope it was defined in,
not the scope it will be called in.
###
new BoundLambda
node: node
scope: scope
invoke: (calling_scope, output_nib, node) ->
inputs = calling_scope.inputs
implementation = inputs[0]()
implementation.invoke output_nib, inputs[1..], calling_scope
get_call_sinks: ->
[@implementation_input].concat @inputs
find_value = (text, type, collection=all_definitions) ->
for id, thing of collection
if thing instanceof type
if not thing.text and thing.value is text
return thing
make_value = (graph, position, user_input, force_string=false) ->
implementation = if user_input instanceof Definition
user_input
else
if force_string
(find_value user_input, Text) or new Text value:user_input
else
value = eval_expression user_input
if value instanceof String
(find_value value, Text) or new Text value:value
else
(find_value user_input, JSON) or new JSON value:user_input
new Value
graph:graph
position: position
implementation: implementation
class Type extends Subroutine
constructor: ->
super
@type_input = new Input
text:''
id:@id
get_call_sinks: -> [@type_input]
get_value_sinks: -> @inputs
get_call_sources: -> @inputs
evaluate: (scope, nib) ->
result = {}
for [value, nib] in _.zip scope.inputs,@inputs
result[nib.text] = value()
result
invoke: (nib, input_values, scope, node, runtime) ->
# TODO: UPDATE THIS
it = input_values[0]()
it[nib.text]
class Symbol extends Definition
evaluate: -> @id
get_content_id: ->
{
type:'symbol'
@id
}
class Literal extends Definition # Abstract
constructor: ({@value}={}) -> super
toJSON: ->
_.extend super,
value:@value
get_content_id: ->
{
@type
@value
}
#content_id: -> CryptoJS.SHA256(@text).toString(CryptoJS.enc.Base64)
class JSON extends Literal
type:'json'
evaluate: -> eval_expression @value
get_content_id: ->
class Text extends Literal
type:'text'
evaluate: -> @value
get_content_id: ->
{
type:'text'
@value
}
definition_classes = [
Graph
JavaScript
CoffeeScript
JSON
Text
Symbol
Lambda
Type
]
definition_class_map = make_index_map definition_classes, 'name'
### NODE TYPES ###
class Node extends BaseType# Abstract
constructor: ({@graph, @id, @position, @implementation}={})->
@id ?= UUID()
@graph.nodes.push @
delete: ->
@graph.delete_node_connections @
@graph.remove_node @
toJSON: ->
_.extend super,
id:@id
implementation_id:@implementation.id
position:@position
clone: (new_scope) ->
data = window.JSON.parse window.JSON.stringify @
old_id = data.id
data.id = UUID()
new_node = resurrect_node new_scope, data
new_node.old_id = old_id
new_node
virtual_inputs: (scope) ->
for sink in @get_node_sinks()
do (sink) => => @graph.evaluate_connection scope, @, sink
get_name: -> @implementation.text
class Call extends Node
evaluate: (parent_scope, output_nib) ->
scope = parent_scope.nodes[@id]
unless scope?
scope = parent_scope.nodes[@id] = make_child_scope parent_scope,
inputs: @virtual_inputs parent_scope
unless output_nib.id of scope.output_values
scope.output_values[output_nib.id] = @implementation.invoke scope, output_nib, @
scope.output_values[output_nib.id]
get_node_sinks: -> @implementation.get_call_sinks()
get_node_sources: -> @implementation.get_call_sources()
###
subroutines_referenced: ->
# TODO: UPDATE
return [] unless @implementation instanceof Graph
results = []
for input in @inputs
parent = input.get_connection()?.connection.output.parent
if parent
results.push parent.id if parent.type is 'function'
resuts = results.concat parent.subroutines_referenced()
return results
###
class Value extends Node
constructor: ->
super
@outputs = [value_output_nib]
@children = []
type:'value'
evaluate:(parent_scope, output_nib)->
@implementation.evaluate parent_scope, @
subroutines_referenced: -> []
get_node_sinks: -> @implementation.get_value_sinks()
get_node_sources: -> @outputs
# For lambdas
add_child: (node) ->
unless node in @children
node.lambda_node = @
@children.push node
remove_child: (node) ->
node.lambda_node = null
@children = _.without @children, node
toJSON: ->
_.extend super,
children: (child.id for child in @children)
class UnknownNode extends Node
constructor:(@position, type, text, @id) ->
@type = 'unknown'
@text = "Unknown #{type}: #{text}"
@inputs = []
@outputs = []
super
node_classes = [
Call
Value
]
node_class_map = make_index_map node_classes, 'name'
### OTHER TYPES ###
class Nib # Abstract. Do not instantiate
constructor: ({@text, @id, @index, @n_ary, @default_value}={}) ->
# Null nib id is allowed for value nodes
@id ?= UUID() unless @id is null
@n_ary ?= false
initialize: -> @
toJSON: ->
text:@text
id:@id
n_ary:@n_ary
default_value:@default_value
class Input extends Nib
class Output extends Nib
class Connection
constructor:({@graph, @from, @to, @id}={}) ->
@id ?= UUID()
@graph.connections.push @
@to.index ?= 0
@from.index ?= 0
throw "WTF" unless @from instanceof Object and @to instanceof Object
toJSON: ->
from:
nib:@from.nib.id
node:@from.node.id
index:@from.node.index
internal:@from.internal
to:
nib:@to.nib.id
node:@to.node.id
index:@to.node.index
internal:@to.internal
value_output_nib = new Output id:'value_output', index:0
sequencer_input_nib = new Input id:'sequencer_input', index:0, text:';'
sequencer_output_nib = new Output id:'sequencer_output', index:0, text:';'
is_input = (it) ->
is_input_class = it.nib instanceof Input
if it.node instanceof Graph or (it.node instanceof Call and it.node.implementation instanceof Type) or it.internal then is_input_class else not is_input_class
make_connection = (graph, {from, to}) ->
# check if it's an internal connection on a lambda
for connector in [to,from]
if connector.node.implementation instanceof Lambda and connector.node instanceof Value and connector.nib isnt value_output_nib
connector.internal = true