| var SyntaxReferenceError = require('./error').SyntaxReferenceError; | 
| var MatchError = require('./error').MatchError; | 
| var names = require('../utils/names'); | 
| var generic = require('./generic'); | 
| var parse = require('../definition-syntax/parse'); | 
| var generate = require('../definition-syntax/generate'); | 
| var walk = require('../definition-syntax/walk'); | 
| var prepareTokens = require('./prepare-tokens'); | 
| var buildMatchGraph = require('./match-graph').buildMatchGraph; | 
| var matchAsTree = require('./match').matchAsTree; | 
| var trace = require('./trace'); | 
| var search = require('./search'); | 
| var getStructureFromConfig = require('./structure').getStructureFromConfig; | 
| var cssWideKeywords = buildMatchGraph('inherit | initial | unset'); | 
| var cssWideKeywordsWithExpression = buildMatchGraph('inherit | initial | unset | <-ms-legacy-expression>'); | 
|   | 
| function dumpMapSyntax(map, compact, syntaxAsAst) { | 
|     var result = {}; | 
|   | 
|     for (var name in map) { | 
|         if (map[name].syntax) { | 
|             result[name] = syntaxAsAst | 
|                 ? map[name].syntax | 
|                 : generate(map[name].syntax, { compact: compact }); | 
|         } | 
|     } | 
|   | 
|     return result; | 
| } | 
|   | 
| function valueHasVar(tokens) { | 
|     for (var i = 0; i < tokens.length; i++) { | 
|         if (tokens[i].value.toLowerCase() === 'var(') { | 
|             return true; | 
|         } | 
|     } | 
|   | 
|     return false; | 
| } | 
|   | 
| function buildMatchResult(match, error, iterations) { | 
|     return { | 
|         matched: match, | 
|         iterations: iterations, | 
|         error: error, | 
|         getTrace: trace.getTrace, | 
|         isType: trace.isType, | 
|         isProperty: trace.isProperty, | 
|         isKeyword: trace.isKeyword | 
|     }; | 
| } | 
|   | 
| function matchSyntax(lexer, syntax, value, useCommon) { | 
|     var tokens = prepareTokens(value, lexer.syntax); | 
|     var result; | 
|   | 
|     if (valueHasVar(tokens)) { | 
|         return buildMatchResult(null, new Error('Matching for a tree with var() is not supported')); | 
|     } | 
|   | 
|     if (useCommon) { | 
|         result = matchAsTree(tokens, lexer.valueCommonSyntax, lexer); | 
|     } | 
|   | 
|     if (!useCommon || !result.match) { | 
|         result = matchAsTree(tokens, syntax.match, lexer); | 
|         if (!result.match) { | 
|             return buildMatchResult( | 
|                 null, | 
|                 new MatchError(result.reason, syntax.syntax, value, result), | 
|                 result.iterations | 
|             ); | 
|         } | 
|     } | 
|   | 
|     return buildMatchResult(result.match, null, result.iterations); | 
| } | 
|   | 
| var Lexer = function(config, syntax, structure) { | 
|     this.valueCommonSyntax = cssWideKeywords; | 
|     this.syntax = syntax; | 
|     this.generic = false; | 
|     this.properties = {}; | 
|     this.types = {}; | 
|     this.structure = structure || getStructureFromConfig(config); | 
|   | 
|     if (config) { | 
|         if (config.types) { | 
|             for (var name in config.types) { | 
|                 this.addType_(name, config.types[name]); | 
|             } | 
|         } | 
|   | 
|         if (config.generic) { | 
|             this.generic = true; | 
|             for (var name in generic) { | 
|                 this.addType_(name, generic[name]); | 
|             } | 
|         } | 
|   | 
|         if (config.properties) { | 
|             for (var name in config.properties) { | 
|                 this.addProperty_(name, config.properties[name]); | 
|             } | 
|         } | 
|     } | 
| }; | 
|   | 
| Lexer.prototype = { | 
|     structure: {}, | 
|     checkStructure: function(ast) { | 
|         function collectWarning(node, message) { | 
|             warns.push({ | 
|                 node: node, | 
|                 message: message | 
|             }); | 
|         } | 
|   | 
|         var structure = this.structure; | 
|         var warns = []; | 
|   | 
|         this.syntax.walk(ast, function(node) { | 
|             if (structure.hasOwnProperty(node.type)) { | 
|                 structure[node.type].check(node, collectWarning); | 
|             } else { | 
|                 collectWarning(node, 'Unknown node type `' + node.type + '`'); | 
|             } | 
|         }); | 
|   | 
|         return warns.length ? warns : false; | 
|     }, | 
|   | 
|     createDescriptor: function(syntax, type, name) { | 
|         var ref = { | 
|             type: type, | 
|             name: name | 
|         }; | 
|         var descriptor = { | 
|             type: type, | 
|             name: name, | 
|             syntax: null, | 
|             match: null | 
|         }; | 
|   | 
|         if (typeof syntax === 'function') { | 
|             descriptor.match = buildMatchGraph(syntax, ref); | 
|         } else { | 
|             if (typeof syntax === 'string') { | 
|                 // lazy parsing on first access | 
|                 Object.defineProperty(descriptor, 'syntax', { | 
|                     get: function() { | 
|                         Object.defineProperty(descriptor, 'syntax', { | 
|                             value: parse(syntax) | 
|                         }); | 
|   | 
|                         return descriptor.syntax; | 
|                     } | 
|                 }); | 
|             } else { | 
|                 descriptor.syntax = syntax; | 
|             } | 
|   | 
|             // lazy graph build on first access | 
|             Object.defineProperty(descriptor, 'match', { | 
|                 get: function() { | 
|                     Object.defineProperty(descriptor, 'match', { | 
|                         value: buildMatchGraph(descriptor.syntax, ref) | 
|                     }); | 
|   | 
|                     return descriptor.match; | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         return descriptor; | 
|     }, | 
|     addProperty_: function(name, syntax) { | 
|         this.properties[name] = this.createDescriptor(syntax, 'Property', name); | 
|     }, | 
|     addType_: function(name, syntax) { | 
|         this.types[name] = this.createDescriptor(syntax, 'Type', name); | 
|   | 
|         if (syntax === generic['-ms-legacy-expression']) { | 
|             this.valueCommonSyntax = cssWideKeywordsWithExpression; | 
|         } | 
|     }, | 
|   | 
|     matchDeclaration: function(node) { | 
|         if (node.type !== 'Declaration') { | 
|             return buildMatchResult(null, new Error('Not a Declaration node')); | 
|         } | 
|   | 
|         return this.matchProperty(node.property, node.value); | 
|     }, | 
|     matchProperty: function(propertyName, value) { | 
|         var property = names.property(propertyName); | 
|   | 
|         // don't match syntax for a custom property | 
|         if (property.custom) { | 
|             return buildMatchResult(null, new Error('Lexer matching doesn\'t applicable for custom properties')); | 
|         } | 
|   | 
|         var propertySyntax = property.vendor | 
|             ? this.getProperty(property.name) || this.getProperty(property.basename) | 
|             : this.getProperty(property.name); | 
|   | 
|         if (!propertySyntax) { | 
|             return buildMatchResult(null, new SyntaxReferenceError('Unknown property', propertyName)); | 
|         } | 
|   | 
|         return matchSyntax(this, propertySyntax, value, true); | 
|     }, | 
|     matchType: function(typeName, value) { | 
|         var typeSyntax = this.getType(typeName); | 
|   | 
|         if (!typeSyntax) { | 
|             return buildMatchResult(null, new SyntaxReferenceError('Unknown type', typeName)); | 
|         } | 
|   | 
|         return matchSyntax(this, typeSyntax, value, false); | 
|     }, | 
|     match: function(syntax, value) { | 
|         if (typeof syntax !== 'string' && (!syntax || !syntax.type)) { | 
|             return buildMatchResult(null, new SyntaxReferenceError('Bad syntax')); | 
|         } | 
|   | 
|         if (typeof syntax === 'string' || !syntax.match) { | 
|             syntax = this.createDescriptor(syntax, 'Type', 'anonymous'); | 
|         } | 
|   | 
|         return matchSyntax(this, syntax, value, false); | 
|     }, | 
|   | 
|     findValueFragments: function(propertyName, value, type, name) { | 
|         return search.matchFragments(this, value, this.matchProperty(propertyName, value), type, name); | 
|     }, | 
|     findDeclarationValueFragments: function(declaration, type, name) { | 
|         return search.matchFragments(this, declaration.value, this.matchDeclaration(declaration), type, name); | 
|     }, | 
|     findAllFragments: function(ast, type, name) { | 
|         var result = []; | 
|   | 
|         this.syntax.walk(ast, { | 
|             visit: 'Declaration', | 
|             enter: function(declaration) { | 
|                 result.push.apply(result, this.findDeclarationValueFragments(declaration, type, name)); | 
|             }.bind(this) | 
|         }); | 
|   | 
|         return result; | 
|     }, | 
|   | 
|     getProperty: function(name) { | 
|         return this.properties.hasOwnProperty(name) ? this.properties[name] : null; | 
|     }, | 
|     getType: function(name) { | 
|         return this.types.hasOwnProperty(name) ? this.types[name] : null; | 
|     }, | 
|   | 
|     validate: function() { | 
|         function validate(syntax, name, broken, descriptor) { | 
|             if (broken.hasOwnProperty(name)) { | 
|                 return broken[name]; | 
|             } | 
|   | 
|             broken[name] = false; | 
|             if (descriptor.syntax !== null) { | 
|                 walk(descriptor.syntax, function(node) { | 
|                     if (node.type !== 'Type' && node.type !== 'Property') { | 
|                         return; | 
|                     } | 
|   | 
|                     var map = node.type === 'Type' ? syntax.types : syntax.properties; | 
|                     var brokenMap = node.type === 'Type' ? brokenTypes : brokenProperties; | 
|   | 
|                     if (!map.hasOwnProperty(node.name) || validate(syntax, node.name, brokenMap, map[node.name])) { | 
|                         broken[name] = true; | 
|                     } | 
|                 }, this); | 
|             } | 
|         } | 
|   | 
|         var brokenTypes = {}; | 
|         var brokenProperties = {}; | 
|   | 
|         for (var key in this.types) { | 
|             validate(this, key, brokenTypes, this.types[key]); | 
|         } | 
|   | 
|         for (var key in this.properties) { | 
|             validate(this, key, brokenProperties, this.properties[key]); | 
|         } | 
|   | 
|         brokenTypes = Object.keys(brokenTypes).filter(function(name) { | 
|             return brokenTypes[name]; | 
|         }); | 
|         brokenProperties = Object.keys(brokenProperties).filter(function(name) { | 
|             return brokenProperties[name]; | 
|         }); | 
|   | 
|         if (brokenTypes.length || brokenProperties.length) { | 
|             return { | 
|                 types: brokenTypes, | 
|                 properties: brokenProperties | 
|             }; | 
|         } | 
|   | 
|         return null; | 
|     }, | 
|     dump: function(syntaxAsAst, pretty) { | 
|         return { | 
|             generic: this.generic, | 
|             types: dumpMapSyntax(this.types, !pretty, syntaxAsAst), | 
|             properties: dumpMapSyntax(this.properties, !pretty, syntaxAsAst) | 
|         }; | 
|     }, | 
|     toString: function() { | 
|         return JSON.stringify(this.dump()); | 
|     } | 
| }; | 
|   | 
| module.exports = Lexer; |