Im trying to generate a stylsheet parser with some extras to experiment with jison. How can I implement the import directive to load other files into the main file? I'm a little bit confused. Is there a way to use the lexer in the grammer file? Can I read the file and then tokenize it?
grammar.jison
%{
var nodes = require('./nodes')
%}
%%
// Parsing starts here.
stylesheet:
statements EOF { return new nodes.StyleSheet($1) }
;
statements:
/* empty */ { $$ = [] }
| statementGroup { $$ = $1 }
| statements ';' statementGroup { $$ = $1.concat($3) }
| statements ';' { $$ = $1 }
;
statementGroup:
statement { $$ = [ $1 ] }
| rules
| rules statement { $$ = $1.concat($2) }
;
statement:
variableDeclaration
;
rules:
rule { $$ = [ $1 ] }
| rules rule { $$ = $1.concat($2) }
;
rule:
selector '{' declarations '}' { $$ = new nodes.Rule($1, $3) }
;
selector:
IDENTIFIER
| SELECTOR
;
declarations:
/* empty */ { $$ = [] }
| declarationGroup { $$ = $1 }
| declarations ';' declarationGroup { $$ = $1.concat($3) }
| declarations ';' { $$ = $1 }
;
declarationGroup:
declaration { $$ = [ $1 ] }
| rules
| rules declaration { $$ = $1.concat($2) }
;
declaration:
property
| variableDeclaration
;
property:
IDENTIFIER ':' values { $$ = new nodes.Property($1, $3) }
;
variableDeclaration:
VARIABLE ':' values { $$ = new nodes.Assign($1, $3) }
;
values:
value { $$ = [ $1 ] }
| values value { $$ = $1.concat($2) }
;
value:
IDENTIFIER { $$ = new nodes.Literal($1) }
| COLOR { $$ = new nodes.Literal($1) }
| NUMBER { $$ = new nodes.Literal($1) }
| DIMENSION { $$ = new nodes.Literal($1) }
| VARIABLE { $$ = new nodes.Variable($1) }
;
tokens.jisonlex
// Order is important. Rules are matches from top to bottom.
//// Macros
DIGIT [0-9]
NUMBER {DIGIT}+(\.{DIGIT}+)? // matches: 10 and 3.14
NAME [a-zA-Z][\w\-]* // matches: body, background-color and myClassName
SELECTOR (\.|\#|\:\:|\:){NAME} // matches: #id, .class, :hover and ::before
PATH (.+)/([^/]+) // matches ./bla/bla/nested.sss
%%
//// Rules
\s+ // ignore spaces, line breaks
// Numbers
{NUMBER}(px|em|\%) return 'DIMENSION' // 10px, 1em, 50%
{NUMBER} return 'NUMBER' // 0
\#[0-9A-Fa-f]{3,6} return 'COLOR' // #fff, #f0f0f0
// Selectors
{SELECTOR} return 'SELECTOR' // .class, #id
{NAME}{SELECTOR} return 'SELECTOR' // div.class, body#id
\@{NAME} return 'VARIABLE' // @variable
{NAME} return 'IDENTIFIER' // body, font-size
. return yytext // {, }, +, :, ;
<<EOF>> return 'EOF'
nodes.js
var Context = require('./context').Context
var compressed
function StyleSheet(rules, ss) {
this.rules = rules
this.ss = ss ? ss : []
}
exports.StyleSheet = StyleSheet
StyleSheet.prototype.toCSS = function(output) {
compressed = output || false
var context = new Context()
var ret = this.rules.map(function (rule) {
return rule.toCSS(context) }).filter(function (value) { return typeof value !== 'undefined' }).join('\n')
return compressed ? ret.replace(/\s+/g, '') : ret
}
function Rule(selector, declarations) {
this.selector = selector
this.declarations = declarations
}
exports.Rule = Rule
Rule.prototype.toCSS = function(parentContext) {
var propertiesCSS = [],
nestedRulesCSS = [],
context = new Context(this, parentContext)
this.declarations.forEach(function(declaration) {
var css = declaration.toCSS(context)
if (declaration instanceof Property) {
propertiesCSS.push(css)
} else if (declaration instanceof Rule) {
nestedRulesCSS.push(css)
}
})
return [ context.selector() + ' { ' + propertiesCSS.join(' ') + ' }' ].
concat(nestedRulesCSS).
join('\n')
}
function Property(name, values) {
this.name = name
this.values = values
}
exports.Property = Property
Property.prototype.toCSS = function(context) {
var valuesCSS = this.values.map(function(value) { return value.toCSS(context) })
return this.name + ': ' + valuesCSS.join(' ') + ';'
}
function Literal(value) {
this.value = value
}
exports.Literal = Literal
Literal.prototype.toCSS = function() {
return this.value
}
function Variable(name) {
this.name = name
}
exports.Variable = Variable
Variable.prototype.toCSS = function(context) {
return context.get(this.name)
}
function Assign(name, values) {
this.name = name
this.values = values
}
exports.Assign = Assign
Assign.prototype.toCSS = function(context) {
var valuesCSS = this.values.map(function(value) { return value.toCSS(context) })
context.set(this.name, valuesCSS.join(' '))
}
As far as I know, the basic jison lexer does not allow for incremental input; you need to give it a single string which it tokenises.
You can, however, use your own custom lexer (including calling into the jison lexer) in order to do incremental lexing. So your custom lexer would need to implement the input stack to implement to include command. It shouldn't be particularly difficult, although I don't have an example near at hand.