Sky Style Language ================== Planed changes -------------- Add //-to-end-of-line comments to be consistent with the script language. Style Parser ------------ (this section is incomplete) ### Tokenisation #### Value parser ##### **Value** state If the current character is... * '``;``': Consume the character and exit the value parser successfully. * '``@``': Consume the character and switch to the **at** state. * '``#``': Consume the character and switch to the **hash** state. * '``$``': Consume the character and switch to the **dollar** state. * '``%``': Consume the character and switch to the **percent** state. * '``&``': Consume the character and switch to the **ampersand** state. * '``'``': Set _value_ to the empty string, consume the character, and switch to the **single-quoted string** state. * '``"``': Set _value_ to the empty string, consume the character, and switch to the **double-quoted string** state. * '``-``': Consume the character, and switch to the **negative integer** state. * '``0``'-'``9``': Set _value_ to the decimal value of the current character, consume the character, and switch to the **integer** state. * '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current character, consume the character, and switch to the **identifier** state. * '``*``', '``^``', '``!``', '``?``', '``,``', '``/``', '``<``', '``[``', '``)``', '``>``', '``]``', '``+``': Emit a symbol token with the current character as the symbol, consume the character, and stay in this state. * Anything else: Consume the character and switch to the **error** state. ##### **At** state * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current character, create a literal token with the unit set to ``@``, consume the character, and switch to the **literal** state. * Anything else: Emit a symbol token with ``@`` as the symbol, and switch to the **value** state without consuming the character. ##### **Hash** state * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current character, create a literal token with the unit set to ``@``, consume the character, and switch to the **literal** state. * Anything else: Emit a symbol token with ``#`` as the symbol, and switch to the **value** state without consuming the character. ##### **Dollar** state * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current character, create a literal token with the unit set to ``@``, consume the character, and switch to the **literal** state. * Anything else: Emit a symbol token with ``$`` as the symbol, and switch to the **value** state without consuming the character. ##### **Percent** state * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current character, create a literal token with the unit set to ``@``, consume the character, and switch to the **literal** state. * Anything else: Emit a symbol token with ``%`` as the symbol, and switch to the **value** state without consuming the character. ##### **Ampersand** state * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current character, create a literal token with the unit set to ``@``, consume the character, and switch to the **literal** state. * Anything else: Emit a symbol token with ``&`` as the symbol, and switch to the **value** state without consuming the character. ##### TODO(ianh): more states... ##### **Error** state If the current character is... * '``;``': Consume the character and exit the value parser in failure. * Anything else: Consume the character and stay in this state. Selectors --------- Sky Style uses whatever SelectorQuery. Maybe one day we'll make SelectorQuery support being extended to support arbitrary selectors, but for now, it supports: ```css tagname #id .class [attrname] [attrname=value] :host ("host" string is fixed) ::pseudo-element ``` These can be combined (without whitespace), with at most one tagname (must be first) and at most one pseudo-element (must be last) as in: ```css tagname[attrname]#id:host.class.class[attrname=value]::foo ``` In debug mode, giving two IDs, or the same selector twice (e.g. the same classname), or specifying other redundant or conflicting selectors (e.g. [foo][foo=bar], or [foo=bar][foo=baz]) will be flagged. Alternatively, a selector can be the special value "@document", optionally followed by a pseudo-element, as in: ```css @document::bar ``` Value Parser ------------ ```javascript class StyleToken { constructor (String king, String value); readonly attribute String kind; // string // identifier // function (identifier + '(') // number // symbol (one of @#$%& if not immediately following numeric or preceding alphanumeric, or one of *^!?,/<[)>]+ or, if not followed by a digit, -) // dimension (number + identifier or number + one of @#$%&) // literal (one of @#$%& + alphanumeric) readonly attribute String value; readonly attribute String unit; // for 'dimension' type, this is the punctuation or identifier that follows the number, for 'literal' type, this is the punctuation that precedes it } class TokenSource { constructor (Array tokens); IteratorResult next(); TokenSourceBookmark getBookmark(); void rewind(TokenSourceBookmark bookmark); } class TokenSourceBookmark { constructor (); // TokenSource stores unforgeable state on this object using symbols or a weakmap or some such } callback ParserCallback = AbstractStyleValue (TokenSource tokens); // return if successful, throw if not class StyleGrammar { constructor (); void addParser(ParserCallback parser); AbstractStyleValue parse(TokenSource tokens, Boolean root = false); // for each parser callback that was registered, in reverse // order (most recently registered first), run these steps: // let bookmark = tokens.getBookmark(); // try { // let result = parser(tokens); // if (root) { // if (!tokens.next().done) // throw new Error(); // } // } except { // tokens.rewind(bookmark); // } // (root is set when you need to parse the entire token stream to be valid) } /* StyleNode | +-- Property | +-- AbstractStyleValue | +-- NumericStyleValue | | | +-- AnimatableNumericStyleValue* | +-- LengthStyleValue | | | +-- AnimatableLengthStyleValue* | | | +-- TransitionLengthStyleValue* | | | +-- PixelLengthStyleValue | | | +-- EmLengthStyleValue* | | | +-- VHLengthStyleValue* | | | +-- CalcLengthStyleValue* | +-- ColorStyleValue | | | +-- RGBColorStyleValue | | | +-- AnimatableColorStyleValue* | +-- AbstractOpaqueStyleValue | | | +-- IdentifierStyleValue | | | | | +-- AnimatableIdentifierStyleValue* | | | +-- URLStyleValue* | | | | | +-- AnimatableURLStyleValue* | | | +-- StringStyleValue* | | | | | +-- AnimatableStringStyleValue* | | | +-- ObjectStyleValue | +-- PrimitiveValuesListStyleValue* */ ``` The types marked with * in the list above are not part of dart:sky, and are only shown here to illustrate what kinds of extensions are possible and where they would fit. TODO(ianh): consider removing 'StyleValue' from these class names ```javascript abstract class StyleNode { abstract void markDirty(); } dictionary StyleValueResolverSettingsSettings { Boolean firstTime = false; any state = null; } class StyleValueResolverSettings { // this is used as an "out" parameter for 'resolve()' below constructor(StyleValueResolverSettingsSettings initial); void reset(StyleValueResolverSettingsSettings initial); // sets firstTime and state to given values // sets layoutDependent to false // sets dependencies to empty set // sets lifetime to Infinity readonly attribute Boolean firstTime; // true if this is the first time this property is being resolved for this element, // or if the last time it was resolved, the value was a different object // attribute Boolean layoutDependent void setLayoutDependent(); // call this if the value should be recomputed each time the ownerLayoutManager's dimensions change, rather than being cached Boolean getLayoutDependent(); // returns true if setLayoutDependent has been called since the last reset() // attribute "BitField" dependencies; // defaults to no bits set void dependsOn(PropertyHandle property); // if the given property doesn't have a dependency bit assigned: // - assign the next bit to the property // - if there's no bits left, throw // set the bit on this StyleValueResolverSettings's dependencies bitfield Array getDependencies(); // returns an array of the PropertyHandle values for the bits that are set in dependencies // attribute (Float or Infinity) lifetime; void setLifetime(Float age); // if the new value is less than the current value of lifetime, update the current value (Float or Infinity) getLifetime(); // return current value of lfietime attribute any state; // initially null, can be set to store value for this RenderNode/property pair // for example, TransitioningColorStyleValue would store // { // initial: /* color at time of transition */, // target: /* color at end of transition */, // start: /* time at start of transition */, // } // ...which would enable it to update appropriately, and would also // let other transitions that come later know that you were half-way // through a transition so they can shorten their time accordingly // // best practices: if you're storing values on the state object, // then remove the values once they are no longer needed. For // example, when your transition ends, set the object to null. // // best practices: if you're a style value that contains multiple // style values, then before you call their resolve you should // replace the state with a state that is specific to them, and // when you get it back you should insert that value into your // state somehow. For example, in a resolve()r with two child // style values a and b: // let ourState; // if (settings.firstTime) // ourState = { a: null, b: null }; // else // ourState = settings.state; // settings.state = ourState.a; // let aResult = a.resolve(node, settings); // ourState.a = settings.state; // settings.state = ourState.b; // let aResult = b.resolve(node, settings); // ourState.b = settings.state; // settings.state = ourState; // return a + b; // or whatever // // best practices: if you're a style value that contains multiple // style values, and all those style values are storing null, then // store null yourself, instead of storing many nulls of your own. // attribute Boolean wasStateSet; Boolean getShouldSaveState(); // returns true if state is not null, and either state was set // since the last reset, or firstTime is false. } class Property : StyleNode { constructor (AbstractStyleDeclaration parentNode, PropertyHandle property, AbstractStyleValue? initialValue = null); readonly attribute AbstractStyleDeclaration parentNode; readonly attribute PropertyHandle property; readonly attribute AbstractStyleValue value; void setValue(AbstractStyleValue? newValue); // updates value and calls markDirty() void markDirty(); // call parentNode.markDirty(property); abstract any resolve(RenderNode node, StyleValueResolverSettings? settings = null); // if value is null, returns null // otherwise, returns value.resolve(property, node, settings) } abstract class AbstractStyleValue : StyleNode { abstract constructor(StyleNode? parentNode = null); attribute StyleNode? parentNode; void markDirty(); // call this.parentNode.markDirty() abstract any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); } abstract class LengthStyleValue : AbstractStyleValue { abstract Float resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); } class PixelLengthStyleValue : LengthStyleValue { constructor(Float number, StyleNode? parentNode = null); attribute Float value; // on setting, calls markDirty(); Float resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); // return value } typedef RawColor Float; // TODO(ianh): figure out what Color should be class ColorStyleValue : LengthStyleValue { constructor(Float red, Float green, Float blue, Float alpha, StyleNode? parentNode = null); // ... color API ... RawColor resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); } class AbstractOpaqueStyleValue : AbstractStyleValue { abstract constructor(any value, StyleNode? parentNode = null); attribute any value; // on setting, calls markDirty(); any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); // returns value } class IdentifierStyleValue : AbstractOpaqueStyleValue { constructor(String value, StyleNode? parentNode = null); // calls superclass constructor } /* class AnimatableIdentifierStyleValue : AbstractOpaqueStyleValue { constructor(String value, String newValue, AnimationFunction player, StyleNode? parentNode = null); readonly attribute String newValue; readonly attribute AnimationFunction player; any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); } */ class ObjectStyleValue : AbstractOpaqueStyleValue { constructor(any value, StyleNode? parentNode = null); // calls superclass constructor } dictionary PropertySettings { String? name = null; // null if the property can't be set from a