UNPKG

foam-framework

Version:
482 lines (465 loc) 19.6 kB
/** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ CLASS({ package: 'foam.grammars', name: 'CSS3', imports: [ 'assert' ], constants: { PREFIXES: [ '-webkit-' ], PREFIXED_KEYS: { 'align-content': true, 'align-items': true, 'align-self': true, 'animation': true, 'box-shadow': true, 'column-count': true, 'column-gap': true, 'column-rule': true, 'display': 'flex', 'filter': true, 'flex': true, 'flex-basis': true, 'flex-direction': true, 'flex-flow': true, 'flex-grow': true, 'flex-shrink': true, 'flex-wrap': true, 'font-feature-settings': true, 'hyphens': true, 'justify-content': true, 'keyframes': true, 'order': true, 'transform': true, 'transform-origin': true, 'user-select': true } }, properties: [ { name: 'parser', lazyFactory: function() { var css = this; return SkipGrammar.create({ __proto__: grammar, START: sym('stylesheet'), // CSS 3 "tokens" (see http://www.w3.org/TR/css-syntax-3/#tokenization). nl: alt('\n', literal('\r\n'), '\r', '\f'), ws: alt(' ', '\t', sym('nl')), digit: range('0', '9'), hd: alt(range('0', '9'), range('a', 'f'), range('A', 'F')), hdQM: alt(sym('hd'), '?'), hd16: str(seq(sym('hd'), optional(sym('hd')), optional(sym('hd')), optional(sym('hd')), optional(sym('hd')), optional(sym('hd')))), hd16QM: str(seq(sym('hd'), optional(sym('hdQM')), optional(sym('hdQM')), optional(sym('hdQM')), optional(sym('hdQM')), optional(sym('hdQM')))), esc: str(seq('\\', alt(not(alt(sym('nl'), sym('hd')), anyChar), str(seq(sym('hd16'), optional(sym('ws'))))))), wsTok: str(plus(sym('ws'))), wsStar: str(repeat(sym('ws'))), azAZ_nonASCII: alt(range('a', 'z'), range('A', 'Z'), '_', range(String.fromCharCode(128), String.fromCharCode(0xFFFFFFFF | 0))), azAZ09__nonASCII: alt(range('a', 'z'), range('A', 'Z'), range('0', '9'), '_', '-', range(String.fromCharCode(128), String.fromCharCode(0xFFFFFFFF | 0))), identTok: str(seq(optional('-'), alt(sym('azAZ_nonASCII'), sym('esc')), str(repeat(alt(sym('azAZ09__nonASCII'), sym('esc')))))), funcTok: seq1(0, sym('identTok'), '('), atkwTok: seq1(1, '@', sym('identTok')), hashTok: seq1(1, '#', str(plus(alt(sym('azAZ09__nonASCII'), sym('esc'))))), innerDblStr: str(repeat(alt(not(alt('"', '\\', sym('nl')), anyChar), sym('esc'), str(seq('\\', sym('nl')))))), innerSglStr: str(repeat(alt(not(alt('\'', '\\', sym('nl')), anyChar), sym('esc'), str(seq('\\', sym('nl')))))), dblStr: seq1(1, '"', sym('innerDblStr'), '"'), sglStr: seq1(1, '\'', sym('innerSglStr'), '\''), strTok: alt(sym('dblStr'), sym('sglStr')), urlTok: seq1(2, 'url(', sym('wsStar'), optional(seq1(0, alt(sym('urlUnquoted'), sym('strTok')), sym('wsStar'))), ')'), urlUnquoted: str(plus(alt(not(alt('"', '\'', '(', ')', '\\', sym('ws'), sym('nonPrintable')), anyChar), sym('esc')))), nonPrintable: alt(range(String.fromCharCode(0x0000), String.fromCharCode(0x0008)), String.fromCharCode(0x000B), range(String.fromCharCode(0x000E), String.fromCharCode(0x001F)), String.fromCharCode(0x007F)), numTok: str(seq(optional(alt('+', '-')), alt(str(seq(str(plus(sym('digit'))), '.', str(plus(sym('digit'))))), str(plus(sym('digit'))), str(seq('.', str(plus(sym('digit')))))), optional(str(seq(alt('e', 'E'), optional(alt('+', '-')), str(plus(sym('digit')))))))), dimTok: str(seq(sym('numTok'), sym('identTok'))), perTok: str(seq(sym('numTok'), '%')), uniRangeTok: str(seq(alt('U', 'u'), '+', alt(str(seq(sym('hd16'), '-', sym('hd16'))), sym('hd16QM'), sym('hd16')))), incMatchTok: literal('~='), dashMatchTok: literal('|='), prefMatchTok: literal('^='), sufMatchTok: literal('$='), subStrMatchTok: literal('*='), colTok: literal('||'), cdoTok: literal('<!--'), cdcTok: literal('-->'), colonTok: literal(':'), semicolonTok: literal(';'), commaTok: literal(','), // "Preserved token" from http://www.w3.org/TR/css-syntax-3/#parsing // is "Any token produced by the tokenizer except for // <function-token>s, <{-token>s, <(-token>s, and <[-token>s.". // We approximate this with the list of tokens below. Note that // '}', ')', and ']' are usually included in "preserved tokens", // but cause parse errors. According to the standard, this is to // allow for better error recovery in contexts such as media queries. preserved1: alt(sym('cdoTok'), sym('cdcTok'), sym('urlTok'), sym('strTok'), sym('perTok'), sym('dimTok'), sym('numTok'), sym('uniRangeTok'), sym('atkwTok'), sym('hashTok'), sym('identTok'), sym('incMatchTok'), sym('dashMatchTok'), sym('prefMatchTok'), sym('sufMatchTok'), sym('subStrMatchTok'), sym('colTok'), sym('wsTok'), sym('colonTok'), sym('commaTok')), // NoSemicolon cases: There are a few places where the "informal" // railroad diagrams in the spec specify a sequence of component // values (each maybe-is-a preserved token) terminated by a semicolon. // The trouble is, a semicolon token is a preserved token. It is for // such cases that we introduce the "NoSemicolon" variant of several // productions. // delimTok comes last: <delim-token> is emitted by standard lexer // when parsing tokens such as "~=", "<!--", etc. fails and we wish // to simply emit one delimiter. It is probably emitted even more // often (i.e., as a catch-all when every other token match has // failed). That is why some additions that are common in CSS (such as // '>') have been added. preservedTokNoSemicolon: alt(sym('preserved1'), sym('delimTok')), preservedTok: alt(sym('preserved1'), sym('semicolonTok'), sym('delimTok')), delimTok: alt('#', '$', '*', '+', '-', '.', '/', '<', '>', '@', '^', '|', '~', '='), // CSS 3 "parsing" productions (see http://www.w3.org/TR/css-syntax-3/#parsing). stylesheet: repeat(alt(sym('cdoTok'), sym('cdcTok'), sym('wsTok'), sym('atRule'), sym('qualifiedRule'))), atRule: seq(sym('atkwTok'), sym('atRuleContents')), atRuleContents: alt(sym('atRuleBody'), sym('atRuleContentsContinued')), atRuleBody: alt(sym('braceBlock'), ';'), atRuleContentsContinued: seq(sym('componentValueNoSemicolon'), sym('atRuleContents')), qualifiedRule: alt(sym('qualifiedRuleBlock'), sym('qualifiedRuleContinued')), qualifiedRuleBlock: sym('braceBlock'), qualifiedRuleContinued: seq(sym('componentValue'), sym('qualifiedRule')), rule: alt(sym('atRule'), sym('qualifiedRule')), ruleList: seq1(1, sym('wsStar'), alt(seq(sym('rule'), sym('ruleList')), sym('rule'))), declList: seq1(1, sym('wsStar'), alt(sym('declListOptDeclList'), sym('declListAtList'))), declListOptDeclList: seq(sym('declListOptDecl'), sym('declListOptList')), declListOptDecl: optional(sym('decl')), declListOptList: optional(seq(';', sym('declList'))), declListAtList: seq(sym('atRule'), sym('declList')), decl: seq(sym('identTok'), sym('wsStar'), ':', str(repeat(sym('componentValueNoSemicolon'))), optional(sym('important'))), important: str(seq('!', sym('wsStar'), 'important', sym('wsStar'))), componentValue: alt(sym('braceBlock'), sym('parenBlock'), sym('brackBlock'), sym('funcBlock'), sym('preservedTok')), componentValueNoSemicolon: alt(sym('braceBlock'), sym('parenBlock'), sym('brackBlock'), sym('funcBlock'), sym('preservedTokNoSemicolon')), // So far, we only really care about the structure of declarations // inside brace blocks. braceBlock: seq1(1, '{', sym('braceBlockContents'), '}'), braceBlockContents: seq(sym('wsStar'), optional(alt(sym('ruleList'), sym('declList'))), str(repeat(sym('componentValue')))), parenBlock: seq1(1, '(', str(repeat(sym('componentValue'))), ')'), brackBlock: seq1(1, '[', str(repeat(sym('componentValue'))), ']'), funcBlock: seq(sym('funcTok'), str(repeat(sym('componentValue'))), ')') }.addActions({ // TODO(markdittmer): We should probably "properly model" non-string // nodes rather than using { model_: 'NodeType', ... }. atkwTok: function(tok) { return { model_: 'AtKeyword', value: tok, toString: function() { return '@' + this.value.toString(); } }; }, hashTok: function(tok) { return { model_: 'HashIdent', value: tok, toString: function() { return '#' + this.value.toString(); } }; }, qualifiedRuleBlock: function(block) { return { model_: 'QualifiedRule', prelude: [], body: block, toString: function() { return this.prelude.map(function(p) { return p.toString(); }).join('') + this.body.toString(); } }; }, qualifiedRuleContinued: function(parts) { // TODO(markdittmer): Performance issue: Array.prototype.unshift // considered harmful. if ( css.isWhitespace(parts[0]) ) parts[1].prelude.unshift(' '); else parts[1].prelude.unshift(parts[0]); return parts[1]; }, atRule: function(parts) { return { model_: 'AtRule', atName: parts[0], contents: parts[1], toString: function() { return this.atName.toString() + ' ' + this.contents.toString(); } }; }, atRuleBody: function(body) { return { model_: 'AtRuleContents', prelude: [], body: body, toString: function() { return this.prelude.join('') + this.body.toString(); } }; }, atRuleContentsContinued: function(parts) { // TODO(markdittmer): Performance issue: Array.prototype.unshift // considered harmful. if ( css.isWhitespace(parts[0]) ) parts[1].prelude.unshift(' '); else parts[1].prelude.unshift(parts[0]); return parts[1]; }, braceBlockContents: function(parts) { var block = { model_: 'BraceBlock', contents: parts[1] ? parts[1] : '', tail: parts[2] ? css.stripWhitespace(parts[2]) : '', toString: function() { return '{' + this.contents.toString() + this.tail.toString() + '}'; } }; return block; }, brackBlock: function(body) { var block = { model_: 'BracketBlock', body: body, toString: function() { return '[' + css.stripWhitespace(this.body.toString()) + ']'; } }; return block; }, parenBlock: function(body) { var block = { model_: 'ParenBlock', body: body, toString: function() { return '(' + css.stripWhitespace(this.body.toString()) + ')'; } }; return block; }, dblStr: function(contents) { var block = { model_: 'DoubleStr', contents: contents, toString: function() { return '"' + this.contents.toString() + '"'; } }; return block; }, sglStr: function(contents) { var block = { model_: 'SingleStr', contents: contents, toString: function() { return '\'' + this.contents.toString() + '\''; } }; return block; }, urlTok: function(optUrl) { return { model_: 'Url', url: optUrl ? optUrl : '', toString: function() { return 'url(' + this.url.toString() + ')'; } }; }, funcBlock: function(parts) { var block = { model_: 'FuncBlock', name: parts[0], contents: parts[1].trim(), toString: function() { return this.name.toString() + '(' + this.contents.toString() + ')'; } }; return block; }, decl: function(parts) { var decl = { model_: 'Decl', key: parts[0], value: parts[3].trim(), important: !!parts[4], toString: function() { var key = this.key.toString(); var important = this.important ? '!important' : ''; var prefixData = css.PREFIXED_KEYS[key]; var value = this.value.toString(); var values = css.PREFIXES.length > 0 && prefixData === value ? css.PREFIXES.map(function(p) { return p + value; }).concat(value) : [value]; var rtn = values.map(function(v) { return key + ':' + v + important; }); if ( prefixData === true /*|| prefixData === value*/ ) { for ( var i = 0; i < css.PREFIXES.length; ++i ) { rtn = rtn.concat(values.map(function(v) { return css.PREFIXES[i] + key + ':' + v + important; })); } } return rtn.join(';'); } }; return decl; }, ruleList: function(parts) { if ( parts.length === 2 ) { // TODO(markdittmer): Performance issue: Array.prototype.unshift // considered harmful. parts[1].list.unshift(parts[0]); return parts[1]; } else { return { model_: 'RuleList', list: [parts], toString: function() { return this.list.join(''); } }; } }, declListOptDecl: function(decl) { return { model_: 'DeclList', list: decl ? [decl] : [], toString: function() { return this.list.map(function(decl) { return decl.toString(); }).join(';'); } }; }, declListOptList: function(parts) { return parts && parts[1] ? parts[1] : ''; }, declListAtList: function(atList) { atList[1].list = [atList[0]].concat(atList[1].list); return atList[1]; }, declListOptDeclList: function(declAndList) { if ( declAndList[1] ) declAndList[0].list = declAndList[0].list.concat(declAndList[1].list); return declAndList[0]; }, stylesheet: function(parts) { var stylesheet = { model_: 'Stylesheet', contents: parts.filter(function(p) { // Strip whitespace. return !css.isWhitespace(p); }), toString: function() { return this.contents.map(function(c) { return c.toString(); }).join(''); } }; return stylesheet; } }), seq('/*', str(repeat(not('*/', anyChar))), '*/')); } } ], methods: [ function isWhitespace(str) { return str.model_ ? false : !!str.match(/^[ \t\r\n\f]*$/g); }, function stripWhitespace(str) { return str.model_ ? str : str.replace(/[ \t\r\n\f]/g, ''); } ] });