var sax = require('sax'); var fs = require('fs'); var assert = require('assert'); var oe = process.exit; process.exit = function() { console.trace(); oe(0); } var keywords = { 'class': 'windowClass' }; function camelcase(name) { name = name.split('_').reduce(function(str, word){ return str + word[0].toUpperCase() + word.slice(1); }); var keyword = keywords[name]; return keyword ? keyword : name; } function camelParam(a) { return a[0].toLowerCase() + a.substr(1); } var baseTypeSize = { 'enum': 1, 'BOOL': 1, 'UInt8': 1, 'Int8': 1, 'UInt16LE': 2, 'Int16LE': 2, 'UInt32LE': 4, 'Int32LE': 4 } var types = { 'BOOL': 'UInt8', 'BYTE': 'Int8', 'CARD8': 'UInt8', 'INT8': 'Int8', 'CARD16': 'UInt16LE', 'INT16': 'Int16LE', 'CARD32': 'UInt32LE', 'INT32': 'Int32LE' } function readProto(index, protoname, structs) { var name = index[protoname].file; var structs = {} var parser = sax.createStream(true); parser.startStruct = function(name, type) { parser.offset = 0; if (structs[name]) { console.log(structs[name]); console.log(name, type); //process.exit(0); } //assert.isUndefinedOrNull(structs[name]); parser.struct = structs[name] = {}; parser.struct.body = []; parser.struct.name = name; parser.struct.type = type; parser.struct.reply = []; parser.active = parser.struct.body; } parser.stack = []; fs.createReadStream(name).pipe(parser); parser.on('opentag', function(tag) { if (parser.inDoc) return; parser.stack.push(tag.name); switch (tag.name) { case 'typedef': types[tag.attributes.newname] = types[tag.attributes.oldname]; break; case 'xidtype': case 'xidunion': types[tag.attributes.name] = types.CARD32; break; case 'reply': parser.active = parser.struct.reply; break; case 'request': case 'request': case 'struct': case 'event': case 'error': case 'enum': if (!tag.attributes.name) { console.log(['NO NAME!!!', parser.stack, tag.attributes.name, tag]); //process.exit(0); } parser.startStruct(tag.attributes.name, tag.name); if (tag.name == 'request') { var op = parser.struct.opcode = parseInt(tag.attributes.opcode); assert.strictEqual(op.toString(), tag.attributes.opcode); } break; case 'field': var f = {name: camelcase(tag.attributes.name), type: tag.attributes.type }; // copy all values in this list from attr to result ['expression'].forEach(function(ff) { if (tag.attributes[ff]) { f[ff] = tag.attributes[ff] }}); parser.active.push(f); break; case 'pad': parser.active.push({pad: parseInt(tag.attributes.bytes), type: 'pad' }); break; case 'item': parser.active.push({name: tag.attributes.name}); break; case 'valueparam': parser.active.push({ type: 'valueparam', maskRef: camelcase(tag.attributes['value-mask-name']), maskType: tag.attributes['value-mask-type'], listRef: tag.attributes['value-list-name'] }); break; case 'list': parser.active.push({ name: tag.attributes.name, type: 'list', elemType: tag.attributes.type }); break; case 'doc': parser.inDoc = true; break; } } ); parser.on('closetag', function(tag) { parser.stack.pop(); if (tag === 'doc') { parser.inDoc = false; return; } if (parser.inDoc) return; switch (tag) { case 'reply': parser.active = parser.struct.body; break; case 'item': parser.struct.body[parser.struct.body.length-1].value = parser.lastValue; break; case 'value': parser.lastValue = parseInt(parser.lastText); break; case 'bit': parser.lastValue = 1< 1; --i) // we don't support param0 as a valuemask { if (req.body[i].name === field.maskRef) { req.body[i].valueList = field; field.mask = req.body[i]; break; } } result.push(' for (var i in ' + field.listRef + ') {'); result.push(' if (args[i] != undefined) {'); result.push(' extraLength += 4;'); //TODO: value list value type here result.push(' }'); result.push(' }'); } } var reqLen4 = ((reqLen + 3) >> 2); if (extraLength) result.push(' var data = Buffer.alloc(' + reqLen + ' + extraLength);'); else { result.pop(); result.push(' var data = Buffer.alloc(' + reqLen + ');'); } result.push(' data[0] = ' + req.opcode + ';'); if (req.body.length != 0) { if (req.body[0].type != 'pad') result.push(' data[1] = args[\'' + req.body[0].name + '\'];'); var offset = 4; for (var arg = 1; arg < req.body.length; ++arg) { var f = req.body[arg]; if (f.type === 'pad') { offset += f.pad; } else if (f.valueList) { var valueListStruct = structs[f.valueList.listRef]; assert(valueListStruct, "undefined reference to valueList"); result.push(' // value mask for ' + valueListStruct.name + ' parameters'); f.valueList.maskOffset = offset; offset += fieldLength(f); } else if (f.type === 'valueparam') { if (f.mask) f.maskType = f.mask.type; else { f.maskOffset = offset; offset += baseTypeSize[types[f.maskType]]; } // TODO: maybe is better to loop args and check if it present in listRef struct? instead of iterating listRef result.push(' // values for ' + f.listRef); result.push(' var mask = 0;'); result.push(' var offset = ' + offset + ';'); result.push(' for (var i in ' + f.listRef + ') {'); result.push(' if (args[i] != undefined) {'); result.push(' mask |= ' + f.listRef + '[i];'); // TODO: list-item-type? or type for each listRef enum // currently hardcoded CARD32 result.push(' data.writeUInt32LE(args[i], offset);'); result.push(' offset +=4;'); result.push(' }'); result.push(' }'); result.push(' data.write' + types[f.maskType] + '(mask, ' + f.maskOffset + ')'); } else if (f.type === 'STRING8') { result.push(' data.write(args[' + f.name + '], offset);'); result.push(' offset += args[' + f.name + '].lengt'); } else { if (f.expression) result.push(' data.write' + types[f.type] + '(' + f.expression + ', ' + offset + ');'); else result.push(' data.write' + types[f.type] + '(args[\'' + req.body[arg].name + '\'], ' + offset + ');'); offset += fieldLength(f); } } } else { result.push(' data[1] = 0;'); } if (!extraLength) result.push(' data.writeUInt16LE(' + reqLen4 + ', 2);'); else result.push(' data.writeUInt16LE(' + reqLen4 + '+(extraLength+3)>>2, 2);'); result.push(' return data;'); } // if no params if (req.reply.length == 0) { result.push(' }'); } else { genReply(req.reply, result); } result.push(']' + (last ? '}' : ',')) console.log(result.join('\n')); } } var index = require('./makeindex'); index('./proto/', function(index) { readProto(index, 'xproto'); });