-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdatagraph.coffee
1591 lines (1300 loc) · 66.9 KB
/
datagraph.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
Walker = (require 'giraphe').default
uuid = require 'uuid'
{ EventEmitter } = require 'events'
_ = require './utilities.coffee'
debugging = require './debugging.coffee'
# I'll give $US 5,000 to the person who fucking *fixes* how Node handles globals inside modules. ಠ_ಠ
{ constructify, parameterizable, delegated
, passthrough, selfify, modifier
, terminal: term } = _
{ ENV, verbosity, is_silent, colour
, emergency, alert, critical, error, warning, notice, info, debug, verbose, wtf } = debugging
# API entry-point
# ===============
module.exports =
Paws =
utilities: _
debugging: debugging
Paws.debugging.infect Paws
# Core data-types
# ===============
# The Paws object-space implements a single graph (in the computer-science sense) of homogeneous(!)
# objects. Each object, or node in that graph, is called a `Thing`; and is singly-linked² to an
# ordered list of other nodes.
#
# The first member of the metadata-links¹ on a `Thing` (referred to as the ‘noughtie’) is generally
# reserved for special use from within Paws; and thus Paws' lists are effectively one-indexed.
#
# In addition to these links to other nodes that every `Thing` has, some `Thing`s carry around
# additional information; these are implemented as additional JavaScript types, such as `Label`
# (which carries around identity, and a description in the form of a Unicode string) or `Execution`
# (which encapsulates procedure and execution-status information.)
#
# The Paws model is to consider that underlying information as ‘data’ (the actual *concerns* of a
# Paws program), and the links *between* those data as ‘metadata’; describing **the relationships
# amongst** the actual data.
#
# Although objects appear from within Paws to be ordered lists of other objects; they are often
# *treated* as ersatz key-value-dictionaries. To this purpose, a single key-value ‘pair’ is
# often represented as a list-`Thing` containing only a key `Label`, and the associated value.
#
# Each (type of) object also has a `receiver` associated with it, involved in the evaluation
# process; an `Execution` for a procedure that receives messages ‘sent’ to the object in question.
# This defines the (default) action taken when the object is, for example, the subject on the left-
# hand-side of a simple expression.
#
# The default receiver for a flavour of object depends on the additional data it carries around
# (that is, the JavaScript type of the node). For instance, the default receiver for plain `Thing`s
# (those carrying no additional data around) is an equivalent to the `::find` operation; that is, to
# treat the subject-object as a dictionary, and the message-object as a key to search that
# dictionary for. The default for any object, however, can be overridden per-object, changing how a
# given node in the graph responds to `Combination`s with other objects. (See the documentation for
# `Execution` for more exposition on the evaluation model.)
#
# ---- ---- ----
#
# 1. The links from one `Thing` to another are encoded as `Relation` objects, which are further
# annotated with the property of **‘ownership’**: an object that ‘owns’ an object below it in the
# graph is claiming that object as a component of the *overall data-structure* that the parent
# object represents. (Put simply: a series of ownership-annotated links in the datagraph describe a
# single data-structure as a subgraph thereof.)
#
# 2. Note that although Paws objects are, by default, singly-linked, each `Thing` *also* includes
# separate reverse-links to all of the `Thing`s that ‘own’ it, to facilitate responsibility
# calculations. (Although actual ownership flows only-downwards along the graph, responsibility
# existing lower on the graph can still preclude owning ancestors from being adopted; so these back-
# links are maintained on those descendants as hints that they have adopted.)
#
# ---- ---- ----
#
# Many Paws operations change the data-graph; *any* of these operations can, thus, also effectively
# mutate responsibility. Libside, this is handled by the user: ‘<operation> assumes you've taken
# responsibility for its arguments’, or ‘<operation> will return when responsibility is available.’
# This becomes a little more complicated when calling from JavaScript, however.
#
# While *everything* is effectively asynch in Paws (that is, after all, sort of the point!), most
# things happening within a reactor-tick (or in a consuming/embedding application) are
# *synchronous*. This is problematic, both because there is no way to ‘block’ an operation mutating
# responsibility until such responsibility is available, and because when these operations are
# already being called from within the reactor-tick of a particular Paws operation, we can't
# retroactively change that operation to an `Operation['adopt']`! From another perspective,
# JavaScript operations happen atomically, in a single reactor-tick, from perspective of Paws
# operations.)
#
# This is exposed and communicated in this JavaScript API by partitioning available methods into
# three categories, and prefixing the names based on their behaviour. In general,
#
# 1. *underscore-prefixed methods* like `::_set` generally do no responsibility-checking, and must
# be used after explicitly checking that the required responsibility is available (or during a
# tick on a native that has responsibility for the relevant arguments);
#
# 2. *bare methods* like `::set` or `::dedicate` are still synchronous, but will preform
# responsibility-checking in the due course of their behaviour, and throw synchronously if they
# are unable to complete their operations or need to be placed into the operation-queue;
#
# 3. and *dollar-prefixed methods* like `::$dedicate` will return a `Promise`, and preform their
# behaviour *asynchronously*, placing themselves in the Paws operation-queue if necessary
# (meaning that, if called *during* a reactor-tick, these may effectively unstage and block the
# calling operation.)
#
# In general, underscore-prefixed methods may be considered ‘private’, as the bare methods will
# behave exactly the same, but with additional reasonableness-checks; and dollar-prefixed methods
# are often essentially analogues to libside primitives.
#
# ### Method summary:
# `Thing`s are obtained via ...
# - direct creation, `new Thing`, with a list of children,
# - by `::clone`ing an existing `Thing`,
# - or, as a convenience, with a provided JavaScript template, with `.construct`, below.
#
# Their `Relation`s to children are stored in an `Array`, manipulable ...
# - canonically, as an ordered set: `::at`, `::set`, `::push`, `::pop`, `::shift`, and `::unshift`;
# - or as a pseudo-dictionary, with ordered ‘pairs’ added by `::define` and queried by `::find`.
#
# Meanwhile, the data-structure ownership amongst a structure's elements is exposed through:
# - `::own_at` (or `Relation::owned_by`), to create new membership in a data-structure;
# - `::disown_at` (or `Relation::contained_by`), to leave ownership-less links between nodes;
# - and `::is_owned_by` to query ownership relationships.
#
# > Of note, as another convenience: all methods that take a `Thing` and manipulate relationships,
# > can *also* take given a pre-constructed `Relation` indicating the desired relationship. It
# > won't be used directly, but the relationship will be imitated by the changes produced in the
# > data-graph:
# >
# > a_thing.set(1, another_thing.owned_by(a_thing))
# > # Equivalent to:
# > a_thing.set(1, another_thing)
# > a_thing.own_at(1)
Paws.Thing = Thing = parameterizable class Thing extends EventEmitter
constructor: constructify(return:@) (elements...)->
@id = uuid.v4()
@metadata = new Array
@owners = new Array
@custodians = { direct: [], inherited: [] }
@supplicants = { direct: [], inherited: [] }
@push elements... if elements.length
@metadata.unshift undefined if @_?.noughtify != no
# Constructs a generic ‘key/value’ style `Thing` from a `representation` (a JavaScript `Object`-
# hash) thereof. This convenience method expects arguments constructed as pairs of 1. any string
# (as the key, which will be converted into the `Label`), and 2. a Paws `Thing`-subclass (as the
# value.) These may be nested.
#
# > For instance, given `{foo: thing_A, bar: thing_B}`, `construct()` will product a `Thing`
# resembling the following (disregarding noughties):
#
# ((‘foo’, thing_B), (‘bar’, thing_B))
#
# The ‘pair-ish’ values are always owned by their container; as are, by default, the ‘leaf’
# objects passed in. (The latter is a behaviour configurable by `.with(own: no)`.)
#
# @option own: Construct the structure as as `own`ing newly-created sub-Things
# @option names: `rename` the constructed `Thing`s according to the key they're being assigned to
@construct: (representation)->
pairs = for key, value of representation
if _.isFunction value
value = Native.synchronous value
else unless value instanceof Thing or value instanceof Relation
value = @construct value
leave_ownership_alone = value instanceof Relation
should_own = @_?.own ? (if leave_ownership_alone then undefined else yes)
value.rename key if @_?.names
Thing.pair key, value, should_own
return Thing.with(own: yes) pairs...
# ### Common ###
# Creates a copy of the `Thing` it is called on. Alternatively, can be given an extant `Thing`
# copy this `Thing` *to*, over-writing that `Thing`'s metadata. In the process, the
# `Relation`s within this relation are themselves cloned, so that changes to the new clone's
# ownership don't affect the original.
clone: (to)->
to ?= new Thing.with(noughtify: no)()
to.name = @name unless to.name?
to.metadata = @metadata.map (rel)->
rel = rel?.clone()
rel?.from = to
rel
return to
compare: (to)-> to == this
# ### Shared, private methods ###
# Private; implements the pre-checking-and-throwing behaviour for *public* methods that
# eventually call `_add_parent_and_inherit_custodians()`. Expects a pre-constructed array.
_validate_availability_to: (custodians)->
unless @available_to custodians...
# FIXME: Add more useful debugging information
throw new ResponsibilityError(
"Attempt to add Thing held under conflicting responsibility.")
# XXX: N.B., when modifying ownership-mutation: Multiple Relations `from` and `to` the *same pair
# of Things* can exist in `@owners`, because they can exist in the `@metadata` of the
# parent, and one of them could be deleted, leaving the second.
# Private; assumes the caller has checked availability, expects a safe (unused) `Relation`.
_add_parent_and_inherit_custodians: (rel)->
@owners.push rel unless _.contains @owners, rel
rel.from._all_custodians().forEach (li)=>
@_add_custodian li
return this
# Private; does several useful things:
#
# - Remove a passed parent from the `owners` array,
# - check the *other* owners for all responsibility inherited through the removed owner,
# - and only then remove any *no-longer-reachable* `custodians`.
#
# N.B.: May only be called on a Relation *actually present as an owner* (i.e. call only on
# `rel.to`, and after checking `rel.owns`.)
_del_parent_and_inherited_custodians: (rel)->
_.pull @owners, rel
rel.from._all_custodians().forEach (li)=>
unless _.any(@owners, (owner)=> _.includes owner._all_custodians(), li )
@_del_custodian li
return this
# Private; implements the guts of dedication for internal methods.
_add_custodian: (li)->
fam = if this is li.ward then 'direct' else 'inherited'
unless _.includes @custodians[fam], li
@custodians[fam].push li
# Private; implements the guts of emancipation for internal methods.
_del_custodian: (li)->
fam = if this is li.ward then 'direct' else 'inherited'
_.pull @custodians[fam], li
# ### ‘Array-ish’ metadata manipulation ###
at: (idx)-> @metadata[idx]?.to
# Directly set the child at a particular index to the passed value.
#
# @arg {Number} idx Index on the receiver's metadata to change
# @arg {(Relation|Thing)} it Node to place at the given index
# @returns {Relation} New relation placed at idx
# @throws ResponsibilityError If the new child is to be owned by the parent, but is not available
# to one of the parent's custodian-Executions
#---
# TODO: Async `::$set`.
set: (idx, arg)->
unless arg?
return @_set idx, undefined
rel = new Relation this, arg
if rel.owns
rel.to._validate_availability_to @_all_custodians()
return @_set idx, rel
# Private; directly assigns another `Thing` to an index-specified location in the receiver's
# metadata. Assumes the caller has checked availability, expects a safe (brand-new / unused)
# `Relation`.
#
# @see set
_set: (idx, rel)->
prev = @metadata[idx]
if rel?.owns
rel.to._add_parent_and_inherit_custodians rel
if prev?.owns
prev.to._del_parent_and_inherited_custodians prev
@metadata[idx] = rel
return rel
# Append passed values to the receiver's metadata.
#
# Passed values can each be either a `Thing` (`foo.push(a_thing)`), or a `Relation` expressing
# the desired ownership of that value (`foo.push(a_thing.contained_by(foo))` or the same with
# `owned_by`, for instance). The arguments need not be homogenous.
#
# You can configure the ownership of all the passed values, wholesale, by explicitly setting the
# `own:` option to either `true` or `false`:
#
# blah.with(own: yes).push(foo, bar, baz)
#
# If the `Thing` in question is affected by responsibility conflicting with that flowing from /
# through the receiver (that is, it is both marked as owned, and held with a conflicting license
# by other `Liability`), a `ResponsibilityError` will be thrown.
#
# @arg {...(Relation|Thing)} elements Values to be appended as children
# @returns {Thing} The receiver
# @throws ResponsibilityError If one of the new children is to be owned by the parent,
# but is not available to one of the parent's custodian-Executions
# @option own {Boolean} Value with which to override the ownership of the children
# @see unshift
#---
# TODO: Async `::$push`.
# TODO: There's gotta be some shortcut-fusion / lazy-evaluation methodology to squash the
# iteration here, and the iteration in `::_push`, into one iteration-pass ...
push: (elements...)->
relations = elements.map (it)=>
if it instanceof Relation
rel = it.clone()
rel.from = this
if it instanceof Thing
rel = new Relation this, it
rel?.owns = @_?.own if @_?.own?
return rel
unless _.isEmpty (custodians = @_all_custodians())
_.forEach relations, (rel)=>
if rel?.owns
rel.to._validate_availability_to custodians
return @_push relations
# Private; directly adds other `Thing`s to the end of the receiver's metadata. Assumes the caller
# has checked availability, expects an `Array` of safe (brand-new / unused) `Relations`.
#
# @see push
_push: (relations)->
_.forEach relations, (rel)=>
if rel?.owns
rel.to._add_parent_and_inherit_custodians rel
@metadata = @metadata.concat relations
return this
# XXX: pop() and shift(), being remove-only operations, currently are purely synchronous, with no
# possibility of failure. Theoretically, though, at least libside, removal is still a
# mutating operation, and *should* require ownership.
# Remove the ordinally-last `Thing` from the receiver's metadata relations, and returns it.
#
# If the departing `Thing` in question is affected by responsibility flowing from/through the
# receiver, it will be `emancipated()`.
#
# @returns {Thing} The removed entry
pop: ->
rel = @metadata.pop()
if rel?.owns
rel.to._del_parent_and_inherited_custodians rel
return rel?.to
# Remove the ordinally-first `Thing`, after the noughty, from the receiver's metadata relations;
# and returns it.
#
# If the departing `Thing` in question is affected by responsibility flowing from/through the
# receiver, it will be `emancipated()`.
#
# @returns {Thing} The removed entry
shift: ->
noughty = @metadata.shift()
rel = @metadata.shift()
@metadata.unshift noughty
if rel?.owns
rel.to._del_parent_and_inherited_custodians rel
return rel?.to
# Prepend passed values to the receiver's metadata, immediately following the noughty.
#
# Passed values can each be either a `Thing` (`foo.unshift(a_thing)`), or a `Relation` expressing
# the desired ownership of that value (`foo.unshift(a_thing.contained_by(foo))` or the same with
# `owned_by`, for instance). The arguments need not be homogenous.
#
# You can configure the ownership of all the passed values, wholesale, by explicitly setting the
# `own:` option to either `true` or `false`:
#
# blah.with(own: yes).unshift(foo, bar, baz)
#
# If the `Thing` in question is affected by responsibility conflicting with that flowing from /
# through the receiver (that is, it is both marked as owned, and held with a conflicting license
# by other `Liability`), a `ResponsibilityError` will be thrown.
#
# @arg {...(Relation|Thing)} elements Values to be prepended as children
# @returns {Thing} The receiver
# @throws ResponsibilityError If one of the new children is to be owned by the parent,
# but is not available to one of the parent's custodian-Executions
# @option own {Boolean} Value with which to override the ownership of the children
# @see push
#---
# TODO: Async `::$unshift`.
# TODO: cf. TODO on push() re: shortcut-fusion
unshift: (elements...)->
# FIXME: DRYME, this is all a duplicate of ::push
relations = elements.map (it)=>
if it instanceof Relation
rel = it.clone()
rel.from = this
if it instanceof Thing
rel = new Relation this, it
rel?.owns = @_?.own if @_?.own?
return rel
unless _.isEmpty (custodians = @_all_custodians())
_.forEach relations, (rel)=>
if rel?.owns
rel.to._validate_availability_to custodians
return @_unshift relations
# Private; directly adds other `Things` to the beginning-save-one of the receiver's metadata.
# Assumes the caller has checked availability, expects a disposable / brand-new `Array` of safe
# (also brand-new / unused) `Relations`.
#
# @see unshift
_unshift: (relations)->
_.forEach relations, (rel)=>
if rel?.owns
rel.to._add_parent_and_inherit_custodians rel
protect_noughty = @metadata.length != 0
noughty = @metadata.shift() if protect_noughty
@metadata = relations.concat @metadata
@metadata.unshift noughty if protect_noughty
return this
# ### ‘Dictionary-ish’ metadata manipulation ###
# Convenience method to create a ‘pair-ish’ `Thing` (one with only two members, the first of
# which is a `Label` ‘key.’)
#
# The created thing will own the label, but not the value, by default. This can be overriden by
# passing a third indicating whether to `own` the value.
#
# (N.B. that this cannot fail, despite modifying ownership — the parent is newly-created, and
# thus has no custodians.)
@pair: (key, value, own)->
pair = new Thing
pair._push Label(key).owned_by pair
pair._push value.contained_by(pair, own) if value
return pair
# A convenience to append a new ‘pair’ to the end of the receiver (thus “defining” a value, if
# the receiver is seen as a pseudo-dictionary.)
#
# The pair-`Thing` itself and `Label` are always owned by the receiver; but the `value` (although
# it defaults to being not-owned) can be configured by passing a Relation:
#
# blah.define('foo', bar.owned_by(blah))
#
# Availability of the `value` will be checked, and a `ResponsibilityError` will be thrown in the
# case of a conflict. (See `::push`.)
#---
# TODO: handling undefined values
define: (key, value)->
pair = Thing.pair key, value
@push pair.owned_by this
# This implements the core algorithm of the default jux-receiver; this algorithm is very
# crucial to Paws' object system:
#
# Working through the metadata in reverse, select those items whose *first* (not the noughty; but
# subscript-one) item `compare()`s truthfully to the searched-for key. Return them in the order
# found (thus, “in reverse”), such that the latter-most item in the metadata that was found to
# match is returned as the first match. For libside purposes, only this (the very latter-most
# matching item) is used.
#
# Of note, in this implementation, we additionally test *if the matching item is a pair*. For
# most *intended* purposes, this should work fine; but it departs slightly from the spec.
# We'll see if we keep it that way.
#---
# TODO: `pair` option, can be disabled to return the 'valueish' things, instead of the pairs
# TODO: `raw` option, to return the `Relation`s, instead of the wrapped `Thing`s
find: (key)->
key = new Label(key) unless key instanceof Thing
results = @metadata.filter (rel)->
rel?.to?.isPair?() and key.compare rel.to.at 1
_.pluck(results.reverse(), 'to')
# ### Ownership ###
# FIXME: Responsibility ;_;
own_at: (idx)->
if (prev = @metadata[idx])? and not prev.owns
@metadata[idx] = prev.as_owned()
@metadata[idx].to._add_parent_and_inherit_custodians @metadata[idx]
else prev
disown_at: (idx)->
if (prev = @metadata[idx])? and prev.owns
@metadata[idx] = prev.as_contained()
@metadata[idx].to._del_parent_and_inherited_custodians prev
@metadata[idx]
else prev
# FIXME: Okay, this naming is becoming a mess.
owns_at: (idx)-> @metadata[idx]?.owns
is_owned_by: (other)->
if other instanceof Relation
_.contains @owners, other
else
_.any @owners, 'from', other
#---
# XXX: At construct-time, this depends on `Relation` being defined. Sans hoisting (WHY DID I LAY
# THIS OUT THIS WAY?), these can't be exist until init-time — see `Thing._init_walkers`,
# `Thing._init_responsibility_walkers`, etc.
_walk: walk = undefined
# This returns a flat array of all the descendants of the receiver that satisfy a condition,
# supplied by an optional callback.
#
# The callback may explicitly return `false` to indicate a descendant should be excluded; or the
# sentinel value `Thing.abortIteration` to terminate the graph-walking early.
#
# The `descendants` return-value may be *passed in* as a pre-cache; any Things already existing
# in that cache will not be touched by this function. (Tread carefully: if the data-graph is
# modified between the *creation* of `descendants`, and the re-execution of this function, then
# that cache may no longer be valid!)
_walk_descendants: walk_descendants = undefined
@abortIteration: Walker.abortIteration
@_init_walkers: ->
Thing::_walk = walk =
new Walker class: Thing, key: 'id'
, edge: { class: Relation, extract_path: 'to' }
, inspector: Paws.inspect
Thing::_walk_descendants = walk_descendants =
walk -> _.compact(@metadata)
# ### Responsibility ###
is_adopted: ->
not _.isEmpty(@custodians.direct) or not _.isEmpty(@custodians.inherited)
is_supplicated: ->
not _.isEmpty(@supplicants.direct) or not _.isEmpty(@supplicants.inherited)
_any_custodian: (f)->
_.any(@custodians.direct, f) or
_.any(@custodians.inherited, f)
_any_supplicant: (f)->
_.any(@supplicants.direct, f) or
_.any(@supplicants.inherited, f)
_all_custodians: (f)->
custodians = _(@custodians.direct).concat(@custodians.inherited).uniq()
if f?
_.all custodians, f
else
custodians.value()
_all_supplicants: (f)->
supplicants = _(@supplicants.direct).concat(@supplicants.inherited).uniq()
if f?
_.all supplicants, f
else
supplicants.value()
# Returns a walker that only walks descendants *with custodians*.
_walk_adopted: walk_adopted = undefined
@_init_responsibility_walkers: ->
Thing::_walk_adopted = walk_adopted =
walk_descendants -> @is_adopted()
# Indicates success if the passed `Execution` *already* holds the indicated license (or a greater
# one) for the receiver. (For instance, if the `Execution` holds write-license to a parent of the
# receiver, then `belongs_to(exe, 'read')` would indicate success.)
#
# If passed a `Liability` instead, this object is simply checked for the presence of that
# specific `Liability`.
#---
# TODO: Handle being passed 1. an Execution but no License at all, or 2. a Liability *and* a
# License
belongs_to: (it, license)->
return false unless @is_adopted()
if license?
if license is 'write' or license is yes
return @_any_custodian (liability)->
return true if liability.custodian is it and
liability.write() is yes
else
return @_any_custodian (liability)->
return true if liability.custodian is it
else
return _.includes(@custodians.direct, it) or
_.includes(@custodians.inherited, it)
# Private helper that checks *only* the receiver's custodians for conflicts
_directly_available_to: (liability)->
return true if @belongs_to liability.custodian, liability.write()
if liability.write()
return false if @_any_custodian (li)=> li.custodian != this
else
return false if @_any_custodian (li)=> li.custodian != this and li.write()
return true
# Determines if the receiver *can* be `dedicate`-ed to the passed `Liability`.
#
# Returns `true` if all owned-descendants of the receiver are available for adoption (i.e. have
# no conflicting responsibility); and `false` if the receiver or one of its descendants *cannot*
# be adopted (i.e. currently has some form of conflicting responsibility.)
#
# This is, of course, checked as a part of `dedicate`; so explicitly calling this method is
# usually only necessary if something being available for adoption *changes the decision of what
# to adopt*. (Or, possibly, adopting across reactor ticks?)
#---
# TODO: This *badly* needs to share a cache with `::dedicate`, as the previous implementation did
# TODO: Specify how this handles being given an *unrelated* (or blank) Liability
available_to: (liabilities...)->
liabilities = liabilities[0] if _.isArray liabilities[0]
# First, check this object itself,
return false unless _.every liabilities, (li)=> @_directly_available_to li
# Then, depth-first traverse every *owned child*
walk_result = @_walk_descendants ->
return Thing.abortIteration unless _.every liabilities, (li)=> @_directly_available_to li
return (false != walk_result)
# When passed an existing `descendants` object, this assumes you obtained that by already having
# checked their availability via `::available_to`.
#---
# FIXME: The constant `uniq`'ing is going to also be slow: need to collect that into a single
# event after any modifications? Ugh, I need `Set`. /=
_dedicate: (liability)->
unless _.includes @custodians.direct, liability
_.values(@_walk_descendants()).forEach (descendant)=>
family = if descendant is liability.ward then 'direct' else 'inherited'
descendant.custodians[family].push liability
descendant.custodians[family] = _.uniq descendant.custodians[family]
# FIXME: DOCME
# TODO: Make this accept an Execution directly, as a convenience
dedicate: (liabilities...)->
liabilities = liabilities[0] if _.isArray liabilities[0]
return false unless _.every liabilities, (li)=> @available_to li
liabilities.forEach (li)=> @_dedicate li
return true
# The inverse of `::_dedicate`, this removes an existing `Liability` from the receiver (and its
# owned-descendants.)
#
# Returns `true` if the `Liability` was successfully removed (or if the receiver didn't belong to
# it in the first place).
#
# Nota bene: This can *only* remove responsibility *from the root node of the adopted sub-graph*.
# It will return truthfully if the `Liability` on which it is called is not in the
# directly-responsible `custodians` for the receiver; this also applies if the passed
# `Liability` roots on another node! You probably want `Liability::discard`, which
# calls this method.
_emancipate: (liability)->
return true unless _.includes @custodians.direct, liability
@_walk_descendants -> @_del_custodian liability; undefined
return true
# DOCME
# XXX: At the moment, `_emancipate` cannot return false; so this isn't properly transactional. If
# there's any way for emancipation to fail, though, it's important to fix this method to
# *not actually remove the custodians* until the efficacy of the operation can be verified.
emancipate: (liabilities...)->
liabilities = liabilities[0] if _.isArray liabilities[0]
return _.all liabilities, (liability)=> @_emancipate liability
# This adds a passed `Liability` into the receiver's `supplicants` array, indicating that the
# `Execution` needs to be resumed when it will be able to successfully obtain responsibility for
# the receiver.
#
# This should only be called after `::dedicate` has been called, and has indicated failure; this
# is handled for you by ... # FIXME: DOCME
supplicate: (liability)->
@supplicants.push liability
# ### Utility / convenience ###
# TODO: Option to include the noughty
toArray: (cb)-> @metadata.slice(1).map (rel)-> (cb ? _.identity) rel?.to
# This ascertains if the receiver is “pair-shaped” — has precisely two non-metadata elements, the
# first of which is a Label, and the second of which isn't undefined.
#---
# FIXME: This almost certainly *shouldn't* exist publically at all; and especially shouldn't be
# central to the crucial `find()` algorihtm. D:
isPair: -> Boolean (@metadata.length is 3) and @metadata[1].to instanceof Label and @metadata[2]
keyish: -> @at 1
valueish: -> @at 2
# Convenience methods to create `Relation`s *to* the receiver.
contained_by: (other, own)-> new Relation other, this, own # Defaults to `no`
owned_by: (other)-> new Relation other, this, yes
rename: selfify (name)-> @name = name
# ### Initialization ###
@_init: ->
@_init_walkers()
@_init_responsibility_walkers()
# The default receiver for `Thing`s simply preforms a ‘lookup.’
Thing::receiver = new Native (params)->
caller = params.at 0
subject = params.at 1
message = params.at 2
results = subject.find message
if results[0]
caller.respond results[0].valueish()
# FIXME: Welp, this is horrible error-handling. "Print a warning and freeze forevah!!!"
else
notice "~~ No results on #{Paws.inspect subject} for #{Paws.inspect message}."
.rename 'thing✕'
# A `Label` is a type of `Thing` which encapsulates a static Unicode string, serving two purposes:
#
# 1. Unique-comparison: Two `Label`s originally created with equivalent sequences of Unicode
# codepoints will `::compare` as equal. Across codebases, `Label`s share identity, even when they
# don't share objective content / metadata relationships.
#
# That is: `foo ['bar']` will find the same object assigned with a different instance of `'bar'`.
# 2. Description: Although not designed to be used to manipulate character-data as a general
# string-type, `Label`s can be used to associate an arbitrary Unicode sequence with some other
# data, as a name or description. (Hence, ‘label.’)
#
# ---- ---- ----
#
# Due to their intented use as descriptions and comparators, the string-data associated with a
# `Label` cannot be mutated after creation; and they cannot be combined or otherwise manipulated.
# However, they *can* be `::explode`d into an ordered-list of individual codepoints, each
# represented as a new `Label`; and then manipulated in that form. These poor-man's mutable-strings
# (colloquially called ‘character soups’) are provided as a primitive form of string-manipulation.
Paws.Label = Label = class Label extends Thing
constructor: constructify(return:@) (@alien)->
if @alien instanceof Label
@alien.clone this
clone: (to)->
super (to ?= new Label)
to.alien = @alien
return to
compare: (to)->
to instanceof Label and
to.alien == @alien
# FIXME: JavaScript's `split()` doesn't handle wide-character (surrogate in UTF-16, or 4-byte in
# UTF-8) Unicode -very-well.- (at all!)
explode: ->
it = new Thing
it.push.apply it, _.map @alien.split(''), (char)-> new Label char
it
# *Programs* in Paws are a series of nested sequences-of-Paws-objects. For programs that originate
# as text (not all Paws programs do!), those `Thing`s are created at parse-time; Paws doesn't
# operate on text (or an intermediat representation thereof) in the way that many programming-
# languages do.
#
# The primary way you interact with code in Paws, is this, the `Execution`. An `Execution`, however,
# doesn't represent a simple invocable procedure, the way a `Function` might in JavaScript; instead,
# it represents the *partial completion thereof*. When you invoke a Paws `Execution`, you're not
# spawning a wholesale evaluation of that procedure, but rather *continuing* a previous evaluation.
#
# Each time you invoke an `Execution`, the script is advanced a single step to produce a new
# computational task, in the form of a ‘combination:’ a subject-object, and a message-object to be
# sent to it. The `receiver` (another `Execution`, see the above `Thing` documentation) of the
# *subject* will then be invoked in turn, and handed the message-object. (This means that each
# intentional step in a Paws program necessarily involves at *least* two effective steps; the
# invocation of the `Execution` intended, and then the invocation of the resulting subject's
# `receiver`.)
#
# Obviously, however, as Paws is an asynchronous language, *any amount* of actual work can happen
# following an instigator's invocation of an `Execution`; for instance, although the default-
# receivers are all implemented as `Native`s, completable in a single invocation as an atomic
# operation ... a custom receiver could preform any amount of work, before producing a result-value
# for the combination that caused it.
#
# Further, a crucial aspect of Paws is the ability to *branch* `Execution`-flow. This is acheived by
# `::clone`ing a partially-evaluated `Execution`. As the original procedure progresses, its
# instructions are gradually processed and discarded; meanwhile, however, an earlier clone's will
# *not* be. When so-branched, an `Execution`'s state is all duplicated to the clone, unaffected by
# changes to the original `Execution`'s position or objective-context.
#
# **Nota bene:** While clones do not share *additions* to their respective context, `locals`, the
# clone made is *shallow in nature*. This means that the two `Execution`s' `locals`
# objects share the original assignments (that is, the original pairs) created prior
# to the cloning. This further means that either branch can modify an existing
# assignment-pair on `locals` instead of `::define`ing a new, overriding pair, thus
# making the change visible to prior clones, if desired.
#
# ---- ---- ----
#
# This implementation stores the `Execution` information as three crucial elements:
#
# 1. A stack of positions in a `Script`, as `Position`s in the `instructions` array,
# 2. a further stack of the `results` of outer `instruction`s,
# 3. and its objective evaluation context, a `Thing` accessible as `locals`.
#
# The position is primarily maintained by `::advance`; diving into and climbing back out of sub-
# expressions to produce `Combination`s for the reactor. As it digs into a sub-expression, the
# position in the outer `Expression` is maintained in the `instructions` stack; while the results of
# the last `Combination` for each outer expression are correspondingly stored in `results`.
#
# **Nota anche: The `instructions` stack lists *completed* nodes; ones for which a `Combination` has
# already been generated.)**
Paws.Execution = Execution = class Execution extends Thing
constructor: constructify (@begin)->
if typeof @begin == 'function' then return Native.apply this, arguments
@begin = new Position @begin if @begin? and not (@begin instanceof Position)
if @begin
@results = [ null ]
@instructions = [ @begin ]
else
@results = [ ]
@instructions = [ ]
@pristine = yes
@locals = new Thing().rename 'locals'
@locals.define 'locals', @locals
this .define 'locals', @locals.owned_by this
@ops = new Array
@wards = new Array
return this
# ### Common ###
# This method of the `Execution` types will copy all data relevant to advancement of the
# execution to a `Execution` instance. This includes the pristine-ness (boolean), the `results`
# and `instructions` stacks (or for a `Native`, any `bits`.) A clone made thus can be advanced
# just as the original would have been, without affecting the original's position.
#
# Of note: along with all the other data copied from the old instance, the new clone will inherit
# the original `locals`. This is intentional.
#---
# TODO: nuke-API equivalent of lib-API's `branch()()`
clone: (to)->
super (to ?= new Execution)
to.pristine = @pristine
# FIXME: Remove old 'locals' from the Exec's cloned metadata?
to.locals = @locals.clone().rename 'locals'
to.define 'locals', to.locals.owned_by to
to.advancements = @advancements if @advancements?
if @instructions? and @results?
to.instructions = @instructions.slice 0
to.results = @results.slice 0
return to
# ### Operation-queue ###
# Pushes a new `Operation` onto this `Execution`'s `ops`-queue.
queue: (something)->
return @queue new Operation arguments... unless something.op?
@ops.push something
# A convenience method for pushing an 'advance' `Operation`, specifically.
respond: (response)->
@queue new Operation 'advance', arguments...
# This informs an `Execution` of the ‘result’ of the last `Combination` returned from `next`.
# This value is stored in the `results` stack, and is later used as one of the values in furhter
# `Combination`s.
register_response: (response)-> @results[0] = response
# ### Position management and advancement ###
complete:-> !this.instructions.length
# Returns the *current* `Position`; i.e. the top element of the `instructions` stack.
current:-> @instructions[0]
# Returns the next `Combination` that needs to be preformed for the advancement of this
# `Execution`. This is a mutating call, and each time it is called, it will produce a new
# (subsequent) `Combination` for this `Execution`.
#
# It usually only makes sense for this to be called after a response to the *previous*
# combination has been signaled by `register_response` (obviously unless this is the first time
# it's being advanced.) This also accepts an optional argument, the passing of which is identical
# to calling `::register_response` with that value before-hand.
#
# For combinations involving the start of a new expression, `null` will be returned as one part
# of the `Combination`; this indicates no meaningful data from the stack for that node. (The
# reactor will interpret this as an instruction to insert this `Execution`'s `locals` into that
# combo, instead.)
advance: (response)->
return undefined if @complete()
@register_response response if response?
# If we're continuing to advance a partially-completed `Execution`, ...
completed = @instructions[0]
previous_response = @results[0]
if not @pristine
# Gets the next instruction from the current sequence (via `Position#next`)
@instructions[0] =
upcoming = completed.next()
# If we've completed the current sub-expression (a sequence.), then we're going to step out
# (pop the stack.) and preform the indirected combination.
if not upcoming?
outer_current_value = @results[1]
@instructions.shift(); @results.shift()
return new Combination outer_current_value, previous_response
# Discards the last response at the current stack-level, if this is the beginning of a new
# semicolon-delimited expression.
if upcoming.index is 0
@results[0] =
previous_response = null
it = upcoming.valueOf()
# If the current node is a `Thing`, we combine it against the top of the `results`-stack
# (either another Thing, or `null` if this is the start of an expression.)
if it instanceof Thing
upcoming_value = it
return new Combination previous_response, upcoming_value
# If it's not a `Thing`, it must be a sub-expression (that is, a `parse.Sequence`).
else
upcoming = new Position it
# If we've got what appears to be an empty sub-expression, then we're dealing with the
# special-case syntax for referencing the Paws equivalent of `this`. We treat this like
# a simple embedded-`Thing` combination, except with the current `Execution` as the
# `Thing` in question:
if not upcoming.valueOf()?
return new Combination previous_response, this
# Else, the ‘upcoming’ node is a real sub-expression, and we're going to ‘dive’ into it