ESPTerm web interface submodule, separated to make testing and development easier
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
espterm-front-end/lang/lang-loader.js

178 lines
4.5 KiB

/*
* This is a Webpack loader.
* Loads language PHP files by parsing the PHP syntax and returning a Javascript
* Object. This is because using JSON instead of PHP would not allow multiline
* strings, and CSON is unsupported by PHP, and because splitting the language
* files into PHP and JSON seems unwieldy.
*/
const selectedKeys = require('./keys')
const tokenizeRest = function (tokens, tokenize, ...args) {
let i = 0
while (i < tokens.length) {
if (typeof tokens[i] === 'string') {
tokens.splice(i, 1, ...tokenize(tokens[i], ...args))
}
i++
}
return tokens
}
const tokenizers = {
simpleType (code, regex, type, matchIndex = 1) {
let tokens = []
let match
while ((match = code.match(regex))) {
let before = code.substr(0, match.index)
code = code.substr(match.index + match[0].length)
if (before) tokens.push(before)
tokens.push({
type,
match,
content: match[matchIndex]
})
}
if (code) tokens.push(code)
return tokens
}
}
const unescapeString = function (token) {
if (token.match[1] === "'") {
// single-quoted string only has \\ and \'
return token.content.replace(/\\[\\']/g, match => match[1])
} else {
// double-quoted string
// \n -> 0x0A
// \r -> 0x0D
// \t -> 0x09
// \v -> 0x0B
// \e -> 0x1B
// \f -> 0x0C
// \\ -> \
// \$ -> $
// \" -> "
// \[0-7]{1,3} -> char
// \x[\da-f]{1,2} -> char
// \u{[\da-f]+} -> char
let content = token.content
content = content.replace(/\\[nrtvef\\$"]/g, match => match[1])
content = content.replace(/\\[0-7]{1,3}/g, match => {
return String.fromCodePoint(parseInt(match.substr(1), 8) % 0xFF)
})
content = content.replace(/\\x[\da-f]{1,2}/ig, match => {
return String.fromCodePoint(parseInt(match.substr(1), 16) % 0xFF)
})
content = content.replace(/\\u{[\da-f]+}/ig, match => {
return String.fromCodePoint(parseInt(match.substr(1), 16))
})
return content
}
}
module.exports = function (source) {
let originalSource = source
// remove PHP header
source = source.replace(/^\s*<\?(?:php)?\s*/, '')
// remove return statement
source = source.replace(/^return\s*/, '')
// remove trailing semicolon
source = source.replace(/;\s*$/, '')
// strings
let tokens = tokenizeRest([source], tokenizers.simpleType, /(['"])((?:\\.|[^\\\1])*?)\1/, 'string', 2)
// comments
tokenizeRest(tokens, tokenizers.simpleType, /\/\/(.*)/, 'comment')
// map delimiters
tokenizeRest(tokens, tokenizers.simpleType, /([[\]])/, 'map')
// arrows
tokenizeRest(tokens, tokenizers.simpleType, /(=>)/, 'arrow')
// commas
tokenizeRest(tokens, tokenizers.simpleType, /(,)/, 'comma')
// whitespace
tokenizeRest(tokens, tokenizers.simpleType, /(\s+)/, 'whitespace')
// remove whitespace tokens and comments
tokens = tokens.filter(token => !(['whitespace', 'comment'].includes(token.type)))
// split tokens into an array of items, separated by commas
let currentItem = []
let items = [currentItem]
for (let token of tokens) {
let { type } = token
if (type === 'map') continue
if (type === 'comma') items.push(currentItem = [])
else currentItem.push(token)
}
// remove null items
items = items.filter(item => item.length)
// assume that every item will contain [string, arrow, string] and create
// an object
let data = {}
for (let item of items) {
let key = item[0]
let value = item[2]
if (!key || !value) {
console.error('Item has no key or value!', item)
continue
}
data[unescapeString(key)] = unescapeString(value)
}
let result = {}
// put selected keys in result
for (let key of selectedKeys) {
result[key] = data[key]
}
// adapted from webpack/loader-utils
let remainingRequest = this.remainingRequest
if (!remainingRequest) {
remainingRequest = this.loaders.slice(this.loaderIndex + 1)
.map(obj => obj.request)
.concat([this.resource]).join('!')
}
let currentRequest = this.currentRequest
if (!currentRequest) {
remainingRequest = this.loaders.slice(this.loaderIndex)
.map(obj => obj.request)
.concat([this.resource]).join('!')
}
let map = {
version: 3,
file: currentRequest,
sourceRoot: '',
sources: [remainingRequest],
sourcesContent: [originalSource],
names: [],
mappings: 'AAAA;AAAA'
}
this.callback(null, `/* Generated language file */
module.exports=${JSON.stringify(result)}`, map)
}