Skip to content

Instantly share code, notes, and snippets.

@tdurieux
Forked from satabin/synctex-parser.coffee
Last active June 4, 2016 15:35
Show Gist options
  • Save tdurieux/82454f391617a2b94a233b27c851f644 to your computer and use it in GitHub Desktop.
Save tdurieux/82454f391617a2b94a233b27c851f644 to your computer and use it in GitHub Desktop.
class SyncTeXLexer
constructor: (@input) ->
@pointer = 0
current: =>
if @eos()
undefined
else
@input.slice @pointer
eos: =>
@pointer >= @input.length
next: =>
current = @current()
if @eos()
undefined
else if float = /^(-?\d+\.\d+)/.exec(@current())
@pointer += float[0].length
{
token: 'float',
value: parseFloat(float[1])
}
else if int = /^(-?\d+)/.exec(current)
@pointer += int[0].length
{
token: 'int',
value: parseInt(int[0])
}
else if path = /^(\/[a-zA-Z0-9/_.-]+)/.exec(current)
@pointer += path[0].length
{
token: 'path',
value: path[1]
}
else if eol = /^\n|\r\n/.exec(current)
@pointer += eol[0].length
{
token: 'eol'
}
else if symbol = /^([\[\](){}:!, ])/.exec(current)
@pointer += 1
{
token: symbol[1]
}
else if name = /^([a-zA-Z$]+)/.exec(current)
@pointer += name[0].length
{
token: 'name',
value: name[1]
}
else
name = /^([^\s]+)/.exec(current)
@pointer += name[0].length
{
token: 'other',
value: name[1]
}
class SyncTeXParser
constructor: (@lexer) ->
@lookahead = undefined
token: =>
if not @lookahead?
@lookahead = @lexer.next()
@lookahead
consume: =>
@lookahead = undefined
check: (expected) =>
{ token, value } = @token()
token is expected.token and value is expected.value
accept: (expected) =>
if @check(expected)
@consume()
else
console.log expected, @lookahead
throw new Error("#{expected.value} of type #{expected.token} expected but receive #{@lookahead}")
acceptPath: () =>
{ token, value } = @token()
if token != 'path'
throw new Error("Path expected")
@consume()
value
acceptName: (name) =>
@accept {token: 'name', value: name}
acceptFormat: () =>
{ token, value } = @token()
@consume()
value
acceptSymbol: (sym) =>
@accept {token: sym}
acceptInt: =>
{ token, value } = @token()
if token is 'int'
@consume()
value
else
throw new Error("Integer expected")
acceptFloat: =>
{ token, value } = @token()
if token is 'float'
@consume()
value
else
throw new Error("Float expected")
inputLines: =>
inputs = []
while @check {token: 'name', value: 'Input'}
input = {}
@acceptName "Input"
@acceptSymbol ":"
input.id = @acceptInt()
@acceptSymbol ":"
input.path = @acceptPath()
@acceptEol()
inputs.push(input)
inputs
checkCoordType: () =>
{token, value} = @token()
return token is 'name' && value in ['h', 'g', 'x', 'k', '$', 'r']
acceptCoordType: () =>
{token, value} = @token()
if not @checkCoordType()
throw new Error("CoordType expected")
@consume()
value
acceptCoord: () =>
coord = {}
if @checkCoordType()
coord.type = @acceptCoordType()
coord.input = @acceptInt()
@acceptSymbol ","
coord.line = @acceptInt()
@acceptSymbol ":"
coord.left = @acceptInt()
@acceptSymbol ","
coord.bottom = @acceptInt()
if @check {token: ':'}
@acceptSymbol ":"
coord.width = @acceptInt()
if @check {token: ','}
@acceptSymbol ","
coord.height = @acceptInt()
if @check {token: ','}
@acceptSymbol ","
coord.depth = @acceptInt()
coord
acceptBlockLine: () =>
block = @acceptCoord()
@acceptEol()
block
acceptVerticalBlock: () =>
block = {}
@acceptSymbol "["
block.coord = @acceptCoord()
@acceptEol()
block.blocks = @blocks()
@acceptSymbol "]"
@acceptEol()
block
acceptHorizontalBlock: () =>
block = {}
@acceptSymbol "("
block.coord = @acceptCoord()
@acceptEol()
block.blocks = @blocks()
@acceptSymbol ")"
@acceptEol()
block
blocks: () =>
blocks = []
while @check({token: '['}) or @check({token: '('}) or @checkCoordType()
if @check {token: '['}
blocks.push(@acceptVerticalBlock())
else if @check {token: '('}
blocks.push(@acceptHorizontalBlock())
else if @checkCoordType()
blocks.push(@acceptBlockLine())
blocks
pages: () =>
pages = []
while @check {token: '{'}
page = {}
@acceptSymbol "{"
page.page = @acceptInt()
@acceptEol()
page.blocks = @blocks()
@acceptSymbol "!"
page.size = @acceptInt()
@acceptEol()
@acceptSymbol "}"
@acceptInt()
@acceptEol()
pages.push(page)
pages
acceptEol: =>
@accept {token: 'eol'}
parse: =>
file = {}
@preamble(file)
@content(file)
@postamble(file)
file
preamble: (file) =>
@acceptName "SyncTeX"
@acceptSymbol " "
@acceptName "Version"
@acceptSymbol ":"
file.version = @acceptInt()
@acceptEol()
file.inputs = @inputLines()
@acceptName "Output"
@acceptSymbol ":"
file.format = @acceptFormat()
@acceptEol()
@acceptName "Magnification"
@acceptSymbol ":"
file.magnification = @acceptInt()
@acceptEol()
@acceptName "Unit"
@acceptSymbol ":"
file.unit = @acceptInt()
@acceptEol()
file.offset = {}
@acceptName "X"
@acceptSymbol " "
@acceptName "Offset"
@acceptSymbol ":"
file.offset.x = @acceptInt()
@acceptEol()
@acceptName "Y"
@acceptSymbol " "
@acceptName "Offset"
@acceptSymbol ":"
file.offset.y = @acceptInt()
@acceptEol()
content: (file) =>
file.content = {}
@acceptName "Content"
@acceptSymbol ":"
@acceptEol()
@acceptSymbol "!"
file.content.size = @acceptInt()
@acceptEol()
file.content.pages = @pages()
postamble: (file) =>
file.outputs = @inputLines()
@acceptSymbol "!"
@acceptInt()
@acceptEol()
@acceptName "Postamble"
@acceptSymbol ":"
@acceptEol()
@acceptName "Count"
@acceptSymbol ":"
file.count = @acceptInt()
@acceptEol()
@acceptSymbol "!"
@acceptInt()
@acceptEol()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment