mirror of
https://github.com/openstf/stf
synced 2025-10-04 10:19:30 +02:00
Add key character map parser.
This commit is contained in:
parent
277c9b401a
commit
329b862e4f
1 changed files with 456 additions and 0 deletions
456
lib/util/keyutil.js
Normal file
456
lib/util/keyutil.js
Normal file
|
@ -0,0 +1,456 @@
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
var adb = require('adbkit')
|
||||||
|
var Promise = require('bluebird')
|
||||||
|
|
||||||
|
module.exports.parseKeyCharacterMap = function(stream) {
|
||||||
|
var resolver = Promise.defer()
|
||||||
|
, state = 'type_t'
|
||||||
|
, keymap = {
|
||||||
|
keys: []
|
||||||
|
}
|
||||||
|
, lastKey
|
||||||
|
, lastRule
|
||||||
|
, lastModifier
|
||||||
|
, lastBehavior
|
||||||
|
|
||||||
|
function fail(char, state) {
|
||||||
|
throw new Error(util.format(
|
||||||
|
'Unexpected character "%s" in state "%s"'
|
||||||
|
, char
|
||||||
|
, state
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(char) {
|
||||||
|
switch (state) {
|
||||||
|
case 'comment_before_type_t':
|
||||||
|
if (char === '\n') {
|
||||||
|
state = 'type_t'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case 'type_t':
|
||||||
|
if (char === '\n') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === '#') {
|
||||||
|
state = 'comment_before_type_t'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 'k') {
|
||||||
|
state = 'key_k'
|
||||||
|
return parse(char)
|
||||||
|
}
|
||||||
|
if (char === 't') {
|
||||||
|
state = 'type_y'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'type_y':
|
||||||
|
if (char === 'y') {
|
||||||
|
state = 'type_p'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'type_p':
|
||||||
|
if (char === 'p') {
|
||||||
|
state = 'type_e'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'type_e':
|
||||||
|
if (char === 'e') {
|
||||||
|
state = 'type_name_start'
|
||||||
|
keymap.type = ''
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'type_name_start':
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char >= 'A' && char <= 'Z') {
|
||||||
|
keymap.type += char
|
||||||
|
state = 'type_name_continued'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'type_name_continued':
|
||||||
|
if (char === '\n') {
|
||||||
|
// Could have more of these, although it doesn't make much sense
|
||||||
|
state = 'type_t'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char >= 'A' && char <= 'Z') {
|
||||||
|
keymap.type += char
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'comment_before_key_k':
|
||||||
|
if (char === '\n') {
|
||||||
|
state = 'key_k'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case 'key_k':
|
||||||
|
if (char === '\n') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === '#') {
|
||||||
|
state = 'comment_before_key_k'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 'k') {
|
||||||
|
state = 'key_e'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'key_e':
|
||||||
|
if (char === 'e') {
|
||||||
|
state = 'key_y'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'key_y':
|
||||||
|
if (char === 'y') {
|
||||||
|
state = 'key_name_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'key_name_start':
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'A' && char <= 'Z')) {
|
||||||
|
keymap.keys.push(lastKey = {
|
||||||
|
key: char
|
||||||
|
, rules: []
|
||||||
|
})
|
||||||
|
state = 'key_name_continued'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'key_name_continued':
|
||||||
|
if (char === ' ') {
|
||||||
|
state = 'key_start_block'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'A' && char <= 'Z') ||
|
||||||
|
(char === '_')) {
|
||||||
|
lastKey.key += char
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'key_start_block':
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === '{') {
|
||||||
|
state = 'filter_name_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_name_start':
|
||||||
|
if (char === '\n' || char === '\t' || char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === '}') {
|
||||||
|
state = 'key_k'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char >= 'a' && char <= 'z') {
|
||||||
|
lastKey.rules.push(lastRule = {
|
||||||
|
modifiers: [lastModifier = {
|
||||||
|
type: char
|
||||||
|
}]
|
||||||
|
, behaviors: []
|
||||||
|
})
|
||||||
|
state = 'filter_name_continued'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_name_continued':
|
||||||
|
if (char === ':') {
|
||||||
|
state = 'filter_behavior_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === ',') {
|
||||||
|
state = 'filter_name_or_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === '+') {
|
||||||
|
state = 'filter_name_and_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char >= 'a' && char <= 'z') {
|
||||||
|
lastModifier.type += char
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_name_or_start':
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char >= 'a' && char <= 'z') {
|
||||||
|
lastKey.rules.push(lastRule = {
|
||||||
|
modifiers: [lastModifier = {
|
||||||
|
type: char
|
||||||
|
}]
|
||||||
|
, behaviors: lastRule.behaviors
|
||||||
|
})
|
||||||
|
state = 'filter_name_continued'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_name_and_start':
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char >= 'a' && char <= 'z') {
|
||||||
|
lastRule.modifiers.push(lastModifier = {
|
||||||
|
type: char
|
||||||
|
})
|
||||||
|
state = 'filter_name_continued'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value':
|
||||||
|
if (char === '\\') {
|
||||||
|
state = 'filter_value_escape'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char !== "'") {
|
||||||
|
lastRule.behaviors.push({
|
||||||
|
type: 'literal'
|
||||||
|
, value: char
|
||||||
|
})
|
||||||
|
state = 'filter_value_end'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value_escape':
|
||||||
|
if (char === '\\' || char === '\'' || char === '"') {
|
||||||
|
lastRule.behaviors.push({
|
||||||
|
type: 'literal'
|
||||||
|
, value: char
|
||||||
|
})
|
||||||
|
state = 'filter_value_end'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 'n') {
|
||||||
|
lastRule.behaviors.push({
|
||||||
|
type: 'literal'
|
||||||
|
, value: '\n'
|
||||||
|
})
|
||||||
|
state = 'filter_value_end'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 't') {
|
||||||
|
lastRule.behaviors.push({
|
||||||
|
type: 'literal'
|
||||||
|
, value: '\t'
|
||||||
|
})
|
||||||
|
state = 'filter_value_end'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 'u') {
|
||||||
|
state = 'filter_value_unicode_1'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value_end':
|
||||||
|
if (char === '\'') {
|
||||||
|
state = 'filter_behavior_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_start':
|
||||||
|
if (char === '\n') {
|
||||||
|
state = 'filter_name_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === "'") {
|
||||||
|
state = 'filter_value'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 'n') {
|
||||||
|
state = 'filter_behavior_none_2'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === 'f') {
|
||||||
|
state = 'filter_behavior_fallback_2'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_2':
|
||||||
|
if (char === 'a') {
|
||||||
|
state = 'filter_behavior_fallback_3'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_3':
|
||||||
|
if (char === 'l') {
|
||||||
|
state = 'filter_behavior_fallback_4'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_4':
|
||||||
|
if (char === 'l') {
|
||||||
|
state = 'filter_behavior_fallback_5'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_5':
|
||||||
|
if (char === 'b') {
|
||||||
|
state = 'filter_behavior_fallback_6'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_6':
|
||||||
|
if (char === 'a') {
|
||||||
|
state = 'filter_behavior_fallback_7'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_7':
|
||||||
|
if (char === 'c') {
|
||||||
|
state = 'filter_behavior_fallback_8'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_8':
|
||||||
|
if (char === 'k') {
|
||||||
|
state = 'filter_behavior_fallback_key_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_key_start':
|
||||||
|
if (char === ' ') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'A' && char <= 'Z')) {
|
||||||
|
lastRule.behaviors.push(lastBehavior = {
|
||||||
|
type: 'fallback'
|
||||||
|
, key: char
|
||||||
|
})
|
||||||
|
state = 'filter_behavior_fallback_key_continued'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_fallback_key_continued':
|
||||||
|
if (char === ' ') {
|
||||||
|
state = 'filter_behavior_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (char === '\n') {
|
||||||
|
state = 'filter_name_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'A' && char <= 'Z') ||
|
||||||
|
(char === '_')) {
|
||||||
|
lastBehavior.key += char
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_none_2':
|
||||||
|
if (char === 'o') {
|
||||||
|
state = 'filter_behavior_none_3'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_none_3':
|
||||||
|
if (char === 'n') {
|
||||||
|
state = 'filter_behavior_none_4'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_behavior_none_4':
|
||||||
|
if (char === 'e') {
|
||||||
|
lastRule.behaviors.push({
|
||||||
|
type: 'none'
|
||||||
|
})
|
||||||
|
state = 'filter_behavior_start'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value_unicode_1':
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'a' && char <= 'f')) {
|
||||||
|
lastRule.behaviors.push(lastBehavior = {
|
||||||
|
type: 'literal'
|
||||||
|
, value: parseInt(char, 16) << 12
|
||||||
|
})
|
||||||
|
state = 'filter_value_unicode_2'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value_unicode_2':
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'a' && char <= 'f')) {
|
||||||
|
lastBehavior.value += parseInt(char, 16) << 8
|
||||||
|
state = 'filter_value_unicode_3'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value_unicode_3':
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'a' && char <= 'f')) {
|
||||||
|
lastBehavior.value += parseInt(char, 16) << 4
|
||||||
|
state = 'filter_value_unicode_4'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
case 'filter_value_unicode_4':
|
||||||
|
if ((char >= '0' && char <= '9') ||
|
||||||
|
(char >= 'a' && char <= 'f')) {
|
||||||
|
lastBehavior.value += parseInt(char, 16)
|
||||||
|
state = 'filter_value_end'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return fail(char, state)
|
||||||
|
default:
|
||||||
|
throw new Error(util.format('Unexpected state "%s"', state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorListener(err) {
|
||||||
|
resolver.reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readableListener() {
|
||||||
|
var chunk = stream.read()
|
||||||
|
, i = 0
|
||||||
|
, l = chunk.length
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (i < l) {
|
||||||
|
parse(String.fromCharCode(chunk[i++]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
resolver.reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endListener() {
|
||||||
|
resolver.resolve(keymap)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.on('error', errorListener)
|
||||||
|
stream.on('readable', readableListener)
|
||||||
|
stream.on('end', endListener)
|
||||||
|
|
||||||
|
return resolver.promise.finally(function() {
|
||||||
|
stream.removeListener('error', errorListener)
|
||||||
|
stream.removeListener('readable', readableListener)
|
||||||
|
stream.removeListener('end', endListener)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue