|
|
@@ -0,0 +1,220 @@
|
|
|
+/**
|
|
|
+ * Path-parsing algorithm scooped from Polymer/observe-js
|
|
|
+ */
|
|
|
+
|
|
|
+var pathStateMachine = {
|
|
|
+ 'beforePath': {
|
|
|
+ 'ws': ['beforePath'],
|
|
|
+ 'ident': ['inIdent', 'append'],
|
|
|
+ '[': ['beforeElement'],
|
|
|
+ 'eof': ['afterPath']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'inPath': {
|
|
|
+ 'ws': ['inPath'],
|
|
|
+ '.': ['beforeIdent'],
|
|
|
+ '[': ['beforeElement'],
|
|
|
+ 'eof': ['afterPath']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'beforeIdent': {
|
|
|
+ 'ws': ['beforeIdent'],
|
|
|
+ 'ident': ['inIdent', 'append']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'inIdent': {
|
|
|
+ 'ident': ['inIdent', 'append'],
|
|
|
+ '0': ['inIdent', 'append'],
|
|
|
+ 'number': ['inIdent', 'append'],
|
|
|
+ 'ws': ['inPath', 'push'],
|
|
|
+ '.': ['beforeIdent', 'push'],
|
|
|
+ '[': ['beforeElement', 'push'],
|
|
|
+ 'eof': ['afterPath', 'push']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'beforeElement': {
|
|
|
+ 'ws': ['beforeElement'],
|
|
|
+ '0': ['afterZero', 'append'],
|
|
|
+ 'number': ['inIndex', 'append'],
|
|
|
+ "'": ['inSingleQuote', 'append', ''],
|
|
|
+ '"': ['inDoubleQuote', 'append', '']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'afterZero': {
|
|
|
+ 'ws': ['afterElement', 'push'],
|
|
|
+ ']': ['inPath', 'push']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'inIndex': {
|
|
|
+ '0': ['inIndex', 'append'],
|
|
|
+ 'number': ['inIndex', 'append'],
|
|
|
+ 'ws': ['afterElement'],
|
|
|
+ ']': ['inPath', 'push']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'inSingleQuote': {
|
|
|
+ "'": ['afterElement'],
|
|
|
+ 'eof': ['error'],
|
|
|
+ 'else': ['inSingleQuote', 'append']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'inDoubleQuote': {
|
|
|
+ '"': ['afterElement'],
|
|
|
+ 'eof': ['error'],
|
|
|
+ 'else': ['inDoubleQuote', 'append']
|
|
|
+ },
|
|
|
+
|
|
|
+ 'afterElement': {
|
|
|
+ 'ws': ['afterElement'],
|
|
|
+ ']': ['inPath', 'push']
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function noop () {}
|
|
|
+
|
|
|
+function getPathCharType (char) {
|
|
|
+ if (char === undefined)
|
|
|
+ return 'eof'
|
|
|
+
|
|
|
+ var code = char.charCodeAt(0)
|
|
|
+
|
|
|
+ switch(code) {
|
|
|
+ case 0x5B: // [
|
|
|
+ case 0x5D: // ]
|
|
|
+ case 0x2E: // .
|
|
|
+ case 0x22: // "
|
|
|
+ case 0x27: // '
|
|
|
+ case 0x30: // 0
|
|
|
+ return char
|
|
|
+
|
|
|
+ case 0x5F: // _
|
|
|
+ case 0x24: // $
|
|
|
+ return 'ident'
|
|
|
+
|
|
|
+ case 0x20: // Space
|
|
|
+ case 0x09: // Tab
|
|
|
+ case 0x0A: // Newline
|
|
|
+ case 0x0D: // Return
|
|
|
+ case 0xA0: // No-break space
|
|
|
+ case 0xFEFF: // Byte Order Mark
|
|
|
+ case 0x2028: // Line Separator
|
|
|
+ case 0x2029: // Paragraph Separator
|
|
|
+ return 'ws'
|
|
|
+ }
|
|
|
+
|
|
|
+ // a-z, A-Z
|
|
|
+ if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
|
|
|
+ return 'ident'
|
|
|
+
|
|
|
+ // 1-9
|
|
|
+ if (0x31 <= code && code <= 0x39)
|
|
|
+ return 'number'
|
|
|
+
|
|
|
+ return 'else'
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Parse a string path into an array of segments
|
|
|
+ * Todo implement cache
|
|
|
+ *
|
|
|
+ * @param {String} path
|
|
|
+ * @return {Array|undefined}
|
|
|
+ */
|
|
|
+
|
|
|
+exports.parse = function (path) {
|
|
|
+ var keys = []
|
|
|
+ var index = -1
|
|
|
+ var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'
|
|
|
+
|
|
|
+ var actions = {
|
|
|
+ push: function() {
|
|
|
+ if (key === undefined)
|
|
|
+ return
|
|
|
+
|
|
|
+ keys.push(key)
|
|
|
+ key = undefined
|
|
|
+ },
|
|
|
+
|
|
|
+ append: function() {
|
|
|
+ if (key === undefined)
|
|
|
+ key = newChar
|
|
|
+ else
|
|
|
+ key += newChar
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function maybeUnescapeQuote() {
|
|
|
+ if (index >= path.length)
|
|
|
+ return
|
|
|
+
|
|
|
+ var nextChar = path[index + 1]
|
|
|
+ if ((mode == 'inSingleQuote' && nextChar == "'") ||
|
|
|
+ (mode == 'inDoubleQuote' && nextChar == '"')) {
|
|
|
+ index++
|
|
|
+ newChar = nextChar
|
|
|
+ actions.append()
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while (mode) {
|
|
|
+ index++
|
|
|
+ c = path[index]
|
|
|
+
|
|
|
+ if (c == '\\' && maybeUnescapeQuote(mode))
|
|
|
+ continue
|
|
|
+
|
|
|
+ type = getPathCharType(c)
|
|
|
+ typeMap = pathStateMachine[mode]
|
|
|
+ transition = typeMap[type] || typeMap['else'] || 'error'
|
|
|
+
|
|
|
+ if (transition == 'error')
|
|
|
+ return // parse error
|
|
|
+
|
|
|
+ mode = transition[0]
|
|
|
+ action = actions[transition[1]] || noop
|
|
|
+ newChar = transition[2] === undefined ? c : transition[2]
|
|
|
+ action()
|
|
|
+
|
|
|
+ if (mode === 'afterPath') {
|
|
|
+ return keys
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return // parse error
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get from an object from a path
|
|
|
+ *
|
|
|
+ * @param {Object} obj
|
|
|
+ * @param {String} path
|
|
|
+ */
|
|
|
+
|
|
|
+exports.get = function (obj, path) {
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Set on an object from a path
|
|
|
+ *
|
|
|
+ * @param {Object} obj
|
|
|
+ * @param {String} path
|
|
|
+ * @param {*} val
|
|
|
+ */
|
|
|
+
|
|
|
+exports.set = function (obj, path, val) {
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Traverse an object along a path and trigger callback
|
|
|
+ *
|
|
|
+ * @param {Object} obj
|
|
|
+ * @param {String} path
|
|
|
+ * @param {Function} cb
|
|
|
+ */
|
|
|
+
|
|
|
+exports.traverse = function (obj, path, cb) {
|
|
|
+
|
|
|
+}
|