Last active
August 29, 2015 14:02
-
-
Save jlongster/5858882fac03afbadf20 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function JSXBailError(message) { | |
this.message = message; | |
} | |
function JSXReadError(message, parser) { | |
var sx = syn.syntaxFromToken({ | |
type: null, | |
value: 'nothing matters', | |
lineStart: parser.lineStart(), | |
lineNumber: parser.lineNumber(), | |
range: [parser.index(), parser.index()+1] | |
}); | |
var err = new syn.MacroSyntaxError('JSX', message, sx) | |
return new SyntaxError(syn.printSyntaxError(parser.source(), err)); | |
} | |
var JSXReader = Reader.create({ | |
read: function() { | |
try { | |
this.readElement(); | |
} | |
catch(e) { | |
if(!(e instanceof JSXBailError)) { | |
throw e; | |
} | |
this.reset(); | |
//console.log('bailed: ' + this.parser.ch()); | |
} | |
return this.finish(); | |
}, | |
readElement: function() { | |
var parser = this.parser; | |
var Token = parser.Token; | |
this.shadowBuffer(function(prevBuffer) { | |
var selfClosing = this.readOpeningElement(); | |
var openingName = this.buffer[this.buffer.length-2]; | |
if(!selfClosing) { | |
while(this.parser.index() < this.parser.length()) { | |
if(this.match('<', '/')) { | |
break; | |
} | |
this.readChild(); | |
} | |
this.readClosingElement(); | |
var closingName = this.buffer[this.buffer.length-1]; | |
if(openingName.value !== closingName.value) { | |
throw new JSXReadError( | |
"Expected correspoding closing tag for " + openingName.value, | |
this.parser | |
) | |
} | |
} | |
var firstTok = this.buffer[0]; | |
var lastTok = this.buffer[this.buffer.length-1]; | |
prevBuffer.push(parser.withLoc(parser.index(), { | |
type: Token.Identifier, | |
value: 'DOM', | |
})); | |
prevBuffer.push(parser.withLoc(parser.index(), { | |
type: Token.Delimiter, | |
value: '{}', | |
startLineNumber: firstTok.lineNumber, | |
startLineStart: firstTok.lineStart, | |
startRange: firstTok.range, | |
inner: this.buffer, | |
endLineNumber: lastTok.lineNumber, | |
endLineStart: lastTok.lineStart, | |
endRange: lastTok.range, | |
})); | |
}.bind(this)); | |
}, | |
readOpeningElement: function() { | |
var parser = this.parser; | |
var selfClosing = false; | |
this.expect('<'); | |
this.readElementName(); | |
var openingName = this.buffer[this.buffer.length - 1]; | |
if(JSXTAGS[openingName.value]) { | |
openingName.value = 'React.DOM.' + openingName.value; | |
} | |
parser.skipComment(); | |
this.shadowBuffer(function(prevBuffer) { | |
var end = false; | |
while(parser.index() < parser.length() && | |
!this.match('/') && | |
!this.match('>')) { | |
this.readAttribute(); | |
end = this.match('/') || this.match('>'); | |
if(!end) { | |
this.add({ | |
type: parser.Token.Punctuator, | |
value: ',' | |
}); | |
} | |
} | |
if(this.buffer.length) { | |
prevBuffer.push(parser.withLoc(parser.index(), { | |
type: parser.Token.Delimiter, | |
value: '{}', | |
inner: this.buffer | |
})); | |
} | |
else { | |
prevBuffer.push({ | |
type: parser.Token.NullLiteral, | |
value: 'null' | |
}); | |
} | |
}.bind(this)); | |
parser.skipComment(); | |
if(this.match('/')) { | |
selfClosing = true; | |
this.expect('/'); | |
} | |
this.expect('>'); | |
return selfClosing; | |
}, | |
readClosingElement: function() { | |
this.expect('<'); | |
this.expect('/'); | |
this.readElementName(); | |
this.expect('>'); | |
}, | |
readChild: function() { | |
if(this.match('{')) { | |
this.readExpressionContainer(); | |
} | |
else if(this.match('<')) { | |
this.readElement(); | |
} | |
else { | |
this.readText(['{', '<']); | |
} | |
}, | |
readText: function(stopChars) { | |
var parser = this.parser; | |
var ch, str = ''; | |
var index = parser.index(); | |
var source = parser.source(); | |
var start = index; | |
while(index < parser.length()) { | |
ch = source[index]; | |
if(stopChars.indexOf(ch) !== -1) { | |
break; | |
} | |
if(ch === '&') { | |
parser.index(index); | |
str += this.readEntity(index); | |
index = parser.index(); | |
} | |
else { | |
index++; | |
if(parser.isLineTerminator(ch.charCodeAt(0))) { | |
parser.lineNumber(parser.lineNumber()+1); | |
parser.lineStart(index); | |
} | |
str += ch; | |
} | |
} | |
parser.index(index); | |
this.add(parser.withLoc(start, { | |
type: parser.Token.StringLiteral, | |
value: str | |
})); | |
}, | |
readEntity: function() { | |
var parser = this.parser; | |
var source = parser.source(); | |
var index = parser.index(); | |
var ch = source[index]; | |
var str = '', count = 0, entity; | |
parser.assert(ch === '&', 'Entity must start with an ampersand'); | |
index++; | |
while(index < parser.length() && count < 10) { | |
ch = source[index++]; | |
if(ch === ';') { | |
break; | |
} | |
str += ch; | |
count++; | |
} | |
if (str[0] === '#' && str[1] === 'x') { | |
entity = String.fromCharCode(parseInt(str.substr(2), 16)); | |
} else if (str[0] === '#') { | |
entity = String.fromCharCode(parseInt(str.substr(1), 10)); | |
} else { | |
entity = XHTMLEntities[str]; | |
} | |
parser.index(index); | |
return entity; | |
}, | |
readElementName: function() { | |
var parser = this.parser; | |
if(!parser.isIdentifierStart(parser.ch().charCodeAt(0))) { | |
throw new JSXBailError('bailed while reading element ' + | |
'name: ' + parser.ch().charCodeAt(0)); | |
} | |
this.readIdentifier(); | |
if(this.match(':')) { | |
this.readPunc(); | |
this.readIdentifier(); | |
} | |
if(this.match('.')) { | |
this.readMemberParts(); | |
} | |
}, | |
readAttribute: function() { | |
var hasValue = false; | |
this.readIdentifier(); | |
if(this.match(':')) { | |
this.readPunc(); | |
this.readIdentifier(); | |
} | |
if((hasValue = this.match('='))) { | |
this.expect('='); | |
} | |
this.add({ | |
type: this.parser.Token.Punctuator, | |
value: ':' | |
}); | |
if(hasValue) { | |
this.readAttributeValue(); | |
} | |
else { | |
this.add({ | |
type: this.parser.Token.BooleanLiteral, | |
value: 'true' | |
}); | |
} | |
this.parser.skipComment(); | |
}, | |
readAttributeValue: function() { | |
if(this.match('{')) { | |
this.readExpressionContainer(); | |
// TODO | |
//throw new Error("can't be empty"); | |
} | |
else if(this.match('<')) { | |
this.read(); | |
} | |
else { | |
var quote = this.parser.ch(); | |
var start = this.parser.index(); | |
if(quote !== '"' && quote !== "'") { | |
throw new JSXReadError( | |
'attributes should be an expression or quoted text', | |
this.parser | |
); | |
} | |
this.parser.index(start + 1); | |
this.readText([quote]); | |
if(quote !== this.parser.source()[this.parser.index()]) { | |
throw new JSXReadError( | |
'badly quoted string', | |
this.parser | |
); | |
} | |
this.parser.index(this.parser.index() + 1); | |
} | |
}, | |
readExpressionContainer: function() { | |
this.expect('{'); | |
var expr; | |
while(!this.match('}')) { | |
this.readToken(); | |
} | |
this.expect('}'); | |
}, | |
readMemberParts: function() { | |
while(this.match('.')) { | |
this.readPunc(); | |
this.readIdentifier(); | |
} | |
}, | |
readIdentifier: function() { | |
var parser = this.parser; | |
parser.skipComment(); | |
var index = parser.index(); | |
var source = parser.source(); | |
var ch, start, value = ''; | |
var chCode = parser.ch().charCodeAt(0); | |
if(chCode === 92 || !parser.isIdentifierStart(chCode)) { | |
throw new JSXBailError('bailed while reading identifier: ' + | |
parser.ch()); | |
} | |
start = index; | |
while(index < parser.length()) { | |
ch = source.charCodeAt(index); | |
// exclude backslash (\) and add hyphen (-) | |
if(ch === 92 || | |
!(ch === 45 || parser.isIdentifierPart(ch))) { | |
break; | |
} | |
value += source[index++]; | |
} | |
this.parser.index(index); | |
this.add(parser.withLoc(start, { | |
type: parser.Token.Identifier, | |
value: value | |
})); | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Reader(parser) { | |
this.buffer = []; | |
this.parser = parser; | |
this.origLineNumber = parser.lineNumber(); | |
this.origLineStart = parser.lineStart(); | |
this.origIndex = parser.index(); | |
} | |
Reader.create = function(cls) { | |
function _Reader() { | |
Reader.apply(this, arguments); | |
} | |
_Reader.prototype = _.extend(Object.create(Reader.prototype), cls); | |
return _Reader; | |
}; | |
Reader.prototype.expect = function(ch) { | |
var parser = this.parser; | |
var punc = parser.scanPunctuator(); | |
if(punc.value !== ch) { | |
throw new Error('unexpected: ' + punc.value + | |
', wanted: ' + ch); | |
} | |
return punc; | |
}; | |
Reader.prototype.match = function(/* ch1, ch2, ... chN */) { | |
var chars = Array.prototype.slice.call(arguments); | |
var parser = this.parser; | |
var idx = parser.index(); | |
var matched = false; | |
try { | |
var punc = parser.scanPunctuator(); | |
matched = punc.value === chars[0]; | |
chars.shift(); | |
if(chars.length) { | |
matched = matched && this.match.apply(this, chars); | |
} | |
} | |
catch(e) { | |
// TODO: check specific error type here | |
//console.log(e); | |
} | |
parser.index(idx); | |
return matched; | |
} | |
Reader.prototype.advance = function() { | |
this.add(this.parser.advance()); | |
} | |
Reader.prototype.readToken = function() { | |
this.add(this.parser.readToken([], false, false)); | |
} | |
Reader.prototype.reset = function() { | |
this.parser.index(this.origIndex); | |
this.parser.lineStart(this.origLineStart); | |
this.parser.lineNumber(this.origLineNumber); | |
this.finish(); | |
}; | |
Reader.prototype.add = function(toks) { | |
if(Array.isArray(toks)) { | |
this.buffer = this.buffer.concat(toks); | |
} | |
else { | |
this.buffer.push(toks); | |
} | |
}; | |
Reader.prototype.shadowBuffer = function(func) { | |
var prev = this.buffer; | |
this.buffer = []; | |
func(prev); | |
this.buffer = prev; | |
}; | |
Reader.prototype.finish = function() { | |
var buf = this.buffer; | |
this.buffer = []; | |
return buf; | |
}; | |
Reader.prototype.readPunc = function(ch) { | |
var tok; | |
if(ch) { | |
tok = this.expect(ch); | |
} | |
else { | |
tok = this.parser.scanPunctuator(); | |
} | |
this.add(tok); | |
}; | |
Reader.prototype.readIdent = function() { | |
this.add(this.parser.scanIdentifier()); | |
}; | |
Reader.prototype.readIdent = function() { | |
this.add(this.parser.scanIdentifier()); | |
}; | |
Reader.prototype.readLiteral = function() { | |
//this.add(this.parser.scanIdentifier()); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment