diff --git a/.vscodeignore b/.vscodeignore index e1590c9..5053536 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,5 +1,6 @@ .vscode/** .vscode-test/** +releases/** out/test/** out/**/*.map src/** diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ec648..e6be1a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -- Configurarable setting to change format of the disassembled code +- HTML views for sections + +- Navigation between view via links + +- Erlang assembly formatter + +## 1.0.0 + +- Dedicated view in the explorer panel for every .beam file in the workspace + +- Each section of a beam file has a subitem in the tree view ## 0.0.2 - 2018-04-03 diff --git a/README.md b/README.md index 11bf8b7..daaff4b 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,13 @@ BEAM files disassembler extension for Visual Studio Code. ## Description Erlang\Elixir bytecode viewer. -This extension allows to "peek" inside compiled bytecode for BEAM (Bogdan/Björn Erlang Abstract machine) binary files. +This extension allows to "peek" inside compiled bytecode for BEAM (Bogdan/Björn Erlang Abstract machine). ## Features - Dedicated View in the Explorer for all .beam files in the workspace - Subitems for main sections: + - Bytecode instructions (**Code** section) - Atoms ('**Atom**' and '**AtU8**' sections) - Exported functions ('**ExpT**' section) - Imported functions ('**ImpT**' section) @@ -24,9 +25,9 @@ This extension allows to "peek" inside compiled bytecode for BEAM (Bogdan/Björn - Strings '**StrT**' section) - Attributes ('**Attr**' section) - Literals ('**LitT**' section) -- Opcodes, labels, registers highlighting -- Hover description for opcodes taken from erlang source code (when available) -- Gutter images for start of the functions +- Opcodes, labels and registers color highlighting +- Hover description for opcodes taken from erlang source code (when available) +- Gutter images to indicate start of each function To activate the extension select "Disassemble BEAM" from the context menu for a .beam file in the Explorer View: @@ -48,7 +49,7 @@ None ## Release Notes -Initial previe release +First non-preview release ## Contributing diff --git a/media/main.gif b/media/main.gif index 0a8ee99..7f18024 100644 Binary files a/media/main.gif and b/media/main.gif differ diff --git a/package.json b/package.json index c258b42..ddfe173 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "BEAMdasm", "description": "BEAM file disassembler", "license": "SEE LICENSE IN LICENSE.md", - "version": "0.0.2", + "version": "1.0.0", "publisher": "Valentin", "engines": { "vscode": "^1.21.0" @@ -26,7 +26,6 @@ "color": "#1E1E1E", "theme": "dark" }, - "preview": true, "activationEvents": [ "onView:beamdasm.beamFilesTree", "onCommand:beamdasm.disassemble" diff --git a/resources/dark/attr.svg b/resources/dark/attr.svg new file mode 100644 index 0000000..e5260e2 --- /dev/null +++ b/resources/dark/attr.svg @@ -0,0 +1 @@ +attr \ No newline at end of file diff --git a/resources/dark/code.svg b/resources/dark/code.svg index 44bc97a..865b4ab 100644 --- a/resources/dark/code.svg +++ b/resources/dark/code.svg @@ -1,29 +1 @@ - - - - - - - - - - - - - - - - +code \ No newline at end of file diff --git a/resources/dark/litt.svg b/resources/dark/litt.svg index b98ce38..6fdba1a 100644 --- a/resources/dark/litt.svg +++ b/resources/dark/litt.svg @@ -1 +1 @@ -litt \ No newline at end of file +litt \ No newline at end of file diff --git a/resources/dark/strt.svg b/resources/dark/strt.svg new file mode 100644 index 0000000..79b9c48 --- /dev/null +++ b/resources/dark/strt.svg @@ -0,0 +1 @@ +strt \ No newline at end of file diff --git a/resources/light/attr.svg b/resources/light/attr.svg new file mode 100644 index 0000000..9c130b4 --- /dev/null +++ b/resources/light/attr.svg @@ -0,0 +1 @@ +attr \ No newline at end of file diff --git a/resources/light/code.svg b/resources/light/code.svg index 55da68f..ffc45aa 100644 --- a/resources/light/code.svg +++ b/resources/light/code.svg @@ -1,29 +1 @@ - - - - - - - - - - - - - - - - +code \ No newline at end of file diff --git a/resources/light/litt.svg b/resources/light/litt.svg index 6eeb323..5bda23c 100644 --- a/resources/light/litt.svg +++ b/resources/light/litt.svg @@ -1 +1 @@ -litt \ No newline at end of file +litt \ No newline at end of file diff --git a/resources/light/strt.svg b/resources/light/strt.svg new file mode 100644 index 0000000..694938a --- /dev/null +++ b/resources/light/strt.svg @@ -0,0 +1 @@ +strt \ No newline at end of file diff --git a/src/beam/beamFile.ts b/src/beam/beamFile.ts index 4de176b..316ba95 100644 --- a/src/beam/beamFile.ts +++ b/src/beam/beamFile.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,13 +18,14 @@ import * as fs from 'fs'; import * as zlib from 'zlib'; import List from './terms/list'; import Tuple from './terms/tuple'; +import Map from './terms/map'; import { opcodes } from './opcodes'; -import * as tags from './tags'; +import * as Tags from './tags'; /// -export default class BeamFile implements beamdasm.IBeamFile { +export default class BeamFile implements beamdasm.Beam { readonly code: any[] = []; _codeNumberOfFunctions: number = 0; @@ -33,9 +34,12 @@ export default class BeamFile implements beamdasm.IBeamFile { _codeInstructionSet: number = 0; _codeExtraFields: number = 0; - sections:any = {}; + sections: any = {}; atoms: string[] = ["nil"]; + numberOfAtoms: number = 0; + maxAtomNameLength: number = 0; + imports: any[] = []; exports: any[] = []; LocT: any[] = []; @@ -61,6 +65,7 @@ export default class BeamFile implements beamdasm.IBeamFile { } readBeamFile(filePath: string) { + let buffer: Buffer = fs.readFileSync(filePath); let for1 = buffer.toString('utf8', 0, 4).toLowerCase(); @@ -79,25 +84,24 @@ export default class BeamFile implements beamdasm.IBeamFile { let offset = 12; - this.sections ={}; + this.sections = {}; //Quick scan to get chunks offsets and sizes //We want to read them in particular order, not the order //chunks are present in the file - while ( offset < length ) - { - let name = buffer.toString('utf8', offset, offset+4); - let size = buffer.readUInt32BE(offset+4); + while (offset < length) { + let name = buffer.toString('utf8', offset, offset + 4); + let size = buffer.readUInt32BE(offset + 4); - this.sections[name.toLowerCase()] = {start: offset + 8, length: size}; + this.sections[name.toLowerCase()] = { start: offset + 8, length: size }; - offset = offset + 8 + (((size + 3)>>2)<<2); + offset = offset + 8 + (((size + 3) >> 2) << 2); } this.atoms = ['nil']; - + this.readAtu8Section(buffer); - this.readAtomSection(buffer); + this.readAtomSection(buffer); this.readImptSection(buffer); this.readExptSection(buffer); this.readFuntSection(buffer); @@ -107,13 +111,13 @@ export default class BeamFile implements beamdasm.IBeamFile { this.readAttrSection(buffer); this.readLittSection(buffer); this.readLineSection(buffer); - - if( 'catt' in this.sections ){ + + if ('catt' in this.sections) { console.log('Found CatT chunk'); } - if( 'abst' in this.sections ){ + if ('abst' in this.sections) { console.log('Found Abst chunk'); } @@ -244,7 +248,7 @@ export default class BeamFile implements beamdasm.IBeamFile { let term = this.readTerm(buffer, offset); offset = term.offset; - if (term.tag === tags.TAG_ATOM) { + if (term.tag === Tags.TAG_ATOM) { num_line_refs++; fname_index = term.data; } @@ -290,7 +294,7 @@ export default class BeamFile implements beamdasm.IBeamFile { } readAttrSection(buffer: Buffer) { - if( 'attr' in this.sections ){ + if ('attr' in this.sections) { let section = this.sections['attr']; section.name = 'Attributes (Attr)'; let offset = section.start; @@ -301,7 +305,7 @@ export default class BeamFile implements beamdasm.IBeamFile { } readCInfSection(buffer: Buffer) { - if('cinf' in this.sections){ + if ('cinf' in this.sections) { let section = this.sections['cinf']; section.name = 'Compilation Info (CInf)'; let offset = section.start; @@ -328,8 +332,7 @@ export default class BeamFile implements beamdasm.IBeamFile { readImptSection(buffer: Buffer) { - if( 'impt' in this.sections ) - { + if ('impt' in this.sections) { let section = this.sections['impt']; section.name = 'Imports (ImpT)'; this.imports = []; @@ -337,7 +340,7 @@ export default class BeamFile implements beamdasm.IBeamFile { let offset = section.start; let nImports = buffer.readUInt32BE(offset); - if( nImports === 0){ + if (nImports === 0) { section.empty = true; return; } @@ -364,12 +367,12 @@ export default class BeamFile implements beamdasm.IBeamFile { section.name = 'Exports (ExpT)'; let offset = section.start; let nExport = buffer.readUInt32BE(offset); - - if( nExport === 0){ + + if (nExport === 0) { section.empty = true; return; } - + offset += 4; while (nExport-- > 0) { @@ -429,38 +432,42 @@ export default class BeamFile implements beamdasm.IBeamFile { } readAtu8Section(buffer: Buffer) { - if( 'atu8' in this.sections ) - { + if ('atu8' in this.sections) { let section = this.sections['atu8']; section.name = 'Atoms (AtU8)'; let offset = section.start; let nAtoms = buffer.readUInt32BE(offset); - if( nAtoms === 0){ + this.numberOfAtoms = nAtoms; + if (nAtoms === 0) { section.empty = 0; return; } offset += 4; + let maxLength = 0; + while (nAtoms-- > 0) { let atomLength = buffer.readUInt8(offset); let atom = buffer.toString('utf8', offset + 1, offset + 1 + atomLength); + maxLength = ( atom.length > maxLength) ? atom.length : maxLength; offset = offset + 1 + atomLength; this.atoms.push(atom); } + + this.maxAtomNameLength = maxLength; } } readAtomSection(buffer: Buffer) { - if( 'atom' in this.sections ) - { + if ('atom' in this.sections) { let section = this.sections['atom']; section.name = 'Atoms (Atom)'; let offset = section.start; let nAtoms = buffer.readUInt32BE(offset); - if( nAtoms === 0){ + if (nAtoms === 0) { section.empty = 0; return; } @@ -474,14 +481,14 @@ export default class BeamFile implements beamdasm.IBeamFile { this.atoms.push(atom); } } - } + } //TODO: implement the rest of the terms readObject(tag: number, buffer: Buffer, offset: number): any { switch (tag) { case 70: { - return { data: "float8bytes", offset: offset + 8}; + return { data: "float8bytes", offset: offset + 8 }; } case 97: return { data: buffer.readUInt8(offset), offset: offset + 1 }; @@ -544,23 +551,23 @@ export default class BeamFile implements beamdasm.IBeamFile { readMap(buffer: Buffer, offset: number): any { let arity = buffer.readUInt32BE(offset); offset += 4; + + let map = new Map(); + while (arity-- > 0) { let keyTag = buffer.readUInt8(offset++); let keyObj = this.readObject(keyTag, buffer, offset); offset = keyObj.offset; - //console.log(`${keyObj.data}`); - let valTag = buffer.readUInt8(offset++); let valObj = this.readObject(valTag, buffer, offset); offset = valObj.offset; - //console.log(`${valObj.data}`); - //Add object to the map + map.add(keyObj.data, valObj.data); } - return { data: "Map", offset: offset }; + return { data: map, offset: offset }; } readAtom(buffer: Buffer, offset: number): any { @@ -584,7 +591,7 @@ export default class BeamFile implements beamdasm.IBeamFile { return { data: buffer.toString('utf8', offset, offset + length), offset: offset + length }; } - readonly hex: (d:number) => string = (d: number) => ("0" + d.toString(16)).slice(-2).toUpperCase(); + readonly hex: (d: number) => string = (d: number) => ("0" + d.toString(16)).slice(-2).toUpperCase(); readSmallBigNum(buffer: Buffer, offset: number): any { let length = buffer.readUInt8(offset++); @@ -609,7 +616,7 @@ export default class BeamFile implements beamdasm.IBeamFile { } if (index > 11) { - index = tags.TAG_UNKNOWN; + index = Tags.TAG_UNKNOWN; } return index; @@ -619,7 +626,7 @@ export default class BeamFile implements beamdasm.IBeamFile { let firstByte = buffer.readUInt8(offset++); let tag = this.readTag(firstByte); - if (tag === tags.TAG_EXT_LITERAL) { + if (tag === Tags.TAG_EXT_LITERAL) { //Read index in literals table let val = this.readTerm(buffer, offset); offset = val.offset; @@ -630,10 +637,10 @@ export default class BeamFile implements beamdasm.IBeamFile { }; } - if (tag === tags.TAG_EXT_LIST) { + if (tag === Tags.TAG_EXT_LIST) { let size = this.readTerm(buffer, offset); offset = size.offset; - let list:any[] = []; + let list: any[] = []; for (let i = 0; i < size.data; i++) { let obj = this.readTerm(buffer, offset); offset = obj.offset; @@ -646,7 +653,7 @@ export default class BeamFile implements beamdasm.IBeamFile { }; } - if (tag === tags.TAG_EXT_FLOAT_REGISTER) { + if (tag === Tags.TAG_EXT_FLOAT_REGISTER) { //Read register number let val = this.readTerm(buffer, offset); //Would not hurt to assert the bellow assumption @@ -657,11 +664,11 @@ export default class BeamFile implements beamdasm.IBeamFile { //Float registers contain TAG_LITERAL for the register number //safe to pass it directly instead of nesting, but keep the tag //for reporting purposes - data: val.data, + data: val.data, offset: offset }; } - if (tag === tags.TAG_EXT_ALLOC_LIST) { + if (tag === Tags.TAG_EXT_ALLOC_LIST) { let size = this.readTerm(buffer, offset); offset = size.offset; let list = []; @@ -681,7 +688,7 @@ export default class BeamFile implements beamdasm.IBeamFile { }; } - if (tag > tags.TAG_CHARACTER) { + if (tag > Tags.TAG_CHARACTER) { console.log(`READING TAG: ${tag} is not implemented`); } @@ -713,10 +720,10 @@ export default class BeamFile implements beamdasm.IBeamFile { offset: offset + 2 }; } - if( size === 3 ) { + if (size === 3) { return { tag: tag, - data: (buffer.readUInt8(offset) << 16) + (buffer.readUInt8(offset+1) << 8) + buffer.readUInt8(offset+2), + data: (buffer.readUInt8(offset) << 16) + (buffer.readUInt8(offset + 1) << 8) + buffer.readUInt8(offset + 2), offset: offset + 3 }; } @@ -756,7 +763,7 @@ export default class BeamFile implements beamdasm.IBeamFile { if (byteCode > 163) { console.log(`Illegal opcode ${byteCode}`); } - + // if( byteCode === 125 ){ // console.log('fdiv'); @@ -769,16 +776,16 @@ export default class BeamFile implements beamdasm.IBeamFile { list.push(obj); } - if( byteCode === 1){ + if (byteCode === 1) { label = list; continue; } - if( byteCode === 153 ){ + if (byteCode === 153) { line = list; continue; } - + this.code.push({ op: byteCode, params: list, label: label, line: line }); line = null; label = null; diff --git a/src/beam/beamFileCache.ts b/src/beam/beamFileCache.ts new file mode 100644 index 0000000..6fd69c7 --- /dev/null +++ b/src/beam/beamFileCache.ts @@ -0,0 +1,47 @@ +// Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +/// + +import BeamFile from './beamFile'; + +class BeamCacheSingleton +{ + private static _instance: BeamCacheSingleton = new BeamCacheSingleton(); + + private constructor() + {} + + public static get Instance() + { + return this._instance; + } + + beamFilePath: string = ''; + beamFile: beamdasm.Beam | undefined = undefined; + + public getBeamFile(path: string): beamdasm.Beam { + if( this.beamFilePath === path ){ + return this.beamFile as beamdasm.Beam; + } + + this.beamFilePath = path; + this.beamFile = BeamFile.fromFile(this.beamFilePath); + return this.beamFile; + } +} + +export let BeamCache = BeamCacheSingleton.Instance; \ No newline at end of file diff --git a/src/beam/beamdasmFormatter.ts b/src/beam/beamdasmFormatter.ts new file mode 100644 index 0000000..643816e --- /dev/null +++ b/src/beam/beamdasmFormatter.ts @@ -0,0 +1,242 @@ +// Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +/// + +import { opcodes } from './opcodes'; +import * as Tags from './tags'; + +let lbl: (val: number) => string; + +function instructionToString(beamFile: beamdasm.Beam, obj: any, func: number): string { + let name = opcodes[obj.op].nm; + let str = ` ${name}` + ' '.repeat(20 - name.length); + + for (let i = 0; i < opcodes[obj.op].ar; i++) { + if (i === func) { + let func_info = beamFile.imports[obj.params[i].data]; + str += ` ${beamFile.atoms[func_info.module]}:${beamFile.atoms[func_info.function]}/${func_info.arity}`; + } + else { + str += ` ${termToString(beamFile, obj.params[i])}`; + } + } + return str; +} + +function termToString(beamFile: beamdasm.Beam, obj: any): string { + if (obj.tag === Tags.TAG_LABEL) { + return lbl(obj.data); + } + if (obj.tag === Tags.TAG_X_REGISTER) { + return `X[${obj.data}]`; + } + if (obj.tag === Tags.TAG_Y_REGISTER) { + return `Y[${obj.data}]`; + } + if (obj.tag === Tags.TAG_ATOM) { + let value = beamFile.atoms[obj.data]; + return value === undefined ? `.` : `${value}`; + } + if (obj.tag === Tags.TAG_EXT_FLOAT_REGISTER) { + return `FR[${obj.data}]`; + } + if (obj.tag === Tags.TAG_EXT_LITERAL) { + return `${beamFile.literals[obj.data]}`; + } + if (obj.tag === Tags.TAG_EXT_LIST) { + let str = '['; + for (let i = 0; i < obj.data.length; i++) { + str += (i !== 0) ? ', ' : ''; + str += `${termToString(beamFile, obj.data[i])}`; + } + str += ']'; + + return str; + } + + if (obj.tag === Tags.TAG_LITERAL || obj.tag === Tags.TAG_INTEGER) { + return `${obj.data}`; + } + + return `.`; +} + +export class BeamdasmFormatter implements beamdasm.BeamFormatter { + + formatModuleInfo(beamFile: beamdasm.Beam): string { + let str = `Module: ${beamFile.atoms[1]}\n`; + str += '\n'; + str += `Attributes: ${beamFile.attributes}\n`; + str += '\n'; + str += `Compilation Info: ${beamFile.compilationInfo}\n`; + str += '\n'; + return str; + } + + formatcode(beamFile: beamdasm.Beam): string { + + let str = ''; + + let lblLength = beamFile.codeNumberOfLabels.toString().length; + lblLength = lblLength < 2 ? 2 : lblLength; + if (!lbl) { + lbl = (val: number) => `label${("0".repeat(lblLength) + val.toString()).slice(-lblLength)}`; + } + + str += this.formatModuleInfo(beamFile); + + for (let i = 0; i < beamFile.code.length; i++) { + let obj = beamFile.code[i]; + + if (obj.op === 2) { + str += `\n//Function ${beamFile.atoms[obj.params[0].data]}:${beamFile.atoms[obj.params[1].data]}/${obj.params[2].data}\n`; + } + + if (obj.label) { + str += `${lbl(obj.label[0].data)}:`; + } else { + //6 === 'label:'.length + str += ' '.repeat(lblLength + 6); + } + + if (obj.op === 7 || obj.op === 8) { + str += instructionToString(beamFile, obj, 1); + } + else if (obj.op === 9) { + str += instructionToString(beamFile, obj, 0); + } + else if (obj.op === 78) { + str += instructionToString(beamFile, obj, 1); + } + else if (obj.op === 124 || obj.op === 125) { + str += instructionToString(beamFile, obj, 2); + } + else { + str += instructionToString(beamFile, obj, -1); + } + + if (obj.line) { + //skip zero lines + if (obj.line[0].data !== 0) { + let line_ref = beamFile.lineRefs[obj.line[0].data]; + str += ` //line ${beamFile.lineFNames[line_ref[0]]}, ${line_ref[1]}`; + } + } + + str += '\n'; + } + return str; + } + + formatlitt(beamFile: beamdasm.Beam): string { + let str = 'Literals: \n'; + + for( let i = 0; i < beamFile.literals.length; i++ ){ + str += `\t${beamFile.literals[i]}\n`; + } + + str += '\n'; + return str; + } + + formatatu8(beamFile: beamdasm.Beam): string { + let str = 'Atoms: '; + let offset = str.length; + + for (let i = 0; i < beamFile.atoms.length; i++) { + str += (i !== 0) ? ' '.repeat(offset) : ''; + str += `${i}\t${beamFile.atoms[i]}\n`; + } + str += '\n'; + + return str; + } + + formatimpt(beamFile: beamdasm.Beam): string { + let str = 'Imports: '; + let offset = str.length; + + for (let i = 0; i < beamFile.imports.length; i++) { + let func_info = beamFile.imports[i]; + str += (i !== 0) ? ' '.repeat(offset) : ''; + str += `${func_info.module}/${func_info.function}/${func_info.arity} ${beamFile.atoms[func_info.module]}:${beamFile.atoms[func_info.function]}/${func_info.arity}\n`; + } + + str += '\n'; + + return str; + } + + formatexpt(beamFile: beamdasm.Beam): string { + + if (!lbl) { + let lblLength = beamFile.codeNumberOfLabels.toString().length; + lblLength = lblLength < 2 ? 2 : lblLength; + + lbl = (val: number) => `label${("0".repeat(lblLength) + val.toString()).slice(-lblLength)}`; + } + + let str = 'Exports: '; + let offset = str.length; + for (let i = 0; i < beamFile.exports.length; i++) { + let func_info = beamFile.exports[i]; + str += (i !== 0) ? ' '.repeat(offset) : ''; + str += `${func_info.function}/${func_info.arity}/${func_info.label} ${beamFile.atoms[func_info.function]}/${func_info.arity} ${lbl(func_info.label)}\n`; + } + str += '\n'; + return str; + } + + formatloct(beamFile: beamdasm.Beam): string { + + if (!lbl) { + let lblLength = beamFile.codeNumberOfLabels.toString().length; + lblLength = lblLength < 2 ? 2 : lblLength; + + lbl = (val: number) => `label${("0".repeat(lblLength) + val.toString()).slice(-lblLength)}`; + } + + let str = 'Private: '; + let offset = str.length; + + for (let i = 0; i < beamFile.LocT.length; i++) { + let func_info = beamFile.LocT[i]; + str += (i !== 0) ? ' '.repeat(offset) : ''; + str += `${func_info.function}/${func_info.arity}/${func_info.label} ${beamFile.atoms[func_info.function]}/${func_info.arity} ${lbl(func_info.label)}\n`; + } + str += '\n'; + return str; + } + + formatstrt(beamFile: beamdasm.Beam): string { + let str = 'Strings: '; + + str += `\"${beamFile.StrT}\"\n`; + + str += '\n'; + return str; + } + + formatattr(beamFile: beamdasm.Beam): string { + let str = 'Attributes:\n'; + + str += `${beamFile.attributes}`; + return str; + } + + [func: string]: (beamFile: beamdasm.Beam) => string; +} \ No newline at end of file diff --git a/src/beam/erlangFormatter.ts b/src/beam/erlangFormatter.ts new file mode 100644 index 0000000..d9874b2 --- /dev/null +++ b/src/beam/erlangFormatter.ts @@ -0,0 +1,64 @@ +// Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +/// + +export class ErlangFormatter implements beamdasm.BeamFormatter { + + formatModuleInfo(beamFile: beamdasm.Beam): string { + let str = `Module: ${beamFile.atoms[1]}\n`; + str += '\n'; + str += `Attributes: ${beamFile.attributes}\n`; + str += '\n'; + str += `Compilation Info: ${beamFile.compilationInfo}\n`; + str += '\n'; + return str; + } + + formatcode(beamFile: beamdasm.Beam): string { + return ''; + } + + formatlitt(beamFile: beamdasm.Beam): string { + return ''; + } + + formatatu8(beamFile: beamdasm.Beam): string { + return ''; + } + + formatimpt(beamFile: beamdasm.Beam): string { + return ''; + } + + formatexpt(beamFile: beamdasm.Beam): string { + return ''; + } + + formatloct(beamFile: beamdasm.Beam): string { + return ''; + } + + formatstrt(beamFile: beamdasm.Beam): string { + return ''; + } + + formatattr(beamFile: beamdasm.Beam): string { + return ''; + } + + [func: string]: (beamFile: beamdasm.Beam) => string; +} \ No newline at end of file diff --git a/src/beam/interface.ts b/src/beam/interface.ts index ab9937d..0ebfdb1 100644 --- a/src/beam/interface.ts +++ b/src/beam/interface.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,11 +15,14 @@ 'use strict'; namespace beamdasm { - export interface IBeamFile{ + export interface Beam { - sections:any; + sections: any; atoms: string[]; + numberOfAtoms: number; + maxAtomNameLength: number; + code: any[]; lineRefs: any[]; lineFNames: any[]; @@ -29,25 +32,25 @@ namespace beamdasm { imports: any[]; exports: any[]; - literals: any[]; - + literals: any[]; + attributes: any; compilationInfo: any; - + codeNumberOfLabels: number; } - export interface BeamBytecodeFormatter { - - formatModuleInfo(beamFile: IBeamFile): string; - formatcode(beamFile: IBeamFile): string; - formatlitt(beamFile: IBeamFile): string; - formatatu8(beamFile: IBeamFile) :string; - formatimpt(beamFile: IBeamFile): string; - formatexpt(beamFile: IBeamFile): string; - formatloct(beamFile: IBeamFile): string; - formatstrt(beamFile: IBeamFile): string; - - [func: string]: (beamFile: IBeamFile) => string; + export interface BeamFormatter { + + formatcode(beamFile: Beam): string; + formatlitt(beamFile: Beam): string; + formatatu8(beamFile: Beam): string; + formatimpt(beamFile: Beam): string; + formatexpt(beamFile: Beam): string; + formatloct(beamFile: Beam): string; + formatstrt(beamFile: Beam): string; + formatattr(beamFile: Beam): string; + + [func: string]: (beamFile: Beam) => string; } } diff --git a/src/beam/opcodes.ts b/src/beam/opcodes.ts index 774e586..4219e2a 100644 --- a/src/beam/opcodes.ts +++ b/src/beam/opcodes.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,445 +15,445 @@ 'use strict'; export const opcodes: any = { - 1: { - ar: 1, + 1: { + ar: 1, nm: 'label', - doc: '**label** *Lbl*\nSpecify a module local label. Label gives this code address a name *Lbl* and marks the start of a basic block.' + doc: '**label** *Lbl*\nSpecify a module local label. Label gives this code address a name *Lbl* and marks the start of a basic block.' }, - 2: { - ar: 3, + 2: { + ar: 3, nm: 'func_info', - doc: '**func_info** *M* *F* *A*\nDefine a function M:F/A' + doc: '**func_info** *Module* *Function* *Arity*\nDefine a function *Module*:*Function*/*Arity*' }, - 3: { + 3: { ar: 0, nm: 'int_code_end', doc: 'int_code_end\n', }, - 4: { + 4: { ar: 2, nm: 'call', doc: '**call** *Arity* *Label*\nCall the function at *Label*. Save the next instruction as the return address in the CP register.' }, - 5: { + 5: { ar: 3, nm: 'call_last', doc: '**call_last** *Arity* *Label* *Deallocate*\nDeallocate and do a tail recursive call to the function at *Label*. Do not update the CP register. Before the call deallocate *Deallocate* words of stack.' }, - 6: { + 6: { ar: 2, nm: 'call_only', doc: '**call_only** *Arity* *Label*\nDo a tail recursive call to the function at *Label*. Do not update the CP register.' }, - 7: { + 7: { ar: 2, nm: 'call_ext', doc: '**call_ext** *Arity* *Destination*\nCall the function of arity *Arity* pointed by the *Destination*. Save the next instruction as the return address in the CP register.' }, - 8: { + 8: { ar: 3, nm: 'call_ext_last', doc: '**call_ext_last** *Arity* *Destination* *Deallocate*\nDeallocate and do a tail recursive call to function of arity *Arity* pointed by *Destination*. Dealllocate *Deallocte* words from the stack before the call.' }, - 9: { + 9: { ar: 2, nm: 'bif0', doc: '**bif0** *Bif* *Reg*\nCall the built-in function *Bif* and store the result in *Reg*.' }, - 10: { + 10: { ar: 4, nm: 'bif1', doc: '**bif1** *Lbl* *Bif* *Arg* *Reg*\nCall the buil-in function *Bif* with the argument *Arg*, and store the result in *Reg*. On failure jump to *Lbl*.' }, - 11: { + 11: { ar: 5, nm: 'bif2', doc: '**bif2** *Lbl* *Bif* *Arg1* *Arg2* *Reg*\nCall the built-in function *Bif* with the arguments *Arg1* and *Arg2*, and store the result in *Reg*. On failure jump to *Lbl*.' }, - 12: { + 12: { ar: 2, nm: 'allocate', doc: '**allocate** *StackNeed* *Live*\nAllocate space for *StackNeed* words on the stack. If GC is needed during allocation there are *Live* number of live X registers. Also save the continuation pointer (CP) on the stack.' }, - 13: { + 13: { ar: 3, nm: 'allocate_heap', doc: '**allocate_heap** *StackNeed* *HeapNeed* *Live*\nAllocate space for *StackNeed* words on the stack and ensure there is space for *HeepNeed* words on the heap. If GC is needed save *Live* number of X registers. Also save the continuation pointer (CP) on the stack.' }, - 14: { + 14: { ar: 2, nm: 'allocate_zero', doc: '**allocate_zero** *StackNeed* *Live*\nAllocate space for *StackNeed* words on the stack. If GC is needed during allocation there are *Live* number of live X registers. Clear the new stack words (by writing NIL). Also save the continuation pointer (CP) on the stack.' }, - 15: { + 15: { ar: 3, nm: 'allocate_heap_zero', doc: '**allocate_heap_zero** *StackNeed* *HeapNeed* *Live*\nAllocate space for *StackNeed* words on the stack and *HeapNeed* wordson the heap. If GC is needed during allocation there are *Live* number of live X registers. Clear the new stack words (by writing NIL). Also save the continuation pointer (CP) on the stack.' }, - 16: { + 16: { ar: 2, nm: 'test_heap', doc: '**test_heap** *HeepNeed* *Live*\nEnsure there is space for *HeepNeed* words on the heap. If GC is needed save *Live* number of X registers.' }, - 17: { + 17: { ar: 1, nm: 'init', doc: '**init** *N*\nClear the *Nth* stack word (by writing NIL).' }, - 18: { + 18: { ar: 1, nm: 'deallocate', doc: '**deallocate** *N*\nRestore the continuation pointer (CP) from the stack and deallocate *N+1*words from the stack (the +1 is for the CP).' }, - 19: { + 19: { ar: 0, nm: 'return', doc: '**return**\nReturn to the address in the continuation pointer (CP).' }, - 20: { + 20: { ar: 0, nm: 'send', doc: '**send**\n Send argument in x(1) as a message to the destination process in x(0). The message in x(1) ends up as the result of the send in x(0).' }, - 21: { + 21: { ar: 0, nm: 'remove_message', doc: '**remove_message**\nUnlink the current message from the message queue and store a pointer to the message in x(0). Remove any timeout.' }, - 22: { + 22: { ar: 0, nm: 'timeout', doc: '**timeout**\nReset the save point of the mailbox and clear the timeout flag.' }, - 23: { + 23: { ar: 2, nm: 'loop_rec', doc: '**loop_rec** *Label* *Source*\nLoop over the message queue, if it is empty jump to *Label*.' }, - 24: { + 24: { ar: 1, nm: 'loop_rec_end', doc: '**loop_rec_end** *Label*\nAdvance the save pointer to the next message and jump back to *Label*' }, - 25: { + 25: { ar: 1, nm: 'wait', doc: '**wait** *Label*\nSuspend the process and set the entry point to the beginning of the receive loop at *Label*.' }, - 26: { + 26: { ar: 2, nm: 'wait_timeout', doc: '**wait_timeout** *Label* *Time*\nSets up a timeout of *Time* milliseconds and saves the address of the following instruction as the entry point if the timeout triggers.' }, - 27: { ar: 4, nm: 'm_plus' }, - 28: { ar: 4, nm: 'm_minus' }, - 29: { ar: 4, nm: 'm_times' }, - 30: { ar: 4, nm: 'm_div' }, - 31: { ar: 4, nm: 'int_div' }, - 32: { ar: 4, nm: 'int_rem' }, - 33: { ar: 4, nm: 'int_band' }, - 34: { ar: 4, nm: 'int_bor' }, - 35: { ar: 4, nm: 'int_bxor' }, - 36: { ar: 4, nm: 'int_bsl' }, - 37: { ar: 4, nm: 'int_bsr' }, - 38: { ar: 3, nm: 'int_bnot' }, - 39: { + 27: { ar: 4, nm: 'm_plus' }, + 28: { ar: 4, nm: 'm_minus' }, + 29: { ar: 4, nm: 'm_times' }, + 30: { ar: 4, nm: 'm_div' }, + 31: { ar: 4, nm: 'int_div' }, + 32: { ar: 4, nm: 'int_rem' }, + 33: { ar: 4, nm: 'int_band' }, + 34: { ar: 4, nm: 'int_bor' }, + 35: { ar: 4, nm: 'int_bxor' }, + 36: { ar: 4, nm: 'int_bsl' }, + 37: { ar: 4, nm: 'int_bsr' }, + 38: { ar: 3, nm: 'int_bnot' }, + 39: { ar: 3, nm: 'is_lt', doc: '**is_lt** *Label* *Arg1* *Arg2*\nCompare two terms and jump to *Label* if *Arg1* is not less than *Arg2*.' }, - 40: { + 40: { ar: 3, nm: 'is_ge', doc: '**is_ge** *Label* *Arg1* *Arg2*\nCompare two terms and jump to *Label* if *Arg1* is less than *Arg2*.' }, - 41: { + 41: { ar: 3, nm: 'is_eq', doc: '**is_eq** *Label* *Arg1* *Arg2*\nCompare two terms and jump to *Label* is *Arg1* is not (numerically) equal to *Arg2*.' }, - 42: { + 42: { ar: 3, nm: 'is_ne', doc: '**is_ne** *Label* *Arg1* *Arg2*\nCompare two terms and jump to *Label* if *Arg1* is (numerically) equal to *Arg2*.' }, - 43: { + 43: { ar: 3, nm: 'is_eq_exact', doc: '**is_eq_exact** *Label* *Arg1* *Arg2*\nCompare two terms and jump to *Label* if *Arg1* is not exactly equal to *Arg2*.' }, - 44: { + 44: { ar: 3, nm: 'is_ne_exact', doc: '**is _ne_exact** *Label* *Arg1* *Arg2*\nCompare two terms and jump to *Label* if *Arg1* is exactly equal to *Arg2*.' }, - 45: { + 45: { ar: 2, nm: 'is_integer', doc: '**is_integer** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not an integer.' }, - 46: { + 46: { ar: 2, nm: 'is_float', doc: '**is_float** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a float.' }, - 47: { + 47: { ar: 2, nm: 'is_number', doc: '**is_number** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a number.' }, - 48: { + 48: { ar: 2, nm: 'is_atom', doc: '**is_atom** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not an atom.' }, - 49: { + 49: { ar: 2, nm: 'is_pid', doc: '**is_pid** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a pid.' }, - 50: { + 50: { ar: 2, nm: 'is_reference', doc: '**is_reference** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a reference.' }, - 51: { + 51: { ar: 2, nm: 'is_port', doc: '**is_port** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a port.' }, - 52: { + 52: { ar: 2, nm: 'is_nil', doc: '**is_nil** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not nil.' }, - 53: { + 53: { ar: 2, nm: 'is_binary', doc: '**is_binary** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a binary.' }, - 54: { + 54: { ar: 2, nm: 'is_constant', doc: '**is_constant** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a constant.' }, - 55: { + 55: { ar: 2, nm: 'is_list', doc: '**is_list** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a cons or nil.' }, - 56: { + 56: { ar: 2, nm: 'is_nonempty_list', doc: '**is_nonempty_list** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a cons.' }, - 57: { + 57: { ar: 2, nm: 'is_tuple', - doc: '**is_tuple** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a tuple.' + doc: '**is_tuple** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a tuple.' }, - 58: { + 58: { ar: 3, nm: 'test_arity', doc: '**test_arity** *Label* *Arg* *Arity*\nTest the arity of the tuple in *Arg* and jump to *Label* if it is not equal to *Arity*.' }, - 59: { + 59: { ar: 3, nm: 'select_val', doc: '**select_val** *Arg* *FailLabel* *Destinations*\nJump to the destination label corresponding to *Arg* in the *Destinations* list, if no arity matches, jump to *FailLabel*.' }, - 60: { + 60: { ar: 3, nm: 'select_tuple_arity', doc: '**select_tuple_arity** *Tuple* *FailLabel* *Destiantions*\nCheck the arity of the tuple *Tuple* and jump to the corresponding destination label, if no arity matches, jump to *FailLabel*.' }, - 61: { + 61: { ar: 1, nm: 'jump', doc: '**jump** *Label*\nJump to *Label*.' }, - 62: { ar: 2, nm: 'catch' }, - 63: { ar: 1, nm: 'catch_end' }, - 64: { - ar: 2, + 62: { ar: 2, nm: 'catch' }, + 63: { ar: 1, nm: 'catch_end' }, + 64: { + ar: 2, nm: 'move', - doc: '**move** *Source* *Destination*\nMove the source *Source* (a literal or a register) to the destination register *Destination*.' + doc: '**move** *Source* *Destination*\nMove the source *Source* (a literal or a register) to the destination register *Destination*.' }, - 65: { + 65: { ar: 3, nm: 'get_list', doc: '**get_list** *Source* *Head* *Tail*\nGet the head and tail (or car and cdr) parts of a list (a cons cell) from *Source* and put them in the registers *Head* and *Tail*.' }, - 66: { + 66: { ar: 3, nm: 'get_tuple_element', doc: '**get_tuple_element** *Source* *Element* *Destination*\nGet element number *Element* from the tuple in *Source* and put it in the destination register *Destination*.' }, - 67: { + 67: { ar: 3, nm: 'set_tuple_element', doc: '**set_tuple_element** *NewElement* *Tuple* *Position*\nUpdate the element at position *Position* of the tuple *Tuple* with the new element *NewElement*.' }, - 68: { ar: 3, nm: 'put_string' }, - 69: { ar: 3, nm: 'put_list' }, - 70: { ar: 2, nm: 'put_tuple' }, - 71: { ar: 1, nm: 'put' }, - 72: { ar: 1, nm: 'badmatch' }, - 73: { ar: 0, nm: 'if_end' }, - 74: { ar: 1, nm: 'case_end' }, - 75: { + 68: { ar: 3, nm: 'put_string' }, + 69: { ar: 3, nm: 'put_list' }, + 70: { ar: 2, nm: 'put_tuple' }, + 71: { ar: 1, nm: 'put' }, + 72: { ar: 1, nm: 'badmatch' }, + 73: { ar: 0, nm: 'if_end' }, + 74: { ar: 1, nm: 'case_end' }, + 75: { ar: 1, nm: 'call_fun', doc: '**call_fun** *Arity*\nCall a fun of arity *Arity*. Assume arguments in registers x(0) to x(Arity-1) and that the fun is in x(Arity). Save the next instruction as the return address in the CP register.' }, - 76: { ar: 3, nm: 'make_fun' }, - 77: { + 76: { ar: 3, nm: 'make_fun' }, + 77: { ar: 2, nm: 'is_function', doc: '**is_function** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a function (i.e. fun or closure).' }, - 78: { + 78: { ar: 2, nm: 'call_ext_only', doc: '**call_ext_only** *Arity* *Desitnation*\nDo a tail recursive call to the function of arity *Arity* pointed by the *Destination*. Do not update the CP register.', }, - 79: { ar: 2, nm: 'bs_start_match' }, - 80: { ar: 5, nm: 'bs_get_integer' }, - 81: { ar: 5, nm: 'bs_get_float' }, - 82: { ar: 5, nm: 'bs_get_binary' }, - 83: { ar: 4, nm: 'bs_skip_bits' }, - 84: { ar: 2, nm: 'bs_test_tail' }, - 85: { ar: 1, nm: 'bs_save' }, - 86: { ar: 1, nm: 'bs_restore' }, - 87: { ar: 2, nm: 'bs_init' }, - 88: { ar: 2, nm: 'bs_final' }, - 89: { ar: 5, nm: 'bs_put_integer' }, - 90: { ar: 5, nm: 'bs_put_binary' }, - 91: { ar: 5, nm: 'bs_put_float' }, - 92: { ar: 2, nm: 'bs_put_string' }, - 93: { ar: 1, nm: 'bs_need_buf' }, - 94: { ar: 0, nm: 'fclearerror' }, - 95: { ar: 1, nm: 'fcheckerror' }, - 96: { ar: 2, nm: 'fmove' }, - 97: { ar: 2, nm: 'fconv' }, - 98: { ar: 4, nm: 'fadd' }, - 99: { ar: 4, nm: 'fsub' }, - 100: { ar: 4, nm: 'fmul' }, - 101: { ar: 4, nm: 'fdiv' }, - 102: { ar: 3, nm: 'fnegate' }, - 103: { ar: 1, nm: 'make_fun2' }, - 104: { ar: 2, nm: 'try' }, - 105: { ar: 1, nm: 'try_end' }, - 106: { ar: 1, nm: 'try_case' }, - 107: { ar: 1, nm: 'try_case_end' }, - 108: { ar: 2, nm: 'raise' }, - 109: { ar: 6, nm: 'bs_init2' }, - 110: { ar: 3, nm: 'bs_bits_to_bytes' }, - 111: { ar: 5, nm: 'bs_add' }, - 112: { ar: 1, nm: 'apply' }, - 113: { ar: 2, nm: 'apply_last' }, - 114: { + 79: { ar: 2, nm: 'bs_start_match' }, + 80: { ar: 5, nm: 'bs_get_integer' }, + 81: { ar: 5, nm: 'bs_get_float' }, + 82: { ar: 5, nm: 'bs_get_binary' }, + 83: { ar: 4, nm: 'bs_skip_bits' }, + 84: { ar: 2, nm: 'bs_test_tail' }, + 85: { ar: 1, nm: 'bs_save' }, + 86: { ar: 1, nm: 'bs_restore' }, + 87: { ar: 2, nm: 'bs_init' }, + 88: { ar: 2, nm: 'bs_final' }, + 89: { ar: 5, nm: 'bs_put_integer' }, + 90: { ar: 5, nm: 'bs_put_binary' }, + 91: { ar: 5, nm: 'bs_put_float' }, + 92: { ar: 2, nm: 'bs_put_string' }, + 93: { ar: 1, nm: 'bs_need_buf' }, + 94: { ar: 0, nm: 'fclearerror' }, + 95: { ar: 1, nm: 'fcheckerror' }, + 96: { ar: 2, nm: 'fmove' }, + 97: { ar: 2, nm: 'fconv' }, + 98: { ar: 4, nm: 'fadd' }, + 99: { ar: 4, nm: 'fsub' }, + 100: { ar: 4, nm: 'fmul' }, + 101: { ar: 4, nm: 'fdiv' }, + 102: { ar: 3, nm: 'fnegate' }, + 103: { ar: 1, nm: 'make_fun2' }, + 104: { ar: 2, nm: 'try' }, + 105: { ar: 1, nm: 'try_end' }, + 106: { ar: 1, nm: 'try_case' }, + 107: { ar: 1, nm: 'try_case_end' }, + 108: { ar: 2, nm: 'raise' }, + 109: { ar: 6, nm: 'bs_init2' }, + 110: { ar: 3, nm: 'bs_bits_to_bytes' }, + 111: { ar: 5, nm: 'bs_add' }, + 112: { ar: 1, nm: 'apply' }, + 113: { ar: 2, nm: 'apply_last' }, + 114: { ar: 2, nm: 'is_boolean', doc: '**is_boolean** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a Boolean.' }, - 115: { + 115: { ar: 3, nm: 'is_function2', doc: '**is_function2** *Label* *Arg* *Arity*\nTest the type of *Arg* and jump to *Label* if it is not a function of arity *Arity*.' }, - 116: { ar: 5, nm: 'bs_start_match2' }, - 117: { ar: 7, nm: 'bs_get_integer2' }, - 118: { ar: 7, nm: 'bs_get_float2' }, - 119: { ar: 7, nm: 'bs_get_binary2' }, - 120: { ar: 5, nm: 'bs_skip_bits2' }, - 121: { ar: 3, nm: 'bs_test_tail2' }, - 122: { ar: 2, nm: 'bs_save2' }, - 123: { ar: 2, nm: 'bs_restore2' }, - 124: { + 116: { ar: 5, nm: 'bs_start_match2' }, + 117: { ar: 7, nm: 'bs_get_integer2' }, + 118: { ar: 7, nm: 'bs_get_float2' }, + 119: { ar: 7, nm: 'bs_get_binary2' }, + 120: { ar: 5, nm: 'bs_skip_bits2' }, + 121: { ar: 3, nm: 'bs_test_tail2' }, + 122: { ar: 2, nm: 'bs_save2' }, + 123: { ar: 2, nm: 'bs_restore2' }, + 124: { ar: 5, nm: 'gc_bif1', doc: '**gc_bif1** *Label* *Live* *Bif* *Arg* *Reg*\nCall the built-in function *Bif* with the argument *Arg*, and store the result in *Reg*. On failure jump to *Label*. Do a garbage collection if necessary to allocate space on the heap for the result (saving *Live* number of X registers).' }, - 125: { + 125: { ar: 6, nm: 'gc_bif2', doc: '**gc_bif2** *Label* *Live*, *Bif* *Arg1* *Arg2* *Reg*\nCall the built-in function *Bif* with the arguments *Arg1* and *Arg2*, and store the result in *Reg*. On failure jump to *Label*. Do a garbage collection if necessary to allocate space to the heap for the result (saving *Live* number of X registers).' }, - 126: { ar: 2, nm: 'bs_final2' }, - 127: { ar: 2, nm: 'bs_bits_to_bytes2' }, - 128: { ar: 2, nm: 'put_literal' }, - 129: { + 126: { ar: 2, nm: 'bs_final2' }, + 127: { ar: 2, nm: 'bs_bits_to_bytes2' }, + 128: { ar: 2, nm: 'put_literal' }, + 129: { ar: 2, nm: 'is_bitstr', doc: '**is_bitstr** *Label* *Arg*\nTest the type of *Arg* and jump to *Label* if it is not a bit string.' }, - 130: { ar: 1, nm: 'bs_context_to_binary' }, - 131: { ar: 3, nm: 'bs_test_unit' }, - 132: { ar: 4, nm: 'bs_match_string' }, - 133: { ar: 0, nm: 'bs_init_writable' }, - 134: { ar: 8, nm: 'bs_append' }, - 135: { ar: 6, nm: 'bs_private_append' }, - 136: { + 130: { ar: 1, nm: 'bs_context_to_binary' }, + 131: { ar: 3, nm: 'bs_test_unit' }, + 132: { ar: 4, nm: 'bs_match_string' }, + 133: { ar: 0, nm: 'bs_init_writable' }, + 134: { ar: 8, nm: 'bs_append' }, + 135: { ar: 6, nm: 'bs_private_append' }, + 136: { ar: 2, nm: 'trim', doc: '**trim** *N* *Remaining*\nReduce the stack usage by *N* words, keeping the CP on the top of the stack.' }, - 137: { ar: 6, nm: 'bs_init_bits' }, - 138: { ar: 5, nm: 'bs_get_utf8' }, - 139: { ar: 4, nm: 'bs_skip_utf8' }, - 140: { ar: 5, nm: 'bs_get_utf16' }, - 141: { ar: 4, nm: 'bs_skip_utf16' }, - 142: { ar: 5, nm: 'bs_get_utf32' }, - 143: { ar: 4, nm: 'bs_skip_utf32' }, - 144: { ar: 3, nm: 'bs_utf8_size' }, - 145: { ar: 3, nm: 'bs_put_utf8' }, - 146: { ar: 3, nm: 'bs_utf16_size' }, - 147: { ar: 3, nm: 'bs_put_utf16' }, - 148: { ar: 3, nm: 'bs_put_utf32' }, - 149: { ar: 0, nm: 'on_load' }, - 150: { + 137: { ar: 6, nm: 'bs_init_bits' }, + 138: { ar: 5, nm: 'bs_get_utf8' }, + 139: { ar: 4, nm: 'bs_skip_utf8' }, + 140: { ar: 5, nm: 'bs_get_utf16' }, + 141: { ar: 4, nm: 'bs_skip_utf16' }, + 142: { ar: 5, nm: 'bs_get_utf32' }, + 143: { ar: 4, nm: 'bs_skip_utf32' }, + 144: { ar: 3, nm: 'bs_utf8_size' }, + 145: { ar: 3, nm: 'bs_put_utf8' }, + 146: { ar: 3, nm: 'bs_utf16_size' }, + 147: { ar: 3, nm: 'bs_put_utf16' }, + 148: { ar: 3, nm: 'bs_put_utf32' }, + 149: { ar: 0, nm: 'on_load' }, + 150: { ar: 1, nm: 'recv_mark', doc: '**recv_mark** *Label*\nSave the end of the message queue and the address of the label *Label* so that a recv_set instruction can start scanning the inbox from this position.' }, - 151: { + 151: { ar: 1, nm: 'recv_set', doc: '**recv_set** *Label*\nCheck that the saved mark points to *Label* and set the save pointer in the message queue to the last position of the message queue saved by the recv_mark instruction.' }, - 152: { + 152: { ar: 7, nm: 'gc_bif3', doc: '**gc_bif3** *Label* *Live* *Bif* *Arg1* *Arg2* *Arg3* *Reg*\nCall the built-in function *Bif* with the arguments *Arg1*, *Arg2* and *Arg3*, and store the result in *Reg*. On failure jump to *Label*. Do a garbage collection if necessary to allocate space on the heap for the result (saving *Live* number of X registers).' }, - 153: { ar: 1, nm: 'line' }, - 154: { ar: 5, nm: 'put_map_assoc' }, - 155: { ar: 5, nm: 'put_map_exact' }, - 156: { ar: 2, nm: 'is_map' }, - 157: { ar: 3, nm: 'has_map_fields' }, - 158: { ar: 3, nm: 'get_map_elements' }, - 159: { + 153: { ar: 1, nm: 'line' }, + 154: { ar: 5, nm: 'put_map_assoc' }, + 155: { ar: 5, nm: 'put_map_exact' }, + 156: { ar: 2, nm: 'is_map' }, + 157: { ar: 3, nm: 'has_map_fields' }, + 158: { ar: 3, nm: 'get_map_elements' }, + 159: { ar: 4, nm: 'is_tagged_tuple', doc: '**is_tagged_tuple** *Label* *Reg* *N* *Atom*\nTest the type of *Reg* and jumps to *Label* if it is not a tuple. Test the arity of *Reg* and jumps to *Label* if it is not *N*. Test the first element of the tuple and jumps to *Label* if it is not *Atom*.' }, - 160: { + 160: { ar: 0, nm: 'build_stacktrace', doc: '**built_stacktrace**\nGiven the raw stacktrace in x(0), build a cooked stacktrace suitable for human consumption. Store it in x(0). Destroys all other registers. Do a garbage collection if necessary to allocate space on the heap for the result.' }, - 161: { + 161: { ar: 0, nm: 'raw_raise', doc: '**raw_raise**\nThis instruction works like the erlang:raise/3 BIF, except that the stacktrace in x(2) must be a raw stacktrace. x(0) is the class of the exception (error, exit, or throw), x(1) is the exception term, and x(2) is the raw stackframe. If x(0) is not a valid class, the instruction will not throw an exception, but store the atom \'badarg\' in x(0) and execute the next instruction.' }, - 162: { + 162: { ar: 2, nm: 'get_hd', doc: '**get_hd** *Source* *Head*\nGet the head (or car) part of a list (a cons cell) from *Source* and put it into the register *Head*.' }, - 163: { + 163: { ar: 2, nm: 'get_tl', doc: '**get_tl** *Source* *Tail*\nGet the tail (or cdr) part of a list (a cons cell) from *Source* and put it into the register *Tail*.' @@ -461,11 +461,9 @@ export const opcodes: any = { }; export function get_doc(name: string): string { - for(let opcode = 1; opcode<164; opcode++) - { - if( opcodes[opcode].nm === name ) - { - return opcodes[opcode].doc; + for (let opcode = 1; opcode < 164; opcode++) { + if (opcodes[opcode].nm === name) { + return opcodes[opcode].doc; } } return ''; diff --git a/src/beam/tags.ts b/src/beam/tags.ts index fd6c4c1..c9608b5 100644 --- a/src/beam/tags.ts +++ b/src/beam/tags.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,6 +14,7 @@ 'use strict'; + export const TAG_LITERAL = 0; export const TAG_INTEGER = 1; export const TAG_ATOM = 2; @@ -26,4 +27,4 @@ export const TAG_EXT_LIST = 8; export const TAG_EXT_FLOAT_REGISTER = 9; export const TAG_EXT_ALLOC_LIST = 10; export const TAG_EXT_LITERAL = 11; -export const TAG_UNKNOWN = 12; \ No newline at end of file +export const TAG_UNKNOWN = 12; diff --git a/src/beam/terms/list.ts b/src/beam/terms/list.ts index 0dbdec3..46c0f56 100644 --- a/src/beam/terms/list.ts +++ b/src/beam/terms/list.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,25 +16,25 @@ export default class List { - _elements: any[] = []; + items: any[] = []; add(obj: any) { - this._elements.push(obj); + this.items.push(obj); } toString(): string { let str = '['; - for (let i = 0; i < this._elements.length; i++) { - str += i!==0? ', ' : ''; - str += `${this._elements[i]}`; + for (let i = 0; i < this.items.length; i++) { + str += i !== 0 ? ', ' : ''; + str += `${this.items[i]}`; } str += ']'; return str; } - get(index :number) : any | undefined { - if( index >=0 && index < this._elements.length ){ - return this._elements[index]; + get(index: number): any | undefined { + if (index >= 0 && index < this.items.length) { + return this.items[index]; } return; } diff --git a/src/beam/terms/map.ts b/src/beam/terms/map.ts new file mode 100644 index 0000000..ef75d33 --- /dev/null +++ b/src/beam/terms/map.ts @@ -0,0 +1,42 @@ +// Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import Tuple from './tuple'; + +export default class Map { + items: Tuple[] =[]; + + add( key: any, value: any) { + let item = new Tuple(); + item.add(key); + item.add(value); + this.items.push(item); + } + + toString():string { + let str = '%{'; + + for( let i = 0; i< this.items.length; i++) + { + str += i!==0 ? ', ' : ''; + str += `${this.items[i].items[0]} => ${this.items[0].items[1]}`; + } + + str += '}'; + + return str; + } +} \ No newline at end of file diff --git a/src/beam/terms/tuple.ts b/src/beam/terms/tuple.ts index 66f66c0..11de227 100644 --- a/src/beam/terms/tuple.ts +++ b/src/beam/terms/tuple.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,19 +15,18 @@ 'use strict'; export default class Tuple { - _elements : any[] = []; + items: any[] = []; - add( obj: any) { - this._elements.push(obj); + add(obj: any) { + this.items.push(obj); } - toString() : string { + toString(): string { let str = '{'; - for( let i = 0; i { - if( token.isCancellationRequested ){ + if (token.isCancellationRequested) { return; } diff --git a/src/beamTextDocumentContentProvider.ts b/src/beamTextDocumentContentProvider.ts new file mode 100644 index 0000000..a2e94e4 --- /dev/null +++ b/src/beamTextDocumentContentProvider.ts @@ -0,0 +1,85 @@ +// Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import * as fs from 'fs'; +import * as vscode from 'vscode'; + +import { BeamdasmFormatter } from './beam/beamdasmFormatter'; +import { ErlangFormatter } from './beam/erlangFormatter'; +import { BeamCache } from './beam/beamFileCache'; + +/// +/// +/// + + + +export default class BeamTextDocumentContentProvider implements vscode.TextDocumentContentProvider { //, vscode.DocumentLinkProvider { + + provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult { + throw new Error("Method not implemented."); + } + + + formatters: { [s: string]: beamdasm.BeamFormatter; } = {}; + + constructor() { + this.formatters['erlang'] = new ErlangFormatter(); + this.formatters['beamdasm'] = new BeamdasmFormatter(); + } + + public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { + + if (token.isCancellationRequested) { + return; + } + + if (!uri || !(uri instanceof vscode.Uri)) { + return; + } + + let beamFilePath: string = uri.fsPath.substr(0, uri.fsPath.length - 5); + + if (!fs.existsSync(beamFilePath)) { + return; + } + + let beamFile = BeamCache.getBeamFile(beamFilePath); + + let str = ''; + + function formatSection(formatter: beamdasm.BeamFormatter, section: string): string { + let str = ''; + + if (section in beamFile.sections) { + let funcName = `format${section}`; + str = 'BEAM section is missing formatting function'; + if (funcName in formatter) { + str = formatter[funcName](beamFile); + } + } + return str; + } + + //let configuration = vscode.workspace.getConfiguration('beamdasm'); + let formatterToUse = 'beamdasm'; //configuration['formatter']; + + let section = uri.scheme.substr(4, 4); + str = formatSection(this.formatters[formatterToUse], section); + + return str; + } +} \ No newline at end of file diff --git a/src/beamFilesProvider.ts b/src/beamTreeDataProvider.ts similarity index 67% rename from src/beamFilesProvider.ts rename to src/beamTreeDataProvider.ts index df0c2b5..d36f607 100644 --- a/src/beamFilesProvider.ts +++ b/src/beamTreeDataProvider.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,18 +17,13 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import BeamFile from './beam/beamFile'; +import { BeamCache } from './beam/beamFileCache'; -export default class BeamFilesProvider implements vscode.TreeDataProvider { +export default class BeamTreeDataProvider implements vscode.TreeDataProvider { private _didChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeTreeData?: vscode.Event = this._didChangeTreeData.event; - dev: BeamVirtualFolder = new BeamVirtualFolder('dev'); - prod: BeamVirtualFolder = new BeamVirtualFolder('prod'); - deps: BeamVirtualFolder = new BeamVirtualFolder('deps'); - - constructor(context: vscode.ExtensionContext, private workspaceRoot: string | undefined, private supportedSections: string[]) { let watcher = vscode.workspace.createFileSystemWatcher('**/*.{beam}'); @@ -38,7 +33,7 @@ export default class BeamFilesProvider implements vscode.TreeDataProvider triggerUpdateTree(), undefined, context.subscriptions); watcher.onDidDelete(_ => triggerUpdateTree(), undefined, context.subscriptions); - //For some reason onDidDelete is not firing when file is deleted by not a user + //For some reason onDidDelete is not firing when file is deleted outside of VS Code if (workspaceRoot && fs.existsSync(workspaceRoot)) { //Watcher is not 100% consistent on all platforms let fsWatcher = fs.watch(workspaceRoot, { recursive: true }, (event: string, filename: string | Buffer) => { @@ -85,42 +80,42 @@ export default class BeamFilesProvider implements vscode.TreeDataProvider { - let final: vscode.TreeItem[] = []; + let toBeResolved: vscode.TreeItem[] = []; if (!element) { - this.dev.items = []; - this.prod.items = []; - this.deps.items = []; + let dev: BeamVirtualFolder = new BeamVirtualFolder('dev'); + let prod: BeamVirtualFolder = new BeamVirtualFolder('prod'); + let deps: BeamVirtualFolder = new BeamVirtualFolder('deps'); + + let otherBeamFiles: any[] = []; - let beamFiles: any[] = []; const isDirectory = (source: string) => fs.lstatSync(source).isDirectory(); - const getBeamFiles = (source: string, acc: any[]) => { + + const getBeamFiles = (source: string) => { let items = fs.readdirSync(source); items.forEach((value: string) => { try { let fullPath = path.join(source, value); if (isDirectory(fullPath)) { - getBeamFiles(fullPath, acc); + getBeamFiles(fullPath); } else { if (path.extname(fullPath) === ".beam") { if (fullPath.search('_build\\\\prod') !== -1) { - this.prod.items.push(new BeamFileItem(value, fullPath)); + prod.items.push(new BeamFileItem(value, fullPath)); } else if (fullPath.search('_build\\\\dev') !== -1) { - this.dev.items.push(new BeamFileItem(value, fullPath)); + dev.items.push(new BeamFileItem(value, fullPath)); } else if (fullPath.search('deps') !== -1) { - this.deps.items.push(new BeamFileItem(value, fullPath)); + deps.items.push(new BeamFileItem(value, fullPath)); } else { - acc.push({ name: value, location: fullPath }); + otherBeamFiles.push({ name: value, location: fullPath }); } } } @@ -130,12 +125,12 @@ export default class BeamFilesProvider implements vscode.TreeDataProvider( + let top: vscode.TreeItem[] = [dev, prod, deps]; + toBeResolved = top.concat( + otherBeamFiles.map( (item: any, index: number, array: any[]) => { return new BeamFileItem(item.name, item.location @@ -143,41 +138,24 @@ export default class BeamFilesProvider implements vscode.TreeDataProvider - -let lbl: (val: number) => string; - -function instructionToString(bm: beamdasm.IBeamFile, obj: any, func: number): string { - let name = opcodes[obj.op].nm; - let str = ` ${name}` + ' '.repeat(20 - name.length); - - for (let i = 0; i < opcodes[obj.op].ar; i++) { - if (i === func) { - let func_info = bm.imports[obj.params[i].data]; - str += ` ${bm.atoms[func_info.module]}:${bm.atoms[func_info.function]}/${func_info.arity}`; - } - else { - str += ` ${termToString(bm, obj.params[i])}`; - } - } - return str; -} - -function termToString(bm: beamdasm.IBeamFile, obj: any): string { - if (obj.tag === tags.TAG_LABEL) { - return lbl(obj.data); - } - if (obj.tag === tags.TAG_X_REGISTER) { - return `X[${obj.data}]`; - } - if (obj.tag === tags.TAG_Y_REGISTER) { - return `Y[${obj.data}]`; - } - if (obj.tag === tags.TAG_ATOM) { - let value = bm.atoms[obj.data]; - return value === undefined ? `.` : `${value}`; - } - if (obj.tag === tags.TAG_EXT_FLOAT_REGISTER) { - return `FR[${obj.data}]`; - } - if (obj.tag === tags.TAG_EXT_LITERAL) { - return `${bm.literals[obj.data]}`; - } - if (obj.tag === tags.TAG_EXT_LIST) { - let str = '['; - for (let i = 0; i < obj.data.length; i++) { - str += (i !== 0) ? ', ' : ''; - str += `${termToString(bm, obj.data[i])}`; - } - str += ']'; - - return str; - } - - if (obj.tag === tags.TAG_LITERAL || obj.tag === tags.TAG_INTEGER) { - return `${obj.data}`; - } - - return `.`; -} - -export class BeamdasmFormatter implements beamdasm.BeamBytecodeFormatter { - - formatModuleInfo(bm: beamdasm.IBeamFile): string { - let str = `Module: ${bm.atoms[1]}\n`; - str += '\n'; - str += `Attributes: ${bm.attributes}\n`; - str += '\n'; - str += `Compilation Info: ${bm.compilationInfo}\n`; - str += '\n'; - return str; - } - - formatcode(bm: beamdasm.IBeamFile): string { - - let str = ''; - - let lblLength = bm.codeNumberOfLabels.toString().length; - lblLength = lblLength < 2 ? 2 : lblLength; - if (!lbl) { - lbl = (val: number) => `label${("0".repeat(lblLength) + val.toString()).slice(-lblLength)}`; - } - - str += this.formatModuleInfo(bm); - - for (let i = 0; i < bm.code.length; i++) { - let obj = bm.code[i]; - - if (obj.op === 2) { - str += `\n//Function ${bm.atoms[obj.params[0].data]}:${bm.atoms[obj.params[1].data]}/${obj.params[2].data}\n`; - } - - if (obj.label) { - str += `${lbl(obj.label[0].data)}:`; - } else { - //6 === 'label:'.length - str += ' '.repeat(lblLength + 6); - } - - if (obj.op === 7 || obj.op === 8) { - str += instructionToString(bm, obj, 1); - } - else if (obj.op === 9) { - str += instructionToString(bm, obj, 0); - } - else if (obj.op === 78) { - str += instructionToString(bm, obj, 1); - } - else if (obj.op === 124 || obj.op === 125) { - str += instructionToString(bm, obj, 2); - } - else { - str += instructionToString(bm, obj, -1); - } - - if (obj.line) { - //skip zero lines - if (obj.line[0].data !== 0) { - let line_ref = bm.lineRefs[obj.line[0].data]; - str += ` //line ${bm.lineFNames[line_ref[0]]}, ${line_ref[1]}`; - } - } - - str += '\n'; - } - return str; - } - - formatlitt(bm: beamdasm.IBeamFile): string { - let str = 'Literals: '; - str += `${bm.literals}\n`; - - str += '\n'; - return str; - } - - formatatu8(bm: beamdasm.IBeamFile): string { - let str = 'Atoms: '; - let offset = str.length; - - for (let i = 0; i < bm.atoms.length; i++) { - str += (i !== 0) ? ' '.repeat(offset) : ''; - str += `${i}\t${bm.atoms[i]}\n`; - } - str += '\n'; - - return str; - } - - formatimpt(bm: beamdasm.IBeamFile): string { - let str = 'Imports: '; - let offset = str.length; - - for (let i = 0; i < bm.imports.length; i++) { - let func_info = bm.imports[i]; - str += (i !== 0) ? ' '.repeat(offset) : ''; - str += `${func_info.module}/${func_info.function}/${func_info.arity} ${bm.atoms[func_info.module]}:${bm.atoms[func_info.function]}/${func_info.arity}\n`; - } - - str += '\n'; - - return str; - } - - formatexpt(bm: beamdasm.IBeamFile): string { - - if (!lbl) { - let lblLength = bm.codeNumberOfLabels.toString().length; - lblLength = lblLength < 2 ? 2 : lblLength; - - lbl = (val: number) => `label${("0".repeat(lblLength) + val.toString()).slice(-lblLength)}`; - } - - let str = 'Exports: '; - let offset = str.length; - for (let i = 0; i < bm.exports.length; i++) { - let func_info = bm.exports[i]; - str += (i !== 0) ? ' '.repeat(offset) : ''; - str += `${func_info.function}/${func_info.arity}/${func_info.label} ${bm.atoms[func_info.function]}/${func_info.arity} ${lbl(func_info.label)}\n`; - } - str += '\n'; - return str; - } - - formatloct(bm: beamdasm.IBeamFile): string { - - if (!lbl) { - let lblLength = bm.codeNumberOfLabels.toString().length; - lblLength = lblLength < 2 ? 2 : lblLength; - - lbl = (val: number) => `label${("0".repeat(lblLength) + val.toString()).slice(-lblLength)}`; - } - - let str = 'Private: '; - let offset = str.length; - - for (let i = 0; i < bm.LocT.length; i++) { - let func_info = bm.LocT[i]; - str += (i !== 0) ? ' '.repeat(offset) : ''; - str += `${func_info.function}/${func_info.arity}/${func_info.label} ${bm.atoms[func_info.function]}/${func_info.arity} ${lbl(func_info.label)}\n`; - } - str += '\n'; - return str; - } - - formatstrt(bm: beamdasm.IBeamFile): string { - let str = 'Strings: '; - - str += `\"${bm.StrT}\"\n`; - - str += '\n'; - return str; - } - - [func: string]: (beamFile: beamdasm.IBeamFile) => string; -} \ No newline at end of file diff --git a/src/contentProvider.ts b/src/contentProvider.ts deleted file mode 100644 index d02e54f..0000000 --- a/src/contentProvider.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -import * as fs from 'fs'; -import * as vscode from 'vscode'; -import BeamFile from './beam/beamFile'; -import { BeamdasmFormatter } from './beamdasmFormatter'; - -/// -/// - - - -export default class BeamDasmContentProvider implements vscode.TextDocumentContentProvider { - - formatter: beamdasm.BeamBytecodeFormatter; - constructor(){ - this.formatter = new BeamdasmFormatter(); - } - - public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { - - if(token.isCancellationRequested){ - return; - } - - if( !uri || !(uri instanceof vscode.Uri)){ - return; - } - - let beamFilePath : string = uri.fsPath.substr(0,uri.fsPath.length-5); - - if( !fs.existsSync(beamFilePath) ){ - return; - } - - let beamFile = BeamFile.fromFile(beamFilePath); - - let str = ''; - - function formatSection(formatter: beamdasm.BeamBytecodeFormatter, section: string): string { - let str = ''; - - if( section in beamFile.sections ){ - let funcName = `format${section}`; - str = 'BEAM section is missing formatting function'; - if( funcName in formatter ){ - str = formatter[funcName](beamFile); - } - } - return str; - } - - let section = uri.scheme.substr(4,4); - str = formatSection(this.formatter, section); - - return str; - } -} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 9128929..1098a0b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,11 +1,11 @@ // Copyright 2018 Valentin Ivanov (valen.ivanov@gmail.com) - +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 - +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,9 +16,9 @@ import * as vscode from 'vscode'; -import BeamDasmContentProvider from './contentProvider'; -import BeamDasmHoverProvider from './hoverProvider'; -import BeamFilesProvider from './beamFilesProvider'; +import BeamTextDocumentContentProvider from './beamTextDocumentContentProvider'; +import BeamHoverProvider from './beamHoverProvider'; +import BeamTreeDataProvider from './beamTreeDataProvider'; import * as fs from 'fs'; import * as path from 'path'; @@ -27,11 +27,7 @@ export function activate(context: vscode.ExtensionContext) { const rootPath = vscode.workspace.rootPath; - context.subscriptions.push( - vscode.languages.registerHoverProvider("beam", new BeamDasmHoverProvider()) - ); - - let contentProvider = new BeamDasmContentProvider(); + context.subscriptions.push(vscode.languages.registerHoverProvider("beam", new BeamHoverProvider())); let supportedSections = [ "code", @@ -43,38 +39,34 @@ export function activate(context: vscode.ExtensionContext) { "loct", "attr", "strt" - ] - - supportedSections.forEach( (section:string) => { - context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(`beam${section}`, contentProvider)); - }); - - let beamFilesProvider = new BeamFilesProvider(context, rootPath, supportedSections); - let command = vscode.commands.registerCommand('beamdasm.refreshBeamTree', () => beamFilesProvider.refresh()); - context.subscriptions.push(command); + ]; - context.subscriptions.push(vscode.window.registerTreeDataProvider("beamdasm.beamFilesTree", beamFilesProvider)); + let contentProvider = new BeamTextDocumentContentProvider(); + supportedSections.forEach((section: string) => { + context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(`beam${section}`, contentProvider)); + }); + let beamTreeDataProvider = new BeamTreeDataProvider(context, rootPath, supportedSections); + context.subscriptions.push(vscode.window.registerTreeDataProvider("beamdasm.beamFilesTree", beamTreeDataProvider)); - context.subscriptions.push( - vscode.commands.registerCommand('beamdasm.disassemble', (fileUri, beamFile?: any) => { - if (!fileUri || !(fileUri instanceof vscode.Uri)) { - let editor = vscode.window.activeTextEditor; + context.subscriptions.push(vscode.commands.registerCommand('beamdasm.refreshBeamTree', () => beamTreeDataProvider.refresh())); - if (!editor) { - return; - } + context.subscriptions.push(vscode.commands.registerCommand('beamdasm.disassemble', (fileUri, beamFile?: any) => { + if (!fileUri || !(fileUri instanceof vscode.Uri)) { + let editor = vscode.window.activeTextEditor; - fileUri = editor.document.uri; + if (!editor) { + return; } - if (fs.existsSync(fileUri.fsPath)) { - let sectionDocument = vscode.Uri.file(fileUri.fsPath.replace('.beam','.beam_code')); - vscode.commands.executeCommand('vscode.open', sectionDocument.with({ scheme: 'beamcode' })); - } + fileUri = editor.document.uri; } - ) - ); + + if (fs.existsSync(fileUri.fsPath)) { + let sectionDocument = vscode.Uri.file(fileUri.fsPath.replace('.beam', '.beam_code')); + vscode.commands.executeCommand('vscode.open', sectionDocument.with({ scheme: 'beamcode' })); + } + })); setupDecorators(context); } @@ -101,14 +93,14 @@ function setupDecorators(context: vscode.ExtensionContext) { } let activeEditor = vscode.window.activeTextEditor; - + if (activeEditor) { triggerUpdateDecorations(); } - vscode.window.onDidChangeActiveTextEditor( editor => { + vscode.window.onDidChangeActiveTextEditor(editor => { activeEditor = editor; - if( editor ){ + if (editor) { triggerUpdateDecorations(); } }); @@ -118,7 +110,7 @@ function setupDecorators(context: vscode.ExtensionContext) { return; } - if( activeEditor.document.uri.scheme !== "beamcode" ){ + if (activeEditor.document.uri.scheme !== "beamcode") { return; }