diff --git a/Makefile b/Makefile index e2cd4db..29a0db8 100644 --- a/Makefile +++ b/Makefile @@ -19,10 +19,10 @@ MODULES := utility reactive graph model dfa plan enable system bind \ async hd qunit compile-dfa fn-worker utility_LOC := hd/ -utility_UNITS := adt helpers console schedule +utility_UNITS := adt helpers console schedule api reactive_LOC := hd/ -reactive_UNITS := observable property extensions promise ladder function logger +reactive_UNITS := observable property extensions promise accum ladder function logger api reactive_DEPS := utility graph_LOC := hd/ @@ -30,7 +30,7 @@ graph_UNITS := walker digraph cgraph sgraph stay graph_DEPS := utility reactive model_LOC := hd/ -model_UNITS := ids variable method constraint context eqn builder path command array +model_UNITS := ids variable method constraint context eqn builder path command array api model_DEPS := utility reactive dfa_LOC := hd/ @@ -46,18 +46,18 @@ enable_UNITS := egraph report check enable_DEPS := utility reactive graph system_LOC := hd/ -system_UNITS := pm activate topo +system_UNITS := pm activate topo api system_DEPS := utility reactive graph model plan enable bind_LOC := hd/ -bind_UNITS := binding text edit css select checked enable mouse position clicked time forEach when rx factory +bind_UNITS := binding text edit css select checked enable mouse position clicked key time forEach when rx factory api bind_DEPS := utility reactive model async_LOC := hd/ async_UNITS := worker ajax async_DEPS := utility reactive -hd_UNITS := api methods +hd_UNITS := api hd_DEPS := utility reactive model graph plan enable bind system qunit_UNITS := qunit.d utility reactive ladder graph model system @@ -83,10 +83,12 @@ howto_RES := style.css collapse.js spinner.gif ###################################################################### # Target definitions -TARGETS := hotdrink qunit compile-dfa fn-worker +TARGETS := hotdrink hotpdf qunit compile-dfa fn-worker hotdrink_MODS := utility reactive graph model dfa plan enable system bind async hd +hotpdf_MODS := utility reactive graph model dfa plan enable system hd + qunit_MODS := qunit compile-dfa_MODS := shebang $(hotdrink_MODS) compile-dfa @@ -164,7 +166,7 @@ help: $(TARGETS) : % : $(TARGET_DIR)/$$*.js .PHONY : -all : $(TARGETS) hotdrink.min howto +all : $(TARGETS) hotdrink.min hotpdf.min howto .PHONY : clean : @@ -197,14 +199,17 @@ $(TARGET_MAPS) : $(TARGET_DIR)/%.js.map : $(TARGET_DIR)/%.js ; .SECONDEXPANSION : $(TARGET_FILES) :: $(TARGET_DIR)/%.js : $$($$*_TGT_MAPS) $$($$*_TGT_FILES) | $(TARGET_DIR) - @if which -s mapcat ; then \ + @if which mapcat ; then \ echo mapcat -j $(TARGET_DIR)/$*.js -m $(TARGET_DIR)/$*.js.map \ $($*_TGT_MAPS) ; \ mapcat -j $(TARGET_DIR)/$*.js -m $(TARGET_DIR)/$*.js.map $($*_TGT_MAPS) ; \ else \ echo "make: *** Missing mapcat - cannot make scripts/$*.js.map" 1>&2 ; \ echo cat $($*_TGT_FILES) ">" $@ ; \ - cat $($*_TGT_FILES) > $@ ; \ + for file in $($*_TGT_FILES) ; do \ + cat "$${file}" ; \ + echo ; \ + done > $@ ; \ fi .SECONDEXPANSION : @@ -225,6 +230,14 @@ $(TARGET_DIR)/hotdrink.min.js : $(TARGET_DIR)/hotdrink.js uglifyjs $< -m -c warnings=false -o $@ +hotpdf.min : $(TARGET_DIR)/hotpdf.min.js + +$(TARGET_DIR)/hotpdf.min.js.map: $(TARGET_DIR)/hotpdf.min.js + +$(TARGET_DIR)/hotpdf.min.js : $(TARGET_DIR)/hotpdf.js + uglifyjs $< -m -c warnings=false -o $@ + + howto: $(howto_DEP_FILES) $(howto_FILES) $(howto_RES_FILES) $(howto_FILES) :: $(HOWTO_DIR)/$(PUBLISH_DIR)/%.html : $(HOWTO_DIR)/%.org publish-org.el | $(HOWTO_DIR)/$(TANGLE_DIR) $(HOWTO_DIR)/$(PUBLISH_DIR) diff --git a/src/hd/api.ts b/src/hd/api.ts index 56c7b8e..d4180cd 100644 --- a/src/hd/api.ts +++ b/src/hd/api.ts @@ -8,121 +8,72 @@ module hd { import s = hd.system; import b = hd.bindings; - export function arrayOf( elementType: m.ContextClass|m.ContextSpec ) { - return m.ArrayContext.bind( null, elementType ); + export + function id( x: T ): T { + return x; } - export var array = m.ArrayContext; - - /*================================================================== - * Enablement functions - */ - - export function markUsed( p: r.Promise ) { - p.usage.set( r.Usage.Used ); + export + function sum() { + var n = arguments[0]; + for (var i = 1, l = arguments.length; i < l; ++i) { + n+= arguments[i]; + } + return n; } - export function markUnused( p: r.Promise ) { - p.usage.set( r.Usage.Unused ); + export + function diff() { + var n = arguments[0]; + for (var i = 1, l = arguments.length; i < l; ++i) { + n-= arguments[i]; + } + return n; } - export function markDelayed( p: r.Promise ) { - p.usage.set( r.Usage.Delayed ); + export + function prod() { + var n = arguments[0]; + for (var i = 1, l = arguments.length; i < l; ++i) { + n-= arguments[i]; + } + return n; } - /*================================================================== - * Export - */ - - export var dateCompare = u.dateCompare; - export var ProxyObserver = r.ProxyObserver; - export var BasicObservable = r.BasicObservable; - export var Promise = r.Promise; - export var Translator = r.Extension; - export var liftFunction = r.liftFunction; - - export var dir = b.Direction; - export var bind = b.bind; - export var unbind = b.unbind; - export var performDeclaredBindings = b.performDeclaredBindings; - export var isObservable = b.isObservable; - export var isObserver = b.isObserver; - export var isExtension = b.isExtension; - - export var Variable = m.Variable; - export var Constraint = m.Constraint; - export var Method = m.Method; - export var Context = m.Context; - export var ArrayContext = m.ArrayContext; - export var MaxOptional = m.Optional.Max; - export var MinOptional = m.Optional.Min; - - // RunTime - export var PropertyModel = s.PropertyModel; - export var ContextBuilder = m.ContextBuilder; + export + function quot() { + var n = arguments[0]; + for (var i = 1, l = arguments.length; i < l; ++i) { + n/= arguments[i]; + } + } - // Bindings - export var Checked = b.Checked; - export var Click = b.Click; - export var CssClass = b.CssClass; - export var DblClick = b.DblClick; - export var Edit = b.Edit; - export var Enabled = b.Enabled; - export var MouseDown = b.MouseDown; - export var MouseUp = b.MouseUp; - export var MousePosition = b.MousePosition; - export var Position = b.Position; - export var Text = b.Text; - export var Time = b.Time; - export var Value = b.Value; + export + function max() { + var n: any; + for (var i = 0, l = arguments.length; i < l && n === undefined; ++i) { + n = arguments[i]; + } + for (; i < l; ++i) { + if (arguments[i] > n) { + n = arguments[i]; + } + } + return n; + } - // Factories - export var checked = b.checked; - export var click = b.click; - export var cssClass = b.cssClass; - export var dblclick = b.dblclick; - export var edit = b.edit; - export var editVar = b.editVar; - export var date = b.date; - export var dateVar = b.dateVar; - export var enabled = b.enabled; - export var forEach = b.forEach; - export var mousedown = b.mousedown; - export var mouseup = b.mouseup; - export var mousePosition = b.getMousePosition; - export var num = b.num; - export var numVar = b.numVar; - export var position = b.position; - export var text = b.text; - export var value = b.value; - export var when = b.when; + export + function min() { + var n: any; + for (var i = 0, l = arguments.length; i < l && n === undefined; ++i) { + n = arguments[i]; + } + for (; i < l; ++i) { + if (arguments[i] < n) { + n = arguments[i]; + } + } + return n; + } - // Extensions - export var chain = b.chain; - export var cn = b.cn; - export var dateToMilliseconds = b.dateToMilliseconds; - export var dateToDateString = b.dateToDateString; - export var dateToString = b.dateToString; - export var dateToTimeString = b.dateToTimeString; - export var def = b.def; - export var delay = b.delay; - export var exp = b.exp; - export var fix = b.fix; - export var fn = b.fn; - export var millisecondsToDate = b.millisecondsToDate; - export var msg = b.msg; - export var offset = b.offset; - export var path = b.path; - export var or = b.or; - export var pointToString = b.pointToString; - export var prec = b.prec; - export var rw = b.rw; - export var req = b.req; - export var round = b.round; - export var scale = b.scale; - export var stabilize = b.stabilize; - export var toDate = b.toDate; - export var toJson = b.toJson; - export var toNum = b.toNum; - export var toStr = b.toStr; } diff --git a/src/hd/bind/api.ts b/src/hd/bind/api.ts new file mode 100644 index 0000000..c7faa79 --- /dev/null +++ b/src/hd/bind/api.ts @@ -0,0 +1,83 @@ +module hd { + + import b = hd.bindings; + + + // Export + export var bind = b.bind; + export var unbind = b.unbind; + export var performDeclaredBindings = b.performDeclaredBindings; + export var Direction = b.Direction; + export var isObservable = b.isObservable; + export var isObserver = b.isObserver; + export var isExtension = b.isExtension; + + // Bindings + export var Change = b.Change; + export var Checked = b.Checked; + export var Click = b.Click; + export var CssClass = b.CssClass; + export var DblClick = b.DblClick; + export var Edit = b.Edit; + export var Enabled = b.Enabled; + export var KeyDown = b.KeyDown; + export var MouseDown = b.MouseDown; + export var MouseUp = b.MouseUp; + export var MousePosition = b.MousePosition; + export var Position = b.Position; + export var Text = b.Text; + export var Time = b.Time; + export var Value = b.Value; + + export var OnlyKey = b.OnlyKey; + + // Factories + export var checked = b.checked; + export var click = b.click; + export var cssClass = b.cssClass; + export var dblclick = b.dblclick; + export var edit = b.edit; + export var editVar = b.editVar; + export var date = b.date; + export var dateVar = b.dateVar; + export var enabled = b.enabled; + export var forEach = b.forEach; + export var mousedown = b.mousedown; + export var mouseup = b.mouseup; + export var mousePosition = b.getMousePosition; + export var num = b.num; + export var numVar = b.numVar; + export var position = b.position; + export var text = b.text; + export var value = b.value; + export var when = b.when; + + // Extensions + export var chain = b.chain; + export var cn = b.cn; + export var dateToMilliseconds = b.dateToMilliseconds; + export var dateToDateString = b.dateToDateString; + export var dateToString = b.dateToString; + export var dateToTimeString = b.dateToTimeString; + export var def = b.def; + export var delay = b.delay; + export var exp = b.exp; + export var fix = b.fix; + export var fn = b.fn; + export var millisecondsToDate = b.millisecondsToDate; + export var msg = b.msg; + export var offset = b.offset; + export var path = b.path; + export var or = b.or; + export var pointToString = b.pointToString; + export var prec = b.prec; + export var rw = b.rw; + export var req = b.req; + export var round = b.round; + export var scale = b.scale; + export var stabilize = b.stabilize; + export var toDate = b.toDate; + export var toJson = b.toJson; + export var toNum = b.toNum; + export var toStr = b.toStr; +} diff --git a/src/hd/bind/binding.ts b/src/hd/bind/binding.ts index 99931e1..169085f 100644 --- a/src/hd/bind/binding.ts +++ b/src/hd/bind/binding.ts @@ -84,9 +84,7 @@ module hd.bindings { export function isObserver( t: Target ): boolean { return (typeof t === 'object' && - typeof t.onNext === 'function' && - typeof t.onError === 'function' && - typeof t.onCompleted === 'function'); + typeof t.onNext === 'function'); } // Make sure object is observable @@ -102,8 +100,6 @@ module hd.bindings { function isExtension( t: Target ): boolean { return (typeof t === 'object' && typeof t.onNext === 'function' && - typeof t.onError === 'function' && - typeof t.onCompleted === 'function' && typeof t.addObserver === 'function' && typeof t.removeObserver === 'function'); } @@ -298,10 +294,10 @@ module hd.bindings { return scope; } try { - var elBindingsFn = new Function( functionBody ); - var elNestedBindings: any[] = elBindingsFn.call( scope ); + var elBindingsFn = new Function( 'model', functionBody ); + var elNestedBindings: u.MultiArray = elBindingsFn.call( el, scope ); var elBindings: Binding[] = []; - flatten( elNestedBindings, elBindings ); + u.multiArray.flatten( elNestedBindings, elBindings ); } catch (e) { console.error( "Invalid binding declaration: " @@ -345,24 +341,9 @@ module hd.bindings { * of the constructs John implemented. */ function compile( spec: string ): string { - return "with (this) {" + + return "with (model) {" + " return [" + spec + "]" + "}"; } - /*------------------------------------------------------------------ - * Flatten nested lists into a single list. - */ - - function flatten( from: any[], to: T[] ) { - for (var i = 0, l = from.length; i < l; ++i) { - if (Array.isArray( from[i] )) { - flatten( from[i], to ); - } - else { - to.push( from[i] ); - } - } - } - } diff --git a/src/hd/bind/clicked.ts b/src/hd/bind/clicked.ts index 1e08675..844ee1b 100644 --- a/src/hd/bind/clicked.ts +++ b/src/hd/bind/clicked.ts @@ -5,7 +5,6 @@ module hd.bindings { export class MouseDown extends r.BasicObservable { - value: any; el: HTMLElement; constructor( el: HTMLElement ) { @@ -22,7 +21,6 @@ module hd.bindings { export class MouseUp extends r.BasicObservable { - value: any; el: HTMLElement; constructor( el: HTMLElement ) { diff --git a/src/hd/bind/edit.ts b/src/hd/bind/edit.ts index 19fe117..780199b 100644 --- a/src/hd/bind/edit.ts +++ b/src/hd/bind/edit.ts @@ -16,14 +16,11 @@ module hd.bindings { * Observer/Observable for binding */ export - class Edit { + class Edit extends r.BasicObservable { // element bound to el: HTMLInputElement; - // stabilizer - stable: r.Stabilizer; - // value to change from on blur blurFrom: string = null; @@ -37,31 +34,19 @@ module hd.bindings { * Initialize and subscribe to HTML editing events. */ constructor( el: HTMLElement, scope: Scope, time_ms?: number ) { + super(); this.el = checkHtml( el, HTMLInputElement ); - this.stable = new r.Stabilizer( time_ms, flush ); el.addEventListener( 'input', this.update.bind( this ) ); el.addEventListener( 'change', this.onBlur.bind( this ) ); } - /*---------------------------------------------------------------- - */ - addObserver( o: r.Observer ) { - this.stable.addObserver.apply( this.stable, arguments ); - } - - /*---------------------------------------------------------------- - */ - removeObserver( o: r.Observer ) { - this.stable.removeObserver.apply( this.stable, arguments ); - } - /*---------------------------------------------------------------- * When widget is modified. */ update() { this.initialized = true; - this.stable.onNext( this.el.value ); + this.sendNext( this.el.value ); } /*---------------------------------------------------------------- @@ -102,7 +87,7 @@ module hd.bindings { } this.blurTo = null; this.initialized = false; - this.stable.onNext( flush ); + this.sendCompleted(); } /*---------------------------------------------------------------- diff --git a/src/hd/bind/factory.ts b/src/hd/bind/factory.ts index 07280ee..09ab47d 100644 --- a/src/hd/bind/factory.ts +++ b/src/hd/bind/factory.ts @@ -37,7 +37,7 @@ module hd.bindings { model: target, dir: Direction.bi, toView: toView, - toModel: toModel + toModel: concat( toModel, new r.Stabilizer() ) }; } @@ -61,7 +61,8 @@ module hd.bindings { model: target, dir: Direction.bi, toView: toView, - toModel: concat( new r.ToNumber(), toModel ) + toModel: concat( concat( new r.ToNumber(), toModel ), + new r.Stabilizer() ) }; } else { @@ -71,7 +72,8 @@ module hd.bindings { toView: concat( toView, places >= 0 ? new r.NumberToFixed( places ) : new r.Round( places ) ), - toModel: concat( [new r.ToNumber(), new r.Round( places )], toModel ) + toModel: concat( concat( [new r.ToNumber(), new r.Round( places )], toModel ), + new r.Stabilizer() ) }; } } @@ -95,7 +97,8 @@ module hd.bindings { model: target, dir: Direction.bi, toView: concat( toView, new r.DateToDateString() ), - toModel: concat( new r.ToDate(), toModel ) + toModel: concat( concat( new r.ToDate(), toModel ), + new r.Stabilizer() ) }; } diff --git a/src/hd/bind/key.ts b/src/hd/bind/key.ts new file mode 100644 index 0000000..124978d --- /dev/null +++ b/src/hd/bind/key.ts @@ -0,0 +1,52 @@ +module hd.bindings { + + import r = hd.reactive; + + export + class KeyDown extends r.BasicObservable { + + el: HTMLElement; + + constructor( el: HTMLElement ) { + super(); + this.el = checkHtml( el, HTMLElement ); + this.el.addEventListener( 'keydown', this.onkeydown.bind( this ) ); + } + + onkeydown( e: KeyboardEvent ) { + this.sendNext( e ); + } + + } + + export + class Change extends r.BasicObservable { + + el: HTMLElement; + + constructor( el: HTMLElement ) { + super(); + this.el = checkHtml( el, HTMLElement ); + this.el.addEventListener( 'change', this.onchange.bind( this ) ); + } + + onchange( e: Event ) { + this.sendNext( e ); + } + + } + + export + class OnlyKey extends r.Extension { + constructor( private keyCode: number ) { + super(); + } + + onNext( e: KeyboardEvent ) { + if (e.keyCode == this.keyCode) { + this.sendNext( e ); + } + } + } + +} diff --git a/src/hd/bind/rx.ts b/src/hd/bind/rx.ts index 996db35..ef8ad4e 100644 --- a/src/hd/bind/rx.ts +++ b/src/hd/bind/rx.ts @@ -82,8 +82,8 @@ module hd.bindings { } export - function stabilize( time_ms: number, flush?: Object ) { - return new r.Stabilizer( time_ms, flush ); + function stabilize( time_ms: number ) { + return new r.Stabilizer( time_ms ); } export diff --git a/src/hd/methods.ts b/src/hd/methods.ts deleted file mode 100644 index 7e27dd7..0000000 --- a/src/hd/methods.ts +++ /dev/null @@ -1,71 +0,0 @@ -module hd { - - export - function id( x: T ): T { - return x; - } - - export - function sum() { - var n = arguments[0]; - for (var i = 1, l = arguments.length; i < l; ++i) { - n+= arguments[i]; - } - return n; - } - - export - function diff() { - var n = arguments[0]; - for (var i = 1, l = arguments.length; i < l; ++i) { - n-= arguments[i]; - } - return n; - } - - export - function prod() { - var n = arguments[0]; - for (var i = 1, l = arguments.length; i < l; ++i) { - n-= arguments[i]; - } - return n; - } - - export - function quot() { - var n = arguments[0]; - for (var i = 1, l = arguments.length; i < l; ++i) { - n/= arguments[i]; - } - } - - export - function max() { - var n: any; - for (var i = 0, l = arguments.length; i < l && n === undefined; ++i) { - n = arguments[i]; - } - for (; i < l; ++i) { - if (arguments[i] > n) { - n = arguments[i]; - } - } - return n; - } - - export - function min() { - var n: any; - for (var i = 0, l = arguments.length; i < l && n === undefined; ++i) { - n = arguments[i]; - } - for (; i < l; ++i) { - if (arguments[i] < n) { - n = arguments[i]; - } - } - return n; - } - -} diff --git a/src/hd/model/api.ts b/src/hd/model/api.ts new file mode 100644 index 0000000..ca72485 --- /dev/null +++ b/src/hd/model/api.ts @@ -0,0 +1,23 @@ +module hd { + + import u = hd.utility; + import m = hd.model; + + // shortcuts for arrays + export function arrayOf( elementType: u.Constructor|m.ContextSpec ) { + return m.ArrayContext.bind( null, elementType ); + } + + export var array = m.ArrayContext; + + // Exports + export var Variable = m.Variable; + export var Constraint = m.Constraint; + export var Method = m.Method; + export var Context = m.Context; + export var ArrayContext = m.ArrayContext; + export var MaxOptional = m.Optional.Max; + export var MinOptional = m.Optional.Min; + export var ContextBuilder = m.ContextBuilder; + +} diff --git a/src/hd/model/array.ts b/src/hd/model/array.ts index c6571df..aac7015 100644 --- a/src/hd/model/array.ts +++ b/src/hd/model/array.ts @@ -5,14 +5,6 @@ module hd.model { import u = hd.utility; import r = hd.reactive; - /*================================================================== - * JavaScript does not have multi-dimensional arrays, just - * arrays-of-arrays. This interface just supports generic - * arrays-of-arrays without specifying a depth. - */ - export - interface MultiArray extends Array> { }; - /*================================================================== * The ArrayContext class. */ @@ -38,7 +30,7 @@ module hd.model { /*---------------------------------------------------------------- */ - constructor( private elementType?: ContextClass|ContextSpec ) { + constructor( private elementType?: u.Constructor|ContextSpec ) { super(); } @@ -76,6 +68,7 @@ module hd.model { } } + this.elements.length = n; this.$length.set( n ); } @@ -151,9 +144,20 @@ module hd.model { spec = this.elementType; } for (var i = 0, l = count; i < l; ++i) { - var ctx = new klass(); - if (spec) { - Context.construct( ctx, spec, inits[i] ); + var ctx: any; + if (klass === Variable) { + ctx = new Variable( "el" + i, inits[i] ); + } + else { + ctx = new klass(); + if (ctx instanceof ArrayContext) { + if (inits[i] !== undefined) { + ctx.expand( inits[i] ); + } + } + else if (spec) { + Context.construct( ctx, spec, inits[i] ); + } } this.set( start + i, ctx ); Context.claim( this, ctx ); diff --git a/src/hd/model/builder.ts b/src/hd/model/builder.ts index faa31c7..7d4e0b5 100644 --- a/src/hd/model/builder.ts +++ b/src/hd/model/builder.ts @@ -70,6 +70,7 @@ module hd.model { /*---------------------------------------------------------------- * Add a variable. */ + v: (loc: string, init?: T, eq?: u.EqualityPredicate ) => ContextBuilder; variable( loc: string, init?: T, eq?: u.EqualityPredicate ): ContextBuilder { @@ -94,6 +95,7 @@ module hd.model { * This convenience method allows the creation of a bunch of * variables at once. */ + vs: ( a: string|u.Dictionary, b?: u.Dictionary ) => ContextBuilder; variables( varorder: string, vardefs?: u.Dictionary ): ContextBuilder; variables( vardefs: u.Dictionary ): ContextBuilder; variables() { @@ -127,12 +129,13 @@ module hd.model { /*---------------------------------------------------------------- * Add a nested context. */ - nested( loc: string, klass: {new (): Context}, spec?: ContextSpec ): ContextBuilder { + n: (loc: string, klass: {new (): Context}, spec?: ContextSpec) => ContextBuilder; + nested( loc: string, ctxType?: ContextSpec ): ContextBuilder { this.endAll(); if (this.invalidLoc( loc )) { return this; } - this.target.nesteds.push( {loc: loc, klass: klass, spec: spec} ); + this.target.nesteds.push( {loc: loc, ctxType: ctxType} ); this.usedLocs[loc] = true; return this; @@ -141,6 +144,7 @@ module hd.model { /*---------------------------------------------------------------- * Add a reference. */ + r: (loc: string, eq?: u.EqualityPredicate ) => ContextBuilder; reference( loc: string, eq?: u.EqualityPredicate ): ContextBuilder { this.endAll(); @@ -165,6 +169,7 @@ module hd.model { /*---------------------------------------------------------------- * Add a constraint to the property modelcule. */ + c: ((signature: string) => ContextBuilder); constraint( loc: string, signature: string ): ContextBuilder; constraint( signature: string ): ContextBuilder; constraint(): ContextBuilder { @@ -226,6 +231,7 @@ module hd.model { /*---------------------------------------------------------------- * Add a method */ + m: (signature: string, fn: Function, lift?: boolean ) => ContextBuilder; method( signature: string, fn: Function, lift = true ): ContextBuilder { if (! this.lastConstraint) { console.error( 'Builder function "method" called with no constraint' ); @@ -237,8 +243,9 @@ module hd.model { // helper function to make sure variable belongs to constraint var constraintVars = this.lastConstraint.variables; - var isNotConstraintVar = function( name: string ) { - if (constraintVars.indexOf( name ) < 0) { + var isNotConstraintVar = function( name: string, i: number ) { + if ((! p.priorFlags || ! p.priorFlags[i]) && + constraintVars.indexOf( name ) < 0) { console.error( "Variable " + name + " does not belong to constraint in method " + signature ); return true; @@ -247,17 +254,17 @@ module hd.model { }; - if (p.inputs.some( isNotConstraintVar ) || p.outputs.some( isNotConstraintVar ) ) { + if (u.multiArray.some( p.inputs, isNotConstraintVar ) || + u.multiArray.some( p.outputs, isNotConstraintVar )) { return this; } - u.arraySet.addKnownDistinct( - this.lastConstraint.methods, - {inputs: p.inputs, - priorFlags: p.priorFlags, - outputs: p.outputs, - fn: lift ? r.liftFunction( fn, p.outputs.length, p.promiseFlags ) : fn} - ); + u.arraySet.addKnownDistinct( this.lastConstraint.methods, { + inputs: p.inputs, + priorFlags: p.priorFlags, + outputs: p.outputs, + fn: lift ? r.liftFunction( fn, p.promiseFlags ) : fn + } ); return this; } @@ -288,18 +295,23 @@ module hd.model { /*---------------------------------------------------------------- */ - command( loc: string, - signature: string, - fn: Function, - lift = true, sync = false ): ContextBuilder { + command( + loc: string, + signature: string, + fn: Function, + lift = true, + sync = false + ): ContextBuilder { + this.endAll(); if (this.invalidLoc( loc )) { return this; } - var p = parseActivation( 'method', signature ); + var p = parseActivation( 'command', signature ); if (p == null) { return this; } - if (p.inputs.some( invalidPath ) || p.outputs.some( invalidPath )) { + if (u.multiArray.some( p.inputs, invalidPath ) || + u.multiArray.some( p.outputs, invalidPath )) { return this; } @@ -308,7 +320,7 @@ module hd.model { inputs: p.inputs, priorFlags: p.priorFlags, outputs: p.outputs, - fn: lift ? r.liftFunction( fn, p.outputs.length, p.promiseFlags ) : fn, + fn: lift ? r.liftFunction( fn, p.promiseFlags ) : fn, synchronous: sync } ); @@ -464,6 +476,7 @@ module hd.model { (ContextBuilder).prototype['rs'] = ContextBuilder.prototype.references; (ContextBuilder).prototype['c'] = ContextBuilder.prototype.constraint; (ContextBuilder).prototype['m'] = ContextBuilder.prototype.method; + (ContextBuilder).prototype['cmd'] = ContextBuilder.prototype.command; (ContextBuilder).prototype['o'] = ContextBuilder.prototype.output; (ContextBuilder).prototype['os'] = ContextBuilder.prototype.outputs; (ContextBuilder).prototype['td'] = ContextBuilder.prototype.touchDep; @@ -472,8 +485,10 @@ module hd.model { /*================================================================== * Test for invalid variable path */ + var pathRegEx = /^[a-zA-Z][\w$]*(\.[a-zA-Z][\w$]*|\[\s*(\d+|\*)\s*\]|\[\s*(\d+\s*)?[a-zA-Z][\w$]*\s*([+-]\s*\d+\s*)?\])*$/; + function invalidPath( path: string ): boolean { - if (! path.match( /^[a-zA-Z][\w$]*(\.[a-zA-Z][\w$]*|\[\s*(\d+|\*)\s*\]|\[\s*(\d+\s*)?[a-zA-Z][\w$]*\s*([+-]\s*\d+\s*)?\])*$/ )) { + if (! path.match( pathRegEx )) { console.error( 'Invalid variable path: "' + path + '"' ); return true; } @@ -525,9 +540,38 @@ module hd.model { return {inputs: inputs, promiseFlags: promiseFlags.length == 0 ? null : promiseFlags, priorFlags: priorFlags.length == 0 ? null : priorFlags, - outputs: outputs + outputs: outputs.length == 1 ? outputs : [outputs] }; - } + } + + export + var tokensRegEx = /([\w$.]+(\[[^\]]*\][\w$.]*)*|\S)/g; + + export + function parseList( tokens: string[], i: number, list: u.MultiArray ) { + do { + if (tokens[i] === '[') { + var sub: string[] = []; + list.push( sub ); + i = parseList( tokens, i+1, sub ); + if (tokens[i++] !== ']') { + throw 'Expecting "]"'; + } + } + else if (tokens[i] === ',' || tokens[i] === ']') { + throw 'Unexpected token "' + tokens[i] + '"'; + } + else { + list.push( tokens[i++] ); + } + var more = false; + if (tokens[i] === ',') { + ++i; + more = true; + } + } while (more); + return i; + } /*================================================================ * Strip one-character prefixes from front of names diff --git a/src/hd/model/command.ts b/src/hd/model/command.ts index 707675e..5db99eb 100644 --- a/src/hd/model/command.ts +++ b/src/hd/model/command.ts @@ -1,5 +1,6 @@ module hd.model { + import u = hd.utility; import r = hd.reactive; export @@ -20,22 +21,22 @@ module hd.model { // Inputs to pass to the function, in the order they should be passed // Variables in this list will be replaced with their value; everything // else will be treated as constants to be passed to the function. - inputs: any[]; + inputs: u.MultiArray; // Parallel to inputs; true means input comes from prior generation - priorFlags: boolean[]; + priorFlags: u.MultiArray; // Outputs to write to, in the order they are returned form the function - outputs: Variable[]; + outputs: u.MultiArray; // Is this an external operation? (Does it trigger an update after execution?) external = true; constructor( name: string, fn: any, - inputs: any[], - priorFlags: boolean[], - outputs: Variable[] ) { + inputs: u.MultiArray, + priorFlags: u.MultiArray, + outputs: u.MultiArray ) { super(); this.id = makeId( name ); this.name = name; @@ -101,17 +102,22 @@ module hd.model { ready: r.Signal; - constructor( name: string, - fn: Function, - inputs: any[], - usePriors: boolean[], - outputs: any[] ) { + constructor( + name: string, + fn: Function, + inputs: u.MultiArray, + usePriors: u.MultiArray, + outputs: u.MultiArray + ) { + super( name, fn, inputs, usePriors, outputs ); var properties: r.ProxyObservable[] = []; for (var i = 0, l = inputs.length; i < l; ++i) { var vv = inputs[i]; - properties.push( vv.pending ); - properties.push( vv.error ); + if (vv instanceof Variable) { + properties.push( vv.pending ); + properties.push( vv.error ); + } } this.ready = new None( properties ); } diff --git a/src/hd/model/context.ts b/src/hd/model/context.ts index 0b4e4f2..c57cf1e 100644 --- a/src/hd/model/context.ts +++ b/src/hd/model/context.ts @@ -12,7 +12,7 @@ module hd.model { // getter for constant function pathConstant( p: Path ) { - return p.constant; + return p.isConstant(); } // object that can retrieve a path for a string @@ -25,20 +25,6 @@ module hd.model { id: string; } - // lexicographical comparison for arrays of HasId - function compareIds( a: HasId[], b: HasId[] ) { - for (var i = 0, l = a.length, m = b.length; i < l && i < m; ++i) { - if (a[i] !== b[i]) { - var cmp = a[i].id.localeCompare( b[i].id ); - if (cmp != 0) { - return cmp; - } - } - } - - return l - m; - } - // lexicographical comparison for array function compareAnys( a: any[], b: any[] ) { for (var i = 0, l = a.length, m = b.length; i < l && i < m; ++i) { @@ -50,6 +36,9 @@ module hd.model { (bi instanceof Variable || bi instanceof Constraint || bi instanceof Command) ) { cmp = ai.id.localeCompare( bi.id ); } + else if (Array.isArray( ai ) && Array.isArray( bi )) { + cmp = compareAnys( ai, bi ); + } else if (typeof ai === 'number' && typeof bi === 'number') { cmp = ai - bi; } @@ -110,6 +99,19 @@ module hd.model { rightOnly: rightOnly, rightShared: rightShared}; } + function addToList( list: any[], value: any, interpolations: number ) { + if (interpolations == 0) { + list.push( value ); + } + else if (interpolations == 1) { + list.push.apply( list, value ); + } + else { + for (var i = 0, l = value.length; i < l; ++i) { + addToList( list, value[i], interpolations - 1 ); + } + } + } /******************************************************************* Context elements @@ -164,8 +166,7 @@ module hd.model { export interface NestedSpec { loc: string; - klass?: ContextClass; - spec?: ContextSpec; + ctxType?: ContextClass|ContextSpec; } export @@ -176,9 +177,9 @@ module hd.model { export interface MethodSpec { - inputs: string[]; - priorFlags: boolean[]; - outputs: string[]; + inputs: u.MultiArray; + priorFlags: u.MultiArray; + outputs: u.MultiArray; fn: Function; } @@ -194,9 +195,9 @@ module hd.model { export interface CommandSpec { loc: string; - inputs: string[]; - priorFlags: boolean[]; - outputs: string[]; + inputs: u.MultiArray; + priorFlags: u.MultiArray; + outputs: u.MultiArray; fn: Function; synchronous: boolean; } @@ -247,13 +248,11 @@ module hd.model { * - in destructor, unsubscribe to all paths */ class Template extends r.BasicObservable