@webqit/subscript
Advanced tools
Comparing version 2.0.7 to 2.0.8
@@ -11,3 +11,3 @@ { | ||
"homepage": "https://webqit.io/tooling/subscript", | ||
"version": "2.0.7", | ||
"version": "2.0.8", | ||
"license": "MIT", | ||
@@ -14,0 +14,0 @@ "repository": { |
179
README.md
@@ -11,23 +11,12 @@ # Subscript | ||
\> Install via npm | ||
+ [What's A Dependency Thread?](#whats-a-dependency-thread) | ||
+ [What Is Subscript?](#whats-subscript) | ||
+ [Concepts](#concepts) | ||
+ [Motivation](#motivation) | ||
+ [Installation](#installation) | ||
+ [API](#api) | ||
```cmd | ||
npm i @webqit/subscript | ||
``` | ||
```js | ||
import SubscriptFunction from '@webqit/subscript'; | ||
``` | ||
\> Include from a CDN | ||
```html | ||
<script src="https://unpkg.com/@webqit/subscript/dist/main.js"></script> | ||
``` | ||
```js | ||
const SubscriptFunction = WebQit.Subscript; | ||
``` | ||
## What's A Dependency Thread? | ||
That's simply the line of dependencies involving two or more expressions. | ||
That's simply the dependency chain involving two or more JavaScript expressions. | ||
@@ -179,6 +168,2 @@ ```js | ||
+ [Concepts](#concepts) | ||
+ [API](#api) | ||
+ [Why Subscript](#why-subscript) | ||
## Concepts | ||
@@ -560,2 +545,152 @@ | ||
[TODO] | ||
## Motivation | ||
[TODO] | ||
## Installation | ||
\> Install via npm | ||
```cmd | ||
npm i @webqit/subscript | ||
``` | ||
```js | ||
import SubscriptFunction from '@webqit/subscript'; | ||
``` | ||
\> Include from a CDN | ||
```html | ||
<script src="https://unpkg.com/@webqit/subscript/dist/main.js"></script> | ||
``` | ||
```js | ||
const SubscriptFunction = WebQit.Subscript; | ||
``` | ||
## API | ||
`SubscriptFunction` is a one-to-one equivalent of the [JavaScript function constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function). You can just use them interchangeably 😎. | ||
### Syntax | ||
```js | ||
// Statically | ||
let subscrFunction = SubscriptFunction( functionBody ); | ||
let subscrFunction = SubscriptFunction( arg1, functionBody ); | ||
let subscrFunction = SubscriptFunction( arg1, ... argN, functionBody ); | ||
// With the new keyword | ||
let subscrFunction = new SubscriptFunction( functionBody ); | ||
let subscrFunction = new SubscriptFunction( arg1, functionBody ); | ||
let subscrFunction = new SubscriptFunction( arg1, ... argN, functionBody ); | ||
``` | ||
### Parameters | ||
#### `arg1, ... argN` | ||
Names to be used by the function as formal argument names. Each must be a string that corresponds to a valid JavaScript parameter (any of plain identifier, rest parameter, or destructured parameter, optionally with a default), or a list of such strings separated with commas. | ||
#### `functionBody` | ||
A string that represents the function body. | ||
### Return Value | ||
A regular `Function` object, or an `async function` object where the `await` keyword is used within `functionBody`. | ||
```js | ||
// Create a regular function - sum | ||
let sum = SubscriptFunction( 'a', 'b', 'return a + b;' ); | ||
// Call the returned sum function and log the result | ||
console.log( sum( 10, 2 ) ); | ||
< 12 | ||
``` | ||
```js | ||
// Create an async function - sum | ||
let sum = SubscriptFunction( 'a', 'b', 'return a + await b;' ); | ||
// Call the returned sum function and log the result | ||
sum( 10, 2 ).then( result => { | ||
console.log( result ); | ||
} ); | ||
< 12 | ||
``` | ||
### The `this` Binding | ||
Functions returned by `SubscriptFunction` are standard functions that can have their own [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) binding at *call time*. | ||
```js | ||
// Create a function - colorSwitch - that sets a DOM element's color | ||
let colorSwitch = SubscriptFunction( 'color', 'this.style.color = color;' ); | ||
// Call colorSwitch, with document.body as it's this binding | ||
let element = document.body; | ||
colorSwitch.call( element, 'red' ); | ||
``` | ||
But, where the `this` binding is `undefined` at call time, the `this` binding of the `SubscriptFunction` itself is used. This lets us have a default `this` binding at *creation time*. | ||
```js | ||
// Create the same colorSwitch, this time, with a this binding that can be used at call time | ||
let element = document.body; | ||
let colorSwitch = SubscriptFunction.call( element, 'color', 'this.style.color = color;' ); | ||
// Call colorSwitch, without a this binding | ||
colorSwitch( 'red' ); | ||
colorSwitch.call( undefined, 'red' ); | ||
// Call colorSwitch, with a different this binding | ||
let h1Element = document.getElementById( 'h1' ); | ||
colorSwitch.call( h1Element, 'red' ); | ||
``` | ||
### The `subscrFunction.signal()` Method | ||
The `.signal()` method is the *reactivity* API in Subscript functions that lets us send *change signals* into the *reactivity runtime*. It takes a list of the outside variables or properties that have changed; each as an array path. | ||
#### Syntax | ||
```js | ||
let returnValue = subscrFunction.signal( path1, ... pathN ); | ||
``` | ||
#### Parameters | ||
##### `path1, ... pathN` | ||
An array path representing each variable, or object property, that has changed. *See [Signals](#signals) for concepts and usage.* | ||
#### Return Value | ||
The return value of this method depends on the return value of the *dependency thread* it initiates within the function body. | ||
```js | ||
// Global variables to use | ||
a = 10; | ||
b = 2; | ||
// Create a function with two possible values | ||
let sum = SubscriptFunction(` | ||
if ( a > 10 ) { | ||
return a + await b; | ||
} | ||
return a + b; | ||
`); | ||
// Run normally | ||
console.log( sum() ); | ||
< 12 | ||
// Run a thread with a different return value | ||
a = 20; | ||
console.log( sum.signal( [ 'a' ] ) ); | ||
< Promise { 22 } | ||
``` | ||
## Documentation | ||
@@ -562,0 +697,0 @@ |
@@ -53,2 +53,13 @@ | ||
const _returns = ( ...__nodes ) => _nodes.concat( __nodes ); | ||
const _visit = ( _node, _effect = effect ) => Object.keys( _node ).reduce( ( __node, key ) => { | ||
let value = _node[ key ]; | ||
if ( value && value.type === 'Identifier' ) { | ||
_effect.subscriptIdentifiersNoConflict( value ); | ||
} else if ( Array.isArray( value ) ) { | ||
value = value.map( v => _visit( v ) ); | ||
} else if ( typeof value === 'object' && value ) { | ||
value = _visit( value ); | ||
} | ||
return { ...__node, [ key ]: value }; | ||
}, {} ); | ||
if ( !node ) return _returns( node ); | ||
@@ -71,2 +82,21 @@ | ||
* ------------ | ||
* #ArrowFunctionExpression | ||
* #FunctionExpression | ||
* #FunctionDeclaration | ||
* ------------ | ||
*/ | ||
if ( node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' ) { | ||
let createNode = astNodes[ node.type === 'ArrowFunctionExpression' ? 'arrowFuncExpr' : ( | ||
node.type === 'FunctionExpression' ? 'funcExpr' : 'funcDeclaration' | ||
) ].bind( astNodes ); | ||
if ( node.type === 'FunctionDeclaration' ) { | ||
node = _visit( node, scope.ownerEffect ); | ||
} else { | ||
node = _visit( node, effect ); | ||
} | ||
return _returns( node ); | ||
} | ||
/** | ||
* ------------ | ||
* #VariableDeclaration | ||
@@ -334,15 +364,19 @@ * ------------ | ||
if ( node.type === 'ReturnStatement' ) { | ||
let [ argument ] = _transform( [ node.argument ], scope.currentEffect || scope.ownerEffect /* This is a statement that could have had its own effect */ ); | ||
if ( scope.static() || !scope.currentEffect ) { | ||
if ( scope.static() && 0 ) { | ||
let [ argument ] = _transform( [ node.argument ], scope.currentEffect || scope.ownerEffect /* This is a statement that could have had its own effect */ ); | ||
return _returns( astNodes.returnStmt( argument ) ); | ||
} | ||
let subscript$construct = astNodes.identifier( scope.currentEffect.getSubscriptIdentifier( '$construct', true ) ); | ||
let keyword = astNodes.literal( 'return' ); | ||
let arg = argument || astNodes.identifier( 'undefined' ); | ||
let exitCall = astNodes.exprStmt( | ||
astNodes.callExpr( astNodes.memberExpr( subscript$construct, astNodes.identifier( 'exit' ) ), [ keyword, arg ] ), | ||
); | ||
// Return statement hoisting | ||
scope.currentEffect.hoistExitStatement( keyword, astNodes.identifier( 'true' ) ); | ||
return _returns( exitCall, astNodes.returnStmt() ); | ||
let def = { type: node.type }; | ||
return scope.createEffect( def, effect => { | ||
let [ argument ] = effect.causesProduction( def, () => _transform( [ node.argument ], effect ) ); | ||
let subscript$construct = astNodes.identifier( effect.getSubscriptIdentifier( '$construct', true ) ); | ||
let keyword = astNodes.literal( 'return' ); | ||
let arg = argument || astNodes.identifier( 'undefined' ); | ||
let exitCall = astNodes.exprStmt( | ||
astNodes.callExpr( astNodes.memberExpr( subscript$construct, astNodes.identifier( 'exit' ) ), [ keyword, arg ] ), | ||
); | ||
// Return statement hoisting | ||
effect.hoistExitStatement( keyword, astNodes.identifier( 'true' ) ); | ||
return _returns( ...effect.compose( [ exitCall, astNodes.returnStmt() ] ) ); | ||
} ); | ||
} | ||
@@ -558,5 +592,7 @@ | ||
let production = effect.currentProduction; | ||
do { | ||
production.addRef().unshift( $identifier ); | ||
} while( production = production.contextProduction ); | ||
if ( production ) { | ||
do { | ||
production.addRef().unshift( $identifier ); | ||
} while( production = production.contextProduction ); | ||
} | ||
} | ||
@@ -568,33 +604,2 @@ return _returns( createNode() ); | ||
* ------------ | ||
* #ArrowFunctionExpression | ||
* #FunctionExpression | ||
* #FunctionDeclaration | ||
* ------------ | ||
*/ | ||
if ( node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' ) { | ||
let createNode = astNodes[ node.type === 'ArrowFunctionExpression' ? 'arrowFuncExpr' : ( | ||
node.type === 'FunctionExpression' ? 'funcExpr' : 'funcDeclaration' | ||
) ].bind( astNodes ); | ||
let [ id ] = node.type === 'FunctionDeclaration' | ||
? effect.noSelect( () => _transform( [ node.id ] ) ) | ||
: [ node.id ]; | ||
let params = node.params.map( param => { | ||
if ( param.type !== 'AssignmentExpression' ) return param; | ||
let [ right ] = effect.noSelect( () => _transform( [ param.right ] ) ); | ||
return astNodes.assignmentExpr( param.left, right, param.operator ); | ||
} ); | ||
let body, functionScope = ( new Scope( null, { type: 'Function' } ) ).static( true ); | ||
if ( node.body.type === 'BlockStatement' ) { | ||
let statements = _transform( node.body.body, null, functionScope ); | ||
body = astNodes.blockStmt( statements ); | ||
} else { | ||
[ body ] = _transform( [ node.body ], null, functionScope ); | ||
} | ||
return _returns( | ||
createNode( params, body, node.async, id, node.expression, node.generator ) | ||
); | ||
} | ||
/** | ||
* ------------ | ||
* #Other | ||
@@ -601,0 +606,0 @@ * ------------ |
@@ -212,2 +212,5 @@ | ||
this.causes.unshift( causesProduction ); | ||
if ( def.static ) { | ||
resolveInScope = false; | ||
} | ||
return this._runProduction( causesProduction, callback, resolveInScope ); | ||
@@ -219,2 +222,5 @@ } | ||
this.affecteds.unshift( affectedsProduction ); | ||
if ( def.static ) { | ||
resolveInScope = false; | ||
} | ||
return this._runProduction( affectedsProduction, callback, resolveInScope ); | ||
@@ -230,15 +236,13 @@ } | ||
embeddableCausesProduction( def, callback ) { | ||
let result = this.causesProduction( def, ( production, currentProduction ) => { | ||
return this.causesProduction( def, ( production, currentProduction ) => { | ||
production.contextProduction = currentProduction; | ||
return callback( production, currentProduction ); | ||
} ); | ||
return result; | ||
} | ||
embeddableAffectedsProduction( def, callback ) { | ||
let result = this.affectedsProduction( def, ( production, currentProduction ) => { | ||
return this.affectedsProduction( def, ( production, currentProduction ) => { | ||
production.contextProduction = currentProduction; | ||
return callback( production, currentProduction ); | ||
} ); | ||
return result; | ||
} | ||
@@ -419,6 +423,7 @@ | ||
get isIntermediateInstance() { | ||
return this.type === 'BlockStatement' | ||
|| ( this.type === 'LabeledStatement' && this.scopes.length ); | ||
return this.type === 'BlockStatement' || this.type === 'FunctionDeclaration' || ( | ||
this.type === 'LabeledStatement' && this.scopes.length | ||
); | ||
} | ||
} |
@@ -39,2 +39,3 @@ | ||
assignmentExpr( left, right, operator = '=' ) { return { type: 'AssignmentExpression', operator, left, right }; }, | ||
assignmentPattern( left, right ) { return { type: 'AssignmentPattern', left, right }; }, | ||
thisExpr() { return { type: 'ThisExpression' }; }, | ||
@@ -41,0 +42,0 @@ condExpr( test, consequent, alternate, type = 'ConditionalExpression' ) { return { type, test, consequent, alternate }; }, |
@@ -183,3 +183,3 @@ | ||
this._textarea.placeholder = newValue; | ||
break | ||
break; | ||
case 'editable': | ||
@@ -195,2 +195,3 @@ this._editable = newValue; | ||
} | ||
break; | ||
} | ||
@@ -221,5 +222,5 @@ } | ||
display: block; | ||
/* Normal inline styles */ | ||
font-size: 0.8rem; | ||
@@ -264,3 +265,2 @@ font-family: monospace; | ||
textarea, pre { | ||
@@ -276,3 +276,2 @@ /* In the same place */ | ||
/* Move the textarea in front of the result */ | ||
@@ -287,3 +286,2 @@ | ||
/* Make textarea almost completely transparent */ | ||
@@ -290,0 +288,0 @@ |
@@ -40,3 +40,3 @@ | ||
` | ||
.ref-identifier:is(.path-hover, .path-runtime-active) { | ||
.ref-identifier.path-runtime-active { | ||
text-decoration: underline; | ||
@@ -64,2 +64,3 @@ } | ||
color: yellowgreen; | ||
text-decoration: underline; | ||
} | ||
@@ -87,2 +88,9 @@ | ||
} | ||
} | ||
/** | ||
* @define | ||
*/ | ||
customElements.define( 'subscript-codeblock', CodeBlock() ); | ||
customElements.define( 'subscript-effect', Effect ); | ||
customElements.define( 'subscript-console', Console ); |
@@ -5,11 +5,4 @@ | ||
*/ | ||
import CodeBlock from './CodeBlock.js'; | ||
import Console from './Console.js'; | ||
import Effect from './Effect.js'; | ||
/** | ||
* @define | ||
*/ | ||
customElements.define( 'subscript-codeblock', CodeBlock() ); | ||
customElements.define( 'subscript-console', Console ); | ||
customElements.define( 'subscript-effect', Effect ); | ||
import './Console.js'; | ||
import './Inspector.js'; | ||
import './Player.js'; |
@@ -41,3 +41,3 @@ | ||
subscriptElement.subscriptConsole.forEach( ( subscriptFunction, id ) => { | ||
let title = typeof id === 'number' ? `<${ id }>()` : `${ id }()`; | ||
let title = typeof id === 'number' ? `script:${ id }` : `${ id }()`; | ||
this.buttons[ id ] = this.controlsElement.appendChild( document.createElement( 'button' ) ); | ||
@@ -49,3 +49,3 @@ this.buttons[ id ].setAttribute( 'script-id', id ); | ||
let iconElement = this.buttons[ id ].appendChild( document.createElement( 'i' ) ); | ||
let iconClasses = this.getAttribute( `data-icons` ) || `bi bi-braces`; | ||
let iconClasses = this.getAttribute( `data-icons` ) || `bi bi-${ typeof id === 'number' ? 'code' : 'braces' }`; | ||
iconClasses.split( ' ' ).map( str => str.trim() ).forEach( str => iconElement.classList.add( str ) ); | ||
@@ -125,2 +125,5 @@ this.buttons[ id ].addEventListener( 'click', e => { | ||
/** | ||
* @define | ||
*/ | ||
customElements.define( 'subscript-inspector', Inspector ); |
@@ -136,2 +136,5 @@ | ||
/** | ||
* @define | ||
*/ | ||
customElements.define( 'subscript-player', Player ); |
@@ -42,3 +42,5 @@ | ||
let callee = arg1; | ||
let childEffect = new Effect( this, effectGraph, callee, { ...this.params }, this.exits ); | ||
let firstChild = effectGraph.childEffects[ Object.keys( effectGraph.childEffects )[ 0 ] ], | ||
isFunctionExpression = firstChild && firstChild.type === 'FunctionExpression'; | ||
let childEffect = new Effect( this, effectGraph, callee, { ...this.params, isFunctionExpression }, isFunctionExpression ? null : this.exits ); | ||
if ( this.childEffects.has( effectId ) ) { | ||
@@ -67,3 +69,3 @@ this.childEffects.get( effectId ).dispose(); | ||
let ret = this.callee.call( $this, this.construct, ...$arguments ); | ||
if ( !this.parentEffect ) { | ||
if ( !this.parentEffect || this.params.isFunctionExpression ) { | ||
let _ret = this.exits.get( 'return' ); | ||
@@ -82,6 +84,6 @@ this.exits.clear(); | ||
let [ [ /* iterationEffectId */, iterationInstances ] ] = this.childEffects; | ||
let prev, after = ( prev, callback ) => prev instanceof Promise ? prev.then( callback ) : callback(); | ||
let prev, _await = ( prev, callback ) => prev instanceof Promise ? prev.then( callback ) : callback(); | ||
if ( !keys.length || ( keys.includes( 'length') && this.graph.type === 'ForOfStatement' ) ) { | ||
for ( let [ /* iterationId */, iterationInstance ] of iterationInstances ) { | ||
prev = after( prev, () => iterationInstance.call() ); | ||
prev = _await( prev, () => iterationInstance.call() ); | ||
} | ||
@@ -92,3 +94,3 @@ } else { | ||
if ( !instance ) continue; | ||
prev = after( prev, () => instance.call() ); | ||
prev = _await( prev, () => instance.call() ); | ||
} | ||
@@ -129,8 +131,8 @@ } | ||
let prev, entry, refs, after = ( prev, callback ) => prev instanceof Promise ? prev.then( callback ) : callback(); | ||
let prev, entry, refs, _await = ( prev, callback ) => prev instanceof Promise ? prev.then( callback ) : callback(); | ||
while ( ( entry = thread.sequence.shift() ) && ( refs = [ ...thread.entries.get( entry ) ] ) ) { | ||
prev = after( prev, () => { | ||
prev = _await( prev, () => { | ||
if ( entry.disposed || !entry.filterRefs( refs ).length ) return; | ||
let maybePromise = execute( entry, refs ); | ||
after( maybePromise, () => { | ||
_await( maybePromise, () => { | ||
for ( let ref of refs ) { | ||
@@ -146,3 +148,3 @@ [].concat( ref.executionPlan.assigneeRef || ref.executionPlan.assigneeRefs || [] ).forEach( assigneeRef => { | ||
return after( prev, () => { | ||
return _await( prev, () => { | ||
let _ret = this.exits.get( 'return' ); | ||
@@ -149,0 +151,0 @@ this.exits.clear(); |
@@ -5,3 +5,3 @@ | ||
*/ | ||
import { expect } from 'chai'; | ||
import * as Acorn from 'acorn'; | ||
import Subscript from '../src/index.js'; | ||
@@ -12,3 +12,3 @@ import Observer from '../../observer/src/index.js'; | ||
let source2 = ` | ||
/* | ||
for ( let propertyName in entries ) { | ||
@@ -46,4 +46,11 @@ console.log( \`Current property name is: \${ propertyName }, and its alias name is: '\${ entries[ propertyName ].name }'\` ); | ||
} | ||
*/ | ||
let c = 2; | ||
let t = function( $x, b = c ) { | ||
return $x * b; | ||
} | ||
return t( 10 ); | ||
`; | ||
let subscriptFunction = new Subscript( 'param1', 'param2', 'param3', source2 ); | ||
@@ -76,3 +83,3 @@ // ----------- | ||
console.log(''); | ||
console.log( subscriptFunction.toString() ); | ||
console.log( subscriptFunction.subscriptSource ); | ||
console.log(''); | ||
@@ -83,3 +90,3 @@ console.log('--------------------------------------------------'); | ||
let result = subscriptFunction( 'Some param1', 'Some param2', 2 ); | ||
console.log( '+++++', result ); | ||
console.log( '------->', result ); | ||
@@ -86,0 +93,0 @@ setTimeout(() => { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
704
3088597
58
5714