From 6f4cbe9599edf20555bcecff596ad98ad96ee010 Mon Sep 17 00:00:00 2001 From: maziac Date: Sun, 27 May 2018 11:06:03 +0200 Subject: [PATCH] Major check-in of all sources. --- .gitignore | 32 +- .vscodeignore | 11 + CHANGELOG.md | 23 + LICENSE => LICENSE.txt | 0 README.md | 203 ++- appveyor.yml | 8 + design/design.md | 175 ++ design/requests.pu | 58 + design/startup.pu | 64 + design/step_trace.pu | 158 ++ images/z80-debug-icon.png | Bin 0 -> 3171 bytes notes.md | 160 ++ package-lock.json | 3146 +++++++++++++++++++++++++++++++++++ package.json | 254 +++ src/callserializer.ts | 133 ++ src/debugadapter.ts | 4 + src/emuldebugadapter.ts | 842 ++++++++++ src/extension.ts | 83 + src/frame.ts | 32 + src/labels.ts | 437 +++++ src/log.ts | 93 ++ src/machine.ts | 719 ++++++++ src/reflist.ts | 37 + src/settings.ts | 155 ++ src/shallowvar.ts | 451 +++++ src/tests/callserializer.ts | 40 + src/tests/labels.test.ts | 145 ++ src/tests/utility.test.ts | 313 ++++ src/tsconfig.json | 17 + src/tslint.json | 12 + src/utility.ts | 423 +++++ src/z80Registers.ts | 334 ++++ src/zesaruxSocket.ts | 280 ++++ 33 files changed, 8817 insertions(+), 25 deletions(-) create mode 100644 .vscodeignore create mode 100644 CHANGELOG.md rename LICENSE => LICENSE.txt (100%) create mode 100644 appveyor.yml create mode 100644 design/design.md create mode 100644 design/requests.pu create mode 100644 design/startup.pu create mode 100644 design/step_trace.pu create mode 100644 images/z80-debug-icon.png create mode 100644 notes.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/callserializer.ts create mode 100644 src/debugadapter.ts create mode 100644 src/emuldebugadapter.ts create mode 100644 src/extension.ts create mode 100644 src/frame.ts create mode 100644 src/labels.ts create mode 100644 src/log.ts create mode 100644 src/machine.ts create mode 100644 src/reflist.ts create mode 100644 src/settings.ts create mode 100644 src/shallowvar.ts create mode 100644 src/tests/callserializer.ts create mode 100644 src/tests/labels.test.ts create mode 100644 src/tests/utility.test.ts create mode 100644 src/tsconfig.json create mode 100644 src/tslint.json create mode 100644 src/utility.ts create mode 100644 src/z80Registers.ts create mode 100644 src/zesaruxSocket.ts diff --git a/.gitignore b/.gitignore index a1c2a238..65823e21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,9 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +.DS_Store +node_modules/ +out/ +npm-debug.log +z80-debug.txt +*.vsix +logs/ +data/ +.vscode/ diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 00000000..fe9998cd --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,11 @@ +.vscode/**/* +.gitignore +.travis.yml +appveyor.yml +src/**/* +out/tests/**/* +**/*.js.map +data/**/* +design/**/* +logs/**/* +notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0e00ce85 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +## 0.1.0 +Initial version. +Functionality: +- supports ZEsarUX emulator +- reads .list and .labels files + - supports stepping through source code + - either in .list file or in .asm files +- step-over, step-in, step-out, continue, pause +- display of + - disassembly + - Z80 registers + - stack + - callstack +- changing of Z80 registers from vscode IDE +- labels + - number-label resolution, i.e. along with numbers also the corresponding label is displayed +- hovering + - registers: reveals its contents and associated label + - labels: reveals their value +- watches of labels +- formatting registers + - customizable formatting for registers, e.g. format as hex and/or decimal and/or label etc. + - different formatting for registers while hovering \ No newline at end of file diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md index b43c72fc..ea8317b9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,201 @@ -# z80-debug -Debug adapter to use Visual Studio Code (vscode) as IDE for ZEsarUX (a ZX Spectrum emulator). +# VS Code Z80 Debug Adapter + +**Note: the current implementation is still experimental. Most of the features have not been thoroughly tested. It may or may not work for you.** + +The Z80-Debug-Adapter (z80-debug) lets you use Visual Studio Code (vscode) as IDE for ZEsarUX (a ZX Spectrum emulator). +With this extension it is possible to debug assembler programs built for the ZX Spectrum. +It's primary intention is to support building new programs, i.e. programs with existent assembler source code. +(It may also be used without source code to debug binaries but in that case the support is very limited and you could probably directly debug at ZEsarUX.) +The biggest help it offers is that you are able to step through your sources and that z80-debug is aware of all labels and can give hints to what label a number resolves. + +The z80-debug connects to ZEsarUX via a socket connection. ZEsarUX offers quite a few commands accessible via socket according to the so-called ZRCP (Zesarux Remote Control Protocol). See [ZEsarUX](https://github.com/chernandezba/zesarux) for more information. + +Note: The Z80-Debug-Adapter does not include any support for building from assembler sources. For this you need to create a build task yourself. For an example look here: https://github.com/maziac/z80-sample-program + +## Features + +- supports ZEsarUX emulator +- reads .list and .labels files + - supports stepping through source code + - either in .list file or in .asm files +- step-over, step-in, step-out, continue, pause +- display of + - disassembly + - Z80 registers + - stack + - callstack +- changing of Z80 registers from vscode IDE +- labels + - number-label resolution, i.e. along with numbers also the corresponding label is displayed +- hovering + - registers: reveals its contents and associated label + - labels: reveals their value +- watches of labels +- formatting registers + - customizable formatting for registers, e.g. format as hex and/or decimal and/or label etc. + - different formatting for registers while hovering + +## Constraints + +- supports only ZEsarUX emulator +- build output must + - create a .list file (format as of Z80 Assembler: http://savannah.nongnu.org/projects/z80asm/) + - a .sna file containing the binary + + +## Using Z80 Debug Adapter + +### Installation + +#### Prerequisites + +In order to use z80-debug you need +- vscode (of course) +- the ZEsarUX ZX Spectrum emulator (https://github.com/chernandezba/zesarux). Tested was version 7. + +#### z80-debug + +Installation is not (yet) possible through the Extension Marketplace. + +Instead you have to install the vsix file directly: +- download the latest vsix file (under Releases) +- in vscode press F1 and enter "Extensions: Install from VSIX..." +- don't forget to press "Reload" in vscode + + + +### Sample Program + +You can find sample code here: +https://github.com/maziac/z80-sample-program + +It includes the sources and the binaries (.list, .labels, .sna files). So, if you don't want to change the sources, you can try debugging even without building from the sources. + + +### Setup + +After installing you need to add the configuration for "z80-debug". + +A typical configuration looks like this: +~~~~ + "configurations": [ + { + "type": "z80-debug", + "request": "launch", + "name": "Z80 Debugger", + "zhostname": "localhost", + "zport": 10000, + "disassemblies": [ + // [ 0, 16384 ] // Spectrum ROM disassembly + ], + "listFiles": [ + //{ "path": "rom48.list", "useFiles": false }, + { "path": "z80-sample-program.list", "useFiles": true } + ], + "labelsFiles": [ + //"rom48.labels", + "z80-sample-program.labels" + ], + "startAutomatically": true, + "skipInterrupt": true, + "rootFolder": "${workspaceFolder}", + "topOfStack": "stack_top", + "loadSnap": "z80-sample-program.sna", + "disableLabelResolutionBelow": 513, + "tmpDir": ".tmp" + } +~~~~ + +- name: The (human readable) name of the Z80-Debug-Adapter as it appears in vscode. +- zhostname: The host's name. I.e. the IP of the machine that is running ZEsarUX. If you are not doing any remote debugging this is typically "localhost". Note: remote debugging would work, but has not been tested yet. There is also no mechanism included to copy the.sna file to a remote computer. So better stick to local debugging for now. +- zport: The ZEsarUX port. If not changed in ZEsarUX this defaults to 10000. +- disassemblies: You can add address/length tuples here that are disassmbled before startup. Can be used e.g. to disassemble ROM areas. Don't expect too much as the disassembly is not aware of data areas and will disassemble them as they were code. +- listFiles: An array of list files. Typically it includes only one. But if you e.g. have a +list file also for the ROM area you can add it here. + - path: the path to the list file (relative to the 'rootFolder'). + - useFiles: + - false = Use .list file directly for stepping and setting of breakpoints. + - true = Use the (original source) files mentioned in the .list file. I.e. this allows you to step through .asm source files. + - If you build your .list files from .asm files then use 'true'. If you just own the .list file and not the corresponding .asm files use 'false'. +- labelsFiles: The paths (relative to the 'rootFolder') of the labels files. Typically +this is only one file created during building. But you could add multiple files here. +You can also completely omit the label files but in that case the z80-debug support is very limited because it cannot help in resolving any labels to numbers and vice versa. +- startAutomatically: see [Notes](#Notes) +- skipInterrupt: Is passed to ZEsarUX at the start of the debug session. + If true ZEsarUX does not break in interrupts (on manual break) +- rootFolder: Typically = workspaceFolder. All other file paths are relative to this path. +- topOfStack: This is an important parameter to make the callstack display convenient to use. Please add here the label of the top of the stack. Without this information z80.debug does not know when the stack ends and may show useless/misleading/wrong information. In order to use this correctly first you need a label that indicates the top of your stack. Here is an example how this can look: +~~~ +Your assembler file: +stack_bottom: + defs STACK_SIZE*2, 0 +stack_top: + +In your launch.json: +"topOfStack": "stack_top" +~~~ +Note: instead of a label you can also use a fixed number. +- loadSnap": The snaphsot file to load. On start of the debug session ZEsarUX is instructed to load this file. +- disableLabelResolutionBelow: z80-debug will try to resolve numbers into labels. This is fine most of the time, but for very low numbers this can also be annoying because z80-debug will normally find a load of matching labels whcih are all shown. You can disable it here if the label is below a certain value. Disabling it for all values 0-512 seems to be a good choice. +- tmpDir: A temporary directory used for files created during the debugging. At the moment this is only used to create files for the disassemblies given in 'disassemblies'. + + +### Usage + +After configuring you are ready to go. +But before you start z80-debug in vscode make sure that you started ZEsarUX. +In ZEsarUX enable the socket ZRCP protocol either by commandline ("--enable-remoteprotocol") +or from the ZEsarUX UI ("Settings"->"Debug"->"Remote protocol" to "Enabled"). + +Important: Make sure that there is no UI window open in ZEsarUX when you try to connect it from vscode. Sometimes it works but sometimes ZEsarUX will not connect. You might get an error like "ZEsarUX did not communicate!" in vscode. + +Now start z80-debug by pressing the green arrow in the debug pane (make sure that you chose the right debugger, i.e. "Z80 Debug"). + +z80-debug will now +- open the socket connection to ZEsarUX +- instruct ZEsarUX to load the snapshot file +- set breakpoints (if there are breakpoints set in vscode) +- put ZEsarUX into step mode ('enter-cpu-step') and stop/break the jst started assembler program + +z80-debug/vscode will now show you the opcode of the current PC (program counter) in the right position in your .asm file. +At the left side you see the disassembly and the registers in the VARIABLES section and the +call stack in the CALL STACK section. + +You can now try the following: +- hover over registers in your source code -> should display the value of the register +- step-over, step-in etc. +- click in the call stack -> will navigate you directly to the file +- set breakpoints, press continue to run to the breakpoints + +If that is not enough you also have full access to the ZEsarUX ZRCP through vscode's debug console. +Enter "-help" in the debug console to see all available commands. +Enter e.g. "-e h 0 100" to get a hexdump from address 0 to 99. + + +### Stop Debugging + +To stop debugging press the orange square button in vscode. This will stop the z80-debug adapter and disconnect from ZEsarUX. +After disconnecting ZEsarUX, ZEsarUX will also leave cpu-step mode and therefore continue running the program. + + +## Differences to ZEsarUX + +Stepping works slightly different to stepping in ZEsarUX. + +- step-over: A step-over always returns. step-over should work like you would intuitively expect it to work (at least for me :-) ). You can step-over a 'jp' opcode and it will break on the next opcode, the jump address. z80-debug does so by looking at the current opcode: If a 'call' or a 'ldir/lddr' is found a ZEsarUX 'cpu-step-over' is done, in all other case a 'cpu-step' (into) is done. + +- step-out: This is not available in ZEsarUX, but in z80-debug you can make use of a step-out. z80 debug examines the call stack and sets a temporary breakpoint to the return address. So step-out should work as expected. Note: if the stack pointer is already at the top of the call stack a step-out will do nothing because there is nothing to step-out from. + + + + + + +## Notes + +- "startAutomatically" is ignored at the moment. ZEsarUX should be started manually before debugging +- vscode breakpoint conditions: those are directly passed to ZEsarUX. Conditions have not been tested at all. +- Don't use "-exec run" in the debug console. It will lead to a disconnection of ZEsarUX. Instead use the continue button (the green arrow). + + + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..d420ed0a --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,8 @@ +install: + - ps: Install-Product node 7.9 x64 + +build_script: + - npm install + +test_script: + - npm test diff --git a/design/design.md b/design/design.md new file mode 100644 index 00000000..448d7867 --- /dev/null +++ b/design/design.md @@ -0,0 +1,175 @@ +# Design + +## Main Classes + + +- DebugAdapter: Just runs ZesaruxDebug. +- Extension: The extension class. Acitvatees teh extension and registers commands. +- Frame: Represents a Z80 StackObject, i.e. caller address and objects on stack. +- Labels: Singleton which reads the .list and .labels file and associates addresses, labels, filenames and line numbers. +- Settings: Singleton to hold the extension's settings. +- ShallowVar: DisassemblyVar, RegistersMainVar, RegistersSecondaryVar, StackVar, LabelVar. Representations of variables. They know how to retrieve the data from zesarux. +- Z80Registers: Static class to parse (from zesarux) and format registers. +- ZesaruxDebug: Gets requests from vscode and passes them to zesarux (via ZesaruxSocket). +- ZesaruxSocket: Socket connection and communication to zesarux emulator. Knows about the basic communication, but not the commands. + + +Helper classes: +- CallSerializer: Used to queue/serialize function calls from vscode and to the socket. +- Log: Used for logging. Adds the caller and break time to the logs. +- RefList: List to associate variable references to objects. + + +DebugAdapter <-> Machine <-> Connector + ^ + | + Labels + +DebugAdapter: Generalized commands to Machine. +Machine: Z80 + special behaviour, e.g. Spectrum48K or Spectrum128. +Connector: Emulator specifics. + +Communication: + +DebugAdapter <-> Machine: +DebugAdapter takes care of association of vscode references and objects. +- Commands: step-over, step-into, step-out +- Data: Frames (call stack), Registers/Frame, expressions, breakpoints, Labels. + +Machine <-> Connector: +- Commands: run, step-over, step-into, step-out, breakpoints +- Data: registers, memory dump, call stack + + + +## Asynchronicity + +vscode is highly asynchronous. All requests start with the 'request' and end with a 'response'. The 'response' would be typically generated in another function e.g. as a response from the zesarux socket answer. +Meanwhile vscode could have sent another request either for the same +(see stackTraceRequest) or for something else. + +Since this debug adapter has to maintain a reference/object map/list it is +necessary to reset this list sometimes (to free the objects). +This list is global whcih leads to asynchronicity problems. + +Therefore all requests (next, scopesRequest, stackTraceRequest, variablesRequest etc.) from vscode are added to a queue the so-called +CallSerializer (this.serializer). So that they are executed and responded to in exactly the order they come in. + + +## Showing variables, call stacks etc. + +Whenever vscode thinks it needs to update some of the areas (VARIABLES, WATCHES, CALL STACK) it does a request. +The request might be chained. E.g. if an entry in the CALL STACK is selected this first requests the SCOPE for the call stack (frame) ID. +The scopes are displayed as header lines in the VARIABLES area. +They are very similar to variables but appear as head line. +In this adapter they are used for +- The disassembly (either starting at the PC or if a different callstack is selected the disassembly starts at the call stack address). +- The Z80 registers (divided in main and secondary registers). +- The stack, i.e. the stack that is currently used for push/pop. + +For each scope a variables request is done. Here the a variable is returned for each shown line. E.g. one line/variable for each register or one line/variable for each disassembly line. + + +vscode works with IDs/numbers only. Unfortunately it is not possible +to response with an array of objects that vscode passes back in +the next request. +Instead it is necessary to work with IDs/numbers. + +So, when there is a stackTraceRequest (call stack) a list is used to map the IDs to objects. +At the start of the request the list is cleared. Then, for every returned frame (call stack entry) a new ID is generated (the index to the list is simply increased). +The object is created. It holds all necessary data for processing the next step. And the object is put in the list. + +Here are the requests and the list/objects involved: + +### stackTraceRequest: + +List: +listFrames, cleared on start of request, filled until response. +listVariables, cleared on start of request, filled by other requests. + +Object: +- address: the address in the call stack (required for disassembly) +- fileName, lineNr: file and line number so that vscode can show the corresponding file when selected. + + +### scopesRequest: + +The returned scopes list is basically always the same: +'Disassembly', 'Registers', 'Registers 2', 'Stack' +However, for each scope a variable reference is passed. This reference ID is different for different frames. +I.e. an object is created that includes necessary information for decoding in the variablesRequest. + +'Disassembly': +- address + +'Registers'/'Registers 2': +- none + +'Stack': +- address +- size + +List: +listVariables, filled until response. + + + +### variablesRequest: +List: +listVariables, is only read. + +### evaluateRequest: + +An expression is past for hovering or 'add watch'. It normally +contains a label for wich a shallow var is constructed. + +'Labels': +- pseudo variables for + - byte array + - word array + +'Memory Dump' (byte/word array): +- address + + +### Shallow Variable References + +A variable that has not been requested does exist only as number (ID) with associated shallow object. +The shallow object contains enough information to actually retrieve the variable information. +For variables a class is created that behaves similar for all different types of variables. + +Basically it includes a anonymous function that has the knowledge to retrieve the data. + +Examples: +- E.g. the call stack variable gets a function that communicates with the socket to retrieve the stack values. +- From evaluate (e.g. 'add watch') a label has been added. This is immediately displayed. The responses contained a var ID corresponding to an object with a function that communicates with the socket to retrieve a memory dump for that address. +If the variable is opened in the WATCH area a variable request is done for that ID. The corresponding object's function is executed and the data is retrieved and returned in the response. + + +# Problems / Decisions needed + +vscode is highly asynchronous. All requests start with the 'request' and end with a 'response'. The 'response' would be typically generated in another function e.g. as a response from the zesarux socket answer. +Meanwhile vscode could have sent another request either for the same +(see stackTraceRequest) or for something else. +This becomes even more complicated because variables (displayed in vscode) +are referenced by a number only. +Therefore the debug adapter needs to keep track with a map or list of the variable references and the underlying objects. +Unfortunately there is no information available from vscode whenever a variable reference is not required anymore. +I.e. it is impossible to delete variable references and the underlying objects. +Because of the asynchronicity it is also imposible to bound the deleting +of objects to certain requests (like the stack trace request). + +It would be much easier if vscode (instead of using reference numbers) would offer the possibility to set a variables reference as an object. Then simply garbage collection would take care of unused objects. + +A way out would be to use a layer in between (between vscode and the emulator/socket). +This layer would request all required data wenever a 'next' (step) has been performed. +So all data is allocated for one 'step' only. At the start of the 'step' the old data is discarded and new data structures are allocated. +vscode requests would be ansewered (immediately) just by returning the already received data. + +Problem with this approach is that this layer would have to requests all data at once and for every step. +E.g. even while stepping from one assembler instruction to the next all disassemblys (and, when this available later, all registers/stack objects +for that call stack item) need to be retrieved from the emulator in advance. +Even if the data in the vscode UI is collapsed. + +This doesn't seem the rigth way. Let's see how the discussion with vscode guys will turn out. + diff --git a/design/requests.pu b/design/requests.pu new file mode 100644 index 00000000..0f3d7c11 --- /dev/null +++ b/design/requests.pu @@ -0,0 +1,58 @@ +@startuml + +title step + +hide footbox + + +participant vscode +participant ZXDebug +'participant ZXSocket as "Zesarux\nSocket" + +== step == + +vscode -> ZXDebug: threadsRequest() +vscode <-- ZXDebug: response(thread)‚ + +vscode -> ZXDebug: stackTraceRequest(thread) +vscode <-- ZXDebug: response(frames) + +vscode -> ZXDebug: scopesRequest(frameID/selected frame) +vscode <-- ZXDebug: response(scope names + their varID) + +vscode -> ZXDebug: variablesRequest(varID) +vscode <-- ZXDebug: response(variables) + +note over vscode, ZXDebug: The evaluateRequest may also come earlier. Also before the scopes request. +vscode -> ZXDebug: evaluateRequest(expression, frameID) +vscode <-- ZXDebug: response(result) + +== select in CALL STACK area == + +vscode -> ZXDebug: scopesRequest(frameID/selected frame) +vscode <-- ZXDebug: response(scope names + their varID) + +vscode -> ZXDebug: variablesRequest(varID) +vscode <-- ZXDebug: response(variables) + + +== open scope in VARIABLES ares == + +vscode -> ZXDebug: variablesRequest(varID) +vscode <-- ZXDebug: response(variables) + + +== hovering, watch == + +vscode -> ZXDebug: evaluateRequest(expression, frameID, context) +vscode <-- ZXDebug: response(result, varID) + +note over vscode, ZXDebug: If the expression/variable is opened in the WATCH area + +vscode -> ZXDebug: variablesRequest(varID) +vscode <-- ZXDebug: response(variables) + + + + +@enduml diff --git a/design/startup.pu b/design/startup.pu new file mode 100644 index 00000000..19b64047 --- /dev/null +++ b/design/startup.pu @@ -0,0 +1,64 @@ +@startuml + +title startup + +hide footbox + + +participant vscode +participant ZXDebug +participant ZXSocket as "Zesarux\nSocket" + +== init == +vscode -> ZXDebug: initialize, ({"clientID":"vscode","clientName":"Visual Studio Code",\n"adapterID":"z80-debug","pathFormat":"path","linesStartAt1":true,"columnsStartAt1":true,\n"supportsVariableType":true,"supportsVariablePaging":true,\n"supportsRunInTerminalRequest":true,"locale":"en-us"}) +vscode <- ZXDebug: sendResponse, initialize({"supportsConfigurationDoneRequest":false,\n"supportsStepBack":true,"supportTerminateDebuggee":true,"supportsGotoTargetsRequest":true,\n"supportsEvaluateForHovers":true,"supportsSetVariable":true}) + + +== launch== +vscode -> ZXDebug: launch({"type":"z80-debug","request":"launch","name":"Zesarux Debugger",\n"debugServer":4711,"startAutomatically":false,\n"listFile":"/Volumes/Macintosh HD 2/Projects/zesarux/starwarrior/starwarrior.list",\n"labelsFile":"/Volumes/Macintosh HD 2/Projects/zesarux/starwarrior/starwarrior.labels",\n"skipInterrupt":true,"__sessionId":"e0eec1b5-3612-462f-bd08-7c78fcf99476"}) +vscode <- ZXDebug: sendResponse, launch(undefined) +ZXSocket <- ZXDebug: connect +ZXSocket -> ZXDebug: connected +vscode <- ZXDebug: sendEvent, stopped({"reason":"entry","threadId":1}) + +vscode <- ZXDebug: sendEvent, initialized(undefined) + +== threads == +vscode -> ZXDebug: threads(undefined) +vscode <- ZXDebug: sendResponse, threads({"threads":[{"id":1,"name":"thread_1"}]}) + +== breakpoints == +vscode -> ZXDebug: setBreakpoints({"source":{"name":"starwarrior.list",\n"path":"/Volumes/Macintosh HD 2/Projects/zesarux/starwarrior/starwarrior.list"},\n"lines":[14343,14344,17011,21550,21553],"breakpoints":[{"line":14343},{"line":14344},{"line":17011},\n{"line":21550},{"line":21553}],"sourceModified":false}) +vscode <- ZXDebug: sendResponse, setBreakpoints(undefined) + +== stacktrace == +vscode -> ZXDebug: stackTrace({"threadId":1,"startFrame":0,"levels":20}) +vscode -> ZXDebug: threads(undefined) +vscode <- ZXDebug: sendResponse, threads({"threads":[{"id":1,"name":"thread_1"}]}) +vscode -> ZXDebug: stackTrace({"threadId":1,"startFrame":0,"levels":20}) +vscode <- ZXDebug: sendResponse, stackTrace({"stackFrames":[{"id":0,"source":{"name":"starwarrior.list",\n"path":"/Volumes/Macintosh HD 2/Projects/zesarux/starwarrior/starwarrior.list",\n"sourceReference":0,"adapterData":"zesarux-adapter-data"},"line":0,"column":0,"name":null}],\n"totalFrames":1}) + +== var scopes == +vscode -> ZXDebug: scopes({"frameId":0}) +vscode <- ZXDebug: sendResponse, scopes({"scopes":[{"name":"Disassembly",\n"variablesReference":1048577,"expensive":false},{"name":"Registers",\n"variablesReference":1048578,"expensive":false},{"name":"Registers 2",\n"variablesReference":1048579,"expensive":false}]}) + +== from WATCHES == +vscode -> ZXDebug: evaluate({"expression":"VSYNC_COUNTER","frameId":0,"context":"watch"}) +vscode -> ZXDebug: evaluate({"expression":"speech_toggle_rom","frameId":0,"context":"watch"}) +vscode -> ZXDebug: evaluate({"expression":"LBL_SYSTEM_VARIABLES","frameId":0,"context":"watch"}) + +== from VARIABLES == +vscode -> ZXDebug: variables({"variablesReference":1048577}) + +vscode <- ZXDebug: sendResponse, stackTrace({"stackFrames":[{"id":0,"source":{"name":"starwarrior.list",\n"path":"/Volumes/Macintosh HD 2/Projects/zesarux/starwarrior/starwarrior.list",\n"sourceReference":0,"adapterData":"zesarux-adapter-data"},"line":0,"column":0,\n"name":null}],"totalFrames":1}) + +vscode <- ZXDebug: sendResponse, evaluate({"result":"(5C78h)b=NaN/'', (5C78h)w=NaN",\n"variablesReference":285816,"type":"data","namedVariables":2}) +vscode <- ZXDebug: sendResponse, evaluate({"result":"(0038h)b=0/'', (0038h)w=3584",\n"variablesReference":262200,"type":"data","namedVariables":2}) +vscode <- ZXDebug: sendResponse, evaluate({"result":"(5C00h)b=0/'', (5C00h)w=3584",\n"variablesReference":285696,"type":"data","namedVariables":2}) +vscode <- ZXDebug: sendResponse, variables({"variables":[{"name":"00","type":"string",\n"value":"","variablesReference":0},{"name":"mman","type":"string",\n"value":"> PC=fdfd SP=9f0a BC=0007 A=00 HL=5c78 DE=6323 IX=6f00 IY=a926\nA'=1e BC'=0200 HL'=a97a DE'=4e41 I=fe R=58 F=S HNC F'= 3HN \nMEMPTR=0000 IM2 VPS: 0 ","variablesReference":0}]}) + + +@enduml + + + diff --git a/design/step_trace.pu b/design/step_trace.pu new file mode 100644 index 00000000..af187b54 --- /dev/null +++ b/design/step_trace.pu @@ -0,0 +1,158 @@ +# VS Code Zesarux Debug + +## Using Zesarux Debug + + +# Redesign +- 3/4 Hauptklassen: + - DebugAdapter: Interface zu vscode + - Machine (Registers): Abstrahiert die Machine, vor allem die Register aber auch z.B. Spectrum 48k oder Spectrum128 + - Connector (Socket): Interface zu zesarux. + - Sourcen: Labels etc. Hat wahrscheinlich Verbindung mit Machine. (?) +- Kommunikation: + DebugAdapter <-> Machine <-> Connector +- So implementiern, dass man prinzipiell Machine gegen einen anderen Prozessor/Rechner tauschen kann (Vielleicht muss Machine no eine Unterobjekt Processor (für die Register) erhalten) und Connector gegen einen anderen Emulator. + +# TODO + +- get-current-machine +- error handling: if socket is not connected. +- launch zesarux on start of debug (also remotely) +- backwards stepping -> change zesarux + +# To document + + + +# Implementation + +## Activation + +z80-debug is activated in the 'activate' function in extenson.ts +by registering the ZesaruxConfigurationProvider. +This happens e.g. when the Debugger is started. +Short after 'ZesaruxConfigurationProvider::resolveDebugConfiguration' is called. +As the debug adapter is entirely implemented in Typescript it lives in the same process as the extension which simplifies debugging the debug-adapter. +So in 'resolveDebugConfiguration' the 'ZesaruxDebugSession' is instantiated +and started as server (socket). In the same function the server (socket) is also connected by the extension. + +Although the same process is used and therefore it is technically possible to directly call methods of the 'ZesaruxDebugSession' it is not done. +Instead the intended way (through 'customRequest' which uses sockets) is chosen. + + + +# Open Items + +- After 'set-memory' the watches are not updated. There is no way to tell vscode to update that area. -> github issue/feature request created. +Same for updating PC. I would need to send a stacktrace but I cannot. I can only do so as a response. +- If PC is e.g. in ROM code it cannot be stepped through because the code cannot be seen. I would need to create temporary files with the disassembly. + +# Zesarux Wishlist + +- step-out +- Breakpoints: + - Condition: Immediate test if condition syntax is correct. Currently it is possible to write complete nonsense which, of course, is never hit. + - ClearAllBreakpoints + - nice: own zesarux Breakpoint ID handling: SetBreakpoint (without ID), get the ID in return + + + + + + +# Backstep support for ZEsarUX + +On every opcode that is being processed the context has to be saved before the instruction is processed. +- Z80 registers +- Memory (at least the memory that is going to be changed) +- Memory bank settings +- Ports (in-value) +- HW related states. E.g. sprite positions. + +For every opcode a new item with the changed values (i.e. the values before the change, "pre-values") +is added to the record-list. + +Depending on the opcode different values need to be changed, so in general this is a dynamical +list with dynamic content. + +When the record-list is implemented not only backstepping would be possible, it is also possible to +get a history of the e.g. the register values. I.e. If I want to know if the history of the DE register +ZEsarUX could return a list with the recent value and also the past values together with PC and number of +past opcodes when the value was changed. + + +## Restriction +For now only the registers should be saved. No other state. +(I need to check if I need to save the RAM memory as well for the stack-trace.) + + +## Pseudocode (ZEsarUX): + +### General +Recording is always on, i.e. the record-list is filled right from the start of ZEsarUX. There is a max. number of entries (e.g. 100000). If this is reached the oldest entry is removed. + + +### Step back +1. "cpu-step-back" is received: Mode is changed to "reverse-debug-made". The rl_index is decreased. +3. "get-registers" is received: The register values from the record_list[rl_index] is sent. +3. "get-stack-trace" is received: ??? need to check how this list is generated. If it is a simple memory lookup then I need to save the memory values for sure. + +### Continue reverse +1. "run-reverse" is received: Mode is changed to "reverse-debug-made". The rl_index is decreased in a loop until (this is done very fast) + a) the list start is reached + b) a breakpoint condition is met +2. "get-registers" is received: ... same as above + +### Step (forward) +1. "cpu-step" is received while in "reverse-debug-made". The rl_index is increased. +~~~ + If rl_index > record_list then: leave "reverse-debug-made". +~~~ +2. "get-registers" is received: ... same as above + + +### Continue (forward) +1. "run" is received while in "reverse-debug-made". The rl_index is increased in a loop until (this is done very fast) + a) the list end is reached: leave "reverse-debug-made". Run normal "run" (i.e. continue seemingless with normal run mode). + b) a breakpoint condition is met +2. "get-registers" is received: ... same as above + +### Get historic registers +1. "get-historic-register reg" is received: The record list is checked for changes in the register. The current and the past values are sent together with PC values and information when the change happened. E.g. +~~~ +-> "get-historic-register DE" +<- +DE=5F8A +DE=34E9 PC=A123 dCount=15 +DE=7896 PC=A089 dCount=2343 +~~~ +Note: dCount (differential count) is a decimal number, i.e. it can grow bigger than FFFFh. + +vscode could show this when hovering above a register. + +# Misc + +## gdb example + +### (gdb) target record-full +Starts the recording + +### (gdb) continue-reverse +Goes backward through the recording until start of recording or a breakpoint is hit. + +### (gdb) reverse-step / reverse-next +Steps backward. + +### (gdb) reverse-finish +Opposite of "step-out". I.e. it "runs" backward until the start of the function. + +### (gdb) set exec-direction reverse +All step/run commands work in backward direction. + +### (gdb) set record insn-number-max +Set number of maximum instructions for the record list. + + +# Open +- Check what "get-stack-trace" does. +- How to handle out-port values? diff --git a/images/z80-debug-icon.png b/images/z80-debug-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f7fe2b09516781062a59e0c41171635c8a282213 GIT binary patch literal 3171 zcmV-p44m_cP)JKfheOBw+SHqz44*tBUAlO|2N z2dtu^!n>fevy=ArcAQQpm6es0mX=afR76)-7g14B5 z>+1nXNJzM|V0kl-k) z+s(dx`v7?O;fIGmERRQkx^@j38lbydb0cVL>$|LY~oSZzoVY%G`%#|yUoh{~V zHfU_rEjI)fPMIe?J)MmkH!^Ae<&s(f@xJ@+ zBP}hB1q&81ZQ8WqAG1_dfVpu)#49Nsyl!ZqG5vt`R@S+rbe6>;-0r>i;PuyG+&HMM z)x|8o5l7#3*s#rp7cYwA&Xp@-{?@I~u;PK6cN;Lx#iF7_Hq6Np??;Y==H}r0MhIBn zSyl{d1^@NeaQHA(SNG^HgKX#>2|zvYBJhj51(xFYZ)>WtXK&w0hn|L z0PFQ$geDzaF4(qBh|;(?0kEm5cgtD?{2fq$mVN|wJ1x4vVU(TQ+S)jD=ny3(B~(>a z(b(9Cs;b1q#E_hvOnQ1cix)4(ZnuYo)!yFDfp^~FL}@8ipMOrH$D<`YCIdeM76Va8 zC@L^oTGk?9j(pz*cmldcLj~BWQ>S?4l~=r_qhP~owX$l}DwZu-9W6*(S# z^ieW1GjY4!!Z_=nc|`g*dnv&qQF&=o7w($aWt`Eqe4JbM;O zOZld?RrlPn1jHjdBziD#@8GZo6qu^2Y~H+?=72Mr)oLX#FOP_b2*Z78X=ykd4xOul zsPumD!3TJxmU{5P2l?yHokWfq0|yVn^UuS$afab%r1uvCMgcW2(m=Nwu*P@mfB}2= z-FGP~D>HPjDO09|+&gdHJf42~>0Uphq@;uwSFfgi-#*c_5LD?jSnIXfSfB~m0G93g zbsZhSVVMW&@Lg3^#kOtRbo1%y={))5lOgw>IB|k4Tejfz(N9l2@dPJNo}{|ETED)m zjOWVAcn64!jD(gJBBf059o>L+tQ4lVfLYL$YE&%MgHnCmvEPBYTrM_k+N2B6F)=Y@ zXJ>~z_f=F>keQk3bvZ3LWMyTcswx!~73|uzi&Ljg`R)4#*b@=KA3KF@MH>KXiHCS6 z@LS+9@$Fl$>wkynX#e+$bmvN-a3EZ=u&|Je7cc7OH*DBoC4qB9M4+xefjJxwYHMqCYCb0?$50{!%Z`ELwbx$bAMd|UU27{H zCr)tc*=NZ#Fb!Y(odcwp1J1ISVC!~KFKldtty|&rY5z4TDUdcxoVlZb?LL;JdGC-Y z?`Q$Prly9co_dPLP*qTaF&WsS)wE0^N}p+lKbWD*9o{pnR@kuv=FWmIq-7nK6&|ZB zhYi|UGTdRe+u5~i*KOU%{yFeB4VFc|mJEX z>6`C|l!bW{fycvtKl_Z24;*~tiCLiNe>^(q8a#|{O5r-7yRqr zqECD2lIW%Z#Kywp$?(7f@Yv&!Fak<}=YStesnP^G5v&Zkzn9b+7M-1Y%QP706&%#&KHD~ykmd7v8sY@IA$H_5;s^u#b^U`0fT?#^HSBD&H^N#fWOs>zxF zIOVY?PgBsL578Q|D4-Eh+c!(@%|T!dEnwQ!?sMnF1-5U$74uC^y?3OYz@KFFSN%)M zp129p@JeV{9%)_IfVY4Mxh42tN<~P&w*pPy zBQT?mbZo3BGY6~UY1z;z4|u!rcBI3}hFaBOyR2qJO6v+=f5w| z!BSOm55A=Z_U{+3qoa*sDL&tL0;1hBlb(*W+B+<_w5rR%5uc3L8Pc-4fCW;oqynS+ z2JAb-((b_*77AE)ySPOY?8=eWzHg9eZI?8Uj@~n(Dk?d20>76r>xc4e7+R*qOUp_D zGUXH62iYaHn}6C(<(oIXW#yo?VGw=78mX~TeblC@cgq4Ol7O_j{`HUnUMm@zfE}{B z*5MT*mO+K3)qpQvgxp*hHA+}pC|I#R#jYv940<}!y}&ZduYFLu;RyLYI4raLzy=u@ zXGqg-_nNU~Py>8zE$rScB!o5}>bNsn=KCCAu9W3r2KY$eoUD>IOV!~D2}`TkHvk3l zlxp#VH8{Y0I?~*_CE}K5X~ANa0*sNactd(cvP@?onyi`&cFQH-BJNMb%dZ{0(i16l zN-}T;7%#JnUf5X%9G1-{KxJhKZIzfPGGV9&pmytThCC6Q!_!$>^tlSGkdKL%U)vqx zxFdko1M7jEG99!I&RJ4b)t)-NqKJ<4wQD{1;4PM(KY9u#AG)Q2pCGflulcCKw{`-T zq-9<9iAwH}(!m8xmxb+QiNq811N$AAq9~Y5CM;$%N<;)4JczP$r??02bc(V~sF>R= zt?Ab?hP8z!9kgikE3GJqXg`K7xJJvEH5qXkN-Ol(j6YCS`9G}&oxJRLKVkp?002ov JPDHLkV1lOc7YP6W literal 0 HcmV?d00001 diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..26d00c39 --- /dev/null +++ b/notes.md @@ -0,0 +1,160 @@ +# VS Code Z80 Debug Adapter + + + + +# TODO + +- launch zesarux on start of debug (also remotely) +- backwards stepping -> change zesarux +- ZX81 support + + +# To document + +- If a breakpoint is set while the debugged program is being run, a break is executed first. ZEsarUX lacks possiblitiy to set a breakpoint while running. +- Possibility to send a command directly to ZEsarUX in the debug console. + + +# Implementation + +## Activation + +z80-debug is activated in the 'activate' function in extenson.ts +by registering the ZesaruxConfigurationProvider. +This happens e.g. when the Debugger is started. +Short after 'ZesaruxConfigurationProvider::resolveDebugConfiguration' is called. +As the debug adapter is entirely implemented in Typescript it lives in the same process as the extension which simplifies debugging the debug-adapter. +So in 'resolveDebugConfiguration' the 'ZesaruxDebugSession' is instantiated +and started as server (socket). In the same function the server (socket) is also connected by the extension. + +Although the same process is used and therefore it is technically possible to directly call methods of the 'ZesaruxDebugSession' it is not done. +Instead the intended way (through 'customRequest' which uses sockets) is chosen. + + + +# Open Items + +- After 'set-memory' the watches are not updated. There is no way to tell vscode to update that area. -> github issue/feature request created. +Same for updating PC. I would need to send a stacktrace but I cannot. I can only do so as a response. +- If PC is e.g. in ROM code it cannot be stepped through because the code cannot be seen. I would need to create temporary files with the disassembly. + +# Zesarux Wishlist + +- cpu-step-out does not exist. +- more intuively cpu-step-over +- Breakpoints: + - Condition: Immediate test if condition syntax is correct. Currently it is possible to write complete nonsense which, of course, is never hit. + - Condition: If a certain memory bank is selected. I.e. ROM=0, this is required to put a breakpoint on a PC for a certain Bank only. E.g. a list file is associated with a bank. If a breakpoint is set there it should match the right bank. + - ClearAllBreakpoints + - nice: own zesarux Breakpoint ID handling: SetBreakpoint (without ID), get the ID in return + - setting of a breakpoint while zesarux is "run"ning. Currently this leads to immediately stopping the "run" execution. +- snapshot load: It would be nice if there is a way to directly stop after loading a snapshot. Currently first the snapshot need to be loaded, + then entercpu-step. + Otherwise this error can be seen: + command> enter-cpu-step + command@cpu-step> snapshot-load /Volumes/Macintosh HD 2/Projects/zesarux/asm/z80-sample-program/z80-sample-program.sna + Error. Can not enter cpu step mode. You can try closing the menu + command> +- screen update: If something is written to the screen it is not visualized immediately e.g. when single stepping. Although this might be the accurate emulation, for debugging it is not so helpful. It would be good to hav a kind of refresh command to update the screen. (The MAME debugger immediately updates). + + + + + +# Backstep support for ZEsarUX + +On every opcode that is being processed the context has to be saved before the instruction is processed. +- Z80 registers +- Memory (at least the memory that is going to be changed) +- Memory bank settings +- Ports (in-value) +- HW related states. E.g. sprite positions. + +For every opcode a new item with the changed values (i.e. the values before the change, "pre-values") +is added to the record-list. + +Depending on the opcode different values need to be changed, so in general this is a dynamical +list with dynamic content. + +When the record-list is implemented not only backstepping would be possible, it is also possible to +get a history of the e.g. the register values. I.e. If I want to know if the history of the DE register +ZEsarUX could return a list with the recent value and also the past values together with PC and number of +past opcodes when the value was changed. + + +## Restriction +For now only the registers should be saved. No other state. +(I need to check if I need to save the RAM memory as well for the stack-trace.) + + +## Pseudocode (ZEsarUX): + +### General +Recording is always on, i.e. the record-list is filled right from the start of ZEsarUX. There is a max. number of entries (e.g. 100000). If this is reached the oldest entry is removed. + + +### Step back +1. "cpu-step-back" is received: Mode is changed to "reverse-debug-made". The rl_index is decreased. +3. "get-registers" is received: The register values from the record_list[rl_index] is sent. +3. "get-stack-trace" is received: ??? need to check how this list is generated. If it is a simple memory lookup then I need to save the memory values for sure. + +### Continue reverse +1. "run-reverse" is received: Mode is changed to "reverse-debug-made". The rl_index is decreased in a loop until (this is done very fast) + a) the list start is reached + b) a breakpoint condition is met +2. "get-registers" is received: ... same as above + +### Step (forward) +1. "cpu-step" is received while in "reverse-debug-made". The rl_index is increased. +~~~ + If rl_index > record_list then: leave "reverse-debug-made". +~~~ +2. "get-registers" is received: ... same as above + + +### Continue (forward) +1. "run" is received while in "reverse-debug-made". The rl_index is increased in a loop until (this is done very fast) + a) the list end is reached: leave "reverse-debug-made". Run normal "run" (i.e. continue seemingless with normal run mode). + b) a breakpoint condition is met +2. "get-registers" is received: ... same as above + +### Get historic registers +1. "get-historic-register reg" is received: The record list is checked for changes in the register. The current and the past values are sent together with PC values and information when the change happened. E.g. +~~~ +-> "get-historic-register DE" +<- +DE=5F8A +DE=34E9 PC=A123 dCount=15 +DE=7896 PC=A089 dCount=2343 +~~~ +Note: dCount (differential count) is a decimal number, i.e. it can grow bigger than FFFFh. + +vscode could show this when hovering above a register. + +# Misc + +## gdb example + +### (gdb) target record-full +Starts the recording + +### (gdb) continue-reverse +Goes backward through the recording until start of recording or a breakpoint is hit. + +### (gdb) reverse-step / reverse-next +Steps backward. + +### (gdb) reverse-finish +Opposite of "step-out". I.e. it "runs" backward until the start of the function. + +### (gdb) set exec-direction reverse +All step/run commands work in backward direction. + +### (gdb) set record insn-number-max +Set number of maximum instructions for the record list. + + +# Open +- Check what "get-stack-trace" does. +- How to handle out-port values? diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..32a307ac --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3146 @@ +{ + "name": "z80-debug", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, + "@types/node": { + "version": "7.0.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.55.tgz", + "integrity": "sha512-diCxfWNT4g2UM9Y+BPgy4s3egcZ2qOXc0mXLauvbsBUq9SBKQfh0SmuEUEhJVFZt/p6UDsjg1s2EgfM6OSlp4g==", + "dev": true + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "await-notify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/await-notify/-/await-notify-1.0.1.tgz", + "integrity": "sha1-C0gTOyLlJBgeEVV2ZRhfKi885Hw=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "chalk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "5.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "requires": { + "color-convert": "1.9.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "dev": true, + "requires": { + "css-select": "1.2.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.9.2", + "lodash": "4.17.5", + "parse5": "3.0.3" + } + }, + "clone": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", + "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "cloneable-readable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.0.0.tgz", + "integrity": "sha1-pikNQT8hemEjL5XkWP84QYz7ARc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "process-nextick-args": "1.0.7", + "through2": "2.0.3" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", + "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", + "dev": true, + "requires": { + "is-obj": "1.0.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", + "dev": true + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "dev": true, + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "duplexify": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", + "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "stream-shift": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "end-of-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "3.1.0", + "path-dirname": "1.0.2" + } + }, + "glob-stream": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", + "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", + "dev": true, + "requires": { + "extend": "3.0.1", + "glob": "5.0.15", + "glob-parent": "3.1.0", + "micromatch": "2.3.11", + "ordered-read-streams": "0.3.0", + "through2": "0.6.5", + "to-absolute-glob": "0.1.1", + "unique-stream": "2.2.1" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + } + } + }, + "glogg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "gulp-chmod": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-2.0.0.tgz", + "integrity": "sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw=", + "dev": true, + "requires": { + "deep-assign": "1.0.0", + "stat-mode": "0.2.2", + "through2": "2.0.3" + } + }, + "gulp-filter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.0.1.tgz", + "integrity": "sha512-5olRzAhFdXB2klCu1lnazP65aO9YdA/5WfC9VdInIc8PrUeDIoZfaA3Edb0yUBGhVdHv4eHKL9Fg5tUoEJ9z5A==", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "multimatch": "2.1.0", + "streamfilter": "1.0.7" + } + }, + "gulp-gunzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz", + "integrity": "sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak=", + "dev": true, + "requires": { + "through2": "0.6.5", + "vinyl": "0.4.6" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "0.2.0", + "clone-stats": "0.0.1" + } + } + } + }, + "gulp-remote-src": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/gulp-remote-src/-/gulp-remote-src-0.4.3.tgz", + "integrity": "sha1-VyjP1kNDPdSEXd7wlp8PlxoqtKE=", + "dev": true, + "requires": { + "event-stream": "3.3.4", + "node.extend": "1.1.6", + "request": "2.79.0", + "through2": "2.0.3", + "vinyl": "2.0.2" + }, + "dependencies": { + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "request": { + "version": "2.79.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "qs": "6.3.2", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.4.3", + "uuid": "3.1.0" + } + }, + "vinyl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.0.2.tgz", + "integrity": "sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "is-stream": "1.1.0", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + } + } + }, + "gulp-sourcemaps": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", + "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", + "dev": true, + "requires": { + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "strip-bom": "2.0.0", + "through2": "2.0.3", + "vinyl": "1.2.0" + }, + "dependencies": { + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-symdest": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-symdest/-/gulp-symdest-1.1.0.tgz", + "integrity": "sha1-wWUyBzLRks5W/ZQnH/oSMjS/KuA=", + "dev": true, + "requires": { + "event-stream": "3.3.4", + "mkdirp": "0.5.1", + "queue": "3.1.0", + "vinyl-fs": "2.4.4" + } + }, + "gulp-untar": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/gulp-untar/-/gulp-untar-0.0.6.tgz", + "integrity": "sha1-1r3v3n6ajgVMnxYjhaB4LEvnQAA=", + "dev": true, + "requires": { + "event-stream": "3.3.4", + "gulp-util": "3.0.8", + "streamifier": "0.1.1", + "tar": "2.2.1", + "through2": "2.0.3" + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "gulp-vinyl-zip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.0.tgz", + "integrity": "sha1-JOQGhdwFtxSZlSRQmeBZAmO+ja0=", + "dev": true, + "requires": { + "event-stream": "3.3.4", + "queue": "4.4.2", + "through2": "2.0.3", + "vinyl": "2.1.0", + "vinyl-fs": "2.4.4", + "yauzl": "2.9.1", + "yazl": "2.4.3" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "queue": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-4.4.2.tgz", + "integrity": "sha512-fSMRXbwhMwipcDZ08enW2vl+YDmAmhcNcr43sCJL8DIg+CFOsoRLG23ctxA+fwNk1w55SePSiS7oqQQSgQoVJQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.11.0", + "is-my-json-valid": "2.17.1", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.4.1", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz", + "integrity": "sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-my-json-valid": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", + "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", + "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, + "linkify-it": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", + "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", + "dev": true, + "requires": { + "uc.micro": "1.0.5" + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "3.0.1" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==" + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "markdown-it": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.1.tgz", + "integrity": "sha512-CzzqSSNkFRUf9vlWvhK1awpJreMRqdCrBvZ8DIoDWTOkESMIF741UPAhuAmbyWmdiFPA6WARNhnu2M6Nrhwa+A==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "entities": "1.1.1", + "linkify-it": "2.0.3", + "mdurl": "1.0.1", + "uc.micro": "1.0.5" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dev": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz", + "integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-union": "1.0.2", + "arrify": "1.0.1", + "minimatch": "3.0.4" + } + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node.extend": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.6.tgz", + "integrity": "sha1-p7iCyC1sk6SGOlUEvV3o7IYli5Y=", + "dev": true, + "requires": { + "is": "3.2.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "1.0.0" + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "ordered-read-streams": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", + "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", + "dev": true, + "requires": { + "is-stream": "1.1.0", + "readable-stream": "2.3.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", + "dev": true, + "requires": { + "semver": "5.4.1" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "7.0.55" + } + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "dev": true + }, + "querystringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", + "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", + "dev": true + }, + "queue": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/queue/-/queue-3.1.0.tgz", + "integrity": "sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "0.0.7" + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "form-data": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.0.tgz", + "integrity": "sha512-vUoN3I7fHQe0R/SJLKRdKYuEdRGogsviXFkHHo17AWaTGv17VLnxw+CFXvqy+y4ORZ3doWLQcxRYfwKrsd/H7Q==", + "dev": true, + "requires": { + "source-map": "0.6.1" + } + }, + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "dev": true + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "stat-mode": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", + "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", + "dev": true + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamfilter": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.7.tgz", + "integrity": "sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-bom-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", + "dev": true, + "requires": { + "first-chunk-stream": "1.0.0", + "strip-bom": "2.0.0" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.3", + "xtend": "4.0.1" + } + }, + "through2-filter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", + "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", + "dev": true, + "requires": { + "through2": "2.0.3", + "xtend": "4.0.1" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "to-absolute-glob": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", + "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1" + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "ts-node": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.0.tgz", + "integrity": "sha512-+CQev+4J7BAUNUnW9piRzSfSZZWeFCjgUjMSgGs4+dJ2RZa86NVW9MOlP4e6/kEHTyOqdxHxcIMd7KgmY/ynVw==", + "requires": { + "arrify": "1.0.1", + "chalk": "2.3.1", + "diff": "3.3.1", + "make-error": "1.3.4", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.5.4", + "yn": "2.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "source-map-support": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "requires": { + "source-map": "0.6.1" + } + } + } + }, + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", + "dev": true + }, + "tslint": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.3.1", + "commander": "2.14.1", + "diff": "3.3.1", + "glob": "7.1.2", + "js-yaml": "3.10.0", + "minimatch": "3.0.4", + "resolve": "1.5.0", + "semver": "5.4.1", + "tslib": "1.9.0", + "tsutils": "2.22.0" + }, + "dependencies": { + "commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.22.0.tgz", + "integrity": "sha512-05iMIQXsLVBx2vPTvpsHriRuNpKpU1Z9jqYUOBvwglO6I+QzdA1UQcVoNhsVkSZfns5TjFMFbdxdrkOW6cfwUQ==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "tunnel": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", + "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "typed-rest-client": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-0.9.0.tgz", + "integrity": "sha1-92jMDcP06VDwbgSCXDaz54NKofI=", + "dev": true, + "requires": { + "tunnel": "0.0.4", + "underscore": "1.8.3" + } + }, + "typescript": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", + "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", + "dev": true + }, + "uc.micro": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz", + "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==", + "dev": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "unique-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true, + "requires": { + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" + } + }, + "url-join": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", + "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=", + "dev": true + }, + "url-parse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", + "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", + "dev": true, + "requires": { + "querystringify": "1.0.0", + "requires-port": "1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true + }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-fs": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", + "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", + "dev": true, + "requires": { + "duplexify": "3.5.1", + "glob-stream": "5.3.5", + "graceful-fs": "4.1.11", + "gulp-sourcemaps": "1.6.0", + "is-valid-glob": "0.3.0", + "lazystream": "1.0.0", + "lodash.isequal": "4.5.0", + "merge-stream": "1.0.1", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "readable-stream": "2.3.3", + "strip-bom": "2.0.0", + "strip-bom-stream": "1.0.0", + "through2": "2.0.3", + "through2-filter": "2.0.0", + "vali-date": "1.0.0", + "vinyl": "1.2.0" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "vinyl-source-stream": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz", + "integrity": "sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A=", + "dev": true, + "requires": { + "through2": "2.0.3", + "vinyl": "0.4.6" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "0.2.0", + "clone-stats": "0.0.1" + } + } + } + }, + "vsce": { + "version": "1.37.5", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.37.5.tgz", + "integrity": "sha512-AIFKhrdhp/sJ2eYM/Nq1xzJPxo/IHemStkQsNIvbY/pyB8gADvrSayUfw4eLt6ffyKa+6+OeExyDvAg6L3X4Pw==", + "dev": true, + "requires": { + "cheerio": "1.0.0-rc.2", + "commander": "2.11.0", + "denodeify": "1.2.1", + "glob": "7.1.2", + "lodash": "4.17.5", + "markdown-it": "8.4.1", + "mime": "1.6.0", + "minimatch": "3.0.4", + "osenv": "0.1.5", + "parse-semver": "1.1.1", + "read": "1.0.7", + "semver": "5.4.1", + "tmp": "0.0.29", + "url-join": "1.1.0", + "vso-node-api": "6.1.2-preview", + "yauzl": "2.9.1", + "yazl": "2.4.3" + } + }, + "vscode": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.10.tgz", + "integrity": "sha512-MvFXXSGuhw0Q6GC6dQrnRc0ES+63wpttGIoYGBMQnoS9JFCCNC/rWfX0lBCHJyuKL2Q8CYg0ROsMEHbHVwEtVw==", + "dev": true, + "requires": { + "glob": "7.1.2", + "gulp-chmod": "2.0.0", + "gulp-filter": "5.0.1", + "gulp-gunzip": "1.0.0", + "gulp-remote-src": "0.4.3", + "gulp-symdest": "1.1.0", + "gulp-untar": "0.0.6", + "gulp-vinyl-zip": "2.1.0", + "mocha": "4.1.0", + "request": "2.83.0", + "semver": "5.4.1", + "source-map-support": "0.5.0", + "url-parse": "1.2.0", + "vinyl-source-stream": "1.1.2" + }, + "dependencies": { + "mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + } + } + } + }, + "vscode-debugadapter": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.27.0.tgz", + "integrity": "sha512-JwE3fWmKnpjYnFqhff0umqIJi4c26gh/CXZ5LNb4gLIuPd5sEAEoEbGeCcAaajuTrVxFw6FlYEep9y+IQCf+ww==", + "requires": { + "vscode-debugprotocol": "1.27.0" + } + }, + "vscode-debugadapter-testsupport": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.27.0.tgz", + "integrity": "sha512-rhNVFSeEtSfHZ8ZES1AKaY3vjfEgRbnikCsoDV0/Vu/jfuGnUlNMgWm+vbvtTlTIBPNjn2KKXU6ymYDjMwuW2Q==", + "dev": true, + "requires": { + "vscode-debugprotocol": "1.27.0" + } + }, + "vscode-debugprotocol": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz", + "integrity": "sha512-cg3lKqVwxNpO2pLBxSwkBvE7w06+bHfbA/s14u8izSWyhJtPgRu1lQwi5tEyTRuwfEugfoPwerYL4vtY6teQDw==" + }, + "vso-node-api": { + "version": "6.1.2-preview", + "resolved": "https://registry.npmjs.org/vso-node-api/-/vso-node-api-6.1.2-preview.tgz", + "integrity": "sha1-qrNUbfJFHs2JTgcbuZtd8Zxfp48=", + "dev": true, + "requires": { + "q": "1.5.1", + "tunnel": "0.0.4", + "typed-rest-client": "0.9.0", + "underscore": "1.8.3" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.0.1" + } + }, + "yazl": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.3.tgz", + "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..5920fd4b --- /dev/null +++ b/package.json @@ -0,0 +1,254 @@ +{ + "name": "z80-debug", + "displayName": "Z80 Debugger", + "version": "0.1.0", + "publisher": "maziac", + "description": "Especially for the ZEsarUX ZX Spectrum Emulator.", + "author": { + "name": "Thomas Busse" + }, + "license": "MIT", + "keywords": [ + "multi-root ready" + ], + "engines": { + "vscode": "^1.18.0", + "node": "^7.9.0" + }, + "icon": "images/z80-debug-icon.png", + "categories": [ + "Debuggers" + ], + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/xxx" + }, + "scripts": { + "prepublish": "tsc -p ./src", + "compile": "tsc -p ./src", + "tslint": "tslint ./src/**/*.ts", + "watch": "tsc -w -p ./src", + "test": "mocha -u tdd ./out/tests/", + "postinstall": "node ./node_modules/vscode/bin/install", + "package": "vsce package", + "publish": "vsce publish" + }, + "files": [ + "./data/*", + "./data/rom48.labels" + ], + "dependencies": { + "await-notify": "1.0.1", + "ts-node": "^6.0.0", + "vscode-debugadapter": "1.27.0", + "vscode-debugprotocol": "1.27.0" + }, + "devDependencies": { + "@types/node": "7.0.55", + "@types/mocha": "2.2.48", + "typescript": "2.6.2", + "mocha": "5.0.1", + "vscode": "1.1.10", + "vscode-debugadapter-testsupport": "1.27.0", + "tslint": "5.9.1", + "vsce": "1.37.5" + }, + "main": "./out/extension", + "activationEvents": [ + "onDebug" + ], + "contributes": { + "languages": [ + { + "id": "asm-collection", + "extensions": [ + ".list", + ".a80", + ".asm", + ".inc", + ".s" + ], + "aliases": [ + "Assembler file" + ] + } + ], + "breakpoints": [ + { + "language": "asm-collection" + } + ], + "commands": [], + "debuggers": [ + { + "type": "z80-debug", + "label": "Z80 Debugger", + "program": "./out/emuldebugadapter.js", + "runtime": "node", + "configurationAttributes": { + "launch": { + "required": [ + "zport", + "zhostname" + ], + "properties": { + "zhostname": { + "type": "string", + "description": "The hostname/IP address of the Zesarux Debugger." + }, + "zport": { + "type": "number", + "description": "The port of the Zesarux Debugger." + }, + "rootFolder": { + "type": "string", + "description": "The path of the root folder. All other paths are relative to this. Ususally set to ${workspaceFolder}." + }, + "disassemblies": { + "type": "array", + "description": "(Optional) Enter here the areas (address, size) of all areas that should be disassembled on startup. E.g. this can be used for ROM areas.", + "items": { + "type": "array", + "items": { + "type": "number", + "description": "Address/size tupels." + } + } + }, + "listFiles": { + "type": "array", + "description": "All list files. (Typically only one, but it's possible to add more here. E.g. a commented ROM disassembly could be added as complement to the program's list file.)", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "(Optional) Path to the assembler .labels file. If this is available addresseed can be mapped to labels." + }, + "useFiles": { + "type": "boolean", + "description": "If false the list file is used for breakpoints and stepping. If true the real source files are used. The filenames are collected from the list file." + } + } + } + }, + "labelsFiles": { + "type": "array", + "description": "All labels files. (Typically only one.)", + "items": { + "type": "string", + "description": "(Optional) Path to the assembler .labels file. If this is available addresseed can be mapped to labels." + } + }, + "useFilenamesFromListFile": { + "type": "boolean", + "description": "(Optional) If false the list file is used for breakpoints and stepping. If true the real source files are used. The filenames are collected from the list file." + }, + "disableLabelResolutionBelow": { + "tape": "number", + "description": "Disables the number to label conversion if number is below the given value. E.g. if you choose 256 (the typical value)labels below 256 are not resolved." + }, + "tmpDir": { + "type": "string", + "description": "NOT USED!!! A directory for temporary files created by this debug adapter. E.g. '.tmp'" + }, + "topOfStack": { + "type": "string", + "description": "(Optional) You should set this to the label or address which is above the topmost entry on the stack. It is used to determine the end of the call stack." + }, + "loadSnap": { + "type": "string", + "description": "(Optional) If set the snapshot file will be loaded into the emulator on startup of the debug session." + }, + "startAutomatically": { + "type": "boolean", + "description": "Start automatically after launch." + }, + "skipInterrupt": { + "type": "boolean", + "description": "Skips the interrupt during stepping." + }, + "trace": { + "type": "boolean", + "description": "Enable logging of the Debug Adapter Protocol." + }, + "registerVarFormat": { + "type": "array", + "description": "Defines the formatting of the registers when displayed in the VARIABLES area. E.g. as hex value or as integer. Also allows to display labels and various other formats. Use:\n${name} = the name of the register, e.g. HL\n${hex} = value as hex, e.g. A9F5\n${unsigned} = value as unsigned, e.g. 1234\n$(signed) = value as signed, e.g. -59\n$(bits) = value as bits , e.g. 10011011\n$(flags) = value interpreted as status flags (only useful for Fand F#), e.g. ZNC\n${labels} = value as label (or several labels)\n{labelsplus} = value as label (or several labels) plus an index/offset\n${pre:labels:join} = value as label (or several labels). If no label is found nothing is printed. If at leat 1 label is found the 'pre' string is printed followed by the label(s). If more than 1 label is found they are concatenated with the 'join' string.\n${b@:...} = This prefix to hex, unsigned, signed or bits allows to show the memory content of the value, i.e. it uses the value as address and shows it's contents. E.g. you can use ${b@:bits} to show the memory contents of the address the register is pointing at as a bit value.", + "items": { + "type": "string" + } + }, + "registerHoverFormat": { + "type": "array", + "description": "see registerVarFormat. Format when hovering over a register", + "items": { + "type": "string" + } + }, + "labelWatchesByteFormat": { + "type": "string", + "description": "see registerVarFormat. Format for BYTEs in the WATCHES area." + }, + "labelHoverByteFormat": { + "type": "string", + "description": "see registerVarFormat. Format for BYTEs when hovering." + }, + "labelWatchesWordFormat": { + "type": "string", + "description": "see registerVarFormat. Format for WORDs in the WATCHES area." + }, + "labelHoverWordFormat": { + "type": "string", + "description": "see registerVarFormat. Format for WORDs when hovering." + }, + "stackVarFormat": { + "type": "string", + "description": "see registerVarFormat. Format for the pushed values in the STACK area." + } + } + } + }, + "initialConfigurations": [ + { + "type": "z80-debug", + "request": "launch", + "name": "Z80 Debugger", + "zhostname": "localhost", + "zport": 10000, + "listFiles": [ + "path_to_your_assembler_file.list" + ], + "labelsFiles": [ + "path_to_your_assembler_file.labels" + ], + "startAutomatically": true, + "skipInterrupt": true + } + ], + "configurationSnippets": [ + { + "label": "Z80 Debug: Launch", + "description": "A new configuration for 'debugging' a user selected markdown file.", + "body": { + "type": "z80-debug", + "request": "launch", + "name": "Z80 Debugger", + "zhostname": "localhost", + "zport": 10000, + "listFiles": [ + "path_to_your_assembler_file.list" + ], + "labelsFiles": [ + "path_to_your_assembler_file.labels" + ], + "startAutomatically": true, + "skipInterrupt": true + } + } + ] + } + ] + } +} diff --git a/src/callserializer.ts b/src/callserializer.ts new file mode 100644 index 00000000..9b977a46 --- /dev/null +++ b/src/callserializer.ts @@ -0,0 +1,133 @@ +import { Log } from './log'; + + + +/** + * Class that serializes calls. + * I.e. this class takes care that asynchronous calls are executed one after the other. + */ +export class CallSerializer { + + /// Call queue + private queue = new Array(); + + /// name of the queue, for debugging. + private name: string; + + /// Time to print a log message that the queue is not "clean". In secs. + private timeout: number; + + + /// The timer for the log message. + private timer: any; + + /// Enable/disable logging + private logEnabled: boolean; + + + /** + * Constructor. + * @param name Name of the queue (logging) + * @param enableLog Enables/disables logging (logging) + * @param timeout The time to print a log message if queue is not "clean" (empty) + */ + constructor(name: string, enableLog?: boolean, timeout?: number) { + this.name = name; + this.timeout = (timeout == undefined) ? 5*1000: timeout*1000; + this.logEnabled = (enableLog == undefined) ? false : enableLog; + this.timeout = 0; + } + + + /** + * Adds the method (call) to the queue. + * If this is the only call in the queue it is directly executed. + * @param func Function to executed (data that is passed is the call serializer (this)) + * @param funcName A human redable name for debugging + */ + public exec(func: {(callserializer)} = (callserializer) => {}, funcName?: string) { + this.queue.push({func: func, name: funcName}); + Log.log('Pushed (size=' + this.queue.length + ', name=' + funcName + '), ' + func); + + // Timer to check that queue gets empty + if(this.timeout != 0 && this.logEnabled) { + // Cancel previous timer + //if(this.timer) + clearTimeout(this.timer); + // Restart timer + this.timer = setTimeout(() => { + if(this.queue.length > 0) + this.log('Error: queue is not empty, still ' + this.queue.length + ' elements.'); + clearTimeout(this.timer); + }, this.timeout); + this.timer.unref(); + } + + if(this.queue.length == 1) { + // Execute + this.runQueueFunction(); + } + this.log('exec: queue.size = ' + this.queue.length); + } + + + /** + * Add several functions to the queue. + * @param funcs (variadic) a number of functions calls/anonymous functions separated by ",". + */ + public execAll(...funcs) { + funcs.forEach(func => { this.exec(func); }); + } + + + /** + * Should be called from the asynchronous method to inform the call is + * finished. + */ + public endExec() { + // Remove first element = this method call + const entry = this.queue.shift(); + // Log + Log.log('Popped (size=' + this.queue.length + ', name=' + entry.funcName + '), ' + entry.func); + // Execute next + if(this.queue.length != 0) + this.runQueueFunction(); + this.log('endExec: queue.size = ' + this.queue.length); + } + + + /** + * If there is an method in the queue than it is executed. + */ + private runQueueFunction() { + // execute directly + this.log('runQueueFunction ' + this.queue[0].name); + const method = this.queue[0].func; + //method.call(this); + method(this); + } + + + /** + * Logs the given args if logEnabled. + */ + private log(...args) { + if(!this.logEnabled) + return; + // REMOVE: + console.log(this.name + '.CallSerializer: ', ...args); + console.log(this.name + '.CallSerializer: ', ...this.queue); + } + + + /** + * Static function to serialize several function calls. + * Creates a temporary CallSerializer object and adds the function calls to it. + * @param funcs (variadic) a number of functions calls/anonymous functions separated by ",". + */ + public static execAll(...funcs) { + const queue = new CallSerializer("TempSerializer"); + queue.execAll(...funcs); + } + +} diff --git a/src/debugadapter.ts b/src/debugadapter.ts new file mode 100644 index 00000000..d6f17e9b --- /dev/null +++ b/src/debugadapter.ts @@ -0,0 +1,4 @@ + + import { EmulDebugAdapter } from './emuldebugadapter'; + + EmulDebugAdapter.run(EmulDebugAdapter); diff --git a/src/emuldebugadapter.ts b/src/emuldebugadapter.ts new file mode 100644 index 00000000..f0b1e7ba --- /dev/null +++ b/src/emuldebugadapter.ts @@ -0,0 +1,842 @@ + + +import { + DebugSession, + InitializedEvent, TerminatedEvent, StoppedEvent, /*BreakpointEvent,*/ /*OutputEvent,*/ + Thread, StackFrame, Scope, Source, /*Handles,*/ Breakpoint /*, OutputEvent*/ +} from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { basename } from 'path'; +import { zSocket } from './zesaruxSocket'; +import { Z80Registers } from './z80Registers'; +import { Utility } from './utility'; +import { Labels } from './labels'; +import { Settings, SettingsParameters } from './settings'; +import { CallSerializer } from './callserializer'; +import { RefList } from './reflist'; +import { Log } from './log'; +import { /*ShallowVar,*/ DisassemblyVar, RegistersMainVar, RegistersSecondaryVar, StackVar, LabelVar } from './shallowvar'; +//import { Frame } from './frame'; +import { Machine, MachineBreakpoint } from './machine'; +import * as vscode from 'vscode'; + +//import { AssertionError } from 'assert'; +//const { Subject } = require('await-notify'); + + + +/** + * The Emulator Debug Adapter. + * It receives the requests from vscode and sends events to it. + */ +export class EmulDebugAdapter extends DebugSession { + + /// A list for the frames (call stack items) + //private listFrames = new RefList(); + + /// A list for the variables (references) + private listVariables = new RefList(); + + /// Holds the Z80 machine. + private machine: Machine; + + /// Only one thread is supported. + private static THREAD_ID = 1; + + /// Is responsible to serialize asynchronous calls (e.g. to zesarux). + private serializer = new CallSerializer("Main", true); + + /// Counts the number of stackTraceRequests. + private stackTraceResponses = new Array(); + + + /** + * Creates a new debug adapter that is used for one debug session. + * We configure the default implementation of a debug adapter here. + */ + public constructor() { + super(); + + // Start logging + Log.clear(); + + // Init line numbering + this.setDebuggerLinesStartAt1(false); + this.setDebuggerColumnsStartAt1(false); + + + /* + this._runtime.on('stopOnStep', () => { + this.sendEvent(new StoppedEvent('step', ZesaruxDebugSession.THREAD_ID)); + }); + this._runtime.on('stopOnBreakpoint', () => { + this.sendEvent(new StoppedEvent('breakpoint', ZesaruxDebugSession.THREAD_ID)); + }); + this._runtime.on('stopOnException', () => { + this.sendEvent(new StoppedEvent('exception', ZesaruxDebugSession.THREAD_ID)); + }); + this._runtime.on('breakpointValidated', (bp: ZesaruxBreakpoint) => { + this.sendEvent(new BreakpointEvent('changed', { verified: bp.verified, id: bp.id })); + }); + this._runtime.on('output', (text, filePath, line, column) => { + const e: DebugProtocol.OutputEvent = new OutputEvent(`${text}\n`); + e.body.source = this.createSource(filePath); + e.body.line = this.convertDebuggerLineToClient(line); + e.body.column = this.convertDebuggerColumnToClient(column); + this.sendEvent(e); + }); + this._runtime.on('end', () => { + this.sendEvent(new TerminatedEvent()); + }); + */ + } + + + /** + * Used to show an error to the user. + * @param message The message to show. + */ + private showError(message: string) { + Log.log(message) + vscode.window.showErrorMessage(message); + } + + + /** + * Exit from the debugger. + * @param message If defined the message is shown to the user as error. + */ + private exit(message: string|undefined) { + if(message) + this.showError(message); + Log.log("Exit debugger!"); + this.sendEvent(new TerminatedEvent()); + //this.sendEvent(new ExitedEvent()); + } + + + /** + * Overload sendEvent to logger. + */ + public sendEvent(event: DebugProtocol.Event): void { + Log.log(`<-: ${event.event}(${JSON.stringify(event.body)})`); + super.sendEvent(event); + } + + /** + * Overload sendRequest to logger. + */ + public sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void { + Log.log(`<-: ${command}(${JSON.stringify(args)})`); + super.sendRequest(command, args, timeout, (resp) => { + // Response + Log.log(`->: ${resp.command}(${JSON.stringify(resp.body)})`); + // callback + cb(resp); + }); + } + + /** + * Overload sendResponse to logger. + */ + public sendResponse(response: DebugProtocol.Response): void { + Log.log(`<-: ${response.command}(${JSON.stringify(response.body)})`); + super.sendResponse(response); + } + + /** + * Writes all requests to the logger. + * @param request The DebugProtocol request. + */ + protected dispatchRequest(request: DebugProtocol.Request): void { + Log.log(`->: ${request.command}(${JSON.stringify(request.arguments)})`); + super.dispatchRequest(request); + } + + + /** + * Debugadapter disconnects. + */ + protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + this.machine.stop(); + this.sendResponse(response); + } + + /** + * 'initialize'request. + * Respond with supported features. + */ + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + + // build and return the capabilities of this debug adapter: + response.body = response.body || {}; + + // the adapter implements the configurationDoneRequest. + response.body.supportsConfigurationDoneRequest = false; + + // make VS Code to show a 'step back' button + response.body.supportsStepBack = true; + + // Maybe terminated on error + response.body.supportTerminateDebuggee = true; + + // The PC value might be changed. + // REMOVE: seems to have no effect. + response.body.supportsGotoTargetsRequest = true; + + // Support hovering over values (registers) + response.body.supportsEvaluateForHovers = true; + + // Support changing of variables (e.g. registers) + response.body.supportsSetVariable = true; + + this.sendResponse(response); + + // Note: The InitializedEvent will be send when the socket connection has been successfull. Afterwards the breakpoints are set. + } + + + /** + * Called after 'initialize' request. + * Loads the list file and connects the socket to the zesarux debugger. + * Initializes zesarux. + * When zesarux is connected and initialized an 'InitializedEvent' + * is sent. + * @param response + * @param args + */ + protected async launchRequest(response: DebugProtocol.LaunchResponse, args: SettingsParameters) { + + // Save args + Settings.Init(args); + + // Clear all temporary files + Utility.removeAllTmpFiles(); + + // init labels + Labels.init(); + + // wait until configuration has finished (and configurationDoneRequest has been called) + //await this._configurationDone.wait(1000); //funktioniert eh nicht, kann ich auch disablen + + // Create the machine + this.machine = Machine.getMachine(); + + this.machine.once('initialized', () => { + try { + // Load user list and labels files + for(var listFile of Settings.launch.listFiles) + Labels.loadAsmListFile(listFile.path, listFile.useFiles); + for(var labelsFile of Settings.launch.labelsFiles) + Labels.loadAsmLabelsFile(labelsFile); + } + catch(err) { + // Some error occurred during loading, e.g. file not found. + this.exit(err.message); + } + + // Load list and labels file according machine + //const dir = __dirname; + + + + // Now get all disassemblies + for(var area of Settings.launch.disassemblies) { + this.serializer.exec(() => { + // get disassembly + this.machine.getDisassembly(area[0] /*address*/, area[1] /*size*/, (text) => { + // save as temporary file + const fileName = 'TMP_DISASSEMBLY_' + area[0] + '(' + area[1] + ').asm'; + const absFileName = Utility.writeTmpFile(fileName, text); + // add disassembly file without labels + Labels.loadAsmListFile(absFileName, false); + // "Return" + this.serializer.endExec(); + }); + }); + }; + + this.serializer.exec(() => { + // Finishes off the laoding of the list and labels files + Labels.finish(); + // Send stop + this.sendEvent(new StoppedEvent('entry', EmulDebugAdapter.THREAD_ID)); + // socket is connected, allow setting breakpoints + this.sendEvent(new InitializedEvent()); + // "Return" + this.serializer.endExec(); + // Respond + this.sendResponse(response); + }); + }); + + this.machine.once('error', err => { + // Some error occurred + this.machine.stop(); + this.exit(err.message); + }); + + } + + + + /** + * The breakpoints are set for a path (file). + * @param response + * @param args lines=array with line numbers. source.path=the file path + */ + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + + // Serialize + this.serializer.exec(() => { + + const path = args.source.path; + + // convert breakpoints + const givenBps = args.breakpoints || []; + const bps = givenBps.map(bp => { + var mbp: MachineBreakpoint; + mbp = { + bpId: 0, + filePath: path, + lineNr: this.convertClientLineToDebugger(bp.line), + condition: (bp.condition) ? bp.condition : '' + }; + return mbp; + }); + + + // Set breakpoints for the file. + this.machine.setBreakpoints(path, bps, (currentBreakpoints) => { + // Convert breakpoints for vscode + const vscodeBreakpoints = new Array(); + currentBreakpoints.forEach( bp => { + var vscBp = new Breakpoint(true, this.convertDebuggerLineToClient(bp.lineNr)); + vscodeBreakpoints.push(vscBp); + }); + + // send back the actual breakpoint positions + response.body = { + breakpoints: vscodeBreakpoints + }; + this.sendResponse(response); + this.serializer.endExec(); + }); + }); + + } + + + /** + * Returns the one and only "thread". + */ + protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + // Serialize + this.serializer.exec(() => { + // Just return a default thread. + response.body = { + threads: [ + new Thread(EmulDebugAdapter.THREAD_ID, "thread_default") + ] + }; + this.sendResponse(response); + this.serializer.endExec(); + }); + } + + + + /** + * Creates a source reference from the filePath. + * @param filePath + * @returns undefined if filePath is ''. + */ + private createSource(filePath: string): Source|undefined { + if(filePath.length == 0) + return undefined; + const fname = basename(filePath); + const debPath = this.convertDebuggerPathToClient(filePath); + return new Source(fname, debPath, undefined, undefined, undefined); + } + + /** + * Returns the stack frames. + */ + protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + // vscode sometimes sends 2 stack trace requests one after the other. Because the lists are cleared this can lead to race conditions. + this.stackTraceResponses.push(response); + if(this.stackTraceResponses.length > 1) + return; + + // Serialize + this.serializer.exec(() => { + // Clear all variables + this.listVariables.length = 0; + + // Get the call stack trace + this.machine.stackTraceRequest( (frames) => { + + // Create new array but only upto end of stack (name == null) + const sfrs = new Array(); + var index = 1; + for( const frame of frames) { + const src = this.createSource(frame.fileName); + const lineNr = (src) ? this.convertDebuggerLineToClient(frame.lineNr) : 0; + const sf = new StackFrame(index, frame.name, src, lineNr); + sfrs.push(sf); + if(frame.name === null) + break; // rest of stack trace is garbage + index++; + } + + // Send as often as there have been requests + while(this.stackTraceResponses.length > 0) { + const resp = this.stackTraceResponses[0]; + this.stackTraceResponses.shift(); + resp.body = {stackFrames: sfrs, totalFrames: 1}; + this.sendResponse(resp); + } + + // end the serialized call: + this.serializer.endExec(); + }); + }); + } + + + /** + * Returns the different scopes. E.g. 'Disassembly' or 'Registers' that are shown in the Variables area of vscode. + * @param response + * @param args + */ + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + this.serializer.exec(() => { + + const scopes = new Array(); + const frameId = args.frameId; + //const frame = this.listFrames.getObject(frameId); + const frame = this.machine.getFrame(frameId); + if(!frame) { + // No frame found, send empty response + response.body = {scopes: scopes}; + this.sendResponse(response); + return; + } + + // More serialization + const innerSerializer = new CallSerializer("innerScopesRequest"); + + // Serialize Disassembly + innerSerializer.exec(() => { + // get address + if(frame) { + // use address + const addr = frame.addr; + // Create variable object for Disassembly + const varDisassembly = new DisassemblyVar(addr, 8); + // Add to list and get reference ID + const ref = this.listVariables.addObject(varDisassembly); + scopes.push(new Scope("Disassembly", ref)); + } + // Return + innerSerializer.endExec(); + }); + + // Serialize main Registers + innerSerializer.exec(() => { + // TODO: later (with change in zesarux) I need to include the frame ID/callstack address as well + // Create variable object for Registers + const varRegistersMain = new RegistersMainVar(); + // Add to list and get reference ID + const ref = this.listVariables.addObject(varRegistersMain); + scopes.push(new Scope("Registers", ref)); + + // TODO: later (with change in zesarux) I need to include the frame ID/callstack address as well + // Create variable object for secondary Registers + const varRegisters2 = new RegistersSecondaryVar(); + // Add to list and get reference ID + const ref2 = this.listVariables.addObject(varRegisters2); + scopes.push(new Scope("Registers 2", ref2)); + + // Return + innerSerializer.endExec(); + }); + + // Serialize the Stack + innerSerializer.exec(() => { + // Create variable object for the stack + const varStack = new StackVar(frame.stack, frame.stackStartAddress); + // Add to list and get reference ID + const ref = this.listVariables.addObject(varStack); + scopes.push(new Scope("Stack", ref)); + + // Send response + response.body = {scopes: scopes}; + this.sendResponse(response); + + // Return + innerSerializer.endExec(); + this.serializer.endExec(); + }); + }); + } + + + /** + * Returns the variables for the scopes (e.g. 'Disassembly' or 'Registers') + * @param response + * @param args + */ + protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void { + // Get the associated variable object + const ref = args.variablesReference; + const varObj = this.listVariables.getObject(ref); + // Serialize + this.serializer.exec(() => { + // Check if object exists + if(!varObj) { + // Return empty list + var variables = new Array(); + response.body = {variables: variables}; + this.sendResponse(response); + // end the serialized call: + this.serializer.endExec(); + return; + } + // Get contents + varObj.getContent((varList) => { + response.body = {variables: varList}; + this.sendResponse(response); + // end the serialized call: + this.serializer.endExec(); + }, args.start, args.count); + }); + } + + + /** + * vscode requested 'continue'. + * @param response + * @param args + */ + protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + // Serialize + this.serializer.exec(() => { + // Continue debugger + this.machine.continue( () => { + // It returns here not immediately but only when a breakpoint is hit or pause is requested. + this.sendEvent(new StoppedEvent('break', EmulDebugAdapter.THREAD_ID)); + }); + + // Response is sent immediately + this.sendResponse(response); + this.serializer.endExec(); + }); + } + + + /** + * vscode requested 'pause'. + * @param response + * @param args + */ + protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + // Serialize + this.serializer.exec(() => { + // Pause the debugger + this.machine.pause(); + // Response is sent immediately + this.sendResponse(response); + this.serializer.endExec(); + }); + } + + + /** + * vscode requested 'reverse continue'. + * @param response + * @param args + */ + protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) : void { + // Serialize + this.serializer.exec(() => { + // Continue debugger + this.machine.reverseContinue( () => { + // It returns here not immediately but only when a breakpoint is hit or pause is requested. + this.sendEvent(new StoppedEvent('break', EmulDebugAdapter.THREAD_ID)); + }); + + // Response is sent immediately + this.sendResponse(response); + this.serializer.endExec(); + }); + + } + + /** + * vscode requested 'step over'. + * @param response + * @param args + */ + protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + // Serialize + this.serializer.exec(() => { + // Step-Over + this.machine.stepOver( () => { + // Response + this.sendResponse(response); + this.serializer.endExec(); + + // Send event + this.sendEvent(new StoppedEvent('step', EmulDebugAdapter.THREAD_ID)); + }); + }); + } + + + /** + * vscode requested 'step into'. + * @param response + * @param args + */ + protected stepInRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + // Serialize + this.serializer.exec(() => { + // Step-Into + this.machine.stepInto( () => { + // Response + this.sendResponse(response); + this.serializer.endExec(); + + // Send event + this.sendEvent(new StoppedEvent('step', EmulDebugAdapter.THREAD_ID)); + }); + + }); + } + + + /** + * vscode requested 'step out'. + * @param response + * @param args + */ + protected stepOutRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + // Serialize + this.serializer.exec(() => { + // Step-Out + this.machine.stepOut( () => { + // Send event + this.sendEvent(new StoppedEvent('step', EmulDebugAdapter.THREAD_ID)); + }); + + // Response is sent immediately + this.sendResponse(response); + this.serializer.endExec(); + }); + } + + + /** + * vscode requested 'step backwards'. + * @param response + * @param args + */ + protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + // Serialize + this.serializer.exec(() => { + // Step-Back + this.machine.stepBack( () => { + // Response + this.sendResponse(response); + this.serializer.endExec(); + + // Send event + this.sendEvent(new StoppedEvent('step', EmulDebugAdapter.THREAD_ID)); + }); + }); + } + + + /** + * Is called when hovering or when an expression is added to the watches. + */ + protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + // Check if its a debugger command + const expression = args.expression.trim(); + const tokens = expression.split(' '); + const cmd = tokens.shift(); + if(cmd) { + if(cmd.startsWith('-')) { + // All commands start with "-" + if(cmd == '-help' || cmd == '-h') { + // print help + //const output = new OutputEvent('Allowed commands are:', 'console'); + //this.sendEvent(output); + const output = +`Allowed commands are: + "-exec cmd args": cmd and args are directly passed to ZEsarUX. E.g. "-exec get-registers". + "-e": short for "-exec" + "-help|-h": This command. Do "-e help" to get all possible ZEsarUX commands. +Examples: + "-ex h 0 100": Does a hexdump of 100 bytes at address 0. + "-e write-memory 8000h 9fh": Writes 9fh to memory address 8000h. + "-e gr": Shows all registers. +Notes: + "-exec run" will not work at the moment and leads to a disconnect. +`; + response.body = { result: output, type: undefined, presentationHint: undefined, variablesReference:0, namedVariables: undefined, indexedVariables: undefined }; + this.sendResponse(response); + } + else if (cmd == '-exec' || cmd == '-e') { + const machineCmd = tokens.join(' '); + if(machineCmd.length == 0) { + // No command given + response.body = { result: 'No command given.\n\n', type: undefined, presentationHint: undefined, variablesReference:0, namedVariables: undefined, indexedVariables: undefined }; + this.sendResponse(response); + } + else { + this.machine.dbgExec(machineCmd, (data) => { + // Print to console + response.body = { result: data+'\n\n', type: undefined, presentationHint: undefined, variablesReference:0, namedVariables: undefined, indexedVariables: undefined }; + this.sendResponse(response); + }); + } + } + else { + // Unknown command + const output = "Unknown command: '" + expression + "'\n\n"; + response.body = { result: output, type: undefined, presentationHint: undefined, variablesReference:0, namedVariables: undefined, indexedVariables: undefined }; + this.sendResponse(response); + } + + // End + return; + } + } + + // Serialize + this.serializer.exec(() => { + + Log.log('evaluate.expression: ' + args.expression); + Log.log('evaluate.context: ' + args.context); + Log.log('evaluate.format: ' + args.format); + + // get the name + const name = expression; + // Check if it is a register + if(Z80Registers.isRegister(name)) { + zSocket.send('get-registers', data => { + Z80Registers.getHoverFormattedReg(name, data,(formattedValue) => { + response.body = { + result: formattedValue, + variablesReference: 0 + }; + this.sendResponse(response); + this.serializer.endExec(); + }); + }); + return; + } + + // Check if it is a label + var labelValue = Labels.getNumberforLabel(name); + if(labelValue) { + // It's a label + Utility.numberFormattedBy(name, labelValue, 2, + Settings.launch.labelWatchesGeneralFormat, undefined,(formattedValue) => { + // Create a label variable + const labelVar = new LabelVar(labelValue, 100, this.listVariables); + // Add to list + const ref = this.listVariables.addObject(labelVar); + // Response + response.body = { + result: (args.context == 'hover') ? name+': '+formattedValue : formattedValue, + variablesReference: ref, + type: "data", + //presentationHint: , + namedVariables: 2, + //indexedVariables: 100 + }; + this.sendResponse(response); + this.serializer.endExec(); + }); + return; + } + + // Default: return nothing + this.sendResponse(response); + this.serializer.endExec(); + }); + + } + + + + // TODO: Don't know yet how to deal with this. + protected gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + // Serialize + this.serializer.exec( () => { + this.sendResponse(response); + }); + } + + + /** + * Called eg. if user changes a register value. + */ + protected setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): void { + const ref = args.variablesReference; + const name = args.name; + const value = Utility.parseValue(args.value); + + // Serialize + this.serializer.exec( () => { + // get variable object + const varObj = this.listVariables.getObject(ref); + // safety check + if(varObj) { + // Set value + varObj.setValue(name, value, (formattedString) => { + // Send response + response.body = {value: formattedString}; + this.sendResponse(response); + }); + } + else { + this.sendResponse(response); + } + // End serializer + this.serializer.endExec(); + }); + } + + + /** + * Not used at the moment. + * Called from vscode when the user inputs a command in the command palette. + * The method checks if the command is known and executes it. + * If the command is unknown the super method is called. + * @param command The command, e.g. 'set-memory' + * @param response Used for responding. + * @param args The arguments of the command. Usually just 1 text object. + */ + protected customRequest(command: string, response: DebugProtocol.Response, args: any) { + switch(command) { + /* + case 'exec-cmd': + this.cmdExec(args); + break; + case 'set-memory': + this.cmdSetMemory(args[0]); + break; + */ + default: + super.customRequest(command, response, args); + return; + } + // send response + //this.sendResponse(response); + } +} + diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 00000000..c8be4af3 --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,83 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { WorkspaceFolder, DebugConfiguration, ProviderResult, CancellationToken } from 'vscode'; +import { EmulDebugAdapter } from './emuldebugadapter'; +import * as Net from 'net'; + + +/** + * Register configuration provider and command palette commands. + * @param context + */ +export function activate(context: vscode.ExtensionContext) { + + /* Nothing used at the moment: + // Command to send an arbitrary command through the socket to + // zesarux and print the output to the console. + context.subscriptions.push(vscode.commands.registerCommand('extension.z80-debug.exec-cmd', config => { + return vscode.window.showInputBox({ + placeHolder: "e.g. get-breakpoints", + prompt: 'Enter a command that is send to ZEsarUX', + validateInput: () => null + }).then(text => { + if(text && text.length > 0) { + if(vscode.debug.activeDebugSession) + vscode.debug.activeDebugSession.customRequest('exec-cmd', text); + } + }); + })); + */ + + // register a configuration provider for 'zesarux' debug type + const provider = new ZesaruxConfigurationProvider() + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('z80-debug', provider)); + context.subscriptions.push(provider); +} + +/** + * Called to deactivate the debug session. + */ +export function deactivate() { +} + +/** + * Instantiates the ZesaruxDebugAdapter and sets up the + * soccket connection to it. + */ +class ZesaruxConfigurationProvider implements vscode.DebugConfigurationProvider { + + private _server?: Net.Server; + + /** + * Instantiates the ZesaruxDebugAdapter and sets up the + * soccket connection to it. + */ + resolveDebugConfiguration(folder: WorkspaceFolder | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult { + + // start port listener on launch of first debug session + if (!this._server) { + + // start listening on a random port + this._server = Net.createServer(socket => { + const session = new EmulDebugAdapter(); + session.setRunAsServer(true); + session.start(socket, socket); + }).listen(0); + } + + // make VS Code connect to debug server instead of launching debug adapter + config.debugServer = this._server.address().port; + + return config; + } + + /** + * End. + */ + dispose() { + if (this._server) { + this._server.close(); + } + } +} diff --git a/src/frame.ts b/src/frame.ts new file mode 100644 index 00000000..9587c48f --- /dev/null +++ b/src/frame.ts @@ -0,0 +1,32 @@ + +//import { Log } from './log'; + + +/** + * Represents a frame, i.e. an entry on the callstack/a subroutine. + */ +export class Frame { + public addr: number; /// The corresponding address + public name: string; /// The shown name, e.g. "CALL clear_screen" + public fileName: string; /// The corresponding filename + public lineNr: number; /// The corresponding line number in the file + public stack = new Array(); /// The objects currently pushed on the stack + public stackStartAddress = -1; /// The start address of the stack + + /** + * Constructor + * @param addr The corresponding address + * @param stackAddr The start address of the stack + * @param name The shown name, e.g. "CALL clear_screen" + * @param fileName The corresponding filename + * @param lineNr The corresponding line number in the file + */ + constructor(addr: number, stackAddr: number, name: string, fileName: string, lineNr: number) { + this.addr = addr; + this.stackStartAddress = stackAddr; + this.name = name; + this.fileName = fileName; + this.lineNr = lineNr; + } +} + diff --git a/src/labels.ts b/src/labels.ts new file mode 100644 index 00000000..cf57e98e --- /dev/null +++ b/src/labels.ts @@ -0,0 +1,437 @@ +import { readFileSync } from 'fs'; +import { Utility } from './utility'; +import { Settings } from './settings'; +//import { Log } from './log'; +//import { AssertionError } from 'assert'; +//import { start } from 'repl'; + + +/** + * The representation of the list file. + */ +interface ListFileLine { + fileName: string; /// The associated source filename + lineNr: number; /// The line number of the associated source file + addr: number; /// The corresponding address from the list file + line: string; /// The text of the line of the list file +} + +/** + * Calculation of the labels from the input list and labels file. + * + * There is no association between labels/files and memory banks. I.e. it is not + * possible to load 2 disassemblies for the same area, e.g. for ROM0 and ROM1. + * The last one would win. + * + * This is because it is not clear/easy to distinguish the bank for a lable. + * Several other problem areay would need to be taken into account: + * - breakpoints for certain memory banks + * - should a label be displayed even if another memory bank is currently selected (could be valid). + * - ... + * Also the benefit is low. So I decided to stack with a memory bank agnostic implementation: + * All labels can cover all banks. It is not possible to load 2 disassemblies for the same area for different banks. + * + * This also implies that there is no automatic loading e.g. for the ROM. The user + * has to supply the wanted list file e.g. for the ROM and needs to decide which ROM he wants to see. + * + * (Note: this applies only to the labels/list files, the disassembly shown in the VARIABLEs area is always the one from the current bank.) + * + * + * + */ +class LabelsClass { + + /// Map that associates memory addresses (PC values) with line numbers + /// and files. + private fileLineNrs = new Map(); + + /// Map of arrays of line numbers. The key of the map is the filename. + /// The array contains the correspondent memory address for the line number. + private lineArrays = new Map>(); + + /// An element contains either the offset from the last + /// entry with labels or an array of labels for that number. + private labelsForNumber = new Array(); + + /// Map with all labels (from labels file) and corresponding values. + private numberForLabel = new Map(); + + // The top of the stack. Used to limit the call stack. + public topOfStack : number; + + // Constructor. + public constructor() { + } + + + /** + * Initializes the lists/arrays. + */ + public init() { + // clear data + this.fileLineNrs.clear(); + this.lineArrays.clear(); + this.labelsForNumber.length = 0; + this.numberForLabel.clear(); + } + + + /** + * This has to be set in the launchRequest. + * Finishes off the loading of list and labels files. + */ + public finish() { + // Calculate the label offsets + this.calculateLabelOffsets(); + // calculate top of stack in case it is a label + this.topOfStack = Labels.getNumberforLabel(Settings.launch.topOfStack); + if(!this.topOfStack) + this.topOfStack = Utility.parseValue(Settings.launch.topOfStack); + if(isNaN(this.topOfStack)) + this.topOfStack = 0x10000; + } + + + /** + * Reads the given file (an assembler .list file) and extracts all PC + * values (the first 4 digits), so that each line can be associated with a + * PC value. + * Fills listLines and listPCs. + * @param fileName The complete path of the file name. + * @param useIndirectFile Use the filenames in fileName. + */ + public loadAsmListFile(fileName: string, useIndirectFile:boolean) { + /// Array that contains the list file, the associated memory addresses + /// for each line and the associated real filenames/line numbers. + const listFile = new Array(); + + // Read all lines and extract the PC value + var listLines = readFileSync(fileName).toString().split('\n'); + var base = 0; + var prev = -1; + for( var line of listLines) { + // extract pc + var pc = parseInt(line.substr(0,4), 16) + base; + // compare with previous to find wrap around (if any) + if(pc < prev) { + base += 0x10000; + pc += 0x10000; + } + // store + var entry = {fileName: '', lineNr: -1, addr: pc, line: line}; + listFile.push(entry) + prev = pc + } + + /** + * Creates the list structures to reference files and lines in both directions: + * a) get file name and file line number from list-file line number + * b) get list-file line number from file name and file line number + */ + var index = -1; + const stack = new Array(); + + if(!useIndirectFile) { + // Use list file directly instead of real filenames + const relFileName = Utility.getRelFilePath(fileName); + const lineArray = new Array(); + this.lineArrays[relFileName] = lineArray; + for(var lineNr=0; lineNr0; lineNr--) { + const line = listFile[lineNr].line; + // check for end macro + const matchMacroEnd = /^# End of macro\s+(.*)/.exec(line); + if(matchMacroEnd) { + const macroName = matchMacroEnd[1]; + const startLine = this.searchStartOfMacro(macroName, lineNr, listFile); + // skip all lines, i.e. all lines get same line number + for(var i=startLine; i= 0) { + // Associate with right file + listFile[lineNr].fileName = stack[index].fileName; + listFile[lineNr].lineNr = stack[index].lineNr; + // next line + stack[index].lineNr--; + } + else { + // no association + listFile[lineNr].fileName = ''; + listFile[lineNr].lineNr = 0; + } + } + + // Now correct all line numbers (so far the numbers are negative. All numbers need to be added with the max number of lines for that file.) + var lastFileName = ''; + var lastFileLength = 0; + const fileLength = new Map(); + for(var i=0; i(); + } + + // Get array + const lineArray = this.lineArrays[entry.fileName]; + + // Set address + if(!lineArray[entry.lineNr]) // without the check macros would lead to the last addr being stored. + lineArray[entry.lineNr] = entry.addr; + } + } + + + /** + * Reads the given file (an assembler .labels file) and creates a + * map with label <-> number associations. + * Created lists: + * - usedNumbers: sorted array with used addresses. + * - labelsForNumber: addr -> array of labels. Stores an array of labels for a given addr. + * - numberForLabel: label -> addr + * Note: addresses below 256 are not parsed. These are most likely mixed up with + * constants (equ), so they are simply skipped. I.e. single register values are not + * converted to labels. + * @param fileName The complete path of the file name. + */ + public loadAsmLabelsFile(fileName: string) { + // Preset values + //this.labelsForNumber.fill(-1,0,0x10000); + + // Read all labels + const labels = readFileSync(fileName).toString().split('\n'); + for( let line of labels) { + // extract label and number. E.g. + // LBL_SAT_SPRITE_MASK_ARRAY: equ $ce27 + const match = /^(.*):\s+equ\s+\$([0-9a-fA-F]+)/.exec(line); + if(match == null || match.length != 3) + continue; + // Pattern found + const number = parseInt(match[2],16); + if(number < Settings.launch.disableLabelResolutionBelow || number > 0xFFFF) { + continue; // E.g. ignore numbers/labels < 256 or > 65535 + } + const label = match[1]; + + // add to label array + this.numberForLabel[label] = number; + + // Add label + var labelsArray = this.labelsForNumber[number]; + if(labelsArray === undefined) { + // create a new array + labelsArray = new Array(); + this.labelsForNumber[number] = labelsArray; + } + // Add new label + // REMOVE: TRY/CATCH + try { + labelsArray.push(label); + } + catch(err) { + console.log( "Error: " + err); + } + } + } + + + /** + * Calculates the offsets for all labels. + * I.e. for all addresses without a direct label entry. + */ + protected calculateLabelOffsets() { + // Now fill the unset values with the offsets + var offs = -1; + for( var i=0; i<0x10000; i++) { + const labels = this.labelsForNumber[i]; + if(labels === undefined) { + if(offs >= 0) { + this.labelsForNumber[i] = offs; + ++offs; + } + } + else { + // array + offs = 1; + } + } + } + + + /** + * Returns all labels with the exact same address + * to the given address. + * @param number The address value to find + * @returns An array of strings with labels or undefined + */ + public getLabelsForNumber(number: number): Array { + var labels = this.labelsForNumber[number]; + return (typeof labels === 'number') ? undefined : labels; + } + + + /** + * Returns all labels with the same address that are nearest and lower-equal + * to the given address. + * If label is equal to given addr the label itself is returned. + * If label is not equal to given addr the label+offset is returned. + * @param number The address value to find + * @returns An array of strings with labels + offset + */ + public getLabelsPlusIndexForNumber(number: number): Array { + var labels = this.labelsForNumber[number]; + if(labels === undefined) + return new Array(); // Return empty string + if(typeof labels === 'number') { + const offs = labels; + number -= offs; + const baseLabels = this.labelsForNumber[number]; // this is an array + if(baseLabels === undefined) + return new Array(); // Return empty string + labels = baseLabels.map(label => label+'+'+offs); + } + return labels; + } + + + /** + * Returns the corresponding number of a label. + * @param label The label name. + * @returns It's value. undefined if label does not exist. + */ + public getNumberforLabel(label: string): number { + return this.numberForLabel[label]; + } + + + /** + * Searches for the start of a macro. + * @param macroName The name of the macro to search for. + * @param startSearchLine Here the search begins. Search is done upwards. + * @param listFile Array with lines of the file. + * @return The found line number or startSearchLine if nothing found (should not happen). + */ + private searchStartOfMacro(macroName: string, startSearchLine: number, listFile: Array): number { + const macroRegex = new RegExp("[0-9a-fA-F]+\\s+" + macroName + "\\s+.*"); + var k=startSearchLine; + for(; k>0; --k) { + const line2 = listFile[k].line; + const matchMacroStart = macroRegex.exec(line2); + if(matchMacroStart) + return k; // macro start found + } + // Nothing found (should not happen) + return startSearchLine; + } + + + /** + * Returns file name and line number associated with a certain memory address. + * Used e.g. for the call stack. + * @param address The memory address to search for. + * @returns {fileName: string, lineNr: number} The associated filename and line number. + */ + public getFileAndLineForAddress(address: number): {fileName: string, lineNr: number} { + const entry = this.fileLineNrs[address]; + if(!entry) + return {fileName: '', lineNr: 0}; + + var filePath = Utility.getAbsFilePath(entry.fileName); + return {fileName: filePath, lineNr: entry.lineNr}; + } + + + /** + * Returns the memory address associated with a certain file and line number. + * @param fileName The path to the file. + * @param lineNr The line number inside the file. + * @returns The associated address. -1 if file or line does not exist. + */ + public getAddrForFileAndLine(fileName: string, lineNr: number): number { + var filePath = Utility.getRelFilePath(fileName); + var addr = -1; + const lineArray = this.lineArrays[filePath]; + if(lineArray) { + addr = lineArray[lineNr]; + if(!addr) + addr = -1; + } + return addr; + } + +} + + +/// Labels is the singleton object that should be accessed. +export var Labels = new LabelsClass(); diff --git a/src/log.ts b/src/log.ts new file mode 100644 index 00000000..879869fd --- /dev/null +++ b/src/log.ts @@ -0,0 +1,93 @@ + +//import * as fs from 'fs'; +import { writeFileSync, appendFileSync } from 'fs'; +import * as util from 'util'; + + +/// All log output goes additionally here. +const outFilePath = "/Volumes/Macintosh HD 2/Projects/zesarux/vscode/z80-debug-adapter/logs/main.log"; + +/** + * Class for logging. + */ +export class Log { + + /// Last time a log has been written. + private static lastLogTime = Date.now(); + + + /** + * Clears a former log file. + */ + public static clear() { + try { + writeFileSync(outFilePath, (new Date()).toString() + ': log started.\n'); + } + catch(e) { + console.log('Error: '+e); + } + Log.lastLogTime = Date.now(); + } + + + /** + * Logs to console. + * Puts the caller name ('class.method'. E.g. "ZesaruxDebugSession.initializeRequest") + * in front of each log. + * @param args The log arguments + */ + public static log(...args) { + // check time + var diffTime = (Date.now() - Log.lastLogTime)/1000; + if(diffTime > 2) { + // > 2 secs + Log.write('...'); + Log.write('Pause for ' + diffTime + ' secs.'); + Log.write('...'); + } + // write log + var who = Log.callerName() + ": "; + Log.write(who, ...args); + // get new time + Log.lastLogTime = Date.now(); + } + + + /** + * Writes to console and file. + * @param args the values to write. + */ + private static write(format: string, ...args) { + // write to console + console.log(...args); + // Append to file + var text = util.format(format, ...args); + try { + appendFileSync(outFilePath, text + '\n'); + } + catch(e) { + } + } + + + /** + * Returns the caller name. + * @returns 'class.method'. E.g. "ZesaruxDebugSeesion.initializeRequest" + */ + private static callerName(): string { + // Throw error to get call stack + try { + throw new Error(); + } + catch(e) { + try { + // Find caller name + return e.stack.split('at ')[3].split(' ')[0]; + } + catch (e) { + return 'Unknown'; + } + } + } + +} diff --git a/src/machine.ts b/src/machine.ts new file mode 100644 index 00000000..4755fff8 --- /dev/null +++ b/src/machine.ts @@ -0,0 +1,719 @@ + + +//import { basename } from 'path'; +import { zSocket, NO_TIMEOUT } from './zesaruxSocket'; +import { Z80Registers } from './z80Registers'; +import { Utility } from './utility'; +import { Labels } from './labels'; +import { Settings } from './settings'; +//import { CallSerializer } from './callserializer'; +import { RefList } from './reflist'; +import { Log } from './log'; +//import { /*ShallowVar,*/ DisassemblyVar, RegistersMainVar, RegistersSecondaryVar, StackVar, LabelVar } from './shallowvar'; +import { Frame } from './frame'; +import { EventEmitter } from 'events'; +import { MachineBreakpoint } from './machine'; + + +// Max number of Zesarux breakpoints. +const MAX_ZESARUX_BREAKPOINTS = 100; + + +// Set to true to disable breakpoints. +//const DBG_DISABLE_BREAKPOINTS = true; +const DBG_DISABLE_BREAKPOINTS = false; + + +/** + * The breakpoint representation. + */ +export interface MachineBreakpoint { + bpId: number; /// The breakpoint ID/number + filePath: string; /// The file to which the breakpoint belongs + lineNr: number; /// The line number in the file starting at 0 + condition: string; /// Usually the pc value (e.g. "PC=0A7f") +} + + +/// The machine type, e.g. ZX81, Spectrum 16k, Spectrum 128k, etc. +/// NOT USED: +export enum MachineType { + UNKNOWN = 0, + ZX80, + ZX81, + SPECTRUM16K, + SPECTRUM48K, + SPECTRUM128K + +/* + MK14 MK14 + ZX80 ZX-80 + ZX81 ZX-81 + 16k Spectrum 16k + 48k Spectrum 48k + 128k Spectrum 128k + QL QL + P2 Spectrum +2 + P2F Spectrum +2 (French) + P2S Spectrum +2 (Spanish) + P2A40 Spectrum +2A (ROM v4.0) + P2A41 Spectrum +2A (ROM v4.1) + P2AS Spectrum +2A (Spanish) + P340 Spectrum +3 (ROM v4.0) + P341 Spectrum +3 (ROM v4.1) + P3S Spectrum +3 (Spanish) + TS2068 Timex TS 2068 + Inves Inves Spectrum+ + 48ks Spectrum 48k (Spanish) + 128ks Spectrum 128k (Spanish) + TK90X Microdigital TK90X + TK90XS Microdigital TK90X (Spanish) + TK95 Microdigital TK95 + Z88 Cambridge Z88 + Sam Sam Coupe + Pentagon Pentagon + Chloe140 Chloe 140 SE + Chloe280 Chloe 280 SE + Chrome Chrome + Prism Prism + ZXUNO ZX-Uno + TSConf ZX-Evolution TS-Conf + TBBlue TBBlue/ZX Spectrum Next + ACE Jupiter Ace + CPC464 Amstrad CPC 464 + */ + +} + + +/// The internal machine state. +enum MachineState{ + UNINITIALIZED = 0, ///< before connection to ZEsarUX. + IDLE, ///< The normal state. Waiting for a new command. + RUNNING, ///< When a 'continue' or 'stepOut' has benn requested. Until the next break. + RUNNING_REVERSE, ///< Not yet used. Same as 'RUNNING' but in reverse direction. +}; + + +/** + * The representation of the Z80 machine. + * It receives the requests from the EmulDebugAdapter and commincates with + * the EmulConnector. + */ +export class Machine extends EventEmitter { + + + + /// The machine type, e.g. 48k or 128k etc. + public machineType = MachineType.UNKNOWN; + + /// Current state, e.g. RUNNING + private state = MachineState.UNINITIALIZED; + + /// A list for the frames (call stack items) + private listFrames = new RefList(); + + /// Mirror of the machine's breakpoints. + private breakpoints = new Array(); + + /// Is responsible to serialize asynchronous calls (e.g. to zesarux). + //private serializer = new CallSerializer("Main", true); + + /** + * Creates a new Z80 machine. + */ + public constructor() { + super(); + + // Init the registers + Z80Registers.init(); + + // Create the socket for communication (not connected yet) + this.setupSocket(); + + // Connect zesarux debugger + zSocket.connectDebugger(); + } + + + /** + * Stops a machine/the debugger. + * This will disconnect the socket to zesarux and un-use all data. + */ + public stop() { + zSocket.removeAllListeners(); + zSocket.end(); + zSocket.destroy(); + //var sleep = require('sleep'); + //sleep.sleep(3); // 3 secs + } + + + /** + * Factory: At the moment this static method just creates a new Machine. + * Later it may create different machines depending on the type (48K 128). + */ + public static getMachine() { + return new Machine(); + } + + + /** + * Initializes the socket to zesarux but does not connect yet. + * Installs handlers to react on connect and error. + */ + protected setupSocket() { + zSocket.init(); + zSocket.on('error', err => { + // and terminate + err.message += " (Could not connect ZEsarUX!)"; + this.emit('error', err); + }); + zSocket.on('close', err => { + this.listFrames.length = 0; + this.breakpoints.length = 0; + // and terminate + this.emit('error', err); // TODO: Ist das richtig hier einen Error zu returnen? + }); + zSocket.on('end', () => { + // and terminate + const err = new Error('ZEsarUX terminated the connection!'); + this.emit('error', err); + }); + zSocket.on('connected', () => { + // Initialize + zSocket.send('about'); + zSocket.send('get-version'); + zSocket.send('get-current-machine', data => { + // Determine which ZX Spectrum it is, e.g. 48K, 128K + if(data == 'ZX-80') + this.machineType = MachineType.ZX80; + else if(data == 'ZX-81') + this.machineType = MachineType.ZX81; + else if(data == 'Spectrum 16k') + this.machineType = MachineType.SPECTRUM16K; + else if(data == 'Spectrum 48k') + this.machineType = MachineType.SPECTRUM48K; + else if(data == 'Spectrum 128k') + this.machineType = MachineType.SPECTRUM128K; + }); + var debug_settings = (Settings.launch.skipInterrupt) ? 32 : 0; + zSocket.send('set-debug-settings ' + debug_settings); + + // TODO: Should load the snapshot file after 'enter-cpu-step' but somehow this does not work. + /* + command> enter-cpu-step + command@cpu-step> snapshot-load /Volumes/Macintosh HD 2/Projects/zesarux/asm/z80-sample-program/z80-sample-program.sna + Error. Can not enter cpu step mode. You can try closing the menu + command> + */ + // Load snapshot file + if(Settings.launch.loadSnap) + zSocket.send('snapshot-load ' + Settings.launch.loadSnap); + + // Enter step-mode (stop) + zSocket.send('enter-cpu-step'); + + // Clear all breakpoints + if(!DBG_DISABLE_BREAKPOINTS) { + zSocket.send('enable-breakpoints'); + //this.clearAllZesaruxBreakpoints(); //TODO: enable + } + + // Send 'initialize' to Machine. + zSocket.executeWhenQueueIsEmpty( () => { + this.state = MachineState.IDLE; + this.emit('initialized'); + }); + }); + } + + + /** + * Helper function to prepare the callstack for vscode. + * What makes it complicated is the fact that for every word on the stack the zesarux + * has to be called to get the disassembly to check if it was a CALL. + * The function calls itself recursively. + * @param frames The array that is sent at the end which is increased every call. + * @param zStack The original zesarux stack frame. + * @param zStackAddress The start address of the stack. + * @param index The index in zStack. Is increased with every call. + * @param lastCallFrameIndex The index to the last item on stack (in listFrames) that was a CALL. + * @param handler The handler to call when ready. + */ + private setupCallStackFrameArray(frames: RefList, zStack: Array, zStackAddress: number, index: number, lastCallFrameIndex: number, handler:(frames: Array)=>void) { + + // skip invalid addresses (should not happen) + var addrString; + while(index < zStack.length) { + addrString = zStack[index]; + if(addrString.length >= 4) + break; + ++index; + } + + // Check for last frame + if(index >= zStack.length) { + // Use new frames + this.listFrames = frames; + // call handler + handler(frames); + return; + } + + // Get caller address with opcode (e.g. "call sub1") + const addr = parseInt(addrString,16); + const addr_3 = addr-3; // subtract opcode + address + zSocket.send('disassemble ' + addr_3, data => { + const opcode = data.substr(7,4); + // Check if this was a "CALL something" or "CALL n/z,something" + if(opcode == "CALL") { + // get address of call: last 4 bytes + const callAddrString = data.substr(data.length-4); + const callAddr = parseInt(callAddrString,16); + // Now find label for this address + const labelCallAddrArr = Labels.getLabelsForNumber(callAddr); + const labelCallAddr = (labelCallAddrArr) ? labelCallAddrArr[0] : callAddrString+'h'; + const file = Labels.getFileAndLineForAddress(addr_3); + // Save + lastCallFrameIndex = frames.addObject(new Frame(addr_3, zStackAddress+2*index, 'CALL ' + labelCallAddr, file.fileName, file.lineNr)); + } + else { + // Get last call frame + const frame = frames.getObject(lastCallFrameIndex); + frame.stack.push(addr); + } + // Call recursively + this.setupCallStackFrameArray(frames, zStack, zStackAddress, index+1, lastCallFrameIndex, handler); + }); + } + + + /** + * Returns the stack frames. + * @param handler The handler to call when ready. + */ + public stackTraceRequest(handler:(frames: RefList)=>void): void { + // Create a call stack / frame array + const frames = new RefList(); + + // Get current pc + zSocket.send('get-registers', data => { + // Parse the PC value + const pc = Z80Registers.parsePC(data); + const file = Labels.getFileAndLineForAddress(pc); + + const sp = Z80Registers.parseSP(data); + const lastCallIndex = frames.addObject(new Frame(pc, sp, 'PC',file.fileName, file.lineNr)); + + // calculate the depth of the call stack + var depth = Labels.topOfStack - sp; + if(depth>20) depth = 20; + + // Check if callstack need to be called + if(depth > 0) { + // get stack from zesarux + zSocket.send('get-stack-backtrace '+depth, data => { + Log.log('Call stack: ' + data); + // add the received stack, something like: + // get-stack-backtrace 11: + // 744EH D0C5H D12AH CC18H CBD3H 0E01H 0100H 0000H 0000H 3200H 0000H + const zStack = data.split(' '); + zStack.splice(zStack.length-1); // ignore last (is empty) + // rest of callstack + this.setupCallStackFrameArray(frames, zStack, sp, 0, lastCallIndex, handler); + }); + } + else { + // Use new frames + this.listFrames = frames; + // no callstack, call handler immediately + handler(frames); + } + }); + } + + + /** + * @param The reference number to the frame. + * @returns The associated frame or undefined. + */ + public getFrame(ref: number): Frame|undefined { + const frame = this.listFrames.getObject(ref); + return frame; + } + + + /** + * 'continue' debugger program execution. + * @param handler The handler that is called when it's stopped e.g. when a breakpoint is hit. + */ + public continue(handler:()=>void): void { + this.state = MachineState.RUNNING; + zSocket.send('run', (data) => { + this.state = MachineState.IDLE; + // Call handler (could take some time, e.g. until a breakpoint is hit) + handler(); + }, NO_TIMEOUT); + } + + + /** + * 'pause' the debugger. + */ + public pause(): void { + // Send anything through the socket + zSocket.sendBlank(); + } + + + /** + * 'reverse continue' debugger program execution. + * @param handler The handler that is called when it's stopped e.g. when a breakpoint is hit. + */ + public reverseContinue(handler:()=>void) : void { + this.state = MachineState.RUNNING; + this.state = MachineState.IDLE; + // TODO: needs implementation + handler(); + } + + /** + * 'step over' an instruction in the debugger. + * @param handler The handler that is called after the step is performed. + */ + public stepOver(handler:()=>void): void { + // Zesarux is very special in the 'step-over' behaviour. + // In case of e.g a 'jp cc, addr' it will never return + // if the condition is met because + // it simply seems to wait until the PC reaches the next + // instruction what, for a jp-instruction, obviously never happens. + // Therefore a 'step-into' is executed instead. The only problem is that a + // 'step-into' is not the desired behaviour for a CALL. + // So we first check if the instruction is a CALL and + // then either excute a 'step-over' or a step-into'. + zSocket.send('get-registers', data => { + const pc = Z80Registers.parsePC(data); + zSocket.send('disassemble ' + pc, data => { + const opcode = data.substr(7,4); + // Check if this was a "CALL something" or "CALL n/z,something" + const cmd = (opcode=="CALL" || opcode=="LDIR" || opcode=="LDDR") ? 'cpu-step-over' : 'cpu-step'; + + // Step + zSocket.send(cmd, data => { + // Call handler + handler(); + }); + }); + }); + } + + + /** + * 'step into' an instruction in the debugger. + * @param handler The handler that is called after the step is performed. + */ + public stepInto(handler:()=>void): void { + // Step into + zSocket.send('cpu-step', data => { + // Call handler + handler(); + }); + } + + + /** + * 'step out' of current call. + * @param handler The handler that is called after the step out is performed. + */ + public stepOut(handler:()=>void): void { + // zesarux does not implement a step-out. Therefore we analyze the call stack to + // find the first return address. + // Then a breakpoint is created that triggers when the SP changes to that address. + // I.e. when the RET (or (RET cc) gets executed. + + // get current stackpointer + zSocket.send('get-registers', data => { + // Get SP + const sp = Z80Registers.parseSP(data); + + // calculate the depth of the call stack + var depth = Labels.topOfStack - sp; + if(depth>20) depth = 20; + if(depth == 0) { + // no call stack, nothing to step out, i.e. immediately return + handler(); + return; + } + + // get stack from zesarux + zSocket.send('get-stack-backtrace '+depth, data => { + // add the received stack, something like: + // get-stack-backtrace 11: + // 744EH D0C5H D12AH CC18H CBD3H 0E01H 0100H 0000H 0000H 3200H 0000H + const zStack = data.split(' '); + zStack.splice(zStack.length-1); // ignore last (is empty) + // Now search the call stack for an address that points after a 'CALL'. + const recursiveFunc = (stack: Array, sp: number) => { + // Search for "CALL" + const addrString = stack.shift(); + if(!addrString) { + // Stop searching, no "CALL" found. + // Nothing to step out, i.e. immediately return. + handler(); + } + else { + const addr = parseInt(addrString,16); + const addr_3 = addr-3; // subtract opcode + address + zSocket.send('disassemble ' + addr_3, data => { + const opcode = data.substr(7,4); + // Check if this was a "CALL something" or "CALL n/z,something" + if(opcode == "CALL") { + // found, set breakpoint: when SP gets bigger than the current value + const freeBps = this.getFreeBreakpointIds(1); + if(freeBps.length < 1) { + // No breakpoint available + handler(); + return; + } + + // set action first (no action) + const bpId = freeBps[0]; + zSocket.send('set-breakpointaction ' + bpId + ' pause', data => { + // set the breakpoint (conditions are evaluated by order. 'and' does not take precedence before 'or'). + const condition = 'SP>' + sp; + zSocket.send('set-breakpoint ' + bpId + ' ' + condition, data => { + + // Run + this.state = MachineState.RUNNING; + zSocket.send('run', data => { + // takes a little while, then step-over RET + // Remove breakpoint + zSocket.send('set-breakpoint ' + bpId, data => { + this.state = MachineState.IDLE; + handler(); + return; + }); + }); + + }); + + }); + return; + } + // "CALL" not found, so continue searching + recursiveFunc(stack, sp+2); + }); + } + }; // end recursiveFunc + + // Loop the call stack recursively + recursiveFunc(zStack, sp); + + }); + }); + + } + + + /** + * 'step backwards' the program execution in the debugger. + * @param handler The handler that is called after the step is performed. + */ + public stepBack(handler:()=>void): void { + // TODO: implement step-back + // Call handler + handler(); + } + + + /** + * If sytem state is running, a break is done. + */ + private breakIfRunning() { + // Break if currently running + if(this.state == MachineState.RUNNING || this.state == MachineState.RUNNING_REVERSE) { + // Break + this.pause(); + } + } + + + /** + * Returns the first free breakpoint indexes of zesarux. + * Breakpoint indices start at 1 (Zesarux). + * @param count The number of free breakpoint indices to return. + * @return An array with free brakpoint IDs or an empty array. + */ + private getFreeBreakpointIds(count: number): Array { + const freeIndices = new Array(); + // Check all IDs + for( var index=1; index<=MAX_ZESARUX_BREAKPOINTS; index++ ) { + var filteredBps = this.breakpoints.filter(bp => bp.bpId == index); + if(filteredBps.length == 0) { + freeIndices.push(index); + count--; + if(count <= 0) + break; + } + } + return freeIndices; + } + + + /* + * Sets breakpoint in the zesarux debugger. + * Sets the breakpoint ID. + * @param bp The breakpoint. + */ + protected setBreakpoint(bp: MachineBreakpoint) { + // get free id + const freeBps = this.getFreeBreakpointIds(1); + if(freeBps.length < 1) + return; // no free ID + bp.bpId = freeBps[0]; + + // set action first (no action) + zSocket.send('set-breakpointaction ' + bp.bpId + ' pause', (data) => { + // set the breakpoint + zSocket.send('set-breakpoint ' + bp.bpId + ' ' + bp.condition); + }); + + // Add to list + this.breakpoints.push(bp); + } + + + /** + * Clears one breakpoint. + */ + protected removeBreakpoint(bp: MachineBreakpoint) { + // set breakpoint with no condition = disable/remove + zSocket.send('set-breakpoint ' + bp.bpId); + + // Remove from list + var index = this.breakpoints.indexOf(bp); + var assert = require('assert'); + assert(index !== -1, 'Breakpoint should be removed but does not exist.'); + this.breakpoints.splice(index, 1); + } + + + /** + * Set all breakpoints for a file. + * If system is running, first break, then set the breakpoint(s). + * But, because the run-handler is not known here, the 'run' is not continued afterwards. + * @param path The file (which contains the breakpoints). + * @param givenBps The breakpoints in the file. + */ + public setBreakpoints(path: string, givenBps:Array, handler:(bps: Array)=>void) { + this.breakIfRunning(); + + // get all old breakpoints for the path + const oldBps = this.breakpoints.filter(bp => bp.filePath == path); + + // Create new breakpoints + const currentBps = new Array(); + givenBps.forEach( bp => { + // get PC value of that line + const addr = Labels.getAddrForFileAndLine(path, bp.lineNr); + // Check if valid line + if(addr >= 0) { + // Now search last line with that pc + const file = Labels.getFileAndLineForAddress(addr); + // Check if right file + if(path.valueOf() == file.fileName.valueOf()) { + // create breakpoint object + var condition = 'PC='+Utility.getHexString(addr, 4)+'h'; + if(bp.condition && bp.condition.length > 0) + condition += ' and ' + bp.condition; + const ebp = { bpId: 0, filePath: file.fileName, lineNr: file.lineNr, condition: condition }; + // add to array + currentBps.push(ebp); + } + } + }); + + // Now check which breakpoints are new or removed (this includes 'changed'). + const newBps = currentBps.filter(bp => oldBps.filter(obp => obp.condition == bp.condition).length == 0); + const removedBps = oldBps.filter(bp => currentBps.filter(obp => obp.condition == bp.condition).length == 0); + + // remove old breakpoints + removedBps.forEach(bp => { + // from zesarux + this.removeBreakpoint(bp); + }); + + // Add new breakpoints and find free breakpoint ids + newBps.forEach(bp => { + // set breakpoint + this.setBreakpoint(bp); + }); + + // get all breakpoints for the path + const resultingBps = this.breakpoints.filter(bp => bp.filePath == path); + + // Return the real breakpoints for the file and sync with the socket. + zSocket.executeWhenQueueIsEmpty( () => { + handler(resultingBps); + }); + + } + + + /** + * Returns a disassembly of the given address area. + * Can be used e.g. to disassemble ROM areas. + * @param start The start address for the disassembly. + * @param size The size of the memory area. + * @returns The disassembly as text. Format, e.g.: + * 0065 RST 38 + * 0066 PUSH AF + * 0067 PUSH HL + * 0068 LD HL,(5CB0) + */ + public getDisassembly(start: number, size: number, handler:(text)=>void) { + // get disassembly, get more than is required (because zesarux lacks a disassembly with size) + const lines = size; // number of lines is < size + if(isNaN(start)) + console.log("ERROR"); + zSocket.send('disassemble ' + start + ' ' + lines, (data) => { + // Remove the superfluous lines + const lineArr = data.split('\n'); + const resultArr = new Array(); + const endAddr = start + size; + for(var line of lineArr) { + // check if size reached + const addr = parseInt(line.substr(2,4),16); + if(addr >= endAddr) + break; + // add line + resultArr.push(line.substr(2)); + } + // Create string + const text = resultArr.join('\n'); + // Call handler + handler(text); + }); + } + + + /** + * Sends a command to ZEsarUX. + * @param cmd E.g. 'get-registers'. + * @param handler The response (data) is returned. + */ + public dbgExec(cmd: string, handler:(data)=>void) { + cmd = cmd.trim(); + if(cmd.length == 0) return; + + // Check if we need a break + this.breakIfRunning(); + // Send command to ZEsarUX + zSocket.send(cmd, data => { + // Call handler + handler(data); + }); + } + +} diff --git a/src/reflist.ts b/src/reflist.ts new file mode 100644 index 00000000..07ea2eaf --- /dev/null +++ b/src/reflist.ts @@ -0,0 +1,37 @@ + +import { Log } from './log'; + + +/** + * Class for associating IDs with objects. + * Used e.g. for the variable references. + */ +export class RefList extends Array { + + /** + * Adds an object to the list and returns it's index. + * @param obj The object to add. + * @returns The index of the object in the list. I.e. a unique reference number (!=0) to the object. + */ + public addObject(obj: any): number { + this.push(obj); + const id = this.length; + return id; + } + + + /** + * Returns the corresponding object for a given reference. + * @param ref The reference to the object. + * @returns The object or undefined if not found. + */ + public getObject(ref: number): any { + if(ref<=0 || ref>this.length) { + Log.log('Error: reference '+ref+' not found!'); + return undefined; + } + const obj = this[ref-1]; + return obj; + } + +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 00000000..9c8e3691 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,155 @@ +import { DebugProtocol } from 'vscode-debugprotocol'; +import { Utility } from './utility'; + + +/** + * See also package.json. + * The configuration parameters for the zesarux debugger. + */ +export interface SettingsParameters extends DebugProtocol.LaunchRequestArguments { + zhostname: string; /// The Zesarux ZRCP telnet host name + zport: number; /// The Zesarux ZRCP telnet port + + rootFolder: string; /// The path of the root folder. All other paths are relative to this. Ususally = ${workspaceFolder} + + disassemblies: Array>; /// Contains start/size tuples for all memory areas that should be disassembled + + listFiles: Array<{path: string, useFiles: boolean}>; /// The paths to the .list files together with a boolean variable to tell (true) if the referenced files should be used. + labelsFiles: Array; /// The paths to the .labels files. + + disableLabelResolutionBelow: number; /// Disables the number to label conversion if number is below the given value. E.g. labels below 256 are not resolved. + + tmpDir: string; /// A directory for temporary files created by this debug adapter. E.g. ".tmp" + + topOfStack: string; /// label or address which is above the topmost entry on the stack. It is used to determine the end of the call stack. + + loadSnap: string; /// If defined the path to a snapshot file to load at startup + + startAutomatically: boolean; /// Start automatically after launch. + + skipInterrupt: boolean; /// ZEsarUX setting. If enabled steps over the interrupt. + + /// Format how the registers are displayed in the VARIABLES area. + /// Is an array with 2 strings tupels. The first is an regex that checks the register. + /// If fulfilled the 2nd is used to format the value. + registerVarFormat: Array; + + /// Format how the registers are displayed when hovering with the mouse. + /// Is an array with 2 strings tupels. The first is an regex that checks the register. + /// If fulfilled the 2nd is used to format the value. + registerHoverFormat: Array; + + /// The general formatting for labels in the WATCHES area. + labelWatchesGeneralFormat: string; + + /// The 'byte' formatting for labels in the WATCHES area. + labelWatchesByteFormat: string; + + /// The 'word' formatting for labels in the WATCHES area. + labelWatchesWordFormat: string; + + /// Format for the pushed values in the STACK area. + stackVarFormat: string; + + /// Tab size used in formatting. + tabSize: number; + + trace: boolean; /// Enable logging of the DAP +} + + +/// Singleton: +/// A class through which the settings can be accessed. +/// I.e. the paramters in launch.json. +export class Settings { + /// the representation of the launch.json + public static launch: SettingsParameters; + + /* + /// called from InitSingleton only. + private constructor(launchCfg: SettingsParameters) { + Settings.launch = launchCfg; + } + */ + + /// This has to be set in the launchRequest. + /// Initializes all values (sets anything that is not set in the json). + /// All realtive paths are expanded with the 'rootFolder' path. + static Init(launchCfg: SettingsParameters) { + Settings.launch = launchCfg; + // Check for default values (for some reasons the default values from the package.json are not used) + if(!Settings.launch.zhostname) + Settings.launch.zhostname = 'localhost'; + if(!Settings.launch.zport) + Settings.launch.zport = 10000; + if(!Settings.launch.rootFolder) + Settings.launch.rootFolder = ''; + if(!Settings.launch.disassemblies) + Settings.launch.disassemblies = []; + if(Settings.launch.listFiles) + Settings.launch.listFiles = Settings.launch.listFiles.map((fp) => { + return {path: Utility.getAbsFilePath(fp.path), useFiles: fp.useFiles}; + }); + else + Settings.launch.listFiles = []; + if(Settings.launch.labelsFiles) + Settings.launch.labelsFiles = Settings.launch.labelsFiles.map((fp) => Utility.getAbsFilePath(fp)); + else + Settings.launch.labelsFiles = []; + if(!Settings.launch.topOfStack) + Settings.launch.topOfStack = '0x10000'; + if(Settings.launch.loadSnap) + Settings.launch.loadSnap = Utility.getAbsFilePath(Settings.launch.loadSnap); + else + Settings.launch.loadSnap = ''; + if(Settings.launch.tmpDir == undefined) + Settings.launch.tmpDir = '.tmp'; + Settings.launch.tmpDir = Utility.getAbsFilePath + (Settings.launch.tmpDir); + if(isNaN(Settings.launch.disableLabelResolutionBelow)) + Settings.launch.disableLabelResolutionBelow = 256; + if(Settings.launch.startAutomatically == undefined) + Settings.launch.startAutomatically = true; + if(Settings.launch.skipInterrupt == undefined) + Settings.launch.skipInterrupt = false; + if(Settings.launch.trace == undefined) + Settings.launch.trace = false; + if(!Settings.launch.registerVarFormat) + Settings.launch.registerVarFormat = [ + "AF", "A: ${hex}h, F: ${flags}", + "AF'", "A': ${hex}h, F': ${flags}", + "PC", "${hex}h, ${unsigned}u${, :labelsplus|, }", + "SP", "${hex}h, ${unsigned}u${, :labelsplus|, }", + "HL", "(${hex}h)b=${b@:unsigned}, ${unsigned}u, ${signed}i${, :labelsplus|, }", + "..", "${hex}h, ${unsigned}u, ${signed}i${, :labelsplus|, }", + "F", "${flags}", + "R", "${unsigned}u", + "I", "${hex}h", + ".", "${hex}h, ${unsigned}u, ${signed}i, '${char}', ${bits}" + ]; + if(!Settings.launch.registerHoverFormat) + Settings.launch.registerHoverFormat = [ + "AF", "A: ${hex}h, F: ${flags}", + "AF'", "A': ${hex}h, F': ${flags}", + "PC", "${name}: ${hex}h${\n:labelsplus|\n}", + "SP", "${name}: ${hex}h${\n:labelsplus|\n}", + "..", "${hex}h, ${unsigned}u, ${signed}i\n${\n:labelsplus|\n}\n(${hex}h)b=${b@:unsigned}, (${hex}h)w=${w@:unsigned}", + "R", "${name}: ${unsigned}u", + "I", "${name}: ${hex}h", + ".", "${name}: ${hex}h, ${unsigned}u, ${signed}i, '${char}', ${bits}b" + ]; + if(!Settings.launch.labelWatchesGeneralFormat) + Settings.launch.labelWatchesGeneralFormat = "(${hex}h)b=${b@:unsigned}/'${b@:char}', (${hex}h)w=${w@:unsigned}"; + if(!Settings.launch.labelWatchesByteFormat) + Settings.launch.labelWatchesByteFormat = "${b@:hex}h\t${b@:unsigned}u\t${b@:signed}i\t'${char}'\t${b@:bits}b\t${(:labels|, |)}"; + if(!Settings.launch.labelWatchesWordFormat) + Settings.launch.labelWatchesWordFormat = "${w@:hex}h\t${w@:unsigned}u\t${w@:signed}i\t${(:labels|, |)}"; + if(!Settings.launch.stackVarFormat) + Settings.launch.stackVarFormat = "${hex}h\t${unsigned}u\t${signed}i\t${(:labels|, |)}"; + if(!Settings.launch.tabSize) + Settings.launch.tabSize = 6; + + } + +} + diff --git a/src/shallowvar.ts b/src/shallowvar.ts new file mode 100644 index 00000000..284b20ac --- /dev/null +++ b/src/shallowvar.ts @@ -0,0 +1,451 @@ + +//import { Log } from './log'; +import { zSocket } from './zesaruxSocket'; +import { Labels } from './labels'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { Z80Registers } from './z80Registers'; +import { CallSerializer } from './callserializer'; +import { Settings } from './settings' +import { Utility } from './utility'; +import { RefList } from './reflist'; +//import { Variable } from 'vscode-debugadapter/lib/main'; + +/** + * Represents a variable. + * Variables know how to retrieve the data from Zesarux. + */ +export class ShallowVar { + /// Override this. It should retrieve the contents of the variable. E.g. bei commnuicating with zesarux. + public getContent(handler: (varList: Array) => {}, ...args) { + handler([]); + } + + /** + * Override if the variable or its properties can be set. + * Sets the value of the variable. + * @param name The name of the register, e.g. "HL" or "A" + * @param value The value to set. + * @param handler The handler gets the resulting (formatted) string with the value. + */ + public setValue(name: string, value: number, handler: (formattedString: string) => {}) { + handler(''); + }; + +} + + +/** + * The DisassemblyVar class knows how to retrieve the disassembly from zeasrux. + */ +export class DisassemblyVar extends ShallowVar { + + private addr: number; /// The address the disassembly should start + private count: number; /// The number of lines for the disassembly + + + /** + * Constructor. + * @param addr The address the disassembly should start + * @param count The number of lines for the disassembly + */ + public constructor(addr: number, count: number) { + super(); + this.addr = addr; + this.count = count; + } + + + /** + * Communicates with zesarux to retrieve the disassembly. + * @param handler This handler is called when the disassembly is available. + * A list with all disassembled lines is passed (as variables). + */ + public getContent(handler: (varlist: Array) => {}) { + // get disassembly lines + zSocket.send('disassemble ' + this.addr + ' ' + this.count, data => { + // split text output into array + const list = new Array(); + var disLines = data.split('\n'); + for( let line of disLines) { + const addrString = line.substr(2, 4); + const addr = parseInt(addrString, 16); + const labels = Labels.getLabelsForNumber(addr); + var addrLabel = addrString; + if(labels) + addrLabel = labels.join(',\n'); + list.push({ + name: addrString, + type: addrLabel, + value: line.substr(7), + variablesReference: 0 + }); + } + // Pass data to callback + handler(list); + }); + } +} + + +/** + * The RegistersMainVar class knows how to retrieve the register values from zeasrux. + */ +export class RegistersMainVar extends ShallowVar { + + /** + * Communicates with zesarux to retrieve the register values. + * @param handler This handler is called when the register values are available. + * A list with all register values is passed (as variables). + */ + public getContent(handler: (varlist:Array) => {}) { + zSocket.send('get-registers', data => { + const registers = new Array(); + const regNames = this.registerNames(); + + // Serialize formatting calls on independent serializer. + const innerSerializer = new CallSerializer("Inner"); + for(let regName of regNames) { + innerSerializer.exec(() => { + Z80Registers.getVarFormattedReg(regName, data, (formattedValue) => { + registers.push({ + name: regName, + type: formattedValue, + value: formattedValue, + variablesReference: 0 + }); + innerSerializer.endExec(); + }); + }); + } + + // call handler + innerSerializer.exec(() => { + handler(registers); + innerSerializer.endExec(); + }); + }); + } + + + /** + * Sets the value of the variable. + * @param name The name of the register, e.g. "HL" or "A" + * @param value The value to set. + * @param handler The handler gets the resulting (formatted) string with the value. + */ + public setValue(name: string, value: number, handler: (formattedString) => {}) { + // Check if value is valid + if(isNaN(value)) { + // Get old value and send it back + zSocket.send('get-registers', data => { + Z80Registers.getVarFormattedReg(name, data, formatted => { + handler(formatted); + }); + }); + return; + } + + // set value + zSocket.send('set-register ' + name + '=' + value, data => { + // Get real value (should be the same as the set value) + Z80Registers.getVarFormattedReg(name, data, formatted => { + handler(formatted); + }); + }); + } + + + /** + * Returns the register names to show. The 1rst half of the registers. + */ + protected registerNames(): Array { + return ["PC", "SP", "A", "F", "HL", "DE", "BC", "IX", "IY"]; + } + +} + + +/** + * The RegistersMainVar class knows how to retrieve the register values from zeasrux. + */ +export class RegistersSecondaryVar extends RegistersMainVar { + + /** + * Returns the register names to show. The 2nd half of the registers. + */ + protected registerNames(): Array { + return ["A'", "F'", "HL'", "DE'", "BC'", "I", "R"]; + } +} + + +/** + * The StackVar represents the stack variables. I.e. the pushed + * registers that are on the stack. This stack contains only the pushed registers + * until the next CALL return address. + */ +export class StackVar extends ShallowVar { + + private stack: Array; /// The stack objects. + private stackAddress: number; /// The start address of the stack. + + /** + * Constructor. + * @param stack The array containing the pushed data (address + value). + * @param stackAddress The start address of the stack. + */ + public constructor(stack: Array, stackAddress: number) { + super(); + this.stack = stack; + this.stackAddress = stackAddress; + } + + + /** + * Communicates with zesarux to retrieve the register values. + * @param handler This handler is called when the register values are available. + * A list with all register values is passed (as variables). + */ + public getContent(handler: (varlist:Array) => {}) { + const stackList = new Array(); + // Check if stack available + const stackDepth = this.stack.length; + if(stackDepth == 0) { + // Return empty + handler(stackList); + return; + } + + // Calculate tabsizing array + const format = Settings.launch.stackVarFormat; + const tabSizes = Utility.calculateTabSizes(format, 2); + + // Loop list as recursive function + var index = 0; + var value = this.stack[0]; + const recursiveFunction = (formatted) => { + stackList.push({ + name: Utility.getHexString(this.stackAddress+2*index,4), + type: formatted, + value: formatted, + variablesReference: 0 + }); + // Next + index++; + if(index < this.stack.length) { + // Next + value = this.stack[index]; + Utility.numberFormattedBy('', value, 2, format, tabSizes, recursiveFunction); + } + else { + // end, call handler + handler(stackList); + } + }; + + // Call recursively + Utility.numberFormattedBy('', value, 2, format, tabSizes, recursiveFunction); + } + + + /** + * Sets the value of the pushed stack data. + * @param name The 'name', the address as hex value, e.g. "041Ah". + * @param value The value to set. + * @param handler The handler gets the resulting (formatted) string with the value. + */ + public setValue(name: string, value: number, handler: (formattedString) => {}) { + // Serializer + const serializer = new CallSerializer("StackVar"); + + // Check if address and value are valid + const address = Utility.parseValue(name); + if(!isNaN(address) && !isNaN(value)) { + // Change neg to pos + if(value<0) + value += 0x10000; + var hexValue = value.toString(16); + if(hexValue.length == 4) { + hexValue = '0'.repeat(4-hexValue.length) + hexValue; + const cmd = 'write-memory-raw ' + address + ' ' + hexValue.substr(2,2) + hexValue.substr(0,2); + + serializer.exec(() => { + zSocket.send(cmd, data => { + // Retrieve memory values, to see if they really have been set. + zSocket.send( 'read-memory ' + address + ' 2', data => { + const b1 = data.substr(0,2); + const b2 = data.substr(2,2); + const memByte = parseInt(b1,16); + const memWord = memByte + (parseInt(b2,16)<<8); + // Pass formatted string to vscode + Utility.numberFormattedBy(name, memWord, 2, Settings.launch.stackVarFormat, undefined, handler); + }); + }); + }); + } + } + + serializer.exec(() => { + // Retrieve memory values, to see if they really have been set. + zSocket.send( 'read-memory ' + address + ' 2', data => { + const b1 = data.substr(0,2); + const b2 = data.substr(2,2); + const memByte = parseInt(b1,16); + const memWord = memByte + (parseInt(b2,16)<<8); + // Pass formatted string to vscode + Utility.numberFormattedBy(name, memWord, 2, Settings.launch.stackVarFormat, undefined, handler); + // End + serializer.endExec(); + }); + }); + } +} + + +/** + * The LabelVar class is a container class which holds to pseudo properties to open + * a byte or a word array. The user has the possibility to open it from the UI. + */ +export class LabelVar extends ShallowVar { + + private memArray: Array; /// Holds teh 2 pseudo variables for 'byte' and 'word' + + /** + * Constructor. + * @param addr The address of the memory dump + * @param count The count of elements to display. + * @param list The list of variables. The constructor adds the 2 pseudo variables to it. + */ + public constructor(addr: number, count: number, list: RefList) { + super(); + // Create 2 pseudo variables + //const count = 100; + this.memArray = [ + { + name: "byte", + type: "data", + value: "[0.."+(count-1)+"]", + variablesReference: list.addObject(new MemDumpByteVar(addr)), + indexedVariables: count + }, + { + name: "word", + type: "data", + value: "[0.."+(count-1)+"]", + variablesReference: list.addObject(new MemDumpWordVar(addr)), + indexedVariables: count + } + ]; + } + + + /** + * Communicates with zesarux to retrieve the memory dump. + * @param handler This handler is called when the memory dump is available. + */ + public getContent(handler: (varlist:Array) => {}) { + // Pass data to callback + handler(this.memArray); + } +} + + +/** + * The MemDumpByteVar class knows how to retrieve a memory dump from zesarux. + * It allows retrieval of byte arrays. + */ +export class MemDumpByteVar extends ShallowVar { + + private addr: number; /// The address of the memory dump + + /** + * Constructor. + * @param addr The address of the memory dump + */ + public constructor(addr: number) { + super(); + this.addr = addr; + } + + + /** + * Communicates with zesarux to retrieve the memory dump. + * @param handler This handler is called when the memory dump is available. + * @param start The start index of the array. E.g. only the range [100..199] should be displayed. + * @param count The count of elements to display. + */ + public getContent(handler: (varlist:Array) => {}, start: number, count: number) { + var addr = this.addr + start; + const size = this.size(); + const innerSerializer = new CallSerializer("MemDumpVar"); + const memArray = new Array(); + const format = this.formatString(); + // Calculate tabsizing array + const tabSizes = Utility.calculateTabSizes(format, size); + // Format all array elements + for(var i=0; i { + Utility.numberFormattedBy('', addr_i, size, format, tabSizes, (formatted) => { + // check for label + var types = [ Utility.getHexString(addr_i,4) ]; + const labels = Labels.getLabelsPlusIndexForNumber(addr_i); + if(labels) + types = types.concat(labels); + const descr = types.join(',\n'); + // add to array + memArray.push({ + name: "["+memArray.length+"]", + type: descr, //type, + value: formatted, + variablesReference: 0 + }); + cs.endExec(); + }); + }); + } + innerSerializer.exec((cs) => { + // Pass data to callback + handler(memArray); + // end the serialized calls + cs.endExec(); + }); + } + + + /** + * The format to use. + */ + protected formatString(): string { + return Settings.launch.labelWatchesByteFormat; // byte + } + + /** + * The size of the data: 1=byte, 2=word. + */ + protected size(): number { + return 1; // byte + } +} + + +/** + * The MemDumpWordVar class knows how to retrieve a memory dump from zesarux. + * It allows retrieval of word arrays. + */ +export class MemDumpWordVar extends MemDumpByteVar { + /** + * The format to use. + */ + protected formatString(): string { + return Settings.launch.labelWatchesWordFormat; // word + } + + /** + * The size of the data: 1=byte, 2=word. + */ + protected size(): number { + return 2; // word + } +} diff --git a/src/tests/callserializer.ts b/src/tests/callserializer.ts new file mode 100644 index 00000000..579606ea --- /dev/null +++ b/src/tests/callserializer.ts @@ -0,0 +1,40 @@ + +//import assert = require('assert'); +import { CallSerializer } from '../callserializer'; + +suite('CallSerializer', () => { + +/* + setup( () => { + return dc.start(); + }); + + teardown( () => dc.stop() ); +*/ + + suite('execAll', () => { + + test('1 func', (done) => { + CallSerializer.execAll( + cs => { + cs.endExec(); + done(); + } + ); + }); + + test('2 funcs', (done) => { + CallSerializer.execAll( + cs => { + cs.endExec(); + }, + cs => { + cs.endExec(); + done(); + } + ); + }); + + }); + +}); \ No newline at end of file diff --git a/src/tests/labels.test.ts b/src/tests/labels.test.ts new file mode 100644 index 00000000..d666a837 --- /dev/null +++ b/src/tests/labels.test.ts @@ -0,0 +1,145 @@ + +import assert = require('assert'); +//import { Utility } from '../utility'; +import { Labels } from '../labels'; +import { Settings, SettingsParameters } from '../settings'; + +suite('Labels', () => { + var launchCfg: SettingsParameters; + + setup( () => { + launchCfg = { + zhostname: "", + zport: 10000, + rootFolder: "", + disassemblies: [], + listFiles: [ {path: "", useFiles: true} ], + labelsFiles: [""], + disableLabelResolutionBelow: 256, + tmpDir: "", + topOfStack: "0x10000", + loadSnap: "", + startAutomatically: false, + skipInterrupt: true, + registerVarFormat: [ "" ], + registerHoverFormat: [ "" ], + labelWatchesGeneralFormat: "", + labelWatchesByteFormat: "", + labelWatchesWordFormat: "", + stackVarFormat: "", + tabSize: 4, + trace: false, + } + Settings.Init(launchCfg); + }); + +/* + teardown( () => dc.stop() ); +*/ + + suite('Files/lines vs list file', () => { + + test('getFileAndLineForAddress', () => { + Labels.loadAsmListFile('./src/tests/data/test1.list', true); + + // Checks + var res = Labels.getFileAndLineForAddress(0x7700); + assert.equal( res.fileName, 'main.asm', "Path wrong."); + assert.equal( res.lineNr, 0, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x7710); + assert.equal( res.fileName, 'main.asm', "Path wrong."); + assert.equal( res.lineNr, 1, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x7720); + assert.equal( res.fileName, 'main.asm', "Path wrong."); + assert.equal( res.lineNr, 2, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x7721); + assert.equal( res.fileName, 'main.asm', "Path wrong."); + assert.equal( res.lineNr, 2, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x7724); + assert.equal( res.fileName, 'main.asm', "Path wrong."); + assert.equal( res.lineNr, 2, "Expected line wrong."); + + + + var res = Labels.getFileAndLineForAddress(0x7740); + assert.equal( res.fileName, 'zxspectrum.asm', "Path wrong."); + assert.equal( res.lineNr, 0, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x8830); + assert.equal( res.fileName, 'zxspectrum.asm', "Path wrong."); + assert.equal( res.lineNr, 3, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x8833); + assert.equal( res.fileName, 'zxspectrum.asm', "Path wrong."); + assert.equal( res.lineNr, 3, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x8834); + assert.equal( res.fileName, 'zxspectrum.asm', "Path wrong."); + assert.equal( res.lineNr, 4, "Expected line wrong."); + + var res = Labels.getFileAndLineForAddress(0x8837); + assert.equal( res.fileName, 'zxspectrum.asm', "Path wrong."); + assert.equal( res.lineNr, 6, "Expected line wrong."); + + + + var res = Labels.getFileAndLineForAddress(0x8841); + assert.equal( res.fileName, 'zxspectrum.asm', "Path wrong."); + assert.equal( res.lineNr, 9, "Expected line wrong."); + + + var res = Labels.getFileAndLineForAddress(0x8843); + assert.equal( res.fileName, 'main.asm', "Path wrong."); + assert.equal( res.lineNr, 5, "Expected line wrong."); + + }); + + + test('getAddrForFileAndLine', () => { + Labels.loadAsmListFile('./src/tests/data/test1.list', true); + + // main.asm + var addr = Labels.getAddrForFileAndLine('main.asm', 0); + assert.equal( addr, 0x7700, "Expected address wrong."); + + addr = Labels.getAddrForFileAndLine('main.asm', 1); + assert.equal( addr, 0x7710, "Expected address wrong."); + + addr = Labels.getAddrForFileAndLine('main.asm', 2); + assert.equal( addr, 0x7720, "Expected address wrong."); + + + addr = Labels.getAddrForFileAndLine('zxspectrum.asm', 0); + assert.equal( addr, 0x7740, "Expected address wrong."); + + addr = Labels.getAddrForFileAndLine('zxspectrum.asm', 3); + assert.equal( addr, 0x8830, "Expected address wrong."); + + addr = Labels.getAddrForFileAndLine('zxspectrum.asm', 4); + assert.equal( addr, 0x8834, "Expected address wrong."); + + addr = Labels.getAddrForFileAndLine('zxspectrum.asm', 6); + assert.equal( addr, 0x8836, "Expected address wrong."); + + addr = Labels.getAddrForFileAndLine('zxspectrum.asm', 9); + assert.equal( addr, 0x8841, "Expected address wrong."); + + + addr = Labels.getAddrForFileAndLine('main.asm', 5); + assert.equal( addr, 0x8843, "Expected address wrong."); + }); + + + test('misc getFileAndLineFromListLine', () => { + Labels.loadAsmListFile('./src/tests/data/starwarrior.list', true); + + + }); + + }); +}); + diff --git a/src/tests/utility.test.ts b/src/tests/utility.test.ts new file mode 100644 index 00000000..3e69b498 --- /dev/null +++ b/src/tests/utility.test.ts @@ -0,0 +1,313 @@ + +import assert = require('assert'); +import { Utility } from '../utility'; +import { Labels } from '../labels'; + +suite('Utility', () => { + +/* + setup( () => { + return dc.start(); + }); + + teardown( () => dc.stop() ); +*/ + + suite('calculateTabSize', () => { + + test('no tabs', () => { + const res = Utility.calculateTabSizes('1234567890', 1); + assert.equal( res, null, "should result in null"); + }); + + test('1 tab', () => { + const res = Utility.calculateTabSizes('1234567\t890', 1); + assert.equal( res.length, 2, "should be 2 strings"); + assert.equal( res[0].length, 7, "length should be 7"); + assert.equal( res[1].length, 3, "length should be 3"); + }); + + test('tab all formats, size 1', () => { + const res = Utility.calculateTabSizes('${hex}\t1${signed}\t${unsigned}2\t1${char}2\t${bits}\t${flags}', 1); + assert.equal( res.length, 6, "should be 6 strings"); + assert.equal( res[0].length, 2, "${hex} length wrong"); + assert.equal( res[1].length, 1+4, "${signed} length wrong"); + assert.equal( res[2].length, 3+1, "${unsigned} length wrong"); + assert.equal( res[3].length, 1+1+1, "${char} length wrong"); + assert.equal( res[4].length, 8, "${bits} length wrong"); + assert.equal( res[5].length, 6, "${flags} length wrong"); + }); + + test('tab all formats, size 2', () => { + const res = Utility.calculateTabSizes('${hex}\t1${signed}\t${unsigned}2\t1${char}2\t${bits}\t${flags}', 2); + assert.equal( res.length, 6, "should be 6 strings"); + assert.equal( res[0].length, 4, "${hex} length wrong"); + assert.equal( res[1].length, 1+6, "${signed} length wrong"); + assert.equal( res[2].length, 5+1, "${unsigned} length wrong"); + assert.equal( res[3].length, 1+1+1, "${char} length wrong"); + assert.equal( res[4].length, 16, "${bits} length wrong"); + assert.equal( res[5].length, 6, "${flags} length wrong"); + }); + + test('tab name, label format', () => { + const res = Utility.calculateTabSizes('${name}\t1${labels}', 1); + assert.equal( res.length, 2, "should be 2 strings"); + assert.notEqual( res[0].length, 0, "${name} length wrong"); + assert.notEqual( res[1].length, 0, "${labels} length wrong"); + }); + + test('start with tab', () => { + const res = Utility.calculateTabSizes('\t${hex}\t${signed}\t${unsigned}', 1); + assert.equal( res.length, 4, "should be 4 strings"); + assert.equal( res[0].length, 0, "first string len wrong"); + assert.equal( res[1].length, 2, "${hex} length wrong"); + }); + + test('end with tab', () => { + const res = Utility.calculateTabSizes('${hex}\t${signed}\t${unsigned}\t', 1); + assert.equal( res.length, 4, "should be 4 strings"); + assert.equal( res[3].length, 0, "last string len wrong"); + assert.equal( res[0].length, 2, "${hex} length wrong"); + }); + + test('double tab', () => { + const res = Utility.calculateTabSizes('${hex}\t\t${signed}\t${unsigned}', 1); + assert.equal( res.length, 4, "should be 4 strings"); + assert.equal( res[0].length, 2, "${hex} length wrong"); + assert.equal( res[1].length, 0, "tab length wrong"); + }); + + }); + + + suite('numberFormattedBy', () => { + + suite('formats', () => { + + test('formats, size 1', (done) => { + const format = '${name},${hex},${signed},${unsigned},${bits},${char},${flags}'; + Utility.numberFormattedBy('myname', 255, 1, format, undefined, (res) => { + assert.equal( res, 'myname,FF,-1,255,11111111,,SZHPNC', "Unexpected formatting"); + done(); + }); + }); + + test('formats, size 2', (done) => { + const format = '${name},${hex},${signed},${unsigned},${bits},${char},${flags}'; + Utility.numberFormattedBy('myname', 9999, 2, format, undefined, (res) => { + // Note: value of flags doesn't matter + var b = res.startsWith('myname,270F,9999,9999,0010011100001111,,'); + assert.ok( b, "Unexpected formatting"); + done(); + }); + }); + + test('formats, size 2 negative', (done) => { + const format = '${signed},${unsigned}'; + Utility.numberFormattedBy('myname', 32768, 2, format, undefined, (res) => { + assert.equal( res, '-32768,32768', "Unexpected formatting"); + done(); + }); + }); + }); + + suite('tabs', () => { + + test('general', (done) => { + const format = '${name}\t${hex}\t${signed}\t${unsigned}\t${bits}\t${char}\t${flags}'; + Utility.numberFormattedBy('myname', 65, 1, format, undefined, (res) => { + assert.equal( res, 'myname 41 65 65 01000001 A ZC ', "Unexpected tab formatting"); + done(); + }); + }); + + test('use tab array 1', (done) => { + const format = '${name},\t${hex},\t${signed},\t${unsigned},\t${bits},\t${char},\t${flags}'; + const predefined = '1234567\t12345678\t123456789\t1234567890\t12345678901\t123456789012\t1234567890123' + const predefArr = predefined.split('\t'); + Utility.numberFormattedBy('myname', 65, 1, format, predefArr, (res) => { + const arr = res.split(','); + assert.equal( arr[0].length+1, 'myname,'.length, "Unexpected formatting"); + var i; + for(i=1; i { + const format = '${name},\t${hex},\t${signed}'; + const predefined = '1234567\t12345678'; + const predefArr = predefined.split('\t'); + Utility.numberFormattedBy('myname', 65, 1, format, predefArr, (res) => { + // Test simply that it returns + done(); + }); + }); + + test('special test 1', (done) => { + const format = "${b#:hex}h\t${b#:unsigned}u\t${b#:signed}i\t'${char}'\t${b#:bits}b"; + Utility.numberFormattedBy('', 65, 1, format, undefined, (res) => { + assert.equal( res, "41h 65u 65i 'A' 01000001b ", "Unexpected tab formatting"); + done(); + }); + }); + + test('special test 2', (done) => { + const format = "${b#:signed}i\t'${char}'\t${b#:bits}b"; + Utility.numberFormattedBy('', 255, 1, format, undefined, (res) => { + assert.equal( res, " -1i '' 11111111b ", "Unexpected tab formatting"); + done(); + }); + }); + + }); + + suite('labels', () => { + + test('single', (done) => { + const format = "${labels}"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 1024, 2, format, undefined, (res) => { + assert.equal(res, "LABEL_1024", "Wrong label"); + done(); + }); + }); + + test('two same labels', (done) => { + const format = "${labels}"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 2048, 2, format, undefined, (res) => { + assert.equal(res, "LABEL_2048_ALABEL_2048_B", "Wrong label"); + done(); + }); + }); + + test('two same labels with pre and inner', (done) => { + const format = "${#:labels|§}"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 2048, 2, format, undefined, (res) => { + assert.equal(res, "#LABEL_2048_A§LABEL_2048_B", "Wrong label"); + done(); + }); + }); + + test('two same labels with pre, inner and post', (done) => { + const format = "${#:labels|§|%}"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 2048, 2, format, undefined, (res) => { + assert.equal(res, "#LABEL_2048_A§LABEL_2048_B%", "Wrong label"); + done(); + }); + }); + + test('two same labels with newlines', (done) => { + const format = "${labels|:\n|:\n}"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 2048, 2, format, undefined, (res) => { + assert.equal(res, "LABEL_2048_A:\nLABEL_2048_B:\n", "Wrong label"); + done(); + }); + }); + + test('two same labelsplus with pre, inner and post', (done) => { + const format = "${#:labelsplus|§|%}"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 2048, 2, format, undefined, (res) => { + assert.equal(res, "#LABEL_2048_A§LABEL_2048_B%", "Wrong label"); + done(); + }); + }); + + test('special 1', (done) => { + const format = "${hex}h${, :labelsplus|, }"; + Labels.loadAsmLabelsFile('./src/tests/data/test1.labels') + Utility.numberFormattedBy('', 512, 2, format, undefined, (res) => { + assert.equal(res, "0200h, LABEL_512", "Wrong label"); + done(); + }); + }); + + }); + + + }); + + suite('parseValue', () => { + + test('decimal', () => { + const res = Utility.parseValue('65301'); + assert.equal(res, 65301, "Wrong parsing result"); + }); + + test('decimal negative', () => { + const res = Utility.parseValue('-32768'); + assert.equal(res, -32768, "Wrong parsing result"); + }); + + test('0x, hex value', () => { + const res = Utility.parseValue('0x1abf'); + assert.equal(res, 0x1ABF, "Wrong parsing result"); + }); + + test('0x, invalid negative input 1', () => { + const res = Utility.parseValue('0x-1abf'); + assert.ok(isNaN(res), "Wrong parsing result"); + }); + + test('0x, invalid negative input 2', () => { + const res = Utility.parseValue('-0x1abf'); + assert.ok(isNaN(res), "Wrong parsing result"); + }); + + test('$, hex value', () => { + const res = Utility.parseValue('$1abf'); + assert.equal(res, 0x1ABF, "Wrong parsing result"); + }); + + test('h, hex value', () => { + const res = Utility.parseValue('1abfh'); + assert.equal(res, 0x1ABF, "Wrong parsing result"); + }); + + test('H uppercase', () => { + const res = Utility.parseValue('1ABFH'); + assert.equal(res, 0x1ABF, "Wrong parsing result"); + }); + + test('b, bit value', () => { + const res = Utility.parseValue('10010001b'); + assert.equal(res, 0x91, "Wrong parsing result"); + }); + + test('_, status flags', () => { + const res = Utility.parseValue('_SZHPNC'); + assert.equal(res, 0xD7, "Wrong parsing result"); + }); + + test('invalid input 1', () => { + const res = Utility.parseValue('1abf'); + assert.ok(isNaN(res), "Wrong parsing result"); + }); + + test('invalid input 2', () => { + const res = Utility.parseValue('0x5gbf'); + assert.ok(isNaN(res), "Wrong parsing result"); + }); + + test('invalid input 3', () => { + const res = Utility.parseValue('dabf'); + assert.ok(isNaN(res), "Wrong parsing result"); + }); + + test('invalid input 4', () => { + const res = Utility.parseValue('10410010b'); + assert.ok(isNaN(res), "Wrong parsing result"); + }); + + + }); + +}); \ No newline at end of file diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 00000000..d5421dbe --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + + "noImplicitAny": false, + "removeComments": false, + "noUnusedLocals": true, + "noImplicitThis": true, + "inlineSourceMap": false, + "sourceMap": true, + "outDir": "../out", + "preserveConstEnums": true, + "strictNullChecks": true, + "noUnusedParameters": false + } +} diff --git a/src/tslint.json b/src/tslint.json new file mode 100644 index 00000000..43db118a --- /dev/null +++ b/src/tslint.json @@ -0,0 +1,12 @@ +{ + "rules": { + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "semicolon": [ "always" ], + "triple-equals": true, + "no-var-keyword": true, + "no-bitwise": true + } +} \ No newline at end of file diff --git a/src/utility.ts b/src/utility.ts new file mode 100644 index 00000000..33dc3b1b --- /dev/null +++ b/src/utility.ts @@ -0,0 +1,423 @@ +//import { Settings } from './settings'; +import { Labels } from './labels'; +import { zSocket } from './zesaruxSocket'; +import { CallSerializer } from './callserializer'; +import { Settings } from './settings'; +import * as fs from 'fs'; +import * as path from 'path'; + + +export class Utility { + /** + * Returns a hex string from a number with leading zeroes. + * @param value The number to convert + * @param size The number of digits for the resulting string. + */ + public static getHexString(value: number, size: number) { + var s = value.toString(16); + return "0".repeat(size - s.length) + s.toUpperCase(); + } + + + /** + * Returns a binary string from a number with leading zeroes. + * @param value The number to convert + * @param size The number of digits for the resulting string. + */ + public static getBitsString(value: number, size: number) { + var s = value.toString(2); + return "0".repeat(size - s.length) + s; + } + + + /** + * Strips the assembler ';' comment from the line. + * @param line The line to strip. + * @returns The line without any comment. + */ + public static stripComment(line: string): string { + // find comment character + const k = line.indexOf(';'); + if(k < 0) + return line; // no comment + // Return anything but the comment + return line.substr(0,k); + } + + + /** + * Parses a string and converts it to a number. + * The string might be decimal or in an hex format. + * If the string begins with '0x' or '$' or ends with 'h' or 'H' + * it is assumed to be a hex value. + * If the string ends with 'b' or 'B' a bit value is assumed. + * Otherwise decimal is used. + * If the string starts with _ a flag value is assumed. I.e. following flags + * are allowed: SZHPNC + * Otherwise decimal is used. + * @param valueString The string to convert. Ignores case. + * @returns The value of valueString. Can also return NaN in error cases. + */ + public static parseValue(valueString: string): number { + const lowerString = valueString.toLowerCase(); + //const match = /\s*(0x|\$|_)?([\-0-9a-fszhpnc]+)(h?)/.exec(lowerString); + const match = /^\s*((0x|\$)([0-9a-f]+)([^0-9a-f]*))?(([0-9a-f]+)h(.*))?(([01]+)b(.*))?(_([szhnpc]+)([^szhnpc])*)?((-?[0-9]+)([^0-9]*))?/.exec(lowerString); + if(!match) + return NaN; // Error during parsing + + const ghex = match[3]; // 0x or $ + const ghex_empty = match[4]; // should be empty + + const ghexh = match[6]; // h + const ghexh_empty = match[7]; // should be empty + + const gbit = match[9]; // b + const gbit_empty = match[10]; // should be empty + + const gflags = match[12]; // _ + const gflags_empty = match[13]; // should be empty + + const gdec = match[15]; // decimal + const gdec_empty = match[16]; // should be empty + + // Hex + if(ghex) { + if(ghex_empty) + return NaN; + return parseInt(ghex, 16); + } + if(ghexh) { + if(ghexh_empty) + return NaN; + return parseInt(ghexh, 16); + } + + // Decimal + if(gdec) { + if(gdec_empty) + return NaN; + return parseInt(gdec, 10);; + } + // Bits + if(gbit) { + if(gbit_empty) + return NaN; + return parseInt(gbit, 2); + } + + // Check if status flag value + if(gflags) { + if(gflags_empty) + return NaN; + var flags = 0; + if(gflags.includes('s')) flags |= 0x80; + if(gflags.includes('z')) flags |= 0x40; + if(gflags.includes('h')) flags |= 0x10; + if(gflags.includes('p')) flags |= 0x04; + if(gflags.includes('n')) flags |= 0x02; + if(gflags.includes('c')) flags |= 0x01; + return flags; + } + + // Unknown + return NaN; + } + + + /** + * Calculates the (minimum) tabsize from the format string. + * For all formats the max. string length is assumed and then + * the tab size is calculated. + * Note 1: this is not meant for ${name} or ${labels} as these can + * vary in size. + * Note 2: This cannot be achieved by running 'numberFormattedBy' with a + * max. value because the max. string may vary for the different formats. + * @param format The format string, e.g. "${hex}\t(${unsigned})" + * @param size The value size in bytes. (1=byte, 2= word). + * @returns An array of numbers with the size of each tab +1 (1 for a space). + */ + public static calculateTabSizes(format: string, size: number): any { + // Test if format string includes tabs + if(!format.includes('\t')) + return null; // no tabs + // Replace every formatting with maximum size replacement + var result = format.replace(/\${([^}]*?:)?([^:]*?)(:[\s\S]*?)?}/g, (match, p1, p2, p3) => { + var usedSize = size; + // Check modifier p1 + const modifier = (p1 == null) ? '' : p1.substr(0, p1.length-1); + switch(modifier) { + case 'b@': + usedSize = 1; + break; + case 'w@': + usedSize = 2; + break; + } + // Check formatting + switch(p2) { + case 'name': + return "nn"; + case 'hex': + return "h".repeat(2*usedSize); + case 'bits': + return "b".repeat(8*usedSize); + case 'unsigned': + return (Math.pow(256, usedSize)-1).toString(); + case 'signed': + return '-' + (Math.pow(256, usedSize)/2).toString(); + case 'char': + return "c"; + case 'flags': + return "SZHPNC"; + case 'labels': + case 'labelsplus': + return "ll"; + } + // default + return ""; + }); + + // Now get max. length + const arr = result.split('\t'); + return arr; + } + + + /** + * Returns a formatted number. + * Formatting is done according to size and especially the format string. + * @param name The name, e.g. a register name "A" etc. or a label name + * @param value The value to convert + * @param size The size of the value, e.g. 1 for a byte and 2 for a word + * @param format The format string: + * ${name} = the name of the register, e.g. HL + * ${hex} = value as hex, e.g. A9F5 + * ${unsigned} = value as unsigned, e.g. 1234 + * $(signed) = value as signed, e.g. -59 + * $(bits) = value as bits , e.g. 10011011 + * $(flags) = value interpreted as status flags (only useful for Fand F#), e.g. ZNC + * ${labels} = value as label (or several labels)" + * @param tabSizeArr An array of strings each string contains the max number of characters for each tab. Or null. If null the tab sizes are calculated on the fly. + * @param handler A function that is called with the formatted string as argument. + * It is required because it might be that for formatting it is required to + * get more data from the socket. + */ + public static numberFormattedBy(name: string, value: number, size: number, format: string, tabSizeArr?: Array, handler: {(formattedString: string)} = (data) => {}) { + // Variables + var memByte = 0; + var memWord = 0; + + // Serialize calls + CallSerializer.execAll( + + // Memory dump retrieving + (cs) => { + // Check first if we need to retrieve address values + const matchAddr = /(\${b@:|\${w@:)/.exec(format); + if(matchAddr) { + // Retrieve memory values + zSocket.send( 'read-memory ' + value + ' 2', data => { + const b1 = data.substr(0,2); + const b2 = data.substr(2,2); + memByte = parseInt(b1,16); + memWord = memByte + (parseInt(b2,16)<<8); + cs.endExec(); + }); + } + else { + // End directly + cs.endExec(); + } + }, + + // Formatting + (cs) => { + // Search for format string '${...}' + // Note: [\s\S] is the same as . but also includes newlines. + var valString = format.replace(/\${([\s\S]*?)}/g, (match, p1) => { + // '${...}' found now check content + const innerMatch = /^([^\|]*?:)?([^\|]*?)(\|[\s\S]*?)?(\|[\s\S]*?)?$/.exec(p1); + if(innerMatch == undefined) + return '${'+p1+'???}'; + // Modifier + var usedValue; + var usedSize; + var modifier = innerMatch[1]; // e.g. 'b@:' or 'w@:' + modifier = (modifier == null) ? '' : modifier.substr(0, modifier.length-1); + switch(modifier) { + case 'b@': + usedValue = memByte; // use byte at address + usedSize = 1; + break; + case 'w@': + usedValue = memWord; // use word at address + usedSize = 2; + break; + case '': // no modifier found + default: // in case of 'labels' + usedValue = value; // normal case + usedSize = size; + break; + } + // Continue formatting + const formatting = innerMatch[2]; // e.g. 'hex' or 'name' or the pre-strign for labels + var innerLabelSeparator = innerMatch[3]; // e.g. ', ' + innerLabelSeparator = (innerLabelSeparator == null) ? '' : innerLabelSeparator.substr(1); + var endLabelSeparator = innerMatch[4]; // e.g. ', ' + endLabelSeparator = (endLabelSeparator == null) ? '' : endLabelSeparator.substr(1); + switch(formatting) { + case 'name': + return name; + case 'hex': + return Utility.getHexString(usedValue,2*usedSize); + case 'bits': + return Utility.getBitsString(usedValue,usedSize*8); + case 'unsigned': + return usedValue.toString(); + case 'signed': + const maxValue = Math.pow(256,usedSize); + const halfMaxValue = maxValue/2; + return ((usedValue >= halfMaxValue) ? usedValue-maxValue : usedValue).toString(); + case 'char': + return (usedValue >= 32 && usedValue < 127) ? String.fromCharCode(usedValue) : ''; + case 'flags': + // interprete byte as Z80 flags: + // Zesarux: (e.g. "SZ5H3PNC") + // S Z X H X P/V N C + var res = (usedValue&0x80)? 'S' : ''; // S=sign + res += (usedValue&0x40)? 'Z' : ''; // Z=zero + res += (usedValue&0x10)? 'H' : ''; // H=Half Carry + res += (usedValue&0x04)? 'P' : ''; // P/V=Parity/Overflow + res += (usedValue&0x02)? 'N' : ''; // N=Add/Subtract + res += (usedValue&0x01)? 'C' : ''; // C=carry + return res; + + case 'labels': + { + // calculate labels + const labels = Labels.getLabelsForNumber(value); + // format + if(labels && labels.length > 0) + return modifier + labels.join(innerLabelSeparator) + endLabelSeparator; + // No label + return ''; + } + + case 'labelsplus': + { + // calculate labels + const labels = Labels.getLabelsPlusIndexForNumber(value); + // format + if(labels && labels.length > 0) + return modifier + labels.join(innerLabelSeparator) + endLabelSeparator; + // No label + return ''; + } + + default: + // unknown formatting + return '${'+1+'???}'; + } + }); + + // Format on tabs + if(!tabSizeArr) + tabSizeArr = Utility.calculateTabSizes(format, size); + if(tabSizeArr) + if(tabSizeArr.length == valString.split('\t').length) { + var index = 0; + valString += '\t'; // to replace also the last string + valString = valString.replace(/(.*?)\t/g, (match, p1, offset) => { + if(!tabSizeArr) return p1; // should not happen, only here to calm the compiler + var tabSize = tabSizeArr[index].length; + //if(index == 0) + // --tabSize; // First line missing the space in front + ++index; + var result = p1 + " "; + // right adjusted + const repeatLen = tabSize-p1.length; + if(repeatLen > 0) + result = " ".repeat(repeatLen) + result; + return result; + }); + } + + // Call handler with the result string + handler(valString); + // End + cs.endExec(); + } + ); + + } + + + /** + * If absFilePath starts with Settings.launchRootFolder + * this part is removed. + * @param absFilePath An absolute path + * @returns A relative path + */ + public static getRelFilePath(absFilePath: string): string { + const filePath = path.relative(Settings.launch.rootFolder, absFilePath); + return filePath; + } + + + /** + * If relFilePath is a relative path the Settings.launchRootFolder + * path is added. + * @param relFilePath A relative path + * @returns An absolute path + */ + public static getAbsFilePath(relFilePath: string, rootPath?: string): string { + if(path.isAbsolute(relFilePath)) + return relFilePath; + // Change from relative to absolute + const usedRootPath = (rootPath) ? rootPath : Settings.launch.rootFolder; + const filePath = path.join(usedRootPath, relFilePath); + return filePath; + } + + + /** + * Writes data to a temporary file. E.g. used to write a disassembly to a file + * that vscode can display. + * The tmp directory is created if it does not exist. + * @param fileName The file name (in the tmp directory) + * @param data The data to write. + * @returns The used file path. + */ + public static writeTmpFile(fileName: string, data: any): string { + // Create dir if not existing + if(!fs.existsSync(Settings.launch.tmpDir)) + fs.mkdirSync(Settings.launch.tmpDir); + // write data to file + const absFilePath = Utility.getAbsFilePath(fileName, Settings.launch.tmpDir); + fs.writeFileSync(absFilePath, data); + // return the file path + return absFilePath; + } + + + /** + * Removes all files in tmpDir that start with "TMP_". + */ + public static removeAllTmpFiles() { + const dir = Settings.launch.tmpDir; + // Check if dir exists + if(!fs.existsSync(dir)) + return; + // Loop through all files + const fileNames = fs.readdirSync(dir); + for(let fName of fileNames) { + // Check that filename starts with "TMP_" + if(fName.startsWith("TMP_")) { + // Remove file + const absFName = Utility.getAbsFilePath(fName,dir); + fs.unlink(absFName); + } + } + + } +} diff --git a/src/z80Registers.ts b/src/z80Registers.ts new file mode 100644 index 00000000..0c0c538d --- /dev/null +++ b/src/z80Registers.ts @@ -0,0 +1,334 @@ +import { Utility } from './utility'; +//import { Labels } from './labels'; +import { Settings } from './settings'; + +var assert = require('assert'); + + + +/// The formating (for VARIABLES) for each register is provided through a map. +var regVarFormat: Map; + +/// The formating (for hovering) for each register is provided through a map. +var regHoverFormat: Map; + +/// All values of the registers are provided in a map. +/// Together with a function to retrieve the value from the data string. +var regMap = new Map(); + + +/** + * Class to deal with the Z80 registers. + */ +export class Z80Registers { + + /** + * Eg. + * PC=a65e SP=9f0a BC=0808 A=01 HL=5c78 DE=0014 IX=0300 IY=5c3a + * A'=1f BC'=0200 HL'=a9b3 DE'=56b5 I=fe R=47 F=S HNC F'= 3HN + * MEMPTR=a656 EI IM2 VPS: 0 TSTATES: 577 + * A65E RETI + */ + + + /** + * Called during the launchRequest. + */ + public static init() { + regMap["AF"] = Z80Registers.parseAF; + regMap["BC"] = Z80Registers.parseBC; + regMap["DE"] = Z80Registers.parseDE; + regMap["HL"] = Z80Registers.parseHL; + regMap["IX"] = Z80Registers.parseIX; + regMap["IY"] = Z80Registers.parseIY; + regMap["SP"] = Z80Registers.parseSP; + regMap["PC"] = Z80Registers.parsePC; + + regMap["AF'"] = Z80Registers.parseAF2; + regMap["BC'"] = Z80Registers.parseBC2; + regMap["DE'"] = Z80Registers.parseDE2; + regMap["HL'"] = Z80Registers.parseHL2; + + regMap["A"] = Z80Registers.parseA; + regMap["F"] = Z80Registers.parseF; + regMap["B"] = Z80Registers.parseB; + regMap["C"] = Z80Registers.parseC; + regMap["D"] = Z80Registers.parseD; + regMap["E"] = Z80Registers.parseE; + regMap["H"] = Z80Registers.parseH; + regMap["L"] = Z80Registers.parseL; + regMap["I"] = Z80Registers.parseI; + regMap["R"] = Z80Registers.parseR; + regMap["A'"] = Z80Registers.parseA2; + regMap["F'"] = Z80Registers.parseF2; + + regVarFormat = Z80Registers.createFormattingMap(Settings.launch.registerVarFormat); + regHoverFormat = Z80Registers.createFormattingMap(Settings.launch.registerHoverFormat); + } + + /** + * Creates a map out of the given formatting. + * @param settingsMap hover or variable map from the settings. + * @returns A map that consists of a formatting for every register. + */ + private static createFormattingMap(settingsMap: any): any { + const formattingMap = new Map(); + + // Read all formatting settings + for(var i=0; i>8; + return res; + } + + public static parseF(data: string): number { + const res = Z80Registers.parseAF(data) & 0xFF; + return res; + } + + public static parseB(data: string): number { + const res = Z80Registers.parseBC(data)>>8; + return res; + } + + public static parseC(data: string): number { + const res = Z80Registers.parseBC(data) & 0xFF; + return res; + } + + public static parseD(data: string): number { + const res = Z80Registers.parseDE(data)>>8; + return res; + } + + public static parseE(data: string): number { + const res = Z80Registers.parseDE(data) & 0xFF; + return res; + } + + public static parseH(data: string): number { + const res = Z80Registers.parseHL(data)>>8; + return res; + } + + public static parseL(data: string): number { + const res = Z80Registers.parseHL(data) & 0xFF; + return res; + } + + public static parseA2(data: string): number { + const res = Z80Registers.parseAF2(data)>>8; + return res; + } + + public static parseF2(data: string): number { + const res = Z80Registers.parseAF2(data) & 0xFF; + return res; + } + + + /** + * Returns true if the string contains a register. + * @param reg To check for a register name. + */ + public static isRegister(reg: string): boolean { + if(reg.length == 2) { + // Check if both are upper case or both are lower case + if( (reg[0] == reg[0].toUpperCase()) != (reg[1] == reg[1].toUpperCase())) + return false; + } + const regUpper = reg.toUpperCase(); + return regMap[regUpper] != undefined; + } + + + /** + * Returns the formatted register value. + * @param regIn The name of the register, e.g. "A" or "BC" + * @param data The data string returned by zesarux. + * @param formatMap The map with the formattings (hover map or variables map) + * @param handler A function that is called with the formatted string as argument. + * It is required because it might be that for formatting it is required to + * get more data from the socket. + */ + private static getFormattedReg(regIn: string, data: string, formatMap: any, handler: {(formattedString: string)} = (data) => {}) { + // Every register has a formatting otherwise it's not a valid register name + const reg = regIn.toUpperCase(); + const format = formatMap[reg]; + assert(format != undefined, 'Register ' + reg + ' does not exist.'); + + // Get value of register + const value = Z80Registers.getRegValueByName(reg, data); + + // do the formatting + var rLen = reg.length; + if(reg[rLen-1] == '\'') --rLen; // Don't count the "'" in the register name + + Utility.numberFormattedBy(reg, value, rLen, format, undefined, handler); + } + + /** + * Returns the 'variable' formatted register value. + * @param reg The name of the register, e.g. "A" or "BC" + * @param data The data string returned by zesarux. + * @param handler A function that is called with the formatted string as argument. + * It is required because it might be that for formatting it is required to + * get more data from the socket. + */ + public static getVarFormattedReg(reg: string, data: string, handler: {(formattedString: string)} = (data) => {}) { + Z80Registers.getFormattedReg(reg, data, regVarFormat, handler); + } + + /** + * Returns the 'hover' formatted register value. + * @param reg The name of the register, e.g. "A" or "BC" + * @param data The data string returned by zesarux. + * @param handler A function that is called with the formatted string as argument. + * It is required because it might be that for formatting it is required to + * get more data from the socket. + */ + public static getHoverFormattedReg(reg: string, data: string, handler: {(formattedString: string)} = (data) => {}) { + Z80Registers.getFormattedReg(reg, data, regHoverFormat, handler); + } + + + /** + * Returns the register value as a number. + * @param regName The register value. + * @param data The data string returned by zesarux. + */ + public static getRegValueByName(regName: string, data:string): number { + var handler = regMap[regName]; + assert(handler != undefined, 'Register ' + regName + ' does not exist.'); + var value = handler(data); + return value; + } + +} diff --git a/src/zesaruxSocket.ts b/src/zesaruxSocket.ts new file mode 100644 index 00000000..4096c05f --- /dev/null +++ b/src/zesaruxSocket.ts @@ -0,0 +1,280 @@ +import { Log } from './log'; +import { Socket } from 'net'; +import { Settings } from './settings'; + +//import { setKeepAliveInterval } from 'net-keepalive'; + +/// Timeouts. +const CONNECTION_TIMEOUT = 1000; ///< 1 sec +const MSG_DEFAULT_TIMEOUT = 1000; ///< 1 sec +export const NO_TIMEOUT = 0; ///< Don't use any timeout + + +/** + * A command send to Zesarux debugger as it is being put in the queue. + */ +class CommandEntry { + public command: string|undefined; ///< The command string + public handler: {(data)}; ///< The handler being executed after receiving data. + public timeout: number; ///< The timeout until a response is expected. + constructor(command: string|undefined, handler: {(data: string)} = (data) => {}, timeout: number) { + this.command = command; + this.handler = handler; + this.timeout = timeout; + } +} + + +/** + * The socket state. + */ +enum SocketState { + UNCONNECTED, + CONNECTING, + CONNECTED_WAITING_ON_WELCOME_MSG, + CONNECTED + +}; + + +/** + * A socket to communicate with the Zesarux debugger. + * Defines a queue htat guarantees that each command is send one-by-one. + */ +export class ZesaruxSocket extends Socket { + + + private state: SocketState; ///< connected, etc. + + private queue: Array; + + private lastCallQueue: Array<()=>void>; + + public zesaruxState: string; + + // Holds the incomplete received message. + private receivedDataChunk: string; + + //public bpHitHandler: {(data)} = (data) => {}; + + /** + * Initialize the socket in the launchRequest. + */ + public init() { + // Remove all previous listeners (in case of a restart) + this.removeAllListeners(); + // Init + this.receivedDataChunk = ''; + this.state = SocketState.UNCONNECTED; + this.queue = new Array(); + this.lastCallQueue = new Array<()=>void>(); + this.zesaruxState = 'unknown'; + // Wait on first text from zesarux after connection + var cEntry = new CommandEntry('connected', data => { + this.state = SocketState.CONNECTED; + Log.log('First text from ZEsarUX received!'); + this.emit('connected'); // data transmission may start now. + }, 0); + this.queue.push(cEntry); + } + + /** + Connects to the Zesarux debug port and initializes it. + zhostname: The IP address, e.g. localhost + zport: The ZRCP port (usually 10000) + startAutomatically: true = start after connecting + */ + public connectDebugger() { + + this.state = SocketState.CONNECTING; + + this.on('data', data => { + this.receiveSocket(data); + }); + + this.on('close', () => { + Log.log('Socket close: disconnected from server'); + this.state = SocketState.UNCONNECTED; + }); + + this.on('error', err => { + Log.log('Socket: ' + err); + this.state = SocketState.UNCONNECTED; + }); + + this.on('timeout', () => { + switch(this.state) { + case SocketState.CONNECTING: + { + const err = new Error('Connection timeout!'); + Log.log('Socket timeout: ' + err); + this.emit('error', err); + } + break; + + case SocketState.CONNECTED_WAITING_ON_WELCOME_MSG: + { + const err = new Error('Connected ZEsarUX, but ZEsarUX does not communicate!'); + Log.log('ZEsarUX does not communicate: ' + err); + this.emit('error', err); + } + break; + + case SocketState.CONNECTED: + { + const err = new Error('ZEsarUX did not answer in time!'); + Log.log('ZEsarUX did not answer in time: ' + err); + this.emit('error', err); + } + break; + } + }); + + this.on('end', () => { + this.state = SocketState.UNCONNECTED; + Log.log('Socket end: disconnected from server'); + }); + + this.setTimeout(CONNECTION_TIMEOUT); + this.connect(Settings.launch.zport, Settings.launch.zhostname, () => { + // set timeout to receive the welcome message + this.setTimeout(MSG_DEFAULT_TIMEOUT); + // almost connected + this.state = SocketState.CONNECTED_WAITING_ON_WELCOME_MSG; + //this.setKeepAlive(true, 1000); I would have to enable keep-alive to get notified if the connection closes, but I was not able to change the default interval (2hrs). The package 'net-keepalive' could not be used. + // Set TCP_KEEPINTVL for this specific socket + //keepAlive.setKeepAliveInterval(this, 3000); // ms + // and TCP_KEEPCNT + //keepAlive.setKeepAliveProbes(this, 1); + + Log.log('Socket: Connected to zesarux server!'); + }); + + } + + /** + * Checks if the queue is empty. + * Calls the lastCallQueue handlers. + */ + private checkLastCommandCompleted() { + if(this.queue.length != 0) + return; // Still commands in the queue + // Call the handler(s) + for(var handler of this.lastCallQueue) + handler(); + // Empty queue + this.lastCallQueue.length = 0; + } + + + /** + * If queue is empty the handler is immediately executed. + * Otherwise it is executed when queue becomes empty. + * @param handler The method to execute. + */ + public executeWhenQueueIsEmpty(handler: ()=>void) { + if(this.queue.length == 0) { + // execute immediately if queue is empty + handler(); + } + else { + // queue the call + this.lastCallQueue.push(handler); + } + } + + + /** + * If messages are still pending the messages is queued. + * Otherwise the message is directly send. + * After the message is executed the 'handler' is called. + * Additionally the timeout can be set until when a repsonse is expected. + * @param command The message to send to ZEsarUX. + * @param handler Is called when the response is received. Can be undefined. + * @param timeout The timeout in ms or 0 if no timeout should be used. Defualt is 100ms. + */ + public send(command: string, handler: {(data)} = (data) => {}, timeout = MSG_DEFAULT_TIMEOUT) { + // Create command entry + var cEntry = new CommandEntry(command, handler, timeout); + this.queue.push(cEntry); + // check if command can be sent right away + if(this.queue.length == 1) { + this.sendSocket(); + } + } + + /** + * Sends the oldest command in the queue through the socket. + */ + private sendSocket() { + // check if connected + if(this.state != SocketState.CONNECTED) + return; + // Send oldest command + var cEntry = this.queue[0]; + if( cEntry == undefined) + return; + // normal processing + var command = cEntry.command + '\n'; + Log.log('=> ' + cEntry.command); + this.write(command); + // Set timeout + this.setTimeout(cEntry.timeout); + } + + /** + * Sends a blank string to zesarux. Used to stop zesarux if it is "run"ning. + */ + public sendBlank() { + // check if connected + if(this.state != SocketState.CONNECTED) + return; + // Send just a newline + this.write('\n'); + } + + + /** + * Receives data from the socket. + */ + private receiveSocket(data: Buffer) { + const sData = data.toString(); + if(!sData) { + Log.log('Error: Received ' + data.length + ' bytes of undefined data!'); + return; + } + Log.log('<= ' + sData); + + // Check if last line asks for a new command + this.receivedDataChunk += sData; + var splitData = this.receivedDataChunk.split('\n'); + const lastLine = splitData[splitData.length-1]; + const bCommand1 = lastLine.startsWith('command'); + const bCommand2 = lastLine.endsWith('> '); + if(bCommand1 && bCommand2) { + // clear timer + this.setTimeout(0); + // clear receive buffer + this.receivedDataChunk = ''; + // remove last line + splitData.splice(splitData.length-1,1); + var concData = splitData.join('\n'); + // remove corresponding command + var cEntry = this.queue.shift(); + // Remember state + this.zesaruxState = lastLine.substr(8); + // Send next entry (if any) + this.sendSocket(); + // Execute handler + if( cEntry != undefined) + cEntry.handler(concData); + // Check if last command is completed (if queue is empty) + this.checkLastCommandCompleted(); + } + } + +} + +/// zSocket is the singleton object that should be accessed. +export const zSocket = new ZesaruxSocket(); +