├── CHANGELOG.md ├── .gitignore ├── docs ├── imgs │ └── demo.gif ├── api.md ├── api_zh-CN.md ├── examples_zh-CN.md └── examples.md ├── resources ├── imgs │ └── icon.png └── code │ └── cpp │ ├── leetcode-types.h │ ├── leetcode-definition.h │ ├── leetcode-main.cpp │ ├── leetcode-entry.h │ ├── leetcode-io.h │ ├── leetcode-convert.h │ └── leetcode-json.h ├── src ├── util │ ├── ex.ts │ ├── codeIndentHelper.ts │ ├── stubCodeHelper.ts │ ├── config.ts │ ├── stubFileHelper.ts │ └── problemFetcher.ts ├── debuggers │ ├── debuggerManager.ts │ ├── debugger.ts │ └── cppDebugger.ts ├── extension.ts ├── commands │ └── debug.ts └── leetCodeDebugger.ts ├── .gitattributes ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── .vscodeignore ├── .eslintrc.json ├── tsconfig.json ├── LICENSE ├── package.json ├── README_zh-CN.md └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | > Minor version only. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /docs/imgs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavier-cai/vscode-leetcode-cpp-debug/HEAD/docs/imgs/demo.gif -------------------------------------------------------------------------------- /resources/imgs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavier-cai/vscode-leetcode-cpp-debug/HEAD/resources/imgs/icon.png -------------------------------------------------------------------------------- /src/util/ex.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | export interface IQuickItemEx extends vscode.QuickPickItem { 4 | value: T; 5 | } -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | - **English Document** | [中文文档](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/api_zh-CN.md) 4 | -------------------------------------------------------------------------------- /docs/api_zh-CN.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | - [English Document](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/api.md) | **中文文档** 4 | -------------------------------------------------------------------------------- /docs/examples_zh-CN.md: -------------------------------------------------------------------------------- 1 | # 示例文档 2 | 3 | - [English Document](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/examples.md) | **中文文档** 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=typescript 2 | *.c linguist-language=typescript 3 | *.hpp linguist-language=typescript 4 | *.cpp linguist-language=typescript 5 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | - **English Document** | [中文文档](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/examples_zh-CN.md) 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | .github 12 | docs 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/class-name-casing": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/debuggers/debuggerManager.ts: -------------------------------------------------------------------------------- 1 | import { Debugger } from "./debugger" 2 | import { CppDebugger } from "./cppDebugger" 3 | import { StubFileHelper } from "../util/stubFileHelper"; 4 | import { StubCodeHelper } from "../util/stubCodeHelper"; 5 | 6 | export interface IDebuggerConstructor { 7 | new(codeTemplate: string, stubCodeHelper: StubCodeHelper, stubFileHelper: StubFileHelper): Debugger 8 | } 9 | 10 | export function getDebugger(language: string): IDebuggerConstructor | undefined { 11 | switch (language) { 12 | case "cpp": return CppDebugger; 13 | } 14 | // unsupported yet! 15 | return undefined; 16 | } 17 | -------------------------------------------------------------------------------- /src/debuggers/debugger.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { StubFileHelper } from "../util/stubFileHelper" 3 | import { StubCodeHelper } from "../util/stubCodeHelper" 4 | 5 | export abstract class Debugger { 6 | public constructor(protected codeTemplate: string, protected stubCodeHelper: StubCodeHelper, protected stubFileHelper: StubFileHelper) { }; 7 | // return [file path] to start debugging, [undefined] to give up debugging 8 | public abstract init(solutionEditor: vscode.TextEditor): Promise; 9 | // dispose after debugging 10 | public abstract dispose(solutionEditor: vscode.TextEditor): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as debug from './commands/debug' 3 | import { problemFetcher } from './util/problemFetcher'; 4 | import { stubFileHelperFactory } from './util/stubFileHelper'; 5 | 6 | export function activate(context: vscode.ExtensionContext) { 7 | try { 8 | stubFileHelperFactory.initialize(context); 9 | context.subscriptions.push( 10 | problemFetcher, 11 | vscode.commands.registerCommand("leetcode-cpp-debugger.debug", (uri?: vscode.Uri) => debug.debugSolution(uri)) 12 | ); 13 | } 14 | catch (error) { 15 | vscode.window.showInformationMessage(`LeetCode Cpp Debugger initialization error: ${error}`); 16 | } 17 | } 18 | 19 | export function deactivate() {} 20 | -------------------------------------------------------------------------------- /src/commands/debug.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { leetCodeDebugger } from "../leetCodeDebugger"; 3 | 4 | export async function debugSolution(uri?: vscode.Uri): Promise { 5 | let textEditor: vscode.TextEditor | undefined; 6 | if (uri) { 7 | textEditor = await vscode.window.showTextDocument(uri, { preview: false }); 8 | } else { 9 | textEditor = vscode.window.activeTextEditor; 10 | } 11 | 12 | if (!textEditor) { 13 | return; 14 | } 15 | 16 | try { 17 | await leetCodeDebugger.startDebugging(textEditor.document.uri.fsPath); 18 | } catch (error) { 19 | if (error instanceof Error) { 20 | await vscode.window.showInformationMessage(`${error}`); 21 | } 22 | else { 23 | await vscode.window.showInformationMessage(`Error: ${error}`); 24 | } 25 | return; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/util/codeIndentHelper.ts: -------------------------------------------------------------------------------- 1 | export class CodeIndentHelper { 2 | private count: number; 3 | private codes: string[]; 4 | constructor(private indent: string = " ") { 5 | this.count = 0; 6 | this.codes = [""]; 7 | } 8 | public line(code: string = ""): CodeIndentHelper { 9 | let init: string = ""; 10 | for (var i = 0; i < this.count; ++i) { 11 | init += this.indent; 12 | } 13 | this.codes.push(init + code); 14 | return this; 15 | } 16 | public right(): CodeIndentHelper { 17 | this.count += 1; 18 | return this; 19 | } 20 | public left() : CodeIndentHelper { 21 | this.count -= 1; 22 | return this; 23 | } 24 | public append(code: string): CodeIndentHelper { 25 | this.codes[this.codes.length - 1] += code; 26 | return this; 27 | } 28 | public str(): string { 29 | return this.codes.join('\n'); 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 XavierCai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "${defaultBuildTask}" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "${defaultBuildTask}" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-types.h: -------------------------------------------------------------------------------- 1 | #ifndef LEETCODE_TYPES 2 | #define LEETCODE_TYPES 3 | 4 | #include 5 | #include 6 | 7 | #ifndef NULL 8 | #define NULL 0 9 | #endif 10 | 11 | namespace lc { 12 | 13 | namespace mem { 14 | 15 | class PtrObject { 16 | public: 17 | virtual ~PtrObject() {}; 18 | 19 | protected: 20 | PtrObject(); 21 | }; // class PtrObject 22 | 23 | class MemoryCleaner { 24 | public: 25 | static void Add(PtrObject* obj); 26 | static void Clean(); 27 | MemoryCleaner(MemoryCleaner&) = delete; 28 | MemoryCleaner& operator=(const MemoryCleaner&) = delete; 29 | 30 | private: 31 | MemoryCleaner(); 32 | std::list objs_; 33 | 34 | static MemoryCleaner instance_; 35 | }; // class MemoryCleaner 36 | 37 | MemoryCleaner MemoryCleaner::instance_; 38 | 39 | MemoryCleaner::MemoryCleaner() {} 40 | 41 | void MemoryCleaner::Add(PtrObject* obj) { 42 | instance_.objs_.push_back(obj); 43 | } 44 | 45 | void MemoryCleaner::Clean() { 46 | for (auto& obj : instance_.objs_) { 47 | delete obj; 48 | } 49 | instance_.objs_.clear(); 50 | } 51 | 52 | PtrObject::PtrObject() { 53 | MemoryCleaner::Add(this); 54 | } 55 | 56 | } // namespace mem 57 | 58 | 59 | 60 | class ListNode : public mem::PtrObject { 61 | public: 62 | int val; 63 | ListNode *next; 64 | ListNode(int x) : val(x), next(NULL) {} 65 | }; 66 | 67 | struct TreeNode : public mem::PtrObject { 68 | public: 69 | int val; 70 | TreeNode *left; 71 | TreeNode *right; 72 | TreeNode(int x) : val(x), left(NULL), right(NULL) {} 73 | }; 74 | 75 | } // namespace lc 76 | 77 | #endif -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-definition.h: -------------------------------------------------------------------------------- 1 | #ifndef LEETCODE_DEFINITION 2 | #define LEETCODE_DEFINITION 3 | 4 | #include "leetcode-types.h" 5 | 6 | namespace lc { 7 | 8 | class ListNode; 9 | class TreeNode; 10 | 11 | } // namespace lc 12 | 13 | //#include 14 | 15 | //#include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | 77 | #if __cplusplus >= 201402L 78 | #include 79 | #endif 80 | 81 | using namespace std; 82 | using namespace lc; 83 | 84 | #endif -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "leetcode-entry.h" 4 | 5 | #ifndef INPUT 6 | #define INPUT std::cin 7 | #endif 8 | 9 | #ifndef OUTPUT 10 | #define OUTPUT std::cout 11 | #endif 12 | 13 | std::string print_error(const std::string& raw, int line, int pos, const std::string& info); 14 | 15 | int main() { 16 | try { 17 | lc::io::SI in(INPUT); 18 | lc::io::MO out(OUTPUT); 19 | try { 20 | lc::Entry::Run(in, out); 21 | std::cout << "Program compelete." << std::endl; 22 | } 23 | catch (lc::json::JsonException& e) { throw print_error(in.GetRaw(), in.GetLineCount(), e.GetPosition(), e.what()); } 24 | catch (lc::conv::ConvertException& e) { 25 | if (e.GetJson().GetObject() == NULL) throw std::string(e.what()); 26 | throw print_error(in.GetRaw(), in.GetLineCount(), e.GetJson().GetObject()->GetPosistion(), e.what()); 27 | } 28 | catch (lc::EntryException& e) { 29 | throw print_error(e.GetRaw(), e.GetLine(), e.GetPosition(), e.what()); 30 | } 31 | catch (std::string& e) { throw e; } 32 | catch (std::exception& e) { throw std::string("Unhandled error. ") + e.what(); } 33 | catch (...) { throw std::string("Unhandled error."); } 34 | } 35 | catch (std::string& e) { 36 | std::cerr << "\nError: " << e << std::endl; 37 | } 38 | // pause here in terminal 39 | std::cout << "Press Any Key to Continue..." << std::endl; 40 | std::cin.clear(); 41 | std::cin.sync(); 42 | std::cin.get(); // pause 43 | return 0; 44 | } 45 | 46 | std::string print_error(const std::string& raw, int line, int pos, const std::string& info) { 47 | const int lmost = 15, rmost = 15, padding = 6; 48 | std::stringstream ss; 49 | ss << info; 50 | if (pos < 0 || line <= 0) return ss.str(); 51 | 52 | int n = raw.size(); 53 | int l = std::max(0, pos - lmost); 54 | if (l <= padding) l = 0; 55 | int r = std::min(n, pos + rmost); 56 | if (r + padding >= n) r = n; 57 | 58 | ss << " @position=" << line << ':' << pos << '\n'; 59 | int count = ss.str().size(); 60 | ss << "raw string : "; 61 | if (l > 0) ss << "(..." << l << ")"; 62 | count = ss.str().size() - count + pos - l; 63 | ss << raw.substr(l, r - l); 64 | if (r < n) ss << "(" << n - r << "...)"; 65 | ss << "\n"; 66 | while (count-- > 0) ss << ' '; 67 | ss << "^"; 68 | 69 | return ss.str(); 70 | } -------------------------------------------------------------------------------- /src/util/stubCodeHelper.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import * as os from "os"; 3 | 4 | export class StubCodeHelper { 5 | private raw: string; 6 | private path: string; 7 | private front: string; 8 | private back: string; 9 | private installed: boolean; 10 | 11 | public constructor(doc: vscode.TextDocument) { 12 | this.raw = doc.getText(); 13 | this.path = doc.uri.fsPath; 14 | this.front = ""; 15 | this.back = ""; 16 | this.installed = false; 17 | } 18 | public getRaw(): string { 19 | return this.raw; 20 | } 21 | public setFront(front: string): void { 22 | if (this.installed) { 23 | return; 24 | } 25 | 26 | this.front = front; 27 | if (front != "") { 28 | this.front += os.EOL; 29 | } 30 | } 31 | public setBack(back: string): void { 32 | if (this.installed) { 33 | return; 34 | } 35 | 36 | if (back != "") { 37 | this.back = os.EOL + back; 38 | } 39 | else { 40 | this.back = back; 41 | } 42 | } 43 | private getDocumentEnd(editor: vscode.TextEditor): vscode.Position { 44 | return editor.document.lineAt(editor.document.lineCount - 1).range.end; 45 | } 46 | public async install(editor: vscode.TextEditor): Promise { 47 | console.log("front=", this.front); 48 | console.log("back=", this.back); 49 | this.installed = true; 50 | if (!await editor.edit((e: vscode.TextEditorEdit) => { 51 | e.insert(new vscode.Position(0, 0), this.front); 52 | e.insert(this.getDocumentEnd(editor), this.back); 53 | })) { 54 | throw Error(`Can not edit the solution text.`); 55 | } 56 | } 57 | public async uninstall(editor: vscode.TextEditor): Promise { 58 | if (this.installed) { 59 | await editor.edit((e: vscode.TextEditorEdit) => { 60 | const front: vscode.Range = new vscode.Range( 61 | new vscode.Position(0, 0), 62 | editor.document.positionAt(this.front.length) 63 | ); 64 | if (editor.document.getText(front) == this.front) { 65 | e.delete(front); 66 | } 67 | const end: vscode.Position = this.getDocumentEnd(editor); 68 | const back: vscode.Range = new vscode.Range( 69 | editor.document.positionAt(editor.document.offsetAt(end) - this.back.length), 70 | end 71 | ); 72 | if (editor.document.getText(back) == this.back) { 73 | e.delete(back); 74 | } 75 | }); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-leetcode-cpp-debug", 3 | "displayName": "LeetCode Debugger for C++", 4 | "description": "Debug support for LeetCode with C++", 5 | "version": "0.0.9", 6 | "author": "Xavier Cai", 7 | "publisher": "XavierCai", 8 | "license": "MIT", 9 | "icon": "resources/imgs/icon.png", 10 | "engines": { 11 | "vscode": "^1.43.0" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/xavier-cai/vscode-leetcode-cpp-debug" 16 | }, 17 | "homepage": "https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/README.md", 18 | "preview": true, 19 | "categories": [ 20 | "Other" 21 | ], 22 | "keywords": [ 23 | "leetcode", 24 | "cpp", 25 | "debug" 26 | ], 27 | "activationEvents": [ 28 | "onCommand:leetcode-cpp-debugger.debug" 29 | ], 30 | "main": "./out/extension.js", 31 | "contributes": { 32 | "commands": [ 33 | { 34 | "command": "leetcode-cpp-debugger.debug", 35 | "title": "LeetCode Debugger: Start Debugging" 36 | } 37 | ], 38 | "configuration": [ 39 | { 40 | "title": "LeetCode Cpp Debugger", 41 | "properties": { 42 | "leetcode-cpp-debugger.source": { 43 | "type": "string", 44 | "enum": [ 45 | "[online]leetcode.com", 46 | "[online]leetcode-cn.com", 47 | "[offline]local" 48 | ], 49 | "scope": "application", 50 | "description": "Source of code template for generating debugging code.", 51 | "default": "[online]leetcode.com" 52 | }, 53 | "leetcode-cpp-debugger.deleteTemporaryContents": { 54 | "type": "boolean", 55 | "default": true, 56 | "scope": "application", 57 | "description": "Delete temporary codes and files after debugging." 58 | }, 59 | "leetcode-cpp-debugger.idMatchPattern": { 60 | "type": "string", 61 | "scope": "application", 62 | "description": "Regular expression for capturing problem ID when fetching problem online.", 63 | "default": "(\\d+).*" 64 | }, 65 | "leetcode-cpp-debugger.outputFileEncoding": { 66 | "type": "string", 67 | "enum": [ 68 | "utf8", 69 | "gbk" 70 | ], 71 | "scope": "application", 72 | "description": "Encoding of temporary code files", 73 | "default": "utf8" 74 | } 75 | } 76 | } 77 | ] 78 | }, 79 | "scripts": { 80 | "vscode:prepublish": "npm run compile", 81 | "compile": "tsc -p ./", 82 | "lint": "eslint src --ext ts", 83 | "watch": "tsc -watch -p ./", 84 | "pretest": "npm run compile && npm run lint", 85 | "test": "node ./out/test/runTest.js" 86 | }, 87 | "devDependencies": { 88 | "minimist": ">=0.2.1", 89 | "@types/glob": "^7.1.1", 90 | "@types/mocha": "^7.0.1", 91 | "@types/node": "^12.11.7", 92 | "@types/vscode": "^1.43.0", 93 | "eslint": "^6.8.0", 94 | "@typescript-eslint/parser": "^2.18.0", 95 | "@typescript-eslint/eslint-plugin": "^2.18.0", 96 | "mocha": "^7.0.1", 97 | "typescript": "^3.7.5", 98 | "vscode-test": "^1.3.0", 99 | "@types/fs-extra": "5.0.0", 100 | "@types/request": "^2.48.4" 101 | }, 102 | "dependencies": { 103 | "fs-extra": "^9.0.0", 104 | "glob": "^7.1.6", 105 | "iconv-lite": "^0.5.1", 106 | "request": "^2.88.2" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/util/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as path from 'path' 3 | 4 | export const configs = { 5 | resourcesPath: "resources", 6 | codePath: "code", 7 | token: "This file is generated by extension [LeetCode C++ Debugger], you can delete it if you want.", 8 | timeout: 5 * 1000, 9 | } 10 | 11 | export enum Source { 12 | LeetCode = "leetcode", 13 | LeetCodeCN = "leetcode-cn", 14 | Local = "local" 15 | } 16 | 17 | const sources = [ 18 | Source.LeetCode, 19 | Source.LeetCodeCN, 20 | Source.Local 21 | ] 22 | 23 | const hosts = { 24 | "leetcode" : "https://leetcode.com/", 25 | "leetcode-cn" : "https://leetcode-cn.com/" 26 | } 27 | 28 | export interface Language { 29 | name: string; 30 | disp: string; 31 | ext: string; 32 | commet: { 33 | begin: string; 34 | end: string 35 | }; 36 | stub: string; //glob expression 37 | } 38 | 39 | const languages: Language[] = [ 40 | { 41 | name: "cpp", 42 | disp: "C++", 43 | ext: "cpp", 44 | commet: { begin: "/*", end: "*/" }, 45 | stub: "**" 46 | } 47 | ] 48 | 49 | function getConfiguration(): vscode.WorkspaceConfiguration { 50 | return vscode.workspace.getConfiguration("leetcode-cpp-debugger"); 51 | } 52 | 53 | export function getSource(): Source | undefined { 54 | const source: string | undefined = getConfiguration().get("source"); 55 | if(!source) { 56 | return; 57 | } 58 | const reg: RegExp = /\[(?:online|offline)\](?:(.+)\.com|(.+))/; 59 | const match: RegExpExecArray | null = reg.exec(source); 60 | if (!match) { 61 | return; 62 | } 63 | const key = match[1] ? match[1] : match[2]; 64 | for (const str of sources) { 65 | if (str == key) { 66 | return str; 67 | } 68 | } 69 | } 70 | 71 | export function getProblemId(file: string): string | undefined { 72 | const pattern: string | undefined = getConfiguration().get("idMatchPattern"); 73 | if (!pattern) { 74 | return; 75 | } 76 | const reg: RegExp = new RegExp(pattern); 77 | const match: RegExpExecArray | null = reg.exec(path.basename(file)); 78 | if (!match || match.length <= 1) { 79 | return; 80 | } 81 | return match[1]; 82 | } 83 | 84 | export function getHost(): string | undefined { 85 | const source: Source | undefined = getSource(); 86 | if (!source || source == Source.Local) { 87 | return; 88 | } 89 | return hosts[source]; 90 | } 91 | 92 | export function getIsDeleteTemporaryContents(): boolean { 93 | return getConfiguration().get("deleteTemporaryContents", true); 94 | } 95 | 96 | export function getEncoding(): string | undefined { 97 | return getConfiguration().get("outputFileEncoding"); 98 | } 99 | 100 | class LanguageHelper { 101 | public getByName(name: string) : Language | undefined { 102 | return languages.find(language => language.name == name); 103 | } 104 | public getByPath(path: string) : Language | undefined { 105 | const parts = path.split("."); 106 | return languages.find(language => language.ext == parts[parts.length-1]); 107 | } 108 | } 109 | 110 | export const languageHelper : LanguageHelper = new LanguageHelper(); 111 | 112 | -------------------------------------------------------------------------------- /src/util/stubFileHelper.ts: -------------------------------------------------------------------------------- 1 | import { Language } from "./config"; 2 | import * as fse from "fs-extra"; 3 | import * as path from "path" 4 | import * as vscode from "vscode" 5 | import * as uc from "./config" 6 | import * as glob from "glob" 7 | import * as iconv from "iconv-lite" 8 | 9 | export interface StubFile { 10 | relative: string; 11 | content: string; 12 | } 13 | 14 | export class StubFileHelper { 15 | public files: StubFile[]; 16 | constructor(private token: string, public resourcesPath: string, private encoding = "utf8") { 17 | this.files = []; 18 | this.validate(); 19 | } 20 | private validate() { 21 | try { 22 | iconv.encode("", this.encoding); 23 | } 24 | catch { 25 | throw new Error(`Encoding not recognized: '${this.encoding}'`); 26 | } 27 | } 28 | private async check(filePath: string): Promise { 29 | return (iconv.decode(await fse.readFile(filePath), this.encoding)).indexOf(this.token) >= 0; 30 | } 31 | private async foreach(dir: string, cb: (stub: StubFile, filePath: string, exists: boolean) => void) { 32 | for (const stub of this.files) { 33 | const file: string = path.join(dir, stub.relative); 34 | const exists: boolean = fse.existsSync(file); 35 | if (exists) { 36 | if (!await this.check(file)) { 37 | throw new Error(`Token missed in file: ${file}`); 38 | } 39 | } 40 | cb(stub, file, exists); 41 | } 42 | } 43 | public async install(dir: string) { 44 | await this.foreach(dir, async (stub, file) => { 45 | await fse.writeFile(file, iconv.encode(this.token + "\n\n" + stub.content, this.encoding)); 46 | }); 47 | } 48 | public async uninstall(dir: string) { 49 | await this.foreach(dir, async (stub, file, exists) => { 50 | if (exists) { 51 | await fse.remove(file); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | class StubFileHelperFactory { 58 | private context: vscode.ExtensionContext | undefined; 59 | public initialize(context: vscode.ExtensionContext) { 60 | this.context = context; 61 | } 62 | public async create(language: Language, filePath: string): Promise { 63 | if (!this.context) { 64 | return; 65 | } 66 | const res: string = this.context.asAbsolutePath(path.join(uc.configs.resourcesPath, uc.configs.codePath, language.name)); 67 | if (!await fse.pathExists(res)) { 68 | return; 69 | } 70 | //const fileName: string = path.basename(filePath); 71 | //const oEncoding: string = (/.*[\u4e00-\u9fa5]+.*$/.test(fileName)) ? "gbk" : "utf8"; 72 | const oEncoding: string | undefined = uc.getEncoding(); 73 | const stubFileHelper = new StubFileHelper(language.commet.begin + ' ' + uc.configs.token + ' ' + language.commet.end, res, oEncoding ? oEncoding : "utf8"); 74 | const files: string[] = glob.sync(language.stub, { 75 | cwd: res, 76 | nodir: true 77 | }); 78 | for (const file of files) { 79 | stubFileHelper.files.push({ 80 | relative: file, 81 | content: await fse.readFile(path.join(res, file), "utf8") 82 | }) 83 | } 84 | return stubFileHelper; 85 | } 86 | } 87 | 88 | export const stubFileHelperFactory = new StubFileHelperFactory(); -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # LeetCode Debugger for C++ 2 | 3 | > 为你的[LeetCode](https://leetcode.com/) C++ 代码在[VSCode](https://code.visualstudio.com/)中提供调试支持. 4 | 5 | - [English Document](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/README.md) | **中文文档** 6 | 7 | ## 快速开始 8 | 9 | ![demo](https://raw.githubusercontent.com/xavier-cai/vscode-leetcode-cpp-debug/master/docs/imgs/demo.gif) 10 | 11 | > **注意**: 在使用此插件启动调试前, 你必须确定你有可用的C++调试工具. 从[官方文档](https://code.visualstudio.com/docs/cpp/config-mingw#cpp-atricles)可以获取更多相关信息. 12 | 13 | ## 特性 14 | 15 | ### 启动调试 16 | 17 | - 为你的题解代码生成调试代码并启动调试. 18 | 19 | - 通过在命令输入板(`Ctrl/Cmd + Shift + P`)输入命令`LeetCode Debugger: Start Debugging`来启动. 20 | 21 | ### 在线/离线 代码模板 22 | 23 | - 代码模板是用来生成调试代码的. 24 | 25 | - 在线: 从`LeetCode`官网获取题目的信息. 要求你的题解代码文件名以该题的ID开始, 例如`1.两数之和.cpp` (或者你可以在插件设置页面中自行设置用于ID匹配的正则表达式). 26 | 27 | - 离线: 直接使用你的题解代码作为代码模板. 28 | 29 | ### 输入/输出 30 | 31 | - 你可以使用以下代码来设置输入/输出 32 | 33 | ```cpp 34 | #define INPUT "test_case.txt" // single-input 35 | #define OUTPUT cout, "output.txt" // multi-output 36 | ``` 37 | 38 | - 对于`INPUT`, `std::istream`和`std::string` (从文件输入) 都是可接受的参数类型, 但是你只能有**一个**输入. 39 | 40 | - 对于`OUTPUT`, `std::ostream`和`std::string` (输出到文件) 都是可接受的参数类型, 你可以同时有多个输出. 41 | 42 | ### 交互题 43 | 44 | - 交互题目前**不**支持. 45 | 46 | - 但是你可以自己实现交互函数! 可以通过[API](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/api_zh-CN.md)和[示例](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/examples_zh-CN.md)了解更多. 这里有一个[题278](https://leetcode-cn.com/problems/first-bad-version/)的例子. 47 | 48 | ```cpp 49 | #ifdef LEETCODE_DEFINITION // protection 50 | int firstVersion; // the first bad version 51 | bool isBadVersion(int version) { // realization 52 | return version >= firstVersion; 53 | } 54 | #define LAZY_INTERACTION firstVersion // input firstVersion 55 | #endif 56 | ``` 57 | 58 | - `LAZY_INTERACTION`指的是在函数参数输入之后的交互输入, 而`INTERACTION`则是在函数参数输入之前的交互输入, 此外, `void ()`类型的函数同样可以作为交互输入. 下面的例子将帮助你理解这些内容. 59 | 60 | ```cpp 61 | #ifdef LEETCODE_DEFINITION // protection 62 | 63 | int value1, value2; // interactive values 64 | void before() { 65 | value1 *= 2; 66 | } 67 | void after() { 68 | value2 *= value1; 69 | } 70 | 71 | #define INTERACTION value1, before // input value1, then call the function 'before()' 72 | #define LAZY_INTERACTION value2, after // input value2, then call the function 'after()' 73 | 74 | #endif 75 | 76 | class Solution { 77 | public: 78 | int exampleFunction(int v) { 79 | return v * value1 * value2; 80 | } 81 | } 82 | 83 | /* 84 | * input: 85 | * 1 // -> value1 -> value1*=2 -> 2 86 | * 2 // -> v 87 | * 3 // -> value2 -> value2*=value1 -> 6 88 | * output: 89 | * 24 // -> v*value1*value2 -> 2*2*6=24 90 | * / 91 | ``` 92 | 93 | ## 插件设置 94 | 95 | 设置项|描述|默认值 96 | :---|:---|:--- 97 | `Source`|用于生成调试代码的代码模板来源.|`"[online]leetcode.com"` 98 | `Delete Temporary Contents`|在调试结束后,是否删除生成的临时文件与代码.|`true` 99 | `Id Match Pattern`|在线获取代码模板时, 用于捕获文件名中问题ID的正则表达式.|`"(\\d+).*"` 100 | `Output File Encoding`|生成的临时代码文件编码|`"utf8"` 101 | 102 | - `Id Match Pattern`的默认值能匹配任何以数字开头的文件名. 103 | - 中文下遇到编码问题如中文头文件无法识别时, 可以尝试将`Ouput File Encoding`设置为`gbk` 104 | 105 | ## 版本日志 106 | 107 | 详细内容请查看[CHANGELOG](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/CHANGELOG.md). 108 | 109 | ## 小贴士 110 | 111 | 你可以使用另外一款很棒的插件[LeetCode](https://marketplace.visualstudio.com/items?itemName=shengchen.vscode-leetcode)来在VS Code中浏览和解决LeetCode问题. 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode Debugger for C++ 2 | 3 | > Debug support for [LeetCode](https://leetcode.com/) with C++ in [VSCode](https://code.visualstudio.com/) 4 | 5 | - **English Document** | [中文文档](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/README_zh-CN.md) 6 | 7 | ## Quick Start 8 | 9 | ![demo](https://raw.githubusercontent.com/xavier-cai/vscode-leetcode-cpp-debug/master/docs/imgs/demo.gif) 10 | 11 | > **Attention**: Before start debugging, you must to check availability of your C++ debugger tools. Get more information from [VSCode documents](https://code.visualstudio.com/docs/cpp/config-mingw#cpp-atricles). 12 | 13 | ## Features 14 | 15 | ### Start Debugging 16 | 17 | - Generate debugging codes and start a debug session for your solution. 18 | 19 | - You can run the `LeetCode Debugger: Start Debugging` command from the command palette (`Ctrl/Cmd + Shift + P`). 20 | 21 | ### Online/Offline Code Template 22 | 23 | - Code template is used to generate the debugging code. 24 | 25 | - Online: Fetching problem from `LeetCode`. Requires your solution file to start with problem ID, for example, `1.two-sum.cpp` (Or you can modify the regular expression for capturing problem ID in extension settings). 26 | 27 | - Offline: Using your solution code as code template. 28 | 29 | ### Input/Output 30 | 31 | - You could use the code below to change the input/output: 32 | 33 | ```cpp 34 | #define INPUT "test_case.txt" // single-input 35 | #define OUTPUT cout, "output.txt" // multi-output 36 | ``` 37 | 38 | - For `INPUT`, both `std::istream` and `std::string` (input from file) are acceptable, but you can only have **ONE** input. 39 | 40 | - For `OUTPUT`, both `std::ostream` and `std::string` (output to file) are acceptable, you can have multiple outputs. 41 | 42 | ### Interactive Problem 43 | 44 | - Interactive problem is **NOT** supported yet. 45 | 46 | - But you can realize the interactive function by yourself! Know more from [API](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/api.md) and [examples](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/docs/examples.md). Here is an example for [problem 278](https://leetcode.com/problems/first-bad-version/). 47 | 48 | ```cpp 49 | #ifdef LEETCODE_DEFINITION // protection 50 | int firstVersion; // the first bad version 51 | bool isBadVersion(int version) { // realization 52 | return version >= firstVersion; 53 | } 54 | #define LAZY_INTERACTION firstVersion // input firstVersion 55 | #endif 56 | ``` 57 | 58 | - `LAZY_INTERACTION` means interactive inputs after function's arguments, `INTERACTION` means interactive inputs before function's arguments, in addition, function with type `void ()` is also acceptable. The example below will help you to understand these stuff. 59 | 60 | ```cpp 61 | #ifdef LEETCODE_DEFINITION // protection 62 | 63 | int value1, value2; // interactive values 64 | void before() { 65 | value1 *= 2; 66 | } 67 | void after() { 68 | value2 *= value1; 69 | } 70 | 71 | #define INTERACTION value1, before // input value1, then call the function 'before()' 72 | #define LAZY_INTERACTION value2, after // input value2, then call the function 'after()' 73 | 74 | #endif 75 | 76 | class Solution { 77 | public: 78 | int exampleFunction(int v) { 79 | return v * value1 * value2; 80 | } 81 | }; 82 | 83 | /* 84 | * input: 85 | * 1 // -> value1 -> value1*=2 -> 2 86 | * 2 // -> v 87 | * 3 // -> value2 -> value2*=value1 -> 6 88 | * output: 89 | * 24 // -> v*value1*value2 -> 2*2*6=24 90 | * / 91 | ``` 92 | 93 | ## Extension Settings 94 | 95 | Setting Name|Description|Default Value 96 | :---|:---|:--- 97 | `Source`|Source of code template for generating debugging code.|`"[online]leetcode.com"` 98 | `Delete Temporary Contents`|Delete temporary codes and files after debugging.|`true` 99 | `Id Match Pattern`|Regular expression for capturing problem ID when fetching problem online.|`"(\\d+).*"` 100 | `Output File Encoding`|Encoding of temporary code files|`"utf8"` 101 | 102 | - The default value of `Id Match Pattern` can match any file name begin with a number. 103 | 104 | ## Release Notes 105 | 106 | Refer to [CHANGELOG](https://github.com/xavier-cai/vscode-leetcode-cpp-debug/blob/master/CHANGELOG.md). 107 | 108 | ## Tips 109 | 110 | You can solve LeetCode problems in VSCode with another amazing extension [LeetCode](https://marketplace.visualstudio.com/items?itemName=shengchen.vscode-leetcode). 111 | -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-entry.h: -------------------------------------------------------------------------------- 1 | #ifndef LEETCODE_ENTRY 2 | #define LEETCODE_ENTRY 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "leetcode-handler.h" 10 | 11 | namespace lc { 12 | 13 | struct EntryException : public std::exception { 14 | public: 15 | EntryException(const std::string& raw, int line, int pos, const std::string& info); 16 | const std::string& GetRaw() const; 17 | int GetLine() const; 18 | int GetPosition() const; 19 | const char* what() const throw (); 20 | 21 | private: 22 | std::string raw_; 23 | int line_; 24 | int pos_; 25 | std::string info_; 26 | }; // struct EntryException 27 | 28 | EntryException::EntryException(const std::string& raw, int line, int pos, const std::string& info) : 29 | raw_(raw), 30 | line_(line), 31 | pos_(pos), 32 | info_("Entry error. ") 33 | { 34 | this->info_ += info; 35 | } 36 | 37 | const std::string& EntryException::GetRaw() const { 38 | return this->raw_; 39 | } 40 | 41 | int EntryException::GetLine() const { 42 | return this->line_ ; 43 | } 44 | 45 | int EntryException::GetPosition() const { 46 | return this->pos_ ; 47 | } 48 | 49 | const char* EntryException::what() const throw () { 50 | return this->info_.c_str(); 51 | } 52 | 53 | 54 | 55 | 56 | class Entry { 57 | public: 58 | static void Run(io::SI& in, io::MO& out); 59 | 60 | private: 61 | static void RunAlgorithm(io::SI& in, io::MO& out); 62 | static void RunSystemDesign(io::SI& in, io::MO& out); 63 | }; 64 | 65 | void Entry::Run(io::SI& in, io::MO& out) { 66 | while (!in.Eof()) { 67 | int startLine = 0; 68 | try { 69 | startLine = in.GetLineCount() + 1; 70 | #ifdef INTERACTION 71 | in.Input(INTERACTION); 72 | #endif 73 | #ifdef SYSTEM_DESIGN 74 | RunSystemDesign(in, out); 75 | #else 76 | RunAlgorithm(in, out); 77 | #endif 78 | } 79 | catch (...) { 80 | if (in.Eof() && startLine == in.GetLineCount()) { 81 | bool blank = true; 82 | for (auto& c : in.GetRaw()) { 83 | if (c != ' ') { 84 | blank = false; 85 | break; 86 | } 87 | } 88 | if (blank) break; 89 | } 90 | throw; 91 | } 92 | 93 | try { 94 | mem::MemoryCleaner::Clean(); 95 | } 96 | catch (...) { 97 | throw std::string("Please only use pointer of [TreeNode] & [ListNode] and do not use [delete]."); 98 | } 99 | } 100 | } 101 | 102 | void Entry::RunAlgorithm(io::SI& in, io::MO& out) { 103 | Handler handler(json::Create()); 104 | handler.Handle(in, out); 105 | } 106 | 107 | void Entry::RunSystemDesign(io::SI& in, io::MO& out) { 108 | json::Json fobj(in.GetLine()); 109 | int fline = in.GetLineCount(); 110 | std::string fraw = in.GetRaw(); 111 | auto quick_throw = [&fline, &fraw, &fobj](int idx, const std::string& info) { 112 | throw EntryException(fraw, fline, fobj.GetObject()->GetArray()[idx].GetObject()->GetPosistion(), info); 113 | }; 114 | 115 | //get functions 116 | std::vector functions; 117 | conv::FromJson(functions, fobj); 118 | int n = functions.size(); 119 | 120 | //get args 121 | json::Json obj(in.GetLine()); 122 | auto args = obj.GetObject(); 123 | if (args == NULL) throw conv::ConvertException(obj, "Input format error."); 124 | if (n != args->GetArray().size()) throw EntryException(in.GetRaw(), in.GetLineCount(), args->GetPosistion(), "Number of functions and arguments not matched."); 125 | 126 | //call functions 127 | json::Json retjs = json::Create(); 128 | json::JArray& ret = *retjs.GetObject(); 129 | if (n > 0) { 130 | if (functions.front() != Handler::GetClassName()) { 131 | quick_throw(0, "The first function need be the constructor."); 132 | } 133 | Handler handler(args->GetArray().front()); 134 | ret.GetArray().emplace_back(json::Create()); 135 | for (int i = 1; i < n; ++i) { 136 | try { 137 | ret.GetArray().emplace_back(handler.Handle(args->GetArray()[i], functions[i])); 138 | } 139 | catch (std::string& e) { 140 | quick_throw(i, e); 141 | } 142 | } 143 | } 144 | out << ret << std::endl; 145 | } 146 | 147 | } // namespace lc 148 | 149 | #endif // LEETCODE_ENTRY -------------------------------------------------------------------------------- /src/util/problemFetcher.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'request'; 2 | import {Source, getSource, Language} from './config' 3 | import * as vscode from 'vscode'; 4 | import * as uc from './config'; 5 | 6 | interface IProblem { 7 | id: string; 8 | fid: string; 9 | name: string; 10 | slug: string; 11 | } 12 | 13 | class ProblemFetcher implements vscode.Disposable { 14 | private cacheSource: Source; 15 | private cacheProblems: IProblem[]; 16 | private cacheId: string | undefined; 17 | private cacheContent: any; 18 | private configurationChangeListener: vscode.Disposable; 19 | private readonly leetCodeApiProblems: string = "api/problems/algorithms"; 20 | private readonly leetCodeApiGraphql: string = "graphql"; 21 | 22 | public constructor() { 23 | this.cacheSource = Source.Local; 24 | this.cacheProblems = []; 25 | this.configurationChangeListener = vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => { 26 | if (event.affectsConfiguration("leetcode-cpp-debugger.source")) { 27 | //this.updateProblems(); 28 | } 29 | }, this); 30 | //this.updateProblems(); 31 | } 32 | 33 | public dispose() { 34 | this.configurationChangeListener.dispose(); 35 | } 36 | 37 | private async request(rq: any, options: any): Promise { 38 | return new Promise((resolve, reject) => { 39 | rq(options, (e: any, resp: any, body: any) => { 40 | if (e || !resp || resp.statusCode != 200) { 41 | reject(`Request connect error.`); 42 | return; 43 | } 44 | resolve(body); 45 | }); 46 | }); 47 | } 48 | 49 | private async updateProblems() { 50 | const source: Source | undefined = getSource(); 51 | if (!source) { 52 | throw new Error("Invalid source."); 53 | } 54 | 55 | if (source == Source.Local || source == this.cacheSource) { 56 | return; 57 | } 58 | 59 | // init 60 | this.cacheSource = Source.Local; 61 | 62 | const body: any = await this.request(request.get, { 63 | uri: uc.getHost() + this.leetCodeApiProblems, 64 | headers: {}, 65 | timeout: uc.configs.timeout 66 | }); 67 | 68 | const json = JSON.parse(body); 69 | this.cacheProblems = (json.stat_status_pairs as any[]) 70 | .filter(p => !p.stat.question__hide) 71 | .map(function(p): IProblem { 72 | return { 73 | id: p.stat.question_id, 74 | fid: p.stat.frontend_question_id, 75 | name: p.stat.question__title, 76 | slug: p.stat.question__title_slug 77 | }; 78 | }); 79 | this.cacheSource = source; 80 | } 81 | 82 | public async fetchProblem(id: string, language: Language) : Promise { 83 | await this.updateProblems(); 84 | const problem: IProblem | undefined = this.cacheProblems.find((v: IProblem) => v.fid == id); 85 | if (!problem) { 86 | throw new Error("Invalid problem ID."); 87 | } 88 | if (!this.cacheId || this.cacheId != id) { 89 | const body: any = await this.request(request.post, { 90 | uri: uc.getHost() + this.leetCodeApiGraphql, 91 | headers: {}, 92 | timeout: uc.configs.timeout, 93 | json: true, 94 | body: { 95 | query: [ 96 | 'query getQuestionDetail($titleSlug: String!) {', 97 | ' question(titleSlug: $titleSlug) {', 98 | ' codeDefinition', 99 | ' sampleTestCase', 100 | ' }', 101 | '}' 102 | ].join('\n'), 103 | variables: {titleSlug: problem.slug}, 104 | operationName: 'getQuestionDetail' 105 | } 106 | }); 107 | const q = body.data.question; 108 | if (!q) { 109 | throw new Error("Invalid problem ID."); 110 | } 111 | this.cacheId = id; 112 | this.cacheContent = { 113 | codeDefinition: JSON.parse(q.codeDefinition), 114 | sampleTestCase: q.sampleTestCase 115 | }; 116 | } 117 | const code: any | undefined = (this.cacheContent.codeDefinition as any[]).find((p) => p.value == language.name); 118 | if (!code || !code.defaultCode) { 119 | throw new Error(`Cant find code definition with language: ${language.disp}.`); 120 | } 121 | return code.defaultCode; 122 | } 123 | } 124 | 125 | export const problemFetcher: ProblemFetcher = new ProblemFetcher(); -------------------------------------------------------------------------------- /src/leetCodeDebugger.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as fse from "fs-extra"; 3 | import * as path from "path"; 4 | import { IDebuggerConstructor, getDebugger } from "./debuggers/debuggerManager"; 5 | import * as uc from "./util/config"; 6 | import { problemFetcher } from "./util/problemFetcher"; 7 | import { StubFileHelper, stubFileHelperFactory } from "./util/stubFileHelper"; 8 | import { StubCodeHelper } from "./util/stubCodeHelper" 9 | 10 | class LeetCodeDebugger { 11 | public async startDebugging(solutionFilePath: string): Promise { 12 | const language: uc.Language | undefined = uc.languageHelper.getByPath(solutionFilePath); 13 | if (!language) { 14 | vscode.window.showInformationMessage(`Unsupport file type.`); 15 | return; 16 | } 17 | 18 | const ctor: IDebuggerConstructor | undefined = getDebugger(language.name); 19 | if (!ctor) { 20 | vscode.window.showInformationMessage(`Unsupport language: ${language.name}.`); 21 | return; 22 | } 23 | 24 | const source: uc.Source | undefined = uc.getSource(); 25 | if (!source) { 26 | vscode.window.showInformationMessage(`Unknown source.`); 27 | return; 28 | } 29 | 30 | let codeTemplate: string = ""; 31 | if (source == uc.Source.Local) { 32 | codeTemplate = await fse.readFile(solutionFilePath, "utf8"); 33 | } 34 | else { // fetch problem online 35 | const id: string | undefined = uc.getProblemId(solutionFilePath); 36 | if (!id) { 37 | vscode.window.showInformationMessage(`Regular expression non-matched.`); 38 | return; 39 | } 40 | try { 41 | codeTemplate = await vscode.window.withProgress({ 42 | location: vscode.ProgressLocation.Notification 43 | }, async (p: vscode.Progress<{}>) => { 44 | return new Promise(async (resolve, reject): Promise => { 45 | p.report({ message: `Fetching problem...` }); 46 | try { 47 | resolve(await problemFetcher.fetchProblem(id, language)); 48 | } catch (e) { 49 | reject(e); 50 | } 51 | }); 52 | } 53 | ); 54 | } 55 | catch (e) { 56 | vscode.window.showInformationMessage(`Fetch problem failed: ${e}`); 57 | } 58 | } 59 | 60 | const stubFileHelper: StubFileHelper | undefined = await stubFileHelperFactory.create(language, solutionFilePath); 61 | if (!stubFileHelper) { 62 | vscode.window.showInformationMessage(`Can not create entry code.`); 63 | return; 64 | } 65 | 66 | const solutionDocument: vscode.TextDocument = await vscode.workspace.openTextDocument(solutionFilePath); 67 | const stubCodeHelper: StubCodeHelper = new StubCodeHelper(solutionDocument); 68 | 69 | const debuggerInstance = new ctor(codeTemplate, stubCodeHelper, stubFileHelper); 70 | async function switchEditor(filePath: string): Promise { 71 | const textDocument: vscode.TextDocument = await vscode.workspace.openTextDocument(filePath); 72 | return await vscode.window.showTextDocument(textDocument, undefined, true); 73 | } 74 | let listenEvent: vscode.Disposable | undefined; 75 | async function afterDebugging(): Promise { 76 | const editor = await switchEditor(solutionFilePath); 77 | await debuggerInstance.dispose(editor); 78 | if (uc.getIsDeleteTemporaryContents()) { 79 | const stub: Promise[] = []; 80 | if (stubFileHelper) { 81 | stub.push(stubFileHelper.uninstall(path.dirname(editor.document.uri.fsPath))); 82 | } 83 | stub.push(stubCodeHelper.uninstall(editor)); 84 | await Promise.all(stub); 85 | } 86 | await editor.document.save(); 87 | if (listenEvent) { 88 | listenEvent.dispose(); 89 | } 90 | } 91 | try { 92 | const solutionEditor: vscode.TextEditor = await vscode.window.showTextDocument(solutionDocument); 93 | const debugEntry: string | undefined = await debuggerInstance.init(solutionEditor); 94 | if (!debugEntry) { 95 | await afterDebugging(); 96 | return; 97 | } 98 | 99 | const stub: Promise[] = [ 100 | stubCodeHelper.install(solutionEditor), 101 | stubFileHelper.install(path.dirname(solutionEditor.document.uri.fsPath)) 102 | ]; 103 | await Promise.all(stub); 104 | await solutionEditor.document.save(); 105 | 106 | if (!await fse.pathExists(debugEntry)) { 107 | await afterDebugging(); 108 | return; 109 | } 110 | 111 | let entryEditor: vscode.TextEditor | undefined; 112 | if (debugEntry) { 113 | entryEditor = await switchEditor(debugEntry); 114 | } 115 | 116 | listenEvent = vscode.debug.onDidTerminateDebugSession(async () => { await afterDebugging(); }); 117 | if (!await this.launch()) { 118 | await afterDebugging(); 119 | } 120 | 121 | if (entryEditor) { 122 | entryEditor.hide(); 123 | } 124 | } 125 | catch (error) { 126 | vscode.window.showInformationMessage(`Failed to start debugging: ${error}`); 127 | await afterDebugging(); 128 | } 129 | } 130 | 131 | private async launch(): Promise { 132 | let textEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; 133 | if (!textEditor) { 134 | return false; 135 | } 136 | 137 | const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", textEditor.document.uri); 138 | const folder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); 139 | const values: any[] | undefined = config.get("configurations"); 140 | if (!folder || !values) { 141 | return false; 142 | } 143 | 144 | const picks: Array = []; 145 | for (const value of values) { 146 | if (!value.name || !value.request) { 147 | continue; 148 | } 149 | picks.push({ 150 | label: value.name, 151 | detail: value.request, 152 | }); 153 | } 154 | if (picks.length <= 0) { 155 | return false; 156 | } 157 | 158 | let launch: string = picks[0].label; 159 | if (picks.length > 1) { // pick one 160 | const choice: vscode.QuickPickItem | undefined = await vscode.window.showQuickPick(picks, { 161 | placeHolder: "Please choose a launch configuration for your debug session. (Press ESC to cancel)", 162 | ignoreFocusOut: true, 163 | }); 164 | if (!choice) { 165 | return false; 166 | } 167 | launch = choice.label; 168 | } 169 | 170 | return await vscode.debug.startDebugging(folder, launch); 171 | } 172 | } 173 | 174 | export const leetCodeDebugger: LeetCodeDebugger = new LeetCodeDebugger(); 175 | -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-io.h: -------------------------------------------------------------------------------- 1 | #ifndef LEETCODE_IO 2 | #define LEETCODE_IO 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #ifdef _WIN32 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | #include "leetcode-convert.h" 18 | #include "leetcode-types.h" 19 | 20 | namespace lc { 21 | 22 | namespace conv { 23 | 24 | template <> 25 | struct Convert { 26 | static void FromJson(ListNode* &v, const json::Json& js) { 27 | auto obj = js.GetObject(); 28 | if (obj == NULL) throw ConvertException(js, "Convert failed. ListNode*."); 29 | int n = obj->GetArray().size(); 30 | if (n <= 0) { v = NULL; return; } 31 | v = new ListNode(conv::FromJson(obj->GetArray().front())); 32 | ListNode* current = v; 33 | for (int i = 1; i < n; ++i) { 34 | current->next = new ListNode(conv::FromJson(obj->GetArray()[i])); 35 | current = current->next; 36 | } 37 | } 38 | 39 | static json::Json ToJson(ListNode* const &v) { 40 | json::Json js = json::Json::Create(); 41 | auto obj = js.GetObject(); 42 | ListNode* current = v; 43 | while (current != NULL) { 44 | obj->GetArray().emplace_back(conv::ToJson(current->val)); 45 | current = current->next; 46 | } 47 | return js; 48 | } 49 | }; 50 | 51 | template <> 52 | struct Convert { 53 | static void FromJson(TreeNode* &v, const json::Json& js) { 54 | auto obj = js.GetObject(); 55 | if (obj == NULL) throw ConvertException(js, "Convert failed. TreeNode*."); 56 | int n = obj->GetArray().size(); 57 | if (n <= 0) { v = NULL; return; } 58 | auto read = [&obj, &n](int idx) -> TreeNode* { 59 | if (idx >= n) return NULL; 60 | auto& js = obj->GetArray()[idx]; 61 | if (js.Is()) return NULL; 62 | return new TreeNode(conv::FromJson(js)); 63 | }; 64 | v = read(0); 65 | std::queue Q; 66 | if (v != NULL) Q.push(v); 67 | for (int i = 1; i < n && !Q.empty();) { 68 | TreeNode* current = Q.front(); Q.pop(); 69 | if ((current->left = read(i++)) != NULL) Q.push(current->left); 70 | if ((current->right = read(i++)) != NULL) Q.push(current->right); 71 | } 72 | } 73 | 74 | static json::Json ToJson(TreeNode* const &v) { 75 | json::Json js = json::Create(); 76 | auto obj = js.GetObject(); 77 | std::queue Q; 78 | if (v != NULL) Q.push(v); 79 | int nullCount = 0; 80 | while (!Q.empty() && nullCount < Q.size()) { 81 | TreeNode* current = Q.front(); Q.pop(); 82 | if (current == NULL) { 83 | obj->GetArray().emplace_back(json::JNull()); 84 | nullCount -= 1; 85 | continue; 86 | } 87 | obj->GetArray().emplace_back(conv::ToJson(current->val)); 88 | Q.push(current->left); 89 | Q.push(current->right); 90 | if (current->left == NULL) nullCount += 1; 91 | if (current->right == NULL) nullCount += 1; 92 | } 93 | return js; 94 | } 95 | }; 96 | 97 | } // namespace conv 98 | 99 | namespace io { 100 | 101 | std::string GetCurrentDirectory() { 102 | char* buffer = getcwd(NULL, 0); 103 | if (buffer == NULL) return ""; 104 | std::string cwd(buffer); 105 | free(buffer); 106 | if (cwd.length() <= 0 || cwd.back() == '/' || cwd.back() == '\\') return cwd; 107 | if (cwd.find('\\') != std::string::npos) return cwd + '\\'; 108 | return cwd + '/'; 109 | } 110 | 111 | class SI { 112 | public: 113 | SI(const std::string& file); 114 | SI(std::istream& is); 115 | ~SI(); 116 | 117 | bool Eof() const; 118 | int GetLineCount() const; 119 | const std::string& GetRaw() const; 120 | json::Json GetLine(); 121 | 122 | template 123 | SI& operator >> (_T& v) { 124 | conv::FromJson(v, GetLine()); 125 | return *this; 126 | } 127 | SI& operator >> (std::function cb) { 128 | cb(); 129 | return *this; 130 | } 131 | SI& operator >> (void (*cb)()) { 132 | cb(); 133 | return *this; 134 | } 135 | 136 | template 137 | void Input(_T& v, _REST& ...r) { 138 | *this >> v; 139 | Input(r...); 140 | } 141 | template 142 | void Input(_T& v) { 143 | *this >> v; 144 | } 145 | 146 | private: 147 | SI(const SI&) = delete; 148 | const SI& operator = (const SI&) = delete; 149 | 150 | std::istream* is_; 151 | bool fromFile_; 152 | int line_; 153 | std::string raw_; 154 | }; // class SI 155 | 156 | SI::SI(const std::string& file) : 157 | is_(NULL), 158 | fromFile_(true), 159 | line_(0) 160 | { 161 | std::cout << "Input from file \"" << file << "\"" << std::endl; 162 | auto ifs = new std::ifstream(file); 163 | if (!ifs->is_open()) { 164 | delete ifs; 165 | throw std::string("Can not open the input file: ") + GetCurrentDirectory() + file; 166 | } 167 | this->is_ = ifs; 168 | } 169 | 170 | SI::SI(std::istream& is) : 171 | is_(&is), 172 | fromFile_(false), 173 | line_(0) 174 | { 175 | std::cout << "Input from [std::cin]" << std::endl; 176 | } 177 | 178 | SI::~SI() { 179 | if (this->fromFile_ && this->is_ != NULL) { 180 | delete dynamic_cast(this->is_); 181 | } 182 | } 183 | 184 | bool SI::Eof() const { 185 | return this->is_->eof(); 186 | } 187 | 188 | int SI::GetLineCount() const { 189 | return this->line_; 190 | } 191 | 192 | const std::string& SI::GetRaw() const { 193 | return this->raw_; 194 | } 195 | 196 | json::Json SI::GetLine() { 197 | this->line_ += 1; 198 | std::getline(*this->is_, this->raw_); 199 | return json::Json(this->raw_); 200 | } 201 | 202 | 203 | 204 | class MO { 205 | public: 206 | template 207 | MO(_ARGS&& ...args) { 208 | this->Init(args...); 209 | } 210 | ~MO(); 211 | 212 | template 213 | MO& operator << (const _T& v) { 214 | auto js = conv::ToJson(v); 215 | std::stringstream ss; 216 | ss << js; 217 | std::string s = ss.str(); 218 | for (auto& os : this->oss_) *os << s; 219 | return *this; 220 | } 221 | MO& operator << (std::ostream& (*v)(std::ostream&)); 222 | 223 | private: 224 | MO(const MO&) = delete; 225 | const MO& operator = (const MO&) = delete; 226 | 227 | void InitOS(std::ostream& os); 228 | void InitOS(const std::string& fname); 229 | 230 | template 231 | void Init(_T&& arg, _REST&& ...r) { 232 | InitOS(arg); 233 | Init(r...); 234 | } 235 | 236 | template 237 | void Init(_T&& arg) { 238 | InitOS(arg); 239 | } 240 | 241 | std::list oss_; 242 | std::list ofs_; 243 | }; // class MO 244 | 245 | MO::~MO() { 246 | for (auto& os : this->oss_) os->flush(); 247 | for (auto& of : this->ofs_) of->close(), delete of; 248 | } 249 | 250 | void MO::InitOS(std::ostream& os) { 251 | this->oss_.push_back(&os); 252 | } 253 | 254 | void MO::InitOS(const std::string& fname) { 255 | auto ofs = new std::ofstream(fname); 256 | if (!ofs->is_open()) { 257 | delete ofs; 258 | throw std::string("Can not open the output file: ") + GetCurrentDirectory() + fname; 259 | } 260 | this->ofs_.push_back(ofs); 261 | this->oss_.push_back(ofs); 262 | } 263 | 264 | MO& MO::operator << (std::ostream& (*v)(std::ostream&)) { 265 | for (auto os : this->oss_) *os << v; 266 | return *this; 267 | } 268 | 269 | } // namespace io 270 | 271 | } // namespace lc 272 | 273 | #endif -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-convert.h: -------------------------------------------------------------------------------- 1 | #ifndef LEETCODE_CONVERT 2 | #define LEETCODE_CONVERT 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "leetcode-json.h" 12 | 13 | namespace lc { 14 | 15 | namespace conv { 16 | 17 | struct ConvertException : public std::exception { 18 | public: 19 | ConvertException(const json::Json& obj, const std::string& info = ""); 20 | const json::Json& GetJson() const; 21 | const char* what() const throw (); 22 | 23 | private: 24 | json::Json obj_; 25 | std::string info_; 26 | }; // struct ConvertException 27 | 28 | ConvertException::ConvertException(const json::Json& obj, const std::string& info) : 29 | obj_(obj), 30 | info_("Convert error. ") 31 | { 32 | this->info_ += info; 33 | } 34 | 35 | const json::Json& ConvertException::GetJson() const { 36 | return this->obj_; 37 | } 38 | 39 | const char* ConvertException::what() const throw () { 40 | return this->info_.c_str(); 41 | } 42 | 43 | 44 | 45 | //common template 46 | template ::value> 47 | struct Convert { 48 | static void FromJson(_T& v, const json::Json& js) { 49 | throw ConvertException(js, std::string("Conversion from JSON to ") + json::_get_name<_T>() + " not implemented."); 50 | } 51 | 52 | static json::Json ToJson(const _T& v) { 53 | throw ConvertException(json::Json(), std::string("Conversion from ") + json::_get_name<_T>() + " to JSON not implemented."); 54 | } 55 | }; 56 | 57 | //object specialization 58 | template 59 | struct Convert<_T, true> { 60 | #define ASSERT_HINT "Hey, this is not a json::Object, right?" 61 | static void FromJson(_T& v, const json::Json& js) { 62 | static_assert(json::_is_object<_T>::value, ASSERT_HINT); 63 | v = *js.GetObject<_T>(); 64 | } 65 | 66 | static json::Json ToJson(const _T& v) { 67 | static_assert(json::_is_object<_T>::value, ASSERT_HINT); 68 | return v; 69 | } 70 | #undef ASSERT_HINT 71 | }; 72 | 73 | //reference type specialization 74 | template 75 | struct Convert<_T&, true> { 76 | static void FromJson(_T& v, const json::Json& js) { 77 | Convert<_T>::FromJson(v, js); 78 | } 79 | 80 | static void FromJson(_T& v, json::Json&& js) { 81 | Convert<_T>::FromJson(v, std::move(js)); 82 | } 83 | 84 | static json::Json ToJson(const _T& v) { 85 | return Convert<_T>::ToJson(v); 86 | } 87 | 88 | static json::Json ToJson(_T&& v) { 89 | return Convert<_T>::ToJson(std::move(v)); 90 | } 91 | }; 92 | 93 | template 94 | struct Convert<_T&, false> { 95 | static void FromJson(_T& v, const json::Json& js) { 96 | Convert<_T>::FromJson(v, js); 97 | } 98 | 99 | static void FromJson(_T& v, json::Json&& js) { 100 | Convert<_T>::FromJson(v, std::move(js)); 101 | } 102 | 103 | static json::Json ToJson(const _T& v) { 104 | return Convert<_T>::ToJson(v); 105 | } 106 | 107 | static json::Json ToJson(_T&& v) { 108 | return Convert<_T>::ToJson(std::move(v)); 109 | } 110 | }; 111 | 112 | template <> 113 | struct Convert { 114 | static void FromJson(json::Json& v, const json::Json& js) { 115 | v = js; 116 | } 117 | 118 | static void FromJson(json::Json& v, json::Json&& js) { 119 | v.Swap(js); 120 | } 121 | 122 | static json::Json ToJson(const json::Json& v) { 123 | return v; 124 | } 125 | 126 | static json::Json ToJson(json::Json&& v) { 127 | return std::move(v); 128 | } 129 | }; 130 | 131 | 132 | 133 | #define QUICK_THROW(js, type) throw ConvertException(js, std::string("Convert from JSON to ") + json::_get_name() + " failed.") 134 | 135 | template <> 136 | struct Convert { 137 | static void FromJson(bool& v, const json::Json& js) { 138 | auto obj = js.GetObject(); 139 | if (obj == NULL) QUICK_THROW(js, bool); 140 | v = obj->GetValue(); 141 | } 142 | static json::Json ToJson(const bool& v) { 143 | return json::JBoolean(v); 144 | } 145 | }; 146 | 147 | 148 | template <> 149 | struct Convert { 150 | static void FromJson(int& v, const json::Json& js) { 151 | auto obj = js.GetObject(); 152 | if (obj == NULL || !obj->IsInteger()) QUICK_THROW(js, int); 153 | v = obj->GetInteger(); 154 | } 155 | static json::Json ToJson(const int& v) { 156 | return json::JNumber((long long)v); 157 | } 158 | }; 159 | 160 | 161 | template <> 162 | struct Convert { 163 | static void FromJson(long long& v, const json::Json& js) { 164 | auto obj = js.GetObject(); 165 | if (obj == NULL || !obj->IsInteger()) QUICK_THROW(js, long long); 166 | v = obj->GetInteger(); 167 | } 168 | static json::Json ToJson(const long long& v) { 169 | return json::JNumber(v); 170 | } 171 | }; 172 | 173 | 174 | template <> 175 | struct Convert { 176 | static void FromJson(double& v, const json::Json& js) { 177 | auto obj = js.GetObject(); 178 | if (obj == NULL) QUICK_THROW(js, double); 179 | v = obj->GetNumber(); 180 | } 181 | 182 | static json::Json ToJson(const double& v) { 183 | return json::JNumber(v); 184 | } 185 | }; 186 | 187 | 188 | 189 | template <> 190 | struct Convert { 191 | static void FromJson(std::string& v, const json::Json& js) { 192 | auto obj = js.GetObject(); 193 | if (obj == NULL) QUICK_THROW(js, std::string); 194 | v = obj->GetString(); 195 | } 196 | 197 | static void FromJson(std::string& v, json::Json&& js) { 198 | auto obj = js.GetObject(); 199 | if (obj == NULL) QUICK_THROW(js, std::string); 200 | v.swap(obj->GetString()); 201 | } 202 | 203 | static json::Json ToJson(const std::string& v) { 204 | return json::Json::Create(v); 205 | } 206 | 207 | static json::Json ToJson(std::string&& v) { 208 | return json::Json::Create(std::move(v)); 209 | } 210 | }; 211 | 212 | 213 | template <> 214 | struct Convert { 215 | static void FromJson(char& v, const json::Json& js) { 216 | auto& str = js.GetObject()->GetString(); 217 | if (str.size() != 1) QUICK_THROW(js, char); 218 | v = str.front(); 219 | } 220 | 221 | static json::Json ToJson(const char& v) { 222 | return json::Json::Create(std::string(1, v)); 223 | } 224 | }; 225 | 226 | 227 | template 228 | struct Convert, false> { 229 | static void FromJson(std::vector<_VAL>& v, const json::Json& js) { 230 | auto obj = js.GetObject(); 231 | if (obj == NULL) QUICK_THROW(js, std::vector<_VAL>); 232 | int n = obj->GetArray().size(); 233 | v.resize(n); 234 | for (int i = 0; i < n; ++i) { 235 | Convert<_VAL>::FromJson(v[i], obj->GetArray()[i]); 236 | } 237 | } 238 | 239 | static json::Json ToJson(const std::vector<_VAL>& v) { 240 | json::Json js = json::Json::Create(); 241 | auto obj = js.GetObject(); 242 | for (auto& sub : v) { 243 | obj->GetArray().emplace_back(Convert<_VAL>::ToJson(sub)); 244 | } 245 | return js; 246 | } 247 | }; 248 | 249 | 250 | 251 | template 252 | struct Convert, false> { 253 | static void FromJson(std::map<_KEY, _VAL>& v, const json::Json& js) { 254 | auto obj = js.GetObject(); 255 | using type = std::map<_KEY, _VAL>; 256 | if (obj == NULL) QUICK_THROW(js, type); 257 | obj->ForEach([&v](const json::Json& key, const json::Json& val) { 258 | _KEY vark; 259 | _VAL varv; 260 | Convert<_KEY>::FromJson(vark, key); 261 | Convert<_VAL>::FromJson(varv, val); 262 | v.emplace(std::move(vark), std::move(varv)); 263 | }); 264 | } 265 | 266 | static json::Json ToJson(const std::map<_KEY, _VAL>& v) { 267 | json::Json js = json::Json::Create(); 268 | auto obj = js.GetObject(); 269 | for (auto& sub : v) { 270 | obj->Add( 271 | Convert<_KEY>::ToJson(sub.first), 272 | Convert<_VAL>::ToJson(sub.second) 273 | ); 274 | } 275 | return js; 276 | } 277 | }; 278 | 279 | 280 | 281 | template 282 | struct Convert, false> { 283 | static void FromJson(std::tuple<_ARGS...>& v, const json::Json& js) { 284 | auto obj = js.GetObject(); 285 | int n = std::tuple_size>::value; 286 | if (obj == NULL || n != obj->GetArray().size()) QUICK_THROW(js, std::tuple<_ARGS...>); 287 | Impl, sizeof...(_ARGS)>::DoFromJson(v, *obj); 288 | } 289 | 290 | static json::Json ToJson(const std::tuple<_ARGS...>& v) { 291 | json::Json js = json::Json::Create(); 292 | auto obj = js.GetObject(); 293 | Impl, sizeof...(_ARGS)>::DoToJson(v, *obj); 294 | return js; 295 | } 296 | 297 | // tuple implementations 298 | private: 299 | template 300 | struct Impl { 301 | static void DoFromJson(_T& v, json::JArray& obj) { 302 | Impl<_T, N-1>::DoFromJson(v, obj); 303 | using SubType = typename std::tuple_element::type; 304 | Convert::FromJson(std::get(v), obj.GetArray()[N-1]); 305 | } 306 | 307 | static void DoToJson(const _T& v, json::JArray& obj) { 308 | Impl<_T, N-1>::DoToJson(v, obj); 309 | using SubType = typename std::tuple_element::type; 310 | obj.GetArray().emplace_back(Convert::ToJson(std::get(v))); 311 | } 312 | }; 313 | 314 | template 315 | struct Impl<_T, 1> { 316 | static void DoFromJson(_T& v, json::JArray& obj) { 317 | using SubType = typename std::tuple_element<0, _T>::type; 318 | Convert::FromJson(std::get<0>(v), obj.GetArray()[0]); 319 | } 320 | 321 | static void DoToJson(const _T& v, json::JArray& obj) { 322 | using SubType = typename std::tuple_element<0, _T>::type; 323 | obj.GetArray().emplace_back(Convert::ToJson(std::get<0>(v))); 324 | } 325 | }; 326 | }; 327 | 328 | #undef QUICK_THROW 329 | 330 | 331 | 332 | template 333 | void FromJson(_T& v, const json::Json& js) { 334 | Convert<_T>::FromJson(v, js); 335 | } 336 | 337 | template 338 | void FromJson(_T& v, json::Json&& js) { 339 | Convert<_T>::FromJson(v, std::move(js)); 340 | } 341 | 342 | template 343 | _T FromJson(const json::Json& js) { 344 | _T v; 345 | FromJson(v, js); 346 | return v; 347 | } 348 | 349 | template 350 | _T FromJson(json::Json&& js) { 351 | _T v; 352 | FromJson(v, std::move(js)); 353 | return v; 354 | } 355 | 356 | template 357 | void FromJson(_T& v, const std::string& raw) { 358 | Convert<_T>::FromJson(v, json::Json(raw)); 359 | } 360 | 361 | template 362 | _T FromJson(const std::string& raw) { 363 | _T v; 364 | FromJson(v, json::Json(raw)); 365 | return v; 366 | } 367 | 368 | template 369 | json::Json ToJson(const _T& v) { 370 | return Convert<_T>::ToJson(v); 371 | } 372 | 373 | template 374 | json::Json ToJson(_T&& v) { 375 | return Convert<_T>::ToJson(std::move(v)); 376 | } 377 | 378 | } // namespace conv 379 | 380 | } // namespace lc 381 | 382 | #endif -------------------------------------------------------------------------------- /src/debuggers/cppDebugger.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as fse from "fs-extra"; 3 | import * as path from "path"; 4 | import { Debugger } from "./debugger"; 5 | import { CodeIndentHelper } from "../util/codeIndentHelper"; 6 | import { IQuickItemEx } from "../util/ex"; 7 | 8 | interface IArgumentMetaInfo { 9 | type: string; 10 | name: string; 11 | } 12 | 13 | interface IFunctionMetaInfo { 14 | name: string; 15 | args: IArgumentMetaInfo[]; 16 | type: string; 17 | } 18 | 19 | interface IProblemMetaInfo { 20 | name: string; // class name 21 | functions: IFunctionMetaInfo[]; 22 | isDesignProblem: boolean; 23 | isInteractiveProblem: boolean; 24 | } 25 | 26 | export class CppDebugger extends Debugger { 27 | private readonly definition = "leetcode-definition.h"; 28 | private readonly io = "leetcode-io.h"; 29 | private readonly handler = "leetcode-handler.h"; 30 | private readonly main = "leetcode-main.cpp"; 31 | 32 | public async init(solutionEditor: vscode.TextEditor): Promise { 33 | if (!solutionEditor || solutionEditor.document.isClosed || !this.codeTemplate) { 34 | return; 35 | } 36 | 37 | const insertContent: string = "#include \"" + this.definition + "\""; 38 | if (this.stubCodeHelper.getRaw().indexOf(insertContent) < 0) { 39 | this.stubCodeHelper.setFront(insertContent); 40 | } 41 | 42 | const dir: string = path.dirname(solutionEditor.document.uri.fsPath); 43 | const solution: string = path.basename(solutionEditor.document.uri.fsPath); 44 | 45 | const stub: string | undefined = await this.genStubCode(solution); 46 | if (!stub) { 47 | return; 48 | } 49 | this.stubFileHelper.files.push({ relative: this.handler, content: stub }); 50 | 51 | return path.join(dir, this.main); 52 | } 53 | 54 | public async dispose(solutionEditor: vscode.TextEditor): Promise { 55 | 56 | } 57 | 58 | private getMetaInfo(code: string): IProblemMetaInfo { 59 | const meta: IProblemMetaInfo = { 60 | name: '', 61 | functions: [], 62 | isDesignProblem: false, 63 | isInteractiveProblem: false 64 | } 65 | 66 | const classPattern: RegExp = /class +(Solution|[\w\d]+) *{?/; 67 | const initPattern: RegExp = / *([\w\d]+) *\(((?:[, ]*[\w\d<>, :\*]+[ \*&]+[\w\d]+)*)\)[ \{\}]*/; 68 | const funcPattern: RegExp = / *([\w\d<>, :\*]+) +([\w\d]+) *\(((?:[, ]*[\w\d<>, :\*]+[ \*&]+[\w\d]+)*)\)[ \{\}]*/; 69 | const argPattern: RegExp = / *([\w\d<>, :\*]+[&\* ]+)([\w\d]+) */; 70 | 71 | function getArgMetaInfo(arg: string): IArgumentMetaInfo { 72 | const match: RegExpExecArray | null = argPattern.exec(arg); 73 | if (match) { 74 | return { type: match[1].replace("&", "").trim(), name: match[2].trim() }; 75 | } 76 | (function (): never { 77 | throw new Error(`Can not get meta info from ${arg}.`); 78 | })(); 79 | } 80 | function getFuncMetaInfo(line: string): IFunctionMetaInfo | undefined { 81 | function normalize(type: string, name: string, args: string): IFunctionMetaInfo { 82 | const ret: IFunctionMetaInfo = { 83 | name: name, 84 | args: [], 85 | type: type 86 | }; 87 | if (args.replace(" ", "").length > 0) { 88 | let parts: string[] = []; 89 | let cnt: number = 0; 90 | let index: number = 0; 91 | let start: number = 0; 92 | while (true) { 93 | if (index >= args.length || (args[index] == ',' && cnt == 0)) { 94 | parts.push(args.substr(start, index - start)); 95 | start = index + 1; 96 | if (index >= args.length) { 97 | break; 98 | } 99 | } 100 | if (args[index] == '<') cnt += 1; 101 | if (args[index] == '>') cnt -= 1; 102 | index += 1; 103 | } 104 | ret.args = parts.map((value) => getArgMetaInfo(value)); 105 | } 106 | return ret; 107 | } 108 | 109 | const eol = line.lastIndexOf(";"); 110 | if (eol >= 0 && eol > line.lastIndexOf("}")) { 111 | return; 112 | } 113 | 114 | if (meta.name.length > 0) { 115 | const match: RegExpExecArray | null = initPattern.exec(line); 116 | if (match && match[1] == meta.name) { 117 | return normalize('void', match[1].trim(), match[2]); 118 | } 119 | } 120 | const match: RegExpExecArray | null = funcPattern.exec(line); 121 | if (!match || match[1].trim().length <= 0) { 122 | return; 123 | } 124 | return normalize(match[1].trim(), match[2].trim(), match[3]); 125 | } 126 | 127 | const lines: string[] = code.split('\n'); 128 | for (const line of lines) { 129 | const match: RegExpExecArray | null = classPattern.exec(line); 130 | if (match) { 131 | meta.name = match[1]; 132 | meta.functions = []; 133 | meta.isDesignProblem = meta.name != 'Solution'; 134 | continue; 135 | } 136 | if (meta.name.length > 0) { 137 | const func: IFunctionMetaInfo | undefined = getFuncMetaInfo(line); 138 | if (func) { 139 | meta.functions.push(func); 140 | } 141 | } 142 | } 143 | return meta; 144 | } 145 | 146 | private async genStubCode(solution: string): Promise { 147 | const meta: IProblemMetaInfo = this.getMetaInfo(this.codeTemplate); 148 | if (meta.name.length <= 0) { 149 | throw new Error("Invalid meta info."); 150 | } 151 | if (meta.isInteractiveProblem) { 152 | throw new Error("Unsupported problem type."); 153 | } 154 | if (meta.functions.length <= 0) { 155 | throw new Error("Can not find the entry function."); 156 | } 157 | 158 | function genArgsCode(func: IFunctionMetaInfo): string { 159 | return func.args.map((arg) => arg.name).join(", "); 160 | } 161 | function genInputCode(func: IFunctionMetaInfo, helper: CodeIndentHelper) { 162 | if (func.args.length <= 0) { 163 | return; 164 | } 165 | const tupleCode: string[] = []; 166 | for (const arg of func.args) { 167 | helper.line(arg.type + " " + arg.name + ";"); 168 | tupleCode.push(arg.type + "&"); 169 | } 170 | const tupleName: string = "__tuple__value"; 171 | helper.line(`std::tuple<` + tupleCode.join(", ") + `> ${tupleName} { ` + genArgsCode(func) + ` };`); 172 | helper.line(`conv::FromJson(${tupleName}, in);`); 173 | } 174 | 175 | const code: CodeIndentHelper = new CodeIndentHelper(); 176 | code.append(`#ifndef LEETCODE_HANDLER`) 177 | .line(`#define LEETCODE_HANDLER`) 178 | .line() 179 | .line(`#include "${solution}"`) 180 | .line(`#include "${this.io}"`) 181 | .line() 182 | .line("namespace lc {") 183 | .line() 184 | .line(`class Handler {`) 185 | .line(`public:`).right() 186 | .line(`static std::string GetClassName() { return "${meta.name}"; } `) 187 | .line(`Handler(const json::Json& in) {`).right(); 188 | 189 | // generate constructor 190 | if (!meta.isDesignProblem) { 191 | code.line(`solution_ = new ${meta.name}();`); 192 | } 193 | else { 194 | const ctor: IFunctionMetaInfo | undefined = meta.functions.find((value: IFunctionMetaInfo) => value.name == meta.name && value.type == "void"); 195 | if (ctor && ctor.args.length > 0) { 196 | genInputCode(ctor, code); 197 | code.line(`solution_ = new ${meta.name}(${genArgsCode(ctor)});`); 198 | } 199 | else { 200 | code.line(`solution_ = new ${meta.name}();`); 201 | } 202 | } 203 | code.left().line(`}`) 204 | .line(`~Handler() { delete solution_; }`); 205 | 206 | // generate handler function 207 | if (!meta.isDesignProblem) { 208 | const candidates: Array> = []; 209 | meta.functions.forEach(f => { 210 | //if ( f.type != "void" && f.args.length > 0) { 211 | const args: string[] = f.args.map((a) => a.type); 212 | candidates.push({ 213 | label: `> ${f.name}(${args.join(", ")}) => ${f.type}`, 214 | value: f 215 | }); 216 | //} 217 | }); 218 | if (candidates.length < 1) { 219 | throw new Error(`Can not find entry function in class [${meta.name}].`); 220 | } 221 | 222 | let func = candidates[0].value; 223 | if (candidates.length > 1) { // pick one 224 | const choice: IQuickItemEx | undefined = await vscode.window.showQuickPick(candidates, { 225 | placeHolder: "Please choose the entry function. (Press ESC to cancel)", 226 | ignoreFocusOut: true, 227 | }); 228 | if (!choice) { 229 | return; 230 | } 231 | func = choice.value; 232 | } 233 | 234 | code.line(`json::Json Handle(const json::Json& in, const std::string& fname) { return json::Create(); }`); 235 | code.line(`void Handle(io::SI& in, io::MO& out) {`).right(); 236 | for (const arg of func.args) { 237 | code.line(`${arg.type} ${arg.name};`) 238 | .line(`in >> ${arg.name};`); 239 | } 240 | code.line(`#ifdef LAZY_INTERACTION`) 241 | .line(`in.Input(LAZY_INTERACTION);`) 242 | .line(`#endif`); 243 | if (func.type != "void") { 244 | code.line(`out << solution_->${func.name}(${genArgsCode(func)}) << std::endl;`); 245 | } 246 | else { 247 | code.line(`solution_->${func.name}(${genArgsCode(func)});`); 248 | for (const arg of func.args) { 249 | code.line(`out << ${arg.name} << std::endl;`); 250 | } 251 | } 252 | code.left().line('}'); 253 | } 254 | else { 255 | code.line(`void Handle(io::SI& in, io::MO& out) {}`); 256 | code.line(`json::Json Handle(const json::Json& in, const std::string& fname) {`).right() 257 | .line(`if (fname == "") throw std::string("Empty function name.");`) 258 | .line(`#define CASE(func) else if (fname == #func)`); 259 | for (const func of meta.functions) { 260 | if (func.name == meta.name) { 261 | continue; 262 | } 263 | code.line(`CASE (${func.name}) {`).right(); 264 | genInputCode(func, code); 265 | const callCode: string = `solution_->${func.name}(${genArgsCode(func)})`; 266 | if (func.type == "void") { 267 | code.line(`${callCode};`) 268 | .line(`return json::Create();`); 269 | } 270 | else { 271 | code.line(`return conv::ToJson(${callCode});`); 272 | } 273 | code.left().line(`}`); 274 | } 275 | code.line(`#undef CASE`) 276 | .line(`throw std::string("Invalid function name.");`) 277 | .line(`return json::Create();`) 278 | .left().line(`}`); 279 | } 280 | 281 | code.line().left().line(`private:`).right() 282 | .line(`${meta.name}* solution_;`) 283 | .left().line(`};`) 284 | .line() 285 | .line(`} // namespace lc`) 286 | .line(); 287 | if (meta.isDesignProblem) { 288 | code.line(`#define SYSTEM_DESIGN`); 289 | } 290 | code.line(`#endif // LEETCODE_HANDLER`); 291 | return code.str(); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /resources/code/cpp/leetcode-json.h: -------------------------------------------------------------------------------- 1 | #ifndef LEETCODE_JSON_H 2 | #define LEETCODE_JSON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef __GNUC__ 17 | #include 18 | #include 19 | #endif 20 | 21 | #ifndef NULL 22 | #define NULL 0 23 | #endif 24 | 25 | namespace lc { 26 | 27 | namespace clone { 28 | 29 | class Base { 30 | public: 31 | virtual ~Base() {} 32 | virtual Base* DoClone() const = 0; 33 | 34 | protected: 35 | virtual void DoCopyTo(Base* clone) const {} 36 | }; // class Base 37 | 38 | template 39 | class Template : public _BASE { 40 | public: 41 | Base* DoClone() const override { 42 | if (dynamic_cast(this) == NULL) return NULL; 43 | if (dynamic_cast(this) == NULL) return NULL; 44 | const _THIS* me = dynamic_cast(this); 45 | _THIS* clone = new _THIS(*me); 46 | this->DoCopyTo(dynamic_cast(clone)); 47 | return dynamic_cast(clone); 48 | } 49 | 50 | _THIS* Clone() const { 51 | return dynamic_cast<_THIS*>(DoClone()); 52 | } 53 | 54 | protected: 55 | virtual void DoCopy(_THIS* clnoe) const {} 56 | virtual void DoCopyTo(Base* clone) const override { 57 | _BASE::DoCopyTo(clone); 58 | this->DoCopy(dynamic_cast<_THIS*>(clone)); 59 | } 60 | }; // class Template 61 | 62 | } // namespace clon 63 | 64 | namespace json { 65 | 66 | struct JsonMask { 67 | public: 68 | JsonMask(int mask); 69 | ~JsonMask(); 70 | JsonMask* Next(int mask); 71 | JsonMask* Set(int mask); 72 | int Mask() const; 73 | JsonMask* Next() const; 74 | 75 | private: 76 | int mask_; 77 | JsonMask* next_; 78 | }; // struct JsonMask 79 | 80 | JsonMask::JsonMask(int mask) : mask_(mask), next_(NULL) {} 81 | 82 | JsonMask::~JsonMask() { 83 | if (this->next_ != NULL) delete this->next_; 84 | } 85 | 86 | JsonMask* JsonMask::Next(int mask) { 87 | if (this->next_ == NULL) this->next_ = new JsonMask(mask); 88 | else this->next_->Set(mask); 89 | return this->next_; 90 | } 91 | 92 | JsonMask* JsonMask::Set(int mask) { 93 | this->mask_ = mask; 94 | return this; 95 | } 96 | 97 | int JsonMask::Mask() const { 98 | return this->mask_; 99 | } 100 | 101 | JsonMask* JsonMask::Next() const { 102 | return this->next_; 103 | } 104 | 105 | 106 | 107 | class Object : public clone::Base { 108 | public: 109 | virtual ~Object() {} 110 | 111 | template 112 | bool Is() const { 113 | return dynamic_cast(this) != NULL; 114 | } 115 | const std::string& GetName() const; 116 | int GetPosistion() const; 117 | void SetPosition(int pos); 118 | int Parse(const std::string& raw, int start = 0, JsonMask* mask = NULL); 119 | 120 | friend std::ostream& operator << (std::ostream& os, const Object& obj); 121 | 122 | protected: 123 | Object(); 124 | virtual const std::string& Name() const = 0; 125 | //return length 126 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) = 0; 127 | virtual void Print(std::ostream& os) const = 0; 128 | 129 | //return length 130 | int SkipSpaces(const std::string& raw, int start, bool error = true); 131 | static int SkipSpacesWithoutError(const std::string& raw, int start); 132 | 133 | private: 134 | int pos_; 135 | }; // class Object 136 | 137 | 138 | 139 | template 140 | struct _is_object : public std::is_base_of {}; 141 | 142 | template 143 | std::string _get_name() { 144 | std::string name(typeid(_T).name()); 145 | #ifdef __GNUC__ 146 | char* real = abi::__cxa_demangle(name.c_str(), NULL, NULL, NULL); 147 | name = std::string(real); 148 | free(real); 149 | #endif 150 | return name; 151 | } 152 | 153 | 154 | 155 | class ObjectManager { 156 | public: 157 | typedef std::function Constructor; 158 | typedef std::function Checker; 159 | struct Api { 160 | int mask; 161 | Constructor constructor; 162 | Checker checker; 163 | }; // struct ObjectApi 164 | 165 | template 166 | static void RegistObject(Checker checker, int id) { 167 | GetInstance().apis_[typeid(_T).hash_code()] = { 168 | 1 << id, 169 | []() { return new _T(); }, 170 | checker 171 | }; 172 | } 173 | 174 | template 175 | static const Api& GetApi() { 176 | auto find = GetInstance().apis_.find(typeid(_T).hash_code()); 177 | if (find == GetInstance().apis_.end()) throw std::string("Unregisted type [") + _get_name<_T>() + "]."; 178 | return find->second; 179 | } 180 | 181 | static void Until(std::function cb) { 182 | for (auto& ite : GetInstance().apis_) { 183 | if (cb(ite.second)) break; 184 | } 185 | } 186 | 187 | private: 188 | std::map apis_; 189 | 190 | ObjectManager() {} 191 | static ObjectManager& GetInstance(); 192 | }; // class JsonManager 193 | 194 | ObjectManager& ObjectManager::GetInstance() { 195 | static ObjectManager instance; 196 | return instance; 197 | } 198 | 199 | #define REGIST_JSON_OBJECT(type, checker, id) \ 200 | struct _##type##_ObjectRegister { \ 201 | _##type##_ObjectRegister() { \ 202 | static_assert(std::is_base_of::value, "Only json::Object can be registed."); \ 203 | json::ObjectManager::RegistObject(checker, id); \ 204 | } \ 205 | } _##type##_ObjectRegister_trigger_instance 206 | 207 | 208 | 209 | class Json { 210 | public: 211 | Json(); 212 | Json(const std::string& raw, int start = 0, JsonMask* mask = NULL, bool all = true); 213 | Json(const std::string& raw, int start, int& len, JsonMask* mask = NULL, bool all = true); 214 | Json(const Object& obj); 215 | Json(const Json& js); 216 | Json(Json&& js); 217 | Json& operator = (const Json& js); 218 | ~Json(); 219 | 220 | template 221 | static Json Create(_ARGS&&... args) { 222 | static_assert(_is_object<_T>::value, "Type need be Object."); 223 | Json js; 224 | js.obj_ = new _T(args...); 225 | return js; 226 | } 227 | 228 | template 229 | bool Is() const { 230 | return this->obj_ != NULL && this->obj_->Is<_T>(); 231 | } 232 | Object* GetObject() const; 233 | template 234 | _T* GetObject() const { return dynamic_cast<_T*>(this->obj_); } 235 | template 236 | static int GetMask() { 237 | return ObjectManager::GetApi<_T>().mask; 238 | } 239 | void Swap(Json& js); 240 | 241 | int Parse(const std::string& raw, int start = 0, JsonMask* mask = NULL, bool all = true); 242 | 243 | friend std::ostream& operator << (std::ostream& os, const Json& js); 244 | 245 | private: 246 | Object* obj_; 247 | }; // class Json 248 | 249 | struct JsonException : public std::exception { 250 | public: 251 | JsonException(const Json& obj, const std::string& raw, int pos, const std::string& info = ""); 252 | JsonException(const Json& obj, const std::string& info); 253 | const Json& GetJson() const; 254 | const std::string& GetRaw() const; 255 | int GetPosition() const; 256 | const char* what() const throw (); 257 | 258 | private: 259 | std::string info_; 260 | Json obj_; 261 | std::string raw_; 262 | int pos_; 263 | }; 264 | 265 | JsonException::JsonException(const Json& obj, const std::string& raw, int pos, const std::string& info) : 266 | info_("JSON parse error. "), 267 | obj_(obj), 268 | raw_(raw), 269 | pos_(pos) 270 | { 271 | this->info_ += info; 272 | } 273 | 274 | JsonException::JsonException(const Json& obj, const std::string& info) : 275 | info_("JSON parse error. "), 276 | obj_(obj), 277 | raw_(""), 278 | pos_(-1) 279 | { 280 | this->info_ += info; 281 | } 282 | 283 | const Json& JsonException::GetJson() const { 284 | return this->obj_; 285 | } 286 | 287 | const std::string& JsonException::GetRaw() const { 288 | return this->raw_; 289 | } 290 | 291 | int JsonException::GetPosition() const { 292 | return this->pos_; 293 | } 294 | 295 | const char* JsonException::what() const throw() { 296 | return this->info_.c_str(); 297 | } 298 | 299 | 300 | // implements after JsonException 301 | Object::Object() : pos_(-1) {} 302 | 303 | const std::string& Object::GetName() const { 304 | return this->Name(); 305 | } 306 | 307 | int Object::GetPosistion() const { 308 | return this->pos_; 309 | } 310 | 311 | void Object::SetPosition(int pos) { 312 | this->pos_ = pos; 313 | } 314 | 315 | int Object::Parse(const std::string& raw, int start, JsonMask* mask) { 316 | return this->DoParse(raw, start, mask); 317 | } 318 | 319 | int Object::SkipSpaces(const std::string& raw, int start, bool error) { 320 | int idx = start, n = raw.size(); 321 | while (idx < n && raw[idx] == ' ') ++idx; 322 | if (error && idx >= n) throw JsonException(*this, raw, idx); 323 | return idx - start; 324 | } 325 | 326 | int Object::SkipSpacesWithoutError(const std::string& raw, int start) { 327 | int idx = start, n = raw.size(); 328 | while (idx < n && raw[idx] == ' ') ++idx; 329 | return idx - start; 330 | } 331 | 332 | std::ostream& operator << (std::ostream& os, const Object& obj) { 333 | obj.Print(os); 334 | return os; 335 | } 336 | 337 | 338 | 339 | // implements after JsonException 340 | Json::Json() : obj_(NULL) {} 341 | 342 | Json::Json(const std::string& raw, int start, JsonMask* mask, bool all) : 343 | obj_(NULL) 344 | { 345 | this->Parse(raw, start, mask, all); 346 | } 347 | 348 | Json::Json(const std::string& raw, int start, int& len, JsonMask* mask, bool all) : 349 | obj_(NULL) 350 | { 351 | len = this->Parse(raw, start, mask, all); 352 | } 353 | 354 | Json::Json(const Object& obj) : obj_(dynamic_cast(obj.DoClone())) {} 355 | 356 | Json::Json(const Json& js) : obj_(NULL) { *this = js; } 357 | 358 | Json::Json(Json&& js) : obj_(NULL) { this->Swap(js); } 359 | 360 | Json& Json::operator = (const Json& js) { 361 | if (this->obj_ != NULL) delete this->obj_; 362 | if (js.GetObject() != NULL) this->obj_ = dynamic_cast(js.GetObject()->DoClone()); 363 | else this->obj_ = NULL; 364 | return *this; 365 | } 366 | 367 | Json::~Json() { 368 | if (this->obj_ != NULL) delete this->obj_; 369 | } 370 | 371 | Object* Json::GetObject() const { 372 | return this->obj_; 373 | } 374 | 375 | void Json::Swap(Json& js) { 376 | std::swap(this->obj_, js.obj_); 377 | } 378 | 379 | std::ostream& operator << (std::ostream& os, const Json& js) { 380 | os << *js.obj_; 381 | return os; 382 | } 383 | 384 | 385 | 386 | class JNull : public clone::Template { 387 | public: 388 | JNull(); 389 | 390 | static bool QuickCheck(const std::string& raw, int start); 391 | 392 | protected: 393 | virtual const std::string& Name() const override; 394 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) override; 395 | virtual void Print(std::ostream& os) const override; 396 | 397 | private: 398 | static const std::string text_; 399 | }; // class JNull 400 | 401 | REGIST_JSON_OBJECT(JNull, JNull::QuickCheck, 0); 402 | 403 | const std::string JNull::text_("null"); 404 | 405 | JNull::JNull() {} 406 | 407 | bool JNull::QuickCheck(const std::string& raw, int start) { 408 | int idx = start + SkipSpacesWithoutError(raw, start); 409 | int n = raw.size(), m = text_.size(); 410 | if (idx + m > n) return false; 411 | return std::strncmp(text_.c_str(), raw.c_str() + idx, m) == 0; 412 | } 413 | 414 | const std::string& JNull::Name() const { 415 | static std::string name("Null"); 416 | return name; 417 | } 418 | 419 | int JNull::DoParse(const std::string& raw, int start, JsonMask* mask) { 420 | int idx = start + SkipSpaces(raw, start); 421 | if (!QuickCheck(raw, idx)) { 422 | throw JsonException(*this, raw, idx); 423 | } 424 | return this->text_.size(); 425 | } 426 | 427 | void JNull::Print(std::ostream& os) const { 428 | os << this->text_; 429 | } 430 | 431 | 432 | 433 | class JBoolean : public clone::Template { 434 | public: 435 | JBoolean(); 436 | JBoolean(bool value); 437 | bool GetValue() const; 438 | //<0: not matched, 0: false, >0: true 439 | static int QuickCheck(const std::string& raw, int start); 440 | 441 | protected: 442 | virtual const std::string& Name() const override; 443 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) override; 444 | virtual void Print(std::ostream& os) const override; 445 | 446 | private: 447 | bool value_; 448 | 449 | static const std::string true_; 450 | static const std::string false_; 451 | }; // class JNull 452 | 453 | #define OBJECT_BOOLEAN_CHECKER [](const std::string& raw, int start) { return JBoolean::QuickCheck(raw, start) >= 0; } 454 | REGIST_JSON_OBJECT(JBoolean, OBJECT_BOOLEAN_CHECKER, 1); 455 | #undef OBJECT_BOOLEAN_CHECKER 456 | 457 | const std::string JBoolean::true_("true"); 458 | const std::string JBoolean::false_("false"); 459 | 460 | int JBoolean::QuickCheck(const std::string& raw, int start) { 461 | int idx = start + SkipSpacesWithoutError(raw, start); 462 | int n = raw.size(), mt = true_.size(), mf = false_.size(); 463 | auto check = [&](const std::string& str) -> bool { 464 | int m = str.size(); 465 | if (idx + m > n) return false; 466 | return std::strncmp(str.c_str(), raw.c_str() + idx, m) == 0; 467 | }; 468 | if (check(true_)) return 1; 469 | if (check(false_)) return 0; 470 | return -1; 471 | } 472 | 473 | JBoolean::JBoolean() : value_(false) {} 474 | 475 | JBoolean::JBoolean(bool value) : value_(value) {} 476 | 477 | bool JBoolean::GetValue() const { 478 | return this->value_; 479 | } 480 | 481 | const std::string& JBoolean::Name() const { 482 | static std::string name("Boolean"); 483 | return name; 484 | } 485 | 486 | int JBoolean::DoParse(const std::string& raw, int start, JsonMask* mask) { 487 | int idx = start + this->SkipSpaces(raw, start); 488 | int ret = this->QuickCheck(raw, idx); 489 | if (ret < 0) throw JsonException(*this, raw, idx); 490 | if (ret > 0) { 491 | this->value_ = true; 492 | return this->true_.size(); 493 | } 494 | this->value_ = false; 495 | return this->false_.size(); 496 | } 497 | 498 | void JBoolean::Print(std::ostream& os) const { 499 | os << (this->value_ ? this->true_ : this->false_); 500 | } 501 | 502 | 503 | 504 | class JNumber : public clone::Template { 505 | public: 506 | JNumber(); 507 | JNumber(long long v); 508 | JNumber(double v); 509 | long long GetInteger() const; 510 | double GetNumber() const; 511 | bool IsInteger() const; 512 | void SetValue(long long v); 513 | void SetValue(double v); 514 | 515 | static bool IsNumberCharacter(char c); 516 | 517 | protected: 518 | virtual const std::string& Name() const override; 519 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) override; 520 | virtual void Print(std::ostream& os) const override; 521 | 522 | private: 523 | long long integer_; 524 | double number_; 525 | bool isInteger_; 526 | }; // class JNumber 527 | 528 | #define OBJECT_NUMBER_CHECKER [](const std::string& raw, int start) { return JNumber::IsNumberCharacter(raw[start]); } 529 | REGIST_JSON_OBJECT(JNumber, OBJECT_NUMBER_CHECKER, 2); 530 | #undef OBJECT_NUMBER_CHECKER 531 | 532 | bool JNumber::IsNumberCharacter(char c) { 533 | return (c >= '0' && c <= '9') 534 | || c == '-' 535 | || c == '.'; 536 | } 537 | 538 | JNumber::JNumber() : 539 | integer_(0), 540 | number_(0.0), 541 | isInteger_(true) 542 | {} 543 | 544 | JNumber::JNumber(long long v) { 545 | this->SetValue(v); 546 | } 547 | 548 | JNumber::JNumber(double v) { 549 | this->SetValue(v); 550 | } 551 | 552 | long long JNumber::GetInteger() const { 553 | return this->integer_; 554 | } 555 | 556 | double JNumber::GetNumber() const { 557 | return this->number_; 558 | } 559 | 560 | bool JNumber::IsInteger() const { 561 | return this->isInteger_; 562 | } 563 | 564 | void JNumber::SetValue(long long v) { 565 | this->number_ = this->integer_ = v; 566 | this->isInteger_ = true; 567 | } 568 | 569 | void JNumber::SetValue(double v) { 570 | this->integer_ = this->number_ = v; 571 | this->isInteger_ = false; 572 | } 573 | 574 | const std::string& JNumber::Name() const { 575 | static std::string name("Number"); 576 | return name; 577 | } 578 | 579 | int JNumber::DoParse(const std::string& raw, int start, JsonMask* mask) { 580 | int idx = start + this->SkipSpaces(raw, start), n = raw.size(); 581 | std::stringstream ss1, ss2; 582 | int from = idx; 583 | while (idx < n && this->IsNumberCharacter(raw[idx])) { 584 | ss1 << raw[idx]; 585 | ss2 << raw[idx]; 586 | ++idx; 587 | } 588 | if (idx == from) throw JsonException(*this, raw, idx); 589 | std::string post1, post2; 590 | ss1 >> this->integer_ >> post1; 591 | ss2 >> this->number_ >> post2; 592 | if (post2.size() > 0) throw JsonException(*this, raw, idx - post2.size()); 593 | this->isInteger_ = post1.size() <= 0; 594 | return idx - start; 595 | } 596 | 597 | void JNumber::Print(std::ostream& os) const { 598 | if (this->isInteger_) os << this->integer_; 599 | else os << this->number_; 600 | } 601 | 602 | 603 | 604 | //TODO: escape not supported yet 605 | class JString : public clone::Template { 606 | public: 607 | JString(); 608 | JString(const std::string& s); 609 | JString(std::string&& s); 610 | const std::string& GetString() const; 611 | std::string& GetString(); 612 | void SetString(const std::string& s); 613 | 614 | protected: 615 | virtual const std::string& Name() const override; 616 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) override; 617 | virtual void Print(std::ostream& os) const override; 618 | 619 | private: 620 | std::string string_; 621 | }; // class JString 622 | 623 | #define OBJECT_STRING_CHECKER [](const std::string& raw, int start) { return raw[start] == '\"'; } 624 | REGIST_JSON_OBJECT(JString, OBJECT_STRING_CHECKER, 3); 625 | #undef OBJECT_STRING_CHECKER 626 | 627 | JString::JString() {} 628 | 629 | JString::JString(const std::string& s) : string_(s) {} 630 | 631 | JString::JString(std::string&& s) { 632 | this->string_.swap(s); 633 | } 634 | 635 | const std::string& JString::GetString() const { 636 | return this->string_; 637 | } 638 | 639 | std::string& JString::GetString() { 640 | return this->string_; 641 | } 642 | 643 | void JString::SetString(const std::string& s) { 644 | this->string_ = s; 645 | } 646 | 647 | const std::string& JString::Name() const { 648 | static std::string name("String"); 649 | return name; 650 | } 651 | 652 | int JString::DoParse(const std::string& raw, int start, JsonMask* mask) { 653 | int idx = start + this->SkipSpaces(raw, start), n = raw.size(); 654 | if (raw[idx] != '\"') throw JsonException(*this, raw, idx); 655 | std::stringstream ss; 656 | for (++idx; idx < n && raw[idx] != '\"'; ++idx) ss << raw[idx]; 657 | if (idx >= n) throw JsonException(*this, raw, idx); 658 | this->string_ = ss.str(); 659 | return ++idx - start; 660 | } 661 | 662 | void JString::Print(std::ostream& os) const { 663 | os << '\"' << this->string_ << '\"'; 664 | } 665 | 666 | 667 | 668 | class JArray : public clone::Template { 669 | public: 670 | typedef std::vector Array; 671 | JArray(); 672 | Array& GetArray(); 673 | const Array& GetArray() const; 674 | 675 | protected: 676 | virtual const std::string& Name() const override; 677 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) override; 678 | virtual void Print(std::ostream& os) const override; 679 | 680 | private: 681 | Array array_; 682 | }; // class JString 683 | 684 | #define OBJECT_ARRAY_CHECKER [](const std::string& raw, int start) { return raw[start] == '['; } 685 | REGIST_JSON_OBJECT(JArray, OBJECT_ARRAY_CHECKER, 4); 686 | #undef OBJECT_ARRAY_CHECKER 687 | 688 | JArray::JArray() {} 689 | 690 | JArray::Array& JArray::GetArray() { 691 | return this->array_; 692 | } 693 | 694 | const JArray::Array& JArray::GetArray() const { 695 | return this->array_; 696 | } 697 | 698 | const std::string& JArray::Name() const { 699 | static std::string name("Array"); 700 | return name; 701 | } 702 | 703 | int JArray::DoParse(const std::string& raw, int start, JsonMask* mask) { 704 | int idx = start + this->SkipSpaces(raw, start), n = raw.size(); 705 | if (raw[idx] != '[') throw JsonException(*this, raw, idx); 706 | idx += this->SkipSpaces(raw, idx + 1) + 1; 707 | if (raw[idx] == ']') return ++idx - start; 708 | for (; idx < n; ++idx) { 709 | int len; 710 | this->array_.emplace_back(raw, idx, len, mask, false); 711 | idx += len; 712 | idx += this->SkipSpaces(raw, idx); 713 | if (raw[idx] != ',' && raw[idx] != ']') throw JsonException(*this, raw, idx); 714 | if (raw[idx] == ']') return ++idx - start; 715 | } 716 | throw JsonException(*this, raw, idx); 717 | return idx - start; 718 | } 719 | 720 | void JArray::Print(std::ostream& os) const { 721 | os << '['; 722 | for (int i = 0, n = this->array_.size(); i < n; ++i) { 723 | if (i != 0) os << ','; 724 | os << this->array_[i]; 725 | } 726 | os << ']'; 727 | } 728 | 729 | 730 | 731 | class JDict : public clone::Template { 732 | public: 733 | JDict(); 734 | JDict(JDict&& dict); 735 | JDict(const JDict& dict); 736 | JDict& operator = (const JDict& dict); 737 | 738 | template 739 | bool Have(const _T& key) { 740 | return this->keys_.find(ToString(key)) != this->keys_.end(); 741 | } 742 | template 743 | bool Remove(const _T& key) { 744 | auto k = ToString(key); 745 | auto find = this->keys_.find(k); 746 | if (find == this->keys_.end()) return false; 747 | this->vals_.erase(find->second); 748 | this->keys_.erase(find); 749 | return true; 750 | } 751 | template 752 | bool Add(_T&& key, Json&& v = JNull()) { 753 | auto k = ToString(key); 754 | auto find = this->keys_.find(k); 755 | if (find != this->keys_.end()) return false; 756 | JString jkey(k); 757 | this->keys_.emplace( 758 | std::move(k), 759 | this->vals_.emplace(this->vals_.end(), 760 | std::move(jkey), 761 | std::forward(v) 762 | ) 763 | ); 764 | return true; 765 | } 766 | bool Add(Json&& key, Json&& v = JNull()) { 767 | auto k = ToString(key); 768 | auto find = this->keys_.find(k); 769 | if (find != this->keys_.end()) return false; 770 | this->keys_.emplace( 771 | std::move(k), 772 | this->vals_.emplace(this->vals_.end(), 773 | std::forward(key), 774 | std::forward(v) 775 | ) 776 | ); 777 | return true; 778 | } 779 | template 780 | Json& operator [] (const _T& key) { 781 | auto find = this->keys_.find(ToString(key)); 782 | if (find == this->keys_.end()) throw JsonException(*this, "Key not find."); 783 | return find->second->second; 784 | } 785 | template 786 | const Json& operator [] (const _T& key) const { 787 | auto find = this->keys_.find(ToString(key)); 788 | if (find == this->keys_.end()) throw JsonException(*this, "Key not find."); 789 | return find->second->second; 790 | } 791 | void Until(std::function cb) { 792 | for (auto ite = this->vals_.begin(); ite != this->vals_.end(); ++ite) { 793 | if (cb(ite->first, ite->second)) break; 794 | } 795 | } 796 | void Until(std::function cb) const { 797 | for (auto ite = this->vals_.begin(); ite != this->vals_.end(); ++ite) { 798 | if (cb(ite->first, ite->second)) break; 799 | } 800 | } 801 | void ForEach(std::function cb) { 802 | for (auto ite = this->vals_.begin(); ite != this->vals_.end(); ++ite) { 803 | cb(ite->first, ite->second); 804 | } 805 | } 806 | void ForEach(std::function cb) const { 807 | for (auto ite = this->vals_.begin(); ite != this->vals_.end(); ++ite) { 808 | cb(ite->first, ite->second); 809 | } 810 | } 811 | 812 | protected: 813 | virtual const std::string& Name() const override; 814 | virtual int DoParse(const std::string& raw, int start, JsonMask* mask) override; 815 | virtual void Print(std::ostream& os) const override; 816 | 817 | private: 818 | template 819 | static std::string ToString(const _T& v) { 820 | std::stringstream ss; 821 | ss << v; 822 | return ss.str(); 823 | } 824 | std::string ToString(const std::string& v) { 825 | return v; 826 | } 827 | 828 | std::map>::iterator> keys_; 829 | std::list> vals_; 830 | }; // class JDict 831 | 832 | #define OBJECT_DICT_CHECKER [](const std::string& raw, int start) { return raw[start] == '{'; } 833 | REGIST_JSON_OBJECT(JDict, OBJECT_DICT_CHECKER, 5); 834 | #undef OBJECT_DICT_CHECKER 835 | 836 | JDict::JDict() {} 837 | 838 | JDict::JDict(JDict&& dict) : 839 | keys_(std::move(dict.keys_)), 840 | vals_(std::move(dict.vals_)) 841 | { 842 | this->SetPosition(dict.GetPosistion()); 843 | } 844 | 845 | JDict::JDict(const JDict& dict) { 846 | *this = dict; 847 | } 848 | 849 | JDict& JDict::operator = (const JDict& dict) { 850 | this->SetPosition(dict.GetPosistion()); 851 | this->vals_ = dict.vals_; 852 | for (auto ite = this->vals_.begin(); ite != this->vals_.end(); ++ite) { 853 | this->keys_.emplace(ToString(ite->first), ite); 854 | } 855 | return *this; 856 | } 857 | 858 | const std::string& JDict::Name() const { 859 | static std::string name("Object"); 860 | return name; 861 | } 862 | 863 | int JDict::DoParse(const std::string& raw, int start, JsonMask* mask) { 864 | int idx = start + this->SkipSpaces(raw, start), n = raw.size(); 865 | if (raw[idx] != '{') throw JsonException(*this, raw, idx); 866 | idx += this->SkipSpaces(raw, idx + 1) + 1; 867 | if (raw[idx] == '}') return ++idx - start; 868 | for (; idx < n; ++idx) { 869 | int len; 870 | Json key(raw, idx, len, mask, false); 871 | idx += len; 872 | idx += this->SkipSpaces(raw, idx); 873 | if (raw[idx] == ':') { 874 | ++idx; 875 | Json val(raw, idx, len, mask, false); 876 | if (!this->Add(std::move(key), std::move(val))) throw JsonException(*this, raw, idx - 2, "Existed key."); 877 | idx += len; 878 | } 879 | else if (raw[idx] == ',' || raw[idx] == '}') { 880 | if (!this->Add(std::move(key), JNull())) throw JsonException(*this, raw, idx - 2, "Existed key."); 881 | } 882 | idx += this->SkipSpaces(raw, idx); 883 | if (raw[idx] != ',' && raw[idx] != '}') throw JsonException(*this, raw, idx); 884 | if (raw[idx] == '}') return ++idx - start; 885 | } 886 | throw JsonException(*this, raw, idx); 887 | return idx - start; 888 | } 889 | 890 | void JDict::Print(std::ostream& os) const { 891 | os << '{'; 892 | int idx = 0; 893 | for (auto ite = this->vals_.begin(); ite != this->vals_.end(); ++ite, ++idx) { 894 | if (idx != 0) os << ','; 895 | os << ite->first; 896 | if (!ite->second.Is()) { 897 | os << ':' << ite->second; 898 | } 899 | } 900 | os << '}'; 901 | } 902 | 903 | 904 | // post implemention 905 | int Json::Parse(const std::string& raw, int start, JsonMask* mask, bool all) { 906 | auto flag = [&mask](int m) -> bool { return mask == NULL || (mask->Mask() & m); }; 907 | if (this->obj_ != NULL) delete this->obj_; 908 | this->obj_ = NULL; 909 | int idx = start, n = raw.size(); 910 | while (idx < n && raw[idx] == ' ') ++idx; 911 | if (idx >= n) throw JsonException(*this, raw, idx); 912 | 913 | ObjectManager::Until([&](const ObjectManager::Api& api) -> bool { 914 | if (flag(api.mask) && api.checker(raw, idx)) { 915 | Object* obj = api.constructor(); 916 | try { 917 | obj->SetPosition(idx); 918 | int len = obj->Parse(raw, idx, mask ? mask->Next() : NULL); 919 | if (len <= 0) { 920 | throw JsonException(*this, raw, idx, 921 | std::string("Invalid JSON parse result with parser = ") 922 | + obj->GetName() + "." 923 | ); 924 | } 925 | idx += len; 926 | this->obj_ = obj; 927 | } 928 | catch (JsonException& e) { 929 | delete obj; 930 | throw e; 931 | } 932 | return true; 933 | } 934 | return false; 935 | }); 936 | if (this->obj_ == NULL) throw JsonException(*this, raw, idx, "Invalid JSON format."); 937 | if (all) { 938 | while (idx < n && raw[idx] == ' ') ++idx; 939 | if (idx < n) throw JsonException(*this, raw, idx, "Invalid JSON format."); 940 | } 941 | return idx - start; 942 | } 943 | 944 | 945 | 946 | //quick APIs 947 | template 948 | Json Create(_ARGS&&... args) { 949 | return Json::Create<_T>(args...); 950 | } 951 | 952 | } // namespace json 953 | 954 | } // namespace lc 955 | 956 | #endif --------------------------------------------------------------------------------