Skip to content

Instantly share code, notes, and snippets.

@treeform
Created April 5, 2018 18:01
Show Gist options
  • Save treeform/3ac009556892b100779c6ed38882429b to your computer and use it in GitHub Desktop.
Save treeform/3ac009556892b100779c6ed38882429b to your computer and use it in GitHub Desktop.
import json, strutils, algorithm
type
Mapping* = object
## mapping object that represents the line
generatedLine: int
generatedColumn: int
originalLine: int
originalColumn: int
source: string
name: string
SourceMap* = ref object
## represents complete source object
file: string
mappings: seq[Mapping]
sources: seq[string]
names: seq[string]
proc newSourceMap*(file: string): SourceMap =
## start creation of a new javascript source map
result = new SourceMap
result.file = file
result.mappings = newSeq[Mapping]()
result.sources = newSeq[string]()
result.names = newSeq[string]()
proc cmp(mappingA, mappingB: Mapping): int =
## compair two source maps
var cmp = mappingA.generatedLine - mappingB.generatedLine;
if cmp != 0:
return cmp
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if cmp != 0:
return cmp;
cmp = cmp(mappingA.source, mappingB.source);
if cmp != 0:
return cmp;
cmp = mappingA.originalLine - mappingB.originalLine;
if cmp != 0:
return cmp
cmp = mappingA.originalColumn - mappingB.originalColumn;
if cmp != 0:
return cmp
return cmp(mappingA.name, mappingB.name);
const integers = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
proc encodeVLQ(i: int): string =
## encode using VLQ see https://en.wikipedia.org/wiki/Variable-length_quantity
result = ""
var n = i
if n < 0:
n = (-n shl 1) or 1
else:
n = n shl 1
var z = 0
while z == 0 or n > 0:
var e = n and 31
n = n shr 5
if n > 0:
e = e or 32
result.add(integers[e])
z += 1
assert encodeVLQ(1) == "C"
assert encodeVLQ(-1) == "D"
assert encodeVLQ(98) == "kG"
proc serializeMappings(sourceMap: var SourceMap): string =
# update the source arrays
for i, mapping in sourceMap.mappings:
if mapping.source != nil:
let sourceIdx = sourceMap.sources.find(mapping.source)
if sourceIdx == -1:
sourceMap.sources.add(mapping.source)
# update the name arrays
for i, mapping in sourceMap.mappings:
if mapping.name != nil:
let nameIdx = sourceMap.names.find(mapping.name)
if nameIdx == -1:
sourceMap.names.add(mapping.name)
# sort the mappings
sourceMap.mappings.sort(cmp)
result = ""
var
prevGeneratedColumn = 0
prevGeneratedLine = 1
prevOriginalColumn = 0
prevOriginalLine = 0
prevName = 0
prevSource = 0
next = ""
nameIdx: int
for i, mapping in sourceMap.mappings:
if mapping.generatedLine != prevGeneratedLine:
prevGeneratedColumn = 0
while mapping.generatedLine != prevGeneratedLine:
next &= ';'
inc prevGeneratedLine
else:
if i > 0:
if cmp(mapping, sourceMap.mappings[i - 1]) != 0:
continue
next &= ','
next &= encodeVLQ(mapping.generatedColumn - prevGeneratedColumn)
prevGeneratedColumn = mapping.generatedColumn
if mapping.source != nil:
let sourceIdx = sourceMap.sources.find(mapping.source)
next &= encodeVLQ(sourceIdx - prevSource)
prevSource = sourceIdx
next &= encodeVLQ(mapping.originalLine - 1 - prevOriginalLine)
prevOriginalLine = mapping.originalLine - 1
next &= encodeVLQ(mapping.originalColumn - prevOriginalColumn)
prevOriginalColumn = mapping.originalColumn
result &= next
proc generate*(sourceMap: var SourceMap): string =
## Generates a source map and returns it as a string
let serializedMappings = sourceMap.serializeMappings()
return $ %* {
"version": 3,
"sources": sourceMap.sources,
"names": sourceMap.names,
"mappings": serializedMappings,
"file": sourceMap.file,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment