├── .travis.yml ├── images ├── OctaveDebugger.gif ├── OctaveDebuggerIcon.png └── ConvertCapture.sh ├── tests ├── env.m ├── hello.m ├── TestMyClass.m ├── SuperSimple.m ├── SecondaryTestFile.m ├── MyClass.m ├── strings.m ├── TestOctaveDebugger.m └── .vscode │ └── launch.json ├── appveyor.yml ├── .gitignore ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── tslint.json ├── .vscodeignore ├── tsconfig.json ├── src ├── Variables │ ├── Bool.ts │ ├── Range.ts │ ├── LazyIndex.ts │ ├── ComplexDiagonalMatrix.ts │ ├── Scalar.ts │ ├── Matrix │ │ ├── MatrixData.ts │ │ └── MatrixParser.ts │ ├── Function.ts │ ├── Scope.ts │ ├── UnknownType.ts │ ├── ClassDef.ts │ ├── Variable.ts │ ├── Cell.ts │ ├── Struct.ts │ ├── ScalarStruct.ts │ ├── SparseMatrix.ts │ ├── String.ts │ ├── Variables.ts │ └── Matrix.ts ├── Constants.ts ├── Utils │ ├── Interval.ts │ └── fsutils.ts ├── OctaveLogger.ts ├── tests │ ├── String.test.ts │ └── Matrix.test.ts ├── extension.ts ├── StackFramesManager.ts ├── Commands.ts ├── CommandList.ts ├── Expression.ts ├── Breakpoints.ts └── Runtime.ts ├── LICENSE.txt ├── development.txt ├── webpack.config.js ├── ThirdPartyNotices.txt ├── package.json ├── readme.md └── CHANGELOG.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7.9" 4 | sudo: false 5 | -------------------------------------------------------------------------------- /images/OctaveDebugger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulo-fernando-silva/vscOctaveDebugger/HEAD/images/OctaveDebugger.gif -------------------------------------------------------------------------------- /images/OctaveDebuggerIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulo-fernando-silva/vscOctaveDebugger/HEAD/images/OctaveDebuggerIcon.png -------------------------------------------------------------------------------- /tests/env.m: -------------------------------------------------------------------------------- 1 | printf('FOO: "%s"\n', getenv('FOO')); 2 | printf('VAR: "%s"\n', getenv('VAR')); 3 | printf('PATH: "%s"\n', getenv('PATH')); 4 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - ps: Install-Product node 7.9 x64 3 | 4 | build_script: 5 | - npm install 6 | 7 | test_script: 8 | - npm test 9 | -------------------------------------------------------------------------------- /tests/hello.m: -------------------------------------------------------------------------------- 1 | function hello() 2 | %myFun - Description 3 | % 4 | % Syntax: hello(str) 5 | % 6 | % Long description 7 | test_function("Hello world"); 8 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | out/ 4 | npm-debug.log 5 | OctaveDebugger.txt 6 | *.vsix 7 | octave-workspace 8 | dist/ 9 | .vscode-test/ 10 | /tests/subtests/ -------------------------------------------------------------------------------- /tests/TestMyClass.m: -------------------------------------------------------------------------------- 1 | N = 10; 2 | mc = MyClass(N); 3 | 4 | for i = 1:N 5 | mc.add(i * i); 6 | end 7 | 8 | assert(mc.full()); 9 | 10 | for i = 1:mc.used() 11 | printf("mc[%d] = %d\n", i, mc.get(i)); 12 | end 13 | 14 | printf("the end"); 15 | -------------------------------------------------------------------------------- /images/ConvertCapture.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ffmpeg -y -t 34 -i input.mov -vf fps=2,scale=800:-1:flags=lanczos,palettegen palette.png 3 | ffmpeg -t 34 -i input.mov -i palette.png -filter_complex "fps=2,scale=800:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif -------------------------------------------------------------------------------- /tests/SuperSimple.m: -------------------------------------------------------------------------------- 1 | x = rand(1, 1, 10); 2 | w = rand(1, 1, 256); 3 | s = rand(100, 2, 256); 4 | X_train = rand(10000, 1); 5 | z = 11; 6 | y = 1:5; 7 | k = 2; 8 | t = 1:k:z; 9 | a = 1:5; 10 | b = a; 11 | 12 | printf('foo'); 13 | printf('bar'); 14 | -------------------------------------------------------------------------------- /tests/SecondaryTestFile.m: -------------------------------------------------------------------------------- 1 | function SecondaryTestFile(input) 2 | printf('SecondaryTestFile\n'); 3 | y = 500; 4 | SecondaryTestFile2ndFunction(); 5 | end 6 | 7 | 8 | function SecondaryTestFile2ndFunction() 9 | printf('SecondaryTestFile2ndFunction\n'); 10 | end -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings.FALSE 2 | { 3 | "javascript.validate.enable": false, 4 | "typescript.tsdk": "node_modules/typescript/lib", 5 | "files.trimTrailingWhitespace": true, 6 | "editor.insertSpaces": false 7 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-unused-expression": true, 4 | "no-duplicate-variable": true, 5 | "curly": true, 6 | "class-name": true, 7 | "semicolon": [ "always" ], 8 | "triple-equals": true, 9 | "no-var-keyword": true, 10 | "no-bitwise": true 11 | } 12 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .vscode/ 3 | bin/ 4 | out/tests/ 5 | src/ 6 | tests/ 7 | tsconfig.json 8 | tslint.json 9 | webpack.config.js 10 | .gitignore 11 | .travis.yml 12 | appveyor.yml 13 | **/*.js.map 14 | images/ConvertCapture.sh 15 | octave-workspace 16 | TODO.txt 17 | vsce 18 | **/test 19 | # For webpack 20 | node_modules 21 | out/ 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "identifier": "npm", 7 | "script": "watch", 8 | "problemMatcher": [ 9 | "$tsc-watch" 10 | ], 11 | "isBackground": true, 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | 6 | "noImplicitAny": false, 7 | "removeComments": false, 8 | "noUnusedLocals": true, 9 | "noImplicitThis": true, 10 | "inlineSourceMap": false, 11 | "sourceMap": true, 12 | "outDir": "./out", 13 | "preserveConstEnums": true, 14 | "strictNullChecks": true, 15 | "noUnusedParameters": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/MyClass.m: -------------------------------------------------------------------------------- 1 | classdef MyClass < handle 2 | properties 3 | M = []; 4 | N = 0; 5 | end 6 | methods 7 | function o = MyClass(N) 8 | if nargin > 0 9 | o.N = 0; 10 | o.M = zeros(N, 1); 11 | end 12 | end 13 | 14 | function add(o, x) 15 | if o.used() < o.size() 16 | o.M(++o.N) = x; 17 | end 18 | end 19 | 20 | function n = get(o, i) 21 | if i <= o.used() 22 | n = o.M(i); 23 | else 24 | n = NaN; 25 | end 26 | end 27 | 28 | function n = used(o) 29 | n = o.N; 30 | end 31 | 32 | function n = size(o) 33 | n = size(o.M, 1); 34 | end 35 | 36 | function b = full(o) 37 | b = o.used() == o.size(); 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /src/Variables/Bool.ts: -------------------------------------------------------------------------------- 1 | import { Scalar } from './Scalar'; 2 | 3 | export class Bool extends Scalar { 4 | //************************************************************************** 5 | constructor( 6 | name: string = '', 7 | value: string = '' 8 | ) 9 | { 10 | super(name, value); 11 | } 12 | 13 | 14 | //************************************************************************** 15 | public typename(): string { return 'bool'; } 16 | 17 | 18 | //************************************************************************** 19 | public loads(type: string): boolean { return type === this.typename(); } 20 | 21 | 22 | //************************************************************************** 23 | public createConcreteType( 24 | name: string, 25 | value: string 26 | ): Bool 27 | { 28 | return new Bool(name, value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Paulo Fernando Silva 2 | 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/Constants.ts: -------------------------------------------------------------------------------- 1 | //************************************************************************** 2 | // Unfortunately not mouch point here as package.json requires literals of these values. :( 3 | // But at least they won't get triplicated... 4 | export const MODULE_NAME = 'vsc-octave-debugger'; 5 | export const DEBUGGER_TYPE = 'OctaveDebugger'; 6 | export const DEFAULT_EXECUTABLE = 'octave-cli'; 7 | export const LANGUAGE = 'matlab'; 8 | export const CHUNKS_SIZE = 100; 9 | export const CHUNKS_PREFETCH = 1; 10 | export const ROW_ELEMENTS_SEPARATOR = ' '; 11 | export const ROW_ELEMENTS_SEPARATOR_REGEX = / +/; 12 | export const COLUMN_ELEMENTS_SEPARATOR = '\n'; 13 | export const SIZE_SEPARATOR = 'x'; 14 | export const CTX_CONSOLE = 'repl'; 15 | export const CTX_WATCH = 'watch'; 16 | export const CTX_HOVER = 'hover'; 17 | export const EVAL_UNDEF = 'undefined'; 18 | export const ERROR_EXP = 'error:'; 19 | export const FIELDS_SEPARATOR = ROW_ELEMENTS_SEPARATOR; 20 | export const DEFAULT_LEFT = '('; 21 | export const DEFAULT_RIGHT = ')'; 22 | export const CELL_LEFT = '{'; 23 | export const CELL_RIGHT = '}'; 24 | export const eEXIT = 'exit'; 25 | export const eEND = 'end'; 26 | export const eERROR = 'error'; 27 | export const eBREAK = 'break'; 28 | -------------------------------------------------------------------------------- /development.txt: -------------------------------------------------------------------------------- 1 | 2 | ## High-Level Description of Inner Workings 3 | 4 | A debug session follows these steps 5 | * Debug session begin or step 6 | * Request stack frames comes in 7 | * Request scopes for selected frame comes in (usually 0, the top frame, but can go up to n where n is the current stack frames depth). 8 | * Request variables for current open scopes comes in (scope reference is only fetched if > 0) If scope is different than 0, then we need to do a `dbup` or `dbdown` before fetching the variables. 9 | * Request child variables as needed (child variable ref > 0) 10 | 11 | More information about vscode Debug Adapter Protocol can be found here [DAP](https://microsoft.github.io/debug-adapter-protocol/overview) and the [API](https://code.visualstudio.com/docs/extensionAPI/api-debugging), and information on publishing extensions can be found [here](https://code.visualstudio.com/docs/extensions/publish-extension#_publishers-and-personal-access-tokens). 12 | Funny fact, I noticed too late that the name of the plugin is not only spelled wrong but also it doesn't follow the expected "no caps and words separated by hyphens" pattern. :p 13 | 14 | This was removed from the readme, as it's more for plugin developers and might confuse the plugin user. -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "extensionHost", 6 | "request": "launch", 7 | "name": "Extension", 8 | "preLaunchTask": "npm", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceFolder}" 12 | ], 13 | "outFiles": [ "${workspaceFolder}/out/**/*.js" ] 14 | }, 15 | { 16 | "type": "node", 17 | "request": "launch", 18 | "name": "Server", 19 | "cwd": "${workspaceFolder}", 20 | "program": "${workspaceFolder}/src/OctaveDebugger.ts", 21 | "args": [ "--server=4711" ], 22 | "outFiles": [ "${workspaceFolder}/out/**/*.js" ] 23 | }, 24 | { 25 | "type": "node", 26 | "request": "launch", 27 | "name": "Unit Tests", 28 | "cwd": "${workspaceFolder}", 29 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 30 | "args": [ 31 | "-u", "bdd", 32 | "--timeout", "999999", 33 | "--colors", 34 | "./out/tests/", 35 | "--debug-brk" 36 | ], 37 | "outFiles": [ "${workspaceFolder}/out/**/*.js" ], 38 | "internalConsoleOptions": "openOnSessionStart" 39 | } 40 | ], 41 | "compounds": [ 42 | { 43 | "name": "Extension + Server", 44 | "configurations": [ "Extension", "Server" ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/Variables/Range.ts: -------------------------------------------------------------------------------- 1 | import { Matrix } from './Matrix'; 2 | 3 | /* 4 | * Class that adds support for range type, which is basically a vector/matrix. 5 | */ 6 | export class Range extends Matrix { 7 | /*************************************************************************** 8 | * @param name the variable name without indices. 9 | * @param value the contents of the variable. 10 | * @param freeIndices the number of elements in each dimension. 11 | * @param fixedIndices if this is a part of a larger matrix, the right-most 12 | * indices that are used to access this submatrix (one based). 13 | * @param validValue actually the variable content and not a placeholder? 14 | **************************************************************************/ 15 | constructor( 16 | name: string = '', 17 | value: string = '', 18 | freeIndices: Array = [], 19 | fixedIndices: Array = [], 20 | validValue: boolean = true 21 | ) 22 | { 23 | super(name, value, freeIndices, fixedIndices, validValue); 24 | } 25 | 26 | 27 | //************************************************************************** 28 | public typename(): string { return 'range'; } 29 | 30 | 31 | //************************************************************************** 32 | public createConcreteType( 33 | name: string, 34 | value: string, 35 | freeIndices: Array, 36 | fixedIndices: Array, 37 | validValue: boolean 38 | ): Range 39 | { 40 | return new Range(name, value, freeIndices, fixedIndices, validValue); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Variables/LazyIndex.ts: -------------------------------------------------------------------------------- 1 | import { Matrix } from './Matrix'; 2 | 3 | /* 4 | * Class that adds support for lazy_index type, which is basically a matrix. 5 | */ 6 | export class LazyIndex extends Matrix { 7 | /*************************************************************************** 8 | * @param name the variable name without indices. 9 | * @param value the contents of the variable. 10 | * @param freeIndices the number of elements in each dimension. 11 | * @param fixedIndices if this is a part of a larger matrix, the right-most 12 | * indices that are used to access this submatrix (one based). 13 | * @param validValue actually the variable content and not a placeholder? 14 | **************************************************************************/ 15 | constructor( 16 | name: string = '', 17 | value: string = '', 18 | freeIndices: Array = [], 19 | fixedIndices: Array = [], 20 | validValue: boolean = true 21 | ) 22 | { 23 | super(name, value, freeIndices, fixedIndices, validValue); 24 | } 25 | 26 | 27 | //************************************************************************** 28 | public typename(): string { return 'lazy_index'; } 29 | 30 | 31 | //************************************************************************** 32 | public createConcreteType( 33 | name: string, 34 | value: string, 35 | freeIndices: Array, 36 | fixedIndices: Array, 37 | validValue: boolean 38 | ): LazyIndex 39 | { 40 | return new LazyIndex(name, value, freeIndices, fixedIndices, validValue); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Variables/ComplexDiagonalMatrix.ts: -------------------------------------------------------------------------------- 1 | import { Matrix } from './Matrix'; 2 | 3 | /* 4 | * Class that adds support for complex diagonal matrices. 5 | * These need special treatment because they are considered Diagonal Matrices. 6 | */ 7 | export class ComplexDiagonalMatrix extends Matrix { 8 | private static TYPENAME: string = 'diagonal matrix'; 9 | /*************************************************************************** 10 | * @param name the variable name without indices. 11 | * @param value the contents of the variable. 12 | * @param freeIndices the number of elements in each dimension. 13 | * @param fixedIndices if this is a part of a larger matrix, the right-most 14 | * indices that are used to access this submatrix (one based). 15 | * @param validValue actually the variable content and not a placeholder? 16 | **************************************************************************/ 17 | constructor( 18 | name: string = '', 19 | value: string = '', 20 | freeIndices: Array = [], 21 | fixedIndices: Array = [], 22 | validValue: boolean = true, 23 | type: string = ComplexDiagonalMatrix.TYPENAME 24 | ) 25 | { 26 | super(name, value, freeIndices, fixedIndices, validValue, type); 27 | } 28 | 29 | 30 | //************************************************************************** 31 | public typename(): string { return `complex ${ComplexDiagonalMatrix.TYPENAME}`; } 32 | 33 | 34 | //************************************************************************** 35 | public createConcreteType( 36 | name: string, 37 | value: string, 38 | freeIndices: Array, 39 | fixedIndices: Array, 40 | validValue: boolean 41 | ): ComplexDiagonalMatrix 42 | { 43 | return new ComplexDiagonalMatrix(name, value, freeIndices, fixedIndices, validValue); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/strings.m: -------------------------------------------------------------------------------- 1 | % string: single long string, since it's less than 100 chars should still load 2 | str0 = "This is a very long string 0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 3 | % string: two childs, less than 100 chars per child, should load completely 4 | str1 = [ 5 | "this is a string", 6 | "this is another string" 7 | ]; 8 | % sq_string: two childs, less than 100 chars per child, should load completely 9 | str2 = char ( 10 | "an apple", 11 | "two pears" 12 | ); 13 | % sq_string: three childs, less than more than 100 chars, but less then 100 chars per child. 14 | % Should load on demand. 15 | str3 = char ( 16 | str2(1,:), 17 | str2(2,:), 18 | str0 19 | ); 20 | % string: more than 100 chars should only load on demand 21 | str4 = strcat(str0, str0); 22 | % string: more than 100 chars per child, should only load on demand 23 | str5 = [ 24 | str4, 25 | str4 26 | ]; 27 | % should load value and children 28 | str6 = [ 29 | repmat('0', 1, 33) 30 | repmat('1', 1, 33) 31 | repmat('2', 1, 33) 32 | ]; 33 | % should not load but should load children on request 34 | str7 = [ 35 | repmat('0', 1, 34) 36 | repmat('1', 1, 34) 37 | repmat('2', 1, 34) 38 | ]; 39 | % should load value and children: special case for spaces 40 | str8 = [ 41 | repmat(' ', 1, 33) 42 | repmat(' ', 1, 33) 43 | repmat(' ', 1, 33) 44 | ]; 45 | % should not load but should load children on request: special case for spaces 46 | str9 = [ 47 | repmat(' ', 1, 34) 48 | repmat(' ', 1, 34) 49 | repmat(' ', 1, 34) 50 | ]; 51 | % should not load value or children, should only load children range on request. 52 | str10 = [ 53 | repmat('0', 1, 101) 54 | repmat('1', 1, 101) 55 | ]; 56 | % should not load value or children, should only load children range on request. 57 | str11 = [ 58 | repmat('0', 1, 101) 59 | ]; 60 | % We're done here. This is only to give me a line to set a breakpoint 61 | disp("the end"); 62 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | //@ts-check 7 | 8 | 'use strict'; 9 | 10 | const path = require('path'); 11 | 12 | /**@type {import('webpack').Configuration}*/ 13 | const config = { 14 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 15 | 16 | entry: { 17 | extension: './src/extension.ts' // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 18 | , OctaveDebugger: './src/OctaveDebugger.ts' 19 | }, 20 | output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 21 | path: path.resolve(__dirname, 'dist'), 22 | filename: '[name].js', 23 | libraryTarget: "commonjs2", 24 | devtoolModuleFilenameTemplate: "../[resource-path]", 25 | }, 26 | devtool: 'source-map', 27 | externals: { 28 | vscode: "commonjs vscode" // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 29 | }, 30 | resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 31 | extensions: ['.ts', '.js'] 32 | }, 33 | module: { 34 | rules: [{ 35 | test: /\.ts$/, 36 | exclude: /node_modules/, 37 | use: [{ 38 | loader: 'ts-loader' 39 | }] 40 | }] 41 | }, 42 | } 43 | 44 | module.exports = config; 45 | -------------------------------------------------------------------------------- /src/Utils/Interval.ts: -------------------------------------------------------------------------------- 1 | //************************************************************************** 2 | export class Interval { 3 | private _min = 0; 4 | private _max = 0; 5 | 6 | 7 | //************************************************************************** 8 | public constructor( 9 | min: number = 0, 10 | max: number = 0) 11 | { 12 | this._min = Math.min(min, max); 13 | this._max = max; 14 | } 15 | 16 | 17 | //************************************************************************** 18 | public intersect(interval: Interval): Interval { 19 | return new Interval( 20 | Math.max(interval.min(), this.min()), 21 | Math.min(interval.max(), this.max()) 22 | ); 23 | } 24 | 25 | 26 | //************************************************************************** 27 | public contains(point: number): boolean { 28 | return this.min() <= point && point <= this.max(); 29 | } 30 | 31 | 32 | //************************************************************************** 33 | public contigous(interval: Interval): boolean { 34 | return this.contains(interval.min()) 35 | || this.contains(interval.max()); 36 | } 37 | 38 | 39 | //************************************************************************** 40 | public expandWith(interval: Interval): void { 41 | this._min = Math.min(this._min, interval._min); 42 | this._max = Math.max(this._max, interval._max); 43 | } 44 | 45 | 46 | //************************************************************************** 47 | public min(): number { 48 | return this._min; 49 | } 50 | 51 | 52 | //************************************************************************** 53 | public max(): number { 54 | return this._max; 55 | } 56 | 57 | 58 | //************************************************************************** 59 | public size(): number { 60 | return this.max() - this.min(); 61 | } 62 | 63 | 64 | //************************************************************************** 65 | public empty(): boolean { 66 | return this.max() === this.min(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Variables/Scalar.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import { Variables } from './Variables'; 3 | import { Variable } from './Variable'; 4 | 5 | 6 | export class Scalar extends Variable { 7 | //************************************************************************** 8 | private static BASE_TYPE: string = 'scalar'; 9 | private _typename: string = Scalar.BASE_TYPE; 10 | 11 | 12 | //************************************************************************** 13 | constructor( 14 | name: string = '', 15 | value: string = '', 16 | type: string = Scalar.BASE_TYPE, 17 | ) 18 | { 19 | super(); 20 | this.setFullname(name); 21 | this._value = value; 22 | this._typename = type; 23 | } 24 | 25 | 26 | //************************************************************************** 27 | public typename(): string { return this._typename; } 28 | 29 | 30 | //************************************************************************** 31 | public extendedTypename(): string { return this.typename(); } 32 | 33 | 34 | //************************************************************************** 35 | public loads(type: string): boolean { 36 | return type.endsWith(this.typename()); 37 | } 38 | 39 | 40 | //************************************************************************** 41 | public createConcreteType( 42 | name: string, 43 | value: string, 44 | type: string 45 | ): Scalar 46 | { 47 | return new Scalar(name, value, type); 48 | } 49 | 50 | 51 | //************************************************************************** 52 | public loadNew( 53 | name: string, 54 | type: string, 55 | runtime: CommandInterface, 56 | callback: (s: Scalar) => void 57 | ): void 58 | { 59 | Variables.getValue(name, runtime, (value: string) => { 60 | callback(this.createConcreteType(name, value, type)); 61 | }); 62 | } 63 | 64 | 65 | //************************************************************************** 66 | public listChildren( 67 | runtime: CommandInterface, 68 | start: number, 69 | count: number, 70 | callback: (vars: Array) => void 71 | ): void 72 | {} // Scalars have no children. 73 | } 74 | -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 2 | Do Not Translate or Localize 3 | 4 | This project incorporates material from the project(s) listed below (collectively, “Third Party Code”). 5 | Microsoft is not the original author of the Third Party Code. The original copyright notice and license 6 | under which Microsoft received such Third Party Code are set out below. This Third Party Code is licensed 7 | to you under their original license terms set forth below. Microsoft reserves all other rights not 8 | expressly granted, whether by implication, estoppel or otherwise. 9 | 10 | 11 | 1. DefinitelyTyped version 0.0.1 (https://github.com/borisyankov/DefinitelyTyped) 12 | 13 | 14 | %% DefinitelyTyped NOTICES AND INFORMATION BEGIN HERE 15 | ========================================= 16 | This project is licensed under the MIT license. 17 | Copyrights are respective of each contributor listed at the beginning of each definition file. 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | ========================================= 37 | END OF DefinitelyTyped NOTICES AND INFORMATION 38 | -------------------------------------------------------------------------------- /src/Variables/Matrix/MatrixData.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Represents the parsed matrix data. 3 | * Assumes the data is in column order. 4 | * Use Transpose to switch between row and column major. 5 | */ 6 | export class MatrixData { 7 | // e.g. given ans(:,:,1,1), it becomes (:,:,1,1) 8 | private _indices: string; 9 | // The original string from where the values were parsed: 10 | private _value: string; 11 | // The matrix values: 12 | private _vectors: Array>; 13 | 14 | public constructor(indices: string, value: string, vectors: Array>) { 15 | this._indices = indices; 16 | this._value = value; 17 | this._vectors = vectors; 18 | } 19 | 20 | public indices(): string { 21 | return this._indices; 22 | } 23 | 24 | public value(): string { 25 | return this._value; 26 | } 27 | 28 | public vector(i: number): Array { 29 | return this._vectors[i]; 30 | } 31 | 32 | public size(): number { 33 | return this._vectors.length; 34 | } 35 | 36 | //************************************************************************** 37 | public transpose() { 38 | this._vectors = MatrixData.transpose(this._vectors); 39 | } 40 | 41 | //************************************************************************** 42 | public static transpose(vectors: Array>): Array> { 43 | const N_rows = vectors.length; 44 | 45 | if(N_rows === 0) 46 | return new Array>(); 47 | 48 | let rowValues = vectors[0]; 49 | const N_cols = rowValues.length; 50 | const transposed = new Array>(N_cols); 51 | 52 | for(let col = 0; col !== N_cols; ++col) { 53 | transposed[col] = new Array(N_rows); 54 | transposed[col][0] = rowValues[col]; 55 | } 56 | 57 | for(let row = 1; row !== N_rows; ++row) { 58 | rowValues = vectors[row]; 59 | 60 | if(rowValues.length !== N_cols) { 61 | // each row has to have the same number of columns 62 | throw `rowValues.length !== Ncols: ${rowValues.length} !== ${N_cols}`; 63 | } 64 | 65 | for(let col = 0; col !== N_cols; ++col) { 66 | transposed[col][row] = rowValues[col]; 67 | } 68 | } 69 | 70 | return transposed; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Variables/Function.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import { Variables } from './Variables'; 3 | import { Variable } from './Variable'; 4 | 5 | 6 | export class Function extends Variable { 7 | //************************************************************************** 8 | private static BASE_TYPE: string = 'function'; 9 | private _typename: string = Function.BASE_TYPE; 10 | 11 | 12 | //************************************************************************** 13 | constructor( 14 | name: string = '', 15 | value: string = '', 16 | type: string = Function.BASE_TYPE 17 | ) 18 | { 19 | super(); 20 | this.setFullname(name); 21 | this._value = value; 22 | this._typename = type; 23 | } 24 | 25 | 26 | //************************************************************************** 27 | public typename(): string { return this._typename; } 28 | 29 | 30 | //************************************************************************** 31 | public loads(type: string): boolean { 32 | return type.includes(this.typename()); 33 | } 34 | 35 | 36 | //************************************************************************** 37 | public extendedTypename(): string { return this.typename(); } 38 | 39 | 40 | //************************************************************************** 41 | public createConcreteType( 42 | name: string, 43 | value: string, 44 | type: string 45 | ): Function 46 | { 47 | return new Function(name, value, type); 48 | } 49 | 50 | 51 | //************************************************************************** 52 | public loadNew( 53 | name: string, 54 | type: string, 55 | runtime: CommandInterface, 56 | callback: (s: Function) => void 57 | ): void 58 | { 59 | Variables.getValue(name, runtime, (value: string) => { 60 | callback(this.createConcreteType(name, value, type)); 61 | }); 62 | } 63 | 64 | 65 | //************************************************************************** 66 | public listChildren( 67 | runtime: CommandInterface, 68 | start: number, 69 | count: number, 70 | callback: (vars: Array) => void 71 | ): void 72 | {} // Function have no children. 73 | } 74 | -------------------------------------------------------------------------------- /src/Variables/Scope.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import { Variables } from './Variables'; 3 | import { Variable } from './Variable'; 4 | 5 | //************************************************************************** 6 | // The idea here is that a Scope is a variable that contains other variables. 7 | export class Scope extends Variable { 8 | //************************************************************************** 9 | constructor(name: string) { 10 | super(); 11 | this.setFullname(name); 12 | // Always keep a reference to scopes. Even ones without children. 13 | Variables.addReferenceTo(this); 14 | } 15 | 16 | 17 | //************************************************************************** 18 | public typename(): string { return 'scope'; } 19 | 20 | 21 | //************************************************************************** 22 | public extendedTypename(): string { return this.typename(); } 23 | 24 | 25 | //************************************************************************** 26 | public loads(type: string): boolean { return false; } 27 | 28 | 29 | //************************************************************************** 30 | public loadNew( 31 | name: string, 32 | type: string, 33 | runtime: CommandInterface, 34 | callback: (s: Scope) => void 35 | ): void 36 | {} 37 | 38 | 39 | //************************************************************************** 40 | private static readonly HEADER_REGEX = /^\s*Variables .* the current scope:$/; 41 | private static readonly SPACE_REGEX = /\s+/; 42 | //************************************************************************** 43 | public listChildren( 44 | runtime: CommandInterface, 45 | start: number, 46 | count: number, 47 | callback: (vars: Array) => void 48 | ): void 49 | { 50 | let matchesHeader = false; 51 | let vars = ''; 52 | 53 | runtime.evaluate(`who ${this.name()}`, (output: string[]) => { 54 | output.forEach(line => { 55 | if(matchesHeader) { 56 | vars += ' ' + line; 57 | } else if(Scope.HEADER_REGEX.test(line)) { 58 | matchesHeader = true; 59 | } 60 | }); 61 | 62 | if(vars.length !== 0) { 63 | const names = vars.trim().split(Scope.SPACE_REGEX).sort(); 64 | Variables.listVariables(names, runtime, callback); 65 | } else { 66 | callback(new Array()); 67 | } 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/OctaveLogger.ts: -------------------------------------------------------------------------------- 1 | import { Logger, logger } from 'vscode-debugadapter'; 2 | import { timestamp } from './Utils/fsutils'; 3 | 4 | 5 | //****************************************************************************** 6 | export class OctaveLogger { 7 | public static logToConsole: boolean = false; 8 | public static outputEnabled: boolean = true; 9 | 10 | 11 | //************************************************************************** 12 | private static shouldOutput(str: string): boolean { 13 | return OctaveLogger.outputEnabled && str.trim().length !== 0; 14 | } 15 | 16 | 17 | //************************************************************************** 18 | public static setup( 19 | verbose: boolean | undefined, 20 | filename: string | undefined 21 | ): void 22 | { 23 | const logLevel = (verbose? Logger.LogLevel.Verbose : Logger.LogLevel.Log); 24 | OctaveLogger.outputEnabled = true; 25 | 26 | if(filename !== undefined) { 27 | logger.setup(logLevel, `${filename}${timestamp()}.txt`, true); 28 | } else { 29 | logger.setup(logLevel, false); 30 | } 31 | } 32 | 33 | 34 | //************************************************************************** 35 | public static verbose(str: string): void { 36 | if(this.shouldOutput(str)) { 37 | if(OctaveLogger.logToConsole) { 38 | console.log(str); 39 | } 40 | logger.verbose(str); 41 | } 42 | } 43 | 44 | 45 | //************************************************************************** 46 | public static log(str: string): void { 47 | if(this.shouldOutput(str)) { 48 | if(OctaveLogger.logToConsole) { 49 | console.log(str); 50 | } 51 | logger.log(str); 52 | } 53 | } 54 | 55 | 56 | //************************************************************************** 57 | public static warn(str: string): void { 58 | if(this.shouldOutput(str)) { 59 | if(OctaveLogger.logToConsole) { 60 | console.warn(str); 61 | } 62 | logger.warn(str); 63 | } 64 | } 65 | 66 | 67 | //************************************************************************** 68 | public static error(str: string): void { 69 | if(this.shouldOutput(str)) { 70 | if(OctaveLogger.logToConsole) { 71 | console.error(str); 72 | } 73 | logger.error(str); 74 | } 75 | } 76 | 77 | 78 | //************************************************************************** 79 | // aliases used to differentiate certain types of communication 80 | //************************************************************************** 81 | public static debug(str: string): void { 82 | OctaveLogger.verbose(str); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Utils/fsutils.ts: -------------------------------------------------------------------------------- 1 | import { parse, sep } from 'path'; 2 | import { DebugProtocol } from 'vscode-debugprotocol'; 3 | import * as fs from 'fs'; 4 | 5 | const MATLAB_EXT = '.m'; 6 | const PKG_REGEX = new RegExp(`(\\+.+[\\${sep}]?)*$`); 7 | 8 | 9 | //************************************************************************** 10 | export const functionFromPath = (path: string): string => { 11 | // check if it's indeed a path, and if so return the filename 12 | // TODO: check if file exists? 13 | if(path.endsWith(MATLAB_EXT)) { 14 | const parsed = parse(path); 15 | var fcn = parsed.name; 16 | const match = parsed.dir.match(PKG_REGEX); 17 | // If we have a package, let's add it to the name: 18 | if(match !== null && match.length === 2 && match[1] !== undefined) { 19 | // The capture starts with +, remove it by taking the substring. 20 | // Then, eeplace all the groups of /+ by a . 21 | var pkg = match[1].substring(1).replace(`${sep}+`, '.'); 22 | // Merge package path and function name together: 23 | fcn = `${pkg}.${fcn}`; 24 | } 25 | 26 | return fcn; 27 | } 28 | return path; 29 | }; 30 | 31 | 32 | //************************************************************************** 33 | export const isMatlabFile = (source: DebugProtocol.Source): boolean => { 34 | if(source.path !== undefined) { 35 | return source.path.endsWith(MATLAB_EXT); 36 | } 37 | 38 | return source.name !== undefined && source.name.endsWith(MATLAB_EXT); 39 | }; 40 | 41 | 42 | //************************************************************************** 43 | export const validDirectory = (dir: string): boolean => { 44 | try { 45 | if(dir !== undefined && dir !== '' && dir !== '.') { 46 | fs.accessSync(dir, fs.constants.F_OK); 47 | return true; 48 | } 49 | } catch (err) {}; 50 | return false; 51 | }; 52 | 53 | 54 | //************************************************************************** 55 | function pad(n: number, size: number, c: string = '0'): string { 56 | let str: string = n.toString(); 57 | 58 | if(str.length < size) { 59 | const required: number = size - str.length; 60 | str = (c.repeat(required) + str).substr(-size); 61 | } 62 | 63 | return str; 64 | } 65 | 66 | 67 | //************************************************************************** 68 | export const timestamp = (): string => { 69 | const now = new Date(); 70 | 71 | const YYYY = now.getFullYear(); 72 | const MM = pad(now.getMonth(), 2); 73 | const DD = pad(now.getDate(), 2); 74 | 75 | const hh = pad(now.getHours(), 2); 76 | const mm = pad(now.getMinutes(), 2); 77 | const ss = pad(now.getSeconds(), 2); 78 | 79 | return `${YYYY}${MM}${DD}_${hh}${mm}${ss}`; 80 | } 81 | -------------------------------------------------------------------------------- /src/tests/String.test.ts: -------------------------------------------------------------------------------- 1 | import { OctaveLogger } from '../OctaveLogger'; 2 | import { String } from '../Variables/String'; 3 | import * as assert from 'assert'; 4 | import * as Constants from '../Constants'; 5 | import { Runtime } from '../Runtime'; 6 | 7 | 8 | describe('Test String', function() { 9 | describe('String.setString/getString', async function() { 10 | const factory = new String(); 11 | const name = 'str'; 12 | const value = "foo\\nbar"; 13 | const expectedValue = "foo\nbar"; 14 | // Set a very large string of 100 0's and 100 1's 15 | const setZerosAndOnes = `${name} = [ repmat('0', 1, 100); repmat('1', 1, 100) ];`; 16 | const child0Value = '0'.repeat(100); 17 | const child1Value = '1'.repeat(100); 18 | const child0Name = `${name}(1,:)`; 19 | const child1Name = `${name}(2,:)`; 20 | 21 | let runtime; 22 | let line: String; 23 | let array: String; 24 | let children: String[]; 25 | 26 | before((done) => { 27 | OctaveLogger.logToConsole = false; 28 | runtime = new Runtime(Constants.DEFAULT_EXECUTABLE, [], {}, ['.'], '.', true, false); 29 | 30 | const cmd = `${name} = "${value}";`; 31 | runtime.evaluateAsLine(cmd, (output: string) => { 32 | factory.loadNew(name, "string", runtime, (s: String) => { 33 | line = s; 34 | runtime.execute(setZerosAndOnes); 35 | factory.loadNew(name, "string", runtime, (s: String) => { 36 | array = s; 37 | array.listChildren(runtime, 0, 0, (vars: String[]) => { 38 | children = vars; 39 | done(); 40 | }); 41 | }); 42 | }); 43 | }) 44 | }); 45 | 46 | describe('String load children', function() { 47 | it(`Name should be ${name}`, function() { 48 | assert.equal(line.name(), name); 49 | }); 50 | it(`Should have value ${expectedValue} child variables`, function() { 51 | assert.equal(line.value(), expectedValue); 52 | }); 53 | it(`Children should not be null`, function() { 54 | assert(children !== null); 55 | }); 56 | it(`Should have 2 children`, function() { 57 | assert.equal(children.length, 2); 58 | }); 59 | it(`Child 0's name should be ${child0Name}`, function() { 60 | assert.equal(children[0].name(), child0Name); 61 | }); 62 | it(`Child 0's value should be ${child0Value[0]} ${child0Value.length}x`, function() { 63 | assert.equal(children[0].value(), child0Value); 64 | }); 65 | it(`Child 1's name should be ${child1Name}`, function() { 66 | assert.equal(children[1].name(), child1Name); 67 | }); 68 | it(`Child 1's value should be ${child1Value[0]} ${child1Value.length}x`, function() { 69 | assert.equal(children[1].value(), child1Value); 70 | }); 71 | }); 72 | 73 | after(() => { 74 | runtime.disconnect(); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | *--------------------------------------------------------*/ 4 | 'use strict'; 5 | 6 | import * as vscode from 'vscode'; 7 | import { 8 | WorkspaceFolder, 9 | DebugConfiguration, 10 | ProviderResult, 11 | CancellationToken 12 | } from 'vscode'; 13 | import * as Constants from './Constants'; 14 | 15 | 16 | //****************************************************************************** 17 | export function activate(context: vscode.ExtensionContext) { 18 | context.subscriptions.push(vscode.commands.registerCommand( 19 | 'extension.' + Constants.MODULE_NAME + '.getProgramName', config => { 20 | return vscode.window.showInputBox({ 21 | placeHolder: "Enter the name of a Octave or Matlab function to debug", 22 | }); 23 | })); 24 | 25 | // register a configuration provider for the module debug type 26 | context.subscriptions.push( 27 | vscode.debug.registerDebugConfigurationProvider( 28 | Constants.MODULE_NAME, 29 | new OctaveDebuggerConfigurationProvider())); 30 | } 31 | 32 | 33 | //****************************************************************************** 34 | export function deactivate() { 35 | // nothing to do 36 | } 37 | 38 | 39 | //****************************************************************************** 40 | class OctaveDebuggerConfigurationProvider implements vscode.DebugConfigurationProvider { 41 | undef(value: any) { 42 | return value === undefined || value === null; 43 | } 44 | 45 | /** 46 | * Massage a debug configuration just before a debug session is being launched, 47 | * e.g. add all missing attributes to the debug configuration. 48 | */ 49 | resolveDebugConfiguration( 50 | folder: WorkspaceFolder | undefined, 51 | config: DebugConfiguration, 52 | token?: CancellationToken 53 | ) : ProviderResult 54 | { 55 | const editor = vscode.window.activeTextEditor; 56 | if(editor && editor.document.languageId === Constants.LANGUAGE) { 57 | if(this.undef(config.type)) { config.type = Constants.DEBUGGER_TYPE; } 58 | if(this.undef(config.name)) { config.name = 'Debug ${file}'; } 59 | if(this.undef(config.request)) { config.request = 'launch'; } 60 | if(this.undef(config.program)) { config.program = '${file}'; } 61 | if(this.undef(config.octave)) { config.octave = Constants.DEFAULT_EXECUTABLE; } 62 | if(this.undef(config.sourceFolder)) { config.sourceFolder = '${workspaceFolder}'; } 63 | if(this.undef(config.autoTerminate)) { config.autoTerminate = true; } 64 | if(this.undef(config.octaveArguments)) { config.octaveArguments = []; } 65 | if(this.undef(config.octaveEnvironment)){ config.octaveEnvironment = {}; } 66 | if(this.undef(config.shell)) { config.shell = true; } 67 | } 68 | 69 | return config; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/TestOctaveDebugger.m: -------------------------------------------------------------------------------- 1 | function TestOctaveDebugger() 2 | %TestOctaveDebugger - tests for VSC Octave debugger plugin. 3 | % 4 | % Syntax: TestOctaveDebugger() 5 | % 6 | % Tests breakpoints set in: 7 | % - comments 8 | % - nested functions 9 | % - nested functions defined before the breakpoint 10 | % - nested functions defined after the breakpoint 11 | % - functions defined in other files 12 | % - conditional breakpoints 13 | % Todo: 14 | % - Currently struct fieldnames can't contain either the fieldname pattern when in Octave style 15 | % 16 | % Octave supports two types of strings. https://octave.org/doc/v5.1.0/Strings.html 17 | sq_str = 'foo''bar''foo''bar' 18 | str = "foo\nbar" 19 | mds = char ("an apple", "two pears") 20 | long_str = repmat(str, 1, 100); 21 | % this will only work when launch contains "splitFieldnamesOctaveStyle": true 22 | % https://octave.org/doc/v5.1.0/Creating-Structures.html 23 | % otherwise fieldnames are expected in Matlab style, i.e. only \w\d or _ 24 | % https://www.mathworks.com/help/matlab/matlab_prog/generate-field-names-from-variables.html 25 | st = struct('', 1, 'b', 2, ' ', 3, ' ', 4, '\n', 5, ' [2,1] = ', 6, '1ab_2', 7, 'ab_2', 8); 26 | cm = 100 * magic(2) + i; 27 | cdm = diag([1:10]+i); 28 | dg_mt = diag([1:20]); 29 | mm = magic(20); 30 | permMat = eye(3)(1:3,:); 31 | 32 | s = struct('f', zeros(101,1)); 33 | ss = [ s s; s s ]; 34 | 35 | int8_ = int8(1); uint8_ = uint8(1); 36 | int16_ = int16(1); uint16_ = uint16(1); 37 | int32_ = int32(1); uint32_ = uint32(1); 38 | int64_ = int64(1); uint64_ = uint64(1); 39 | float_ = single(1); double_ = double(1); 40 | b = logical(1); 41 | f_inl = inline ('x^2 + 2'); 42 | f_hnd = @bsxfun; 43 | printf('hello '); 44 | printf('World\n'); 45 | x = 10; 46 | y = [1*x 2*x; 3*x 4*x]; 47 | yy = [y y]; 48 | sm = sparse([1 2 3], [4 5 6], [-10.2, 5.0, 101]) 49 | csm = sparse([1 2 3], [4 5 6], [-10.2 + i, 5.0, 101]) 50 | lsm = sparse(1:400, 201:600, magic(20)(:)); 51 | manyRowsMatrix = rand(1000, 1); 52 | manyColumnsMatrix = rand(3,9,1) * (1 + i); 53 | testNestedFunction(); 54 | aReallyLongVariableName = 0; 55 | s11 = struct('a', 1, 'b', 1); 56 | s12 = struct('a', 1, 'b', 2); 57 | s21 = struct('a', 2, 'b', 1); 58 | s22 = struct('a', 2, 'b', 2); 59 | a = [s11 s12; s21 s22]; 60 | end 61 | 62 | 63 | function testNestedFunctionDefinedBeforeCaller() 64 | printf('testNestedFunctionDefinedBeforeBreakpoint\n'); 65 | end 66 | 67 | 68 | function testNestedFunctionLevel2() 69 | printf('testNestedFunctionLevel2\n'); 70 | testNestedFunctionDefinedBeforeCaller(); 71 | testNestedFunctionDefinedAfterCaller(); 72 | SecondaryTestFile(); 73 | end 74 | 75 | 76 | function testNestedFunctionDefinedAfterCaller() 77 | printf('testNestedFunctionDefinedAfterBreakpoint\n'); 78 | end 79 | 80 | 81 | function testNestedFunction() 82 | testNestedFunctionLevel2(); 83 | x = 20; 84 | printf('testNestedFunction\n'); 85 | end 86 | -------------------------------------------------------------------------------- /src/Variables/UnknownType.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import * as Constants from '../Constants'; 3 | import { Variables } from './Variables'; 4 | import { Variable } from './Variable'; 5 | 6 | 7 | export class UnknownType extends Variable { 8 | //************************************************************************** 9 | private _typename: string = 'UnknownType'; 10 | private _extendedTypename: string; 11 | private _size: Array; 12 | 13 | 14 | //************************************************************************** 15 | constructor( 16 | name: string = '', 17 | value: string = '', 18 | type: string = '', 19 | size: Array = [], 20 | ) 21 | { 22 | super(); 23 | this.setFullname(name); 24 | this._value = value; 25 | // Use original typename before changing it. 26 | this._extendedTypename = 27 | `${this._typename}: ${type} ${size.join(Constants.SIZE_SEPARATOR)}`; 28 | this._typename = type; 29 | this._size = size; 30 | } 31 | 32 | 33 | //************************************************************************** 34 | public typename(): string { return this._typename; } 35 | 36 | 37 | //************************************************************************** 38 | public extendedTypename(): string { return this._extendedTypename; } 39 | 40 | 41 | //************************************************************************** 42 | public loads(type: string): boolean { 43 | return type.length !== 0; // this loads every non empty type 44 | } 45 | 46 | 47 | //************************************************************************** 48 | public size(): Array { return this._size; } 49 | 50 | 51 | //************************************************************************** 52 | public createConcreteType( 53 | name: string, 54 | value: string, 55 | type: string, 56 | size: Array 57 | ): UnknownType 58 | { 59 | return new UnknownType(name, value, type, size); 60 | } 61 | 62 | 63 | //************************************************************************** 64 | public loadNew( 65 | name: string, 66 | type: string, 67 | runtime: CommandInterface, 68 | callback: (u: UnknownType) => void 69 | ): void 70 | { 71 | Variables.getSize(name, runtime, (size: Array) => { 72 | const loadable = Variables.loadable(size); 73 | 74 | const buildWith = (value: string) => { 75 | const v = this.createConcreteType(name, value, type, size); 76 | callback(v); 77 | }; 78 | 79 | if(loadable) { 80 | Variables.getValue(name, runtime, buildWith); 81 | } else { 82 | buildWith(size.join(Constants.SIZE_SEPARATOR)); 83 | } 84 | }); 85 | } 86 | 87 | 88 | //************************************************************************** 89 | public listChildren( 90 | runtime: CommandInterface, 91 | start: number, 92 | count: number, 93 | callback: (vars: Array) => void 94 | ): void 95 | {} // UnknownType have no children. 96 | } 97 | -------------------------------------------------------------------------------- /tests/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 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 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "OctaveDebugger", 9 | "request": "launch", 10 | "name": "Execute selected file.", 11 | "program": "${file}", 12 | "octave": "octave-gui", 13 | "sourceFolder": "${workspaceFolder}", 14 | "debugServer": 4711 15 | }, 16 | { 17 | "type": "OctaveDebugger", 18 | "request": "launch", 19 | "name": "Execute selected file - interactive", 20 | "program": "${file}", 21 | "octave": "octave-gui", 22 | "sourceFolder": "${workspaceFolder}", 23 | "octaveArguments": [ "--interactive" ], 24 | "autoTerminate": false, 25 | "debugServer": 4711 26 | }, 27 | { 28 | "type": "OctaveDebugger", 29 | "request": "launch", 30 | "name": "Interactive Mode", 31 | "program": "", 32 | "octave": "octave-cli", 33 | "autoTerminate": false 34 | }, 35 | { 36 | "type": "OctaveDebugger", 37 | "request": "launch", 38 | "name": "Debug Octave fieldnames", 39 | "program": "TestOctaveDebugger", 40 | "splitFieldnamesOctaveStyle": true, 41 | "octave": "octave-cli", 42 | "sourceFolder": "${workspaceFolder}", 43 | "debugServer": 4711 44 | }, 45 | { 46 | "type": "OctaveDebugger", 47 | "request": "launch", 48 | "name": "TestEnv", 49 | "program": "env.m", 50 | "octave": "octave-cli", 51 | "sourceFolder": "${workspaceFolder}", 52 | "octaveEnvironment": { "FOO": "bar", "VAR": "XPTO" }, 53 | "shell": false, 54 | "autoTerminate": true 55 | }, 56 | { 57 | "type": "OctaveDebugger", 58 | "request": "launch", 59 | "name": "Environment Check", 60 | "program": "printf('FOO: \"%s\"\n', getenv('PATH'))", 61 | "shell": true, 62 | "octave": "export FOO=bar; octave-cli", 63 | "workingDirectory": "${workspaceFolder}", 64 | "autoTerminate": true 65 | }, 66 | { 67 | "type": "OctaveDebugger", 68 | "request": "launch", 69 | "name": "Echo Commands", 70 | "program": "env.m", 71 | "octave": "octave-cli", 72 | "octaveEnvironment": { "FOO": "bar", "VAR": "XPTO" }, 73 | "octaveArguments": [ "--echo-commands" ], 74 | "workingDirectory": "${workspaceFolder}", 75 | "autoTerminate": true 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/Variables/ClassDef.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import * as Constants from '../Constants'; 3 | import { Variables } from './Variables'; 4 | import { Variable } from './Variable'; 5 | 6 | 7 | export class ClassDef extends Variable { 8 | //************************************************************************** 9 | private _fields: Array; 10 | private _children: Array; 11 | 12 | 13 | //************************************************************************** 14 | constructor( 15 | name: string = '', 16 | fields: Array = [] 17 | ) 18 | { 19 | super(); 20 | this.setFullname(name); 21 | this._value = fields.join(Constants.FIELDS_SEPARATOR); 22 | this._fields = fields; 23 | this._numberOfChildren = this._fields.length; 24 | if(this._numberOfChildren !== 0) { 25 | this._children = new Array(); 26 | Variables.addReferenceTo(this); 27 | } 28 | } 29 | 30 | 31 | //************************************************************************** 32 | public typename(): string { return 'object'; } 33 | 34 | 35 | //************************************************************************** 36 | public extendedTypename(): string { return this.typename(); } 37 | 38 | 39 | //************************************************************************** 40 | public loads(type: string): boolean { return type === this.typename(); } 41 | 42 | 43 | //************************************************************************** 44 | public createConcreteType( 45 | name: string, 46 | fields: Array 47 | ): ClassDef 48 | { 49 | return new ClassDef(name, fields); 50 | } 51 | 52 | 53 | //************************************************************************** 54 | public loadNew( 55 | name: string, 56 | type: string, 57 | runtime: CommandInterface, 58 | callback: (obj: ClassDef) => void 59 | ): void 60 | { 61 | ClassDef.getProperties(name, runtime, (fields: Array) => { 62 | const obj = this.createConcreteType(name, fields); 63 | callback(obj); 64 | }); 65 | } 66 | 67 | 68 | //************************************************************************** 69 | public listChildren( 70 | runtime: CommandInterface, 71 | start: number, 72 | count: number, 73 | callback: (vars: Array) => void 74 | ): void 75 | { 76 | const self = this; 77 | this._fields.forEach(name => { 78 | Variables.loadVariable(name, runtime, (v: Variable) => { 79 | self._children.push(v); 80 | 81 | if(self._children.length === self._numberOfChildren) { 82 | callback(self._children.slice(start, start+count)); 83 | } 84 | }); 85 | }); 86 | } 87 | 88 | 89 | //************************************************************************** 90 | public static getProperties( 91 | name: string, 92 | runtime: CommandInterface, 93 | callback: (f: Array) => void 94 | ): void 95 | { 96 | let fieldnames = new Array(); 97 | runtime.evaluateAsLine(`properties(${name})`, (output: string) => { 98 | output = output.replace(/properties for class .+:/, ''); 99 | const fields = output.split(/\s+/).filter((x) => x); 100 | 101 | fields.forEach(field => { 102 | fieldnames.push(`${name}.${field}`); 103 | }); 104 | 105 | callback(fieldnames); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/StackFramesManager.ts: -------------------------------------------------------------------------------- 1 | import { OctaveLogger } from './OctaveLogger'; 2 | import { StackFrame, Source } from 'vscode-debugadapter'; 3 | import { CommandInterface } from './Commands'; 4 | 5 | 6 | export class StackFramesManager { 7 | private _frame: number = 0; 8 | 9 | 10 | //************************************************************************** 11 | public constructor() {} 12 | 13 | 14 | //************************************************************************** 15 | public clear(): void { 16 | this._frame = 0; 17 | } 18 | 19 | 20 | //************************************************************************** 21 | public selectStackFrame( 22 | frame: number, 23 | runtime: CommandInterface, 24 | callback: () => void 25 | ): void 26 | { 27 | if(frame === this._frame) { 28 | callback(); 29 | } else { 30 | const validation = (output: string[]) => { 31 | this.vadidateTransition(output, frame); 32 | callback(); 33 | }; 34 | // Make sure the frame offset is positive: 35 | if(frame < this._frame) { 36 | runtime.evaluate(`dbdown ${this._frame - frame}`, validation); 37 | } else { 38 | runtime.evaluate(`dbup ${frame - this._frame}`, validation); 39 | } 40 | } 41 | } 42 | 43 | 44 | //************************************************************************** 45 | private static readonly TRANSITION_REGEX = /^\s*(?:stopped in.*)|(?:at top level)$/; 46 | /* Expecting something like: 47 | 'stopped in foom at line 88 [/path/file.m] ' 48 | OR: 49 | 'at top level' 50 | */ 51 | private vadidateTransition(output: string[], frame: number): void { 52 | if(output.length !== 0) { 53 | if(output[0].match(StackFramesManager.TRANSITION_REGEX)) { 54 | this._frame = frame; 55 | } else { 56 | OctaveLogger.warn(`vadidateTransition:unknown output[0] "${output[0]}"!`); 57 | } 58 | } else { 59 | OctaveLogger.warn(`vadidateTransition:output.length: ${output.length}!`); 60 | } 61 | } 62 | 63 | 64 | //************************************************************************** 65 | private static readonly STACK_REGEX = /^\s*(?:-->)?\s*(\w+)(?:>(\S+))*? at line (\d+) \[(.*)\]$/; 66 | /* Example of the expected stack output: 67 | stopped in: 68 | 69 | --> TestOctaveDebugger>main3 at line 15 [/path/TestOctaveDebugger.m] 70 | TestOctaveDebugger>@ at line 33 [/path/TestOctaveDebugger.m] 71 | TestOctaveDebugger at line 11 [/path/TestOctaveDebugger.m] 72 | debug> 73 | */ 74 | public get( 75 | startFrame: number, 76 | endFrame: number, 77 | runtime: CommandInterface, 78 | callback: (frames: Array) => void 79 | ): void 80 | { 81 | const stackframes = new Array(); 82 | runtime.evaluate('dbstack;', (output: string[]) => { 83 | output.forEach(line => { 84 | const match = line.match(StackFramesManager.STACK_REGEX); 85 | 86 | if(match !== null && match.length > 1) { 87 | const functionName = match[match.length - 3]; 88 | const name = (functionName !== undefined? functionName : match[1]); 89 | const id = stackframes.length; 90 | const source = new Source(name, match[match.length - 1]); 91 | const lineNumber = parseInt(match[match.length - 2]); 92 | const frame = new StackFrame(id, name, source, lineNumber); 93 | stackframes.push(frame); 94 | } 95 | }); 96 | 97 | callback(stackframes); 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Commands.ts: -------------------------------------------------------------------------------- 1 | import { validDirectory } from './Utils/fsutils'; 2 | 3 | 4 | export interface CommandInterface { 5 | //************************************************************************** 6 | evaluateAsLine(expression: string, callback: (line: string) => void): void; 7 | 8 | //************************************************************************** 9 | evaluate(expression: string, callback: (lines: string[]) => void): void; 10 | 11 | //************************************************************************** 12 | execute(expression: string): void; 13 | } 14 | 15 | 16 | // Implements common runtime commands, so they can be used with CommandInterface: 17 | export class Commands { 18 | //************************************************************************** 19 | private static _pathsep : string; 20 | 21 | 22 | //************************************************************************** 23 | private static escape(path: string): string { 24 | return path.replace(/([\\"])/g, "\\$1"); 25 | } 26 | 27 | 28 | //************************************************************************** 29 | public static addFolder(ci: CommandInterface, sourceFolder: string): void { 30 | // We can pass multiple directories here separated by pathsep() 31 | // This allows us to run code from anywhere on our HD. 32 | ci.execute(`addpath("${Commands.escape(sourceFolder)}")`); 33 | } 34 | 35 | 36 | //************************************************************************** 37 | public static addFolders(ci: CommandInterface, sourceFolders: string[]): void { 38 | // Grab pathsep, and concatenate source folders with it. 39 | this.getPathsep(ci, (pathsep : string) => { 40 | this.addFolder(ci, sourceFolders.join(pathsep)); 41 | }); 42 | } 43 | 44 | 45 | //************************************************************************** 46 | public static getPathsep(ci: CommandInterface, callback: (pathsep: string) => void): void { 47 | if(this._pathsep == undefined) { 48 | ci.evaluateAsLine('pathsep', (line: string) => { 49 | this._pathsep = Commands.getValue(line); 50 | callback(this._pathsep); 51 | }); 52 | } else { 53 | callback(this._pathsep); 54 | } 55 | } 56 | 57 | 58 | //************************************************************************** 59 | public static cwd(ci: CommandInterface, newWorkingDirectory: string): void { 60 | if(validDirectory(newWorkingDirectory)) { 61 | ci.execute(`cd "${Commands.escape(newWorkingDirectory)}"`); 62 | } 63 | } 64 | 65 | 66 | //************************************************************************** 67 | private static readonly CLEAN_REGEX = /^\s*ans =\s*/; 68 | public static getValue(str: string): string { 69 | return str.replace(Commands.CLEAN_REGEX, '').trim(); 70 | } 71 | 72 | 73 | //************************************************************************** 74 | public static pwd(ci: CommandInterface, callback: (workingDirectory: string) => void): void { 75 | ci.evaluateAsLine('pwd', (line: string) => { 76 | callback(Commands.getValue(line)); 77 | }); 78 | } 79 | 80 | 81 | //************************************************************************** 82 | // Executes callback in dir, and goes back to old working directory at the end. 83 | // Calling function is responsible for calling the popd when needed. 84 | public static pushd( 85 | ci: CommandInterface, 86 | dir: string, 87 | callback: (ci: CommandInterface, popd: (ci: CommandInterface) => void) => void 88 | ): void 89 | { 90 | Commands.pwd(ci, (oldWorkingDirectory: string) => { 91 | Commands.cwd(ci, dir); 92 | callback(ci, (ci: CommandInterface) => { 93 | Commands.cwd(ci, oldWorkingDirectory); 94 | }); 95 | }); 96 | } 97 | } -------------------------------------------------------------------------------- /src/Variables/Variable.ts: -------------------------------------------------------------------------------- 1 | import * as Constants from '../Constants'; 2 | import { CommandInterface } from '../Commands'; 3 | 4 | 5 | //****************************************************************************** 6 | // Variables representation of "Data Types" seen here: 7 | // https://www.mathworks.com/help/matlab/data-types_data-types.html 8 | // This class design is "somewhat" influenced by the DebugProtocol.Variable. 9 | // 10 | // I could choose to add an abstract method that would return the 11 | // DebugProtocol.Variable for the corresponding concrete type, 12 | // but that would coulpe the two APIs even more. 13 | export abstract class Variable { 14 | //************************************************************************** 15 | private _basename: string = ''; // name of the variable without indexing 16 | private _indexing: string = ''; // indices active in this variable 17 | protected _value: string = ''; 18 | protected _reference: number = 0; 19 | protected _namedVariables: number = 0; 20 | protected _numberOfChildren: number = 0; 21 | 22 | 23 | //************************************************************************** 24 | public abstract loadNew( 25 | name: string, 26 | type: string, 27 | runtime: CommandInterface, 28 | callback: (v: Variable) => void): void; 29 | 30 | 31 | //************************************************************************** 32 | public abstract listChildren( 33 | runtime: CommandInterface, 34 | start: number, 35 | count: number, 36 | callback: (vars: Array) => void): void; 37 | 38 | 39 | //************************************************************************** 40 | public abstract loads(type: string): boolean; 41 | 42 | 43 | //************************************************************************** 44 | public abstract typename(): string; 45 | 46 | 47 | //************************************************************************** 48 | public abstract extendedTypename(): string; 49 | 50 | 51 | //************************************************************************** 52 | public setFullname(name: string): void { 53 | // Assuming that indices start after a ({ and no other ({ exists before. 54 | var idx = name.indexOf(Constants.DEFAULT_LEFT); 55 | if(idx === -1) 56 | idx = name.indexOf(Constants.CELL_LEFT); 57 | if(idx !== -1) { 58 | this.setBasename(name.substring(0, idx)); 59 | this.setIndexing(name.substring(idx)); 60 | } else { 61 | this.setBasename(name); 62 | this.setIndexing(''); 63 | } 64 | } 65 | 66 | 67 | //************************************************************************** 68 | public setBasename(name: string): void { 69 | this._basename = name; 70 | } 71 | 72 | 73 | //************************************************************************** 74 | public setIndexing(idx: string): void { 75 | this._indexing = idx; 76 | } 77 | 78 | 79 | //************************************************************************** 80 | public basename(): string { return this._basename; } 81 | 82 | 83 | //************************************************************************** 84 | public indexing(): string { return this._indexing; } 85 | 86 | 87 | //************************************************************************** 88 | public name(): string { return this.basename() + this.indexing(); } 89 | 90 | 91 | //************************************************************************** 92 | public value(): string { return this._value; } 93 | 94 | 95 | //************************************************************************** 96 | public reference(): number { return this._reference; } 97 | 98 | 99 | //************************************************************************** 100 | public namedVariables(): number { return this._namedVariables; } 101 | 102 | 103 | //************************************************************************** 104 | public indexedVariables(): number { return this._numberOfChildren; } 105 | 106 | 107 | //************************************************************************** 108 | public setReference(ref: number): void { this._reference = ref; } 109 | } 110 | -------------------------------------------------------------------------------- /src/Variables/Cell.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import * as Constants from '../Constants'; 3 | import { Variables } from './Variables'; 4 | import { Variable } from './Variable'; 5 | import { Matrix } from './Matrix'; 6 | 7 | 8 | // Cell: another array type. 9 | export class Cell extends Matrix { 10 | //************************************************************************** 11 | private static CELL_TYPENAME: string = 'cell'; 12 | 13 | 14 | /*************************************************************************** 15 | * @param name the variable name without indices. 16 | * @param value the contents of the variable. 17 | * @param freeIndices the number of elements in each dimension. 18 | * @param fixedIndices if this is a part of a larger matrix, the right-most 19 | * indices that are used to access this submatrix (one based). 20 | **************************************************************************/ 21 | constructor( 22 | name: string = '', 23 | value: string = '', 24 | freeIndices: Array = [], 25 | fixedIndices: Array = [], 26 | validValue: boolean = false, 27 | type: string = Cell.CELL_TYPENAME 28 | ) 29 | { 30 | super(name, value, freeIndices, fixedIndices, validValue, type); 31 | } 32 | 33 | 34 | //************************************************************************** 35 | public typename(): string { return Cell.CELL_TYPENAME; } 36 | 37 | 38 | //************************************************************************** 39 | public loads(type: string): boolean { return type === this.typename(); } 40 | 41 | 42 | //************************************************************************** 43 | public createConcreteType( 44 | name: string, 45 | value: string, 46 | freeIndices: Array, 47 | fixedIndices: Array, 48 | validValue: boolean, 49 | type: string 50 | ): Cell 51 | { 52 | return new Cell(name, value, freeIndices, fixedIndices, validValue, type); 53 | } 54 | 55 | 56 | //************************************************************************** 57 | public loadNew( 58 | name: string, 59 | type: string, 60 | runtime: CommandInterface, 61 | callback: (s: Cell) => void 62 | ): void 63 | { 64 | Variables.getSize(name, runtime, (size: Array) => { 65 | const value = size.join(Constants.SIZE_SEPARATOR); 66 | const cell = this.createConcreteType(name, value, size, [], false, type); 67 | callback(cell); 68 | }); 69 | } 70 | 71 | 72 | //************************************************************************** 73 | public loacChild( 74 | runtime: CommandInterface, 75 | freeIndices: Array, 76 | fixedIndices: Array, 77 | callback: (v: Variable) => void 78 | ): void 79 | { 80 | const idx = this.fixIndices(freeIndices, fixedIndices); 81 | if(idx.free.length === 0) { 82 | // If there are no free indices then the variable can be laoded directly. 83 | const name = this.basename() + 84 | this.makeIndexing(idx.free, idx.fixed, Constants.CELL_LEFT, Constants.CELL_RIGHT); 85 | Variables.loadVariable(name, runtime, callback); 86 | } else { 87 | // When we have a free indices, the variable is still a cell 88 | const value = idx.free.join(Constants.SIZE_SEPARATOR); 89 | const cell = this.createConcreteType(this.basename(), value, idx.free, idx.fixed, false, Cell.CELL_TYPENAME); 90 | callback(cell); 91 | } 92 | } 93 | 94 | 95 | //************************************************************************** 96 | public loadChildrenRange( 97 | runtime: CommandInterface, 98 | offset: number, 99 | count: number, 100 | callback: (vars: Array) => void 101 | ): void 102 | { 103 | const vars = new Array(); 104 | const freeIndices = this.freeIndices(); 105 | const fixedIndices = this.fixedIndices(); 106 | const childrenFreeIndices = freeIndices.slice(0, freeIndices.length - 1); 107 | 108 | for(let i = 0; i !== count; ++i) { 109 | const childFixedIndices = [i + offset + 1].concat(fixedIndices); 110 | 111 | this.loacChild(runtime, childrenFreeIndices, childFixedIndices, (v: Variable) => { 112 | vars.push(v); 113 | 114 | if(vars.length === count) { 115 | callback(vars); 116 | } 117 | }); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Variables/Struct.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import { ScalarStruct } from './ScalarStruct'; 3 | import * as Constants from '../Constants'; 4 | import { Variables } from './Variables'; 5 | import { Variable } from './Variable'; 6 | import { Matrix } from './Matrix'; 7 | 8 | 9 | // This is actually an array much like a matrix. 10 | export class Struct extends Matrix { 11 | //************************************************************************** 12 | private static STRUCT_TYPENAME: string = 'struct'; 13 | private _fields: Array; 14 | 15 | 16 | /*************************************************************************** 17 | * @param name the variable name without indices. 18 | * @param value the contents of the variable. 19 | * @param freeIndices the number of elements in each dimension. 20 | * @param fixedIndices if this is a part of a larger matrix, the right-most 21 | * indices that are used to access this submatrix (one based). 22 | **************************************************************************/ 23 | constructor( 24 | name: string = '', 25 | value: string = '', 26 | freeIndices: Array = [], 27 | fixedIndices: Array = [] 28 | ) 29 | { 30 | super(name, value, freeIndices, fixedIndices, false, Struct.STRUCT_TYPENAME); 31 | this._fields = value.split(Constants.FIELDS_SEPARATOR); 32 | } 33 | 34 | 35 | //************************************************************************** 36 | private static remove(prefix: string, fields: Array): Array { 37 | const N = prefix.length; 38 | return fields.map((v: string) => v.substr(N)); 39 | } 40 | 41 | 42 | //************************************************************************** 43 | public typename(): string { return Struct.STRUCT_TYPENAME; } 44 | 45 | 46 | //************************************************************************** 47 | public loads(type: string): boolean { return type === this.typename(); } 48 | 49 | 50 | //************************************************************************** 51 | public createConcreteType( 52 | name: string, 53 | value: string, 54 | freeIndices: Array, 55 | fixedIndices: Array, 56 | validValue: boolean, 57 | type: string 58 | ): Struct 59 | { 60 | return new Struct(name, value, freeIndices, fixedIndices); 61 | } 62 | 63 | 64 | //************************************************************************** 65 | public loadNew( 66 | name: string, 67 | type: string, 68 | runtime: CommandInterface, 69 | callback: (s: Struct) => void 70 | ): void 71 | { 72 | ScalarStruct.getFields(name, runtime, (fields: Array) => { 73 | fields = Struct.remove(name, fields); 74 | const value = fields.join(Constants.FIELDS_SEPARATOR); 75 | 76 | Variables.getSize(name, runtime, (size: Array) => { 77 | const struct = this.createConcreteType(name, value, size, [], false, type); 78 | callback(struct); 79 | }); 80 | }); 81 | } 82 | 83 | 84 | //************************************************************************** 85 | public createChildType( 86 | value: string, 87 | freeIndices: Array, 88 | fixedIndices: Array, 89 | validValue: boolean 90 | ): Variable 91 | { 92 | let struct: Variable; 93 | 94 | if(freeIndices.length === 0) { 95 | // if there are no free indices then the variable is a scalar struct 96 | const name = this.basename() + this.makeIndexing(freeIndices, fixedIndices); 97 | const fields = this._fields.map((v: string) => name + v); 98 | struct = new ScalarStruct(name, fields); 99 | } else { 100 | // when we have free indices, the variable is still a struct 101 | struct = this.createConcreteType(this.basename(), value, freeIndices, fixedIndices, validValue, Struct.STRUCT_TYPENAME); 102 | } 103 | 104 | return struct; 105 | } 106 | 107 | 108 | //************************************************************************** 109 | public loadChildrenRange( 110 | runtime: CommandInterface, 111 | offset: number, 112 | count: number, 113 | callback: (vars: Array) => void 114 | ): void 115 | { 116 | const vars = new Array(count); 117 | const freeIndices = this.freeIndices(); 118 | const fixedIndices = this.fixedIndices(); 119 | const childrenFreeIndices = freeIndices.slice(0, freeIndices.length - 1); 120 | // Just to make sure the value didn't change, we create it again. 121 | const value = this._fields.join(Constants.FIELDS_SEPARATOR); 122 | 123 | for(let i = 0; i !== count; ++i) { 124 | const childFixedIndices = [i + offset + 1].concat(fixedIndices); 125 | vars[i] = this.createChildType(value, childrenFreeIndices, childFixedIndices, false); 126 | } 127 | 128 | callback(vars); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Variables/Matrix/MatrixParser.ts: -------------------------------------------------------------------------------- 1 | import { MatrixData } from './MatrixData'; 2 | 3 | /* 4 | * Contains the logic to parse matrices or all types into string array format. 5 | */ 6 | export class MatrixParser { 7 | //************************************************************************** 8 | public static cleanComplex(value: string): string { 9 | return value.replace(/(?:\s+([\+\-])\s+)/g, "$1"); 10 | } 11 | 12 | 13 | //************************************************************************** 14 | // Splits "123 456 789 0" or "123 + 456i 789 + 0i" into its elements 15 | // i.e. ["123", "456", "789", "0"] and ["123+456i", "789+0i"] respectively 16 | private static split(value: string, isComplex: boolean): Array { 17 | value = value.trim(); 18 | 19 | if(isComplex) { 20 | // Remove spaces in complex numbers 21 | value = MatrixParser.cleanComplex(value); 22 | } 23 | 24 | // split by spaces, and remove non-empty elements 25 | const elements = value.split(' ').filter(line => line); 26 | 27 | return elements; 28 | } 29 | 30 | //************************************************************************** 31 | // Skips lines like "Columns \d+ through \d+" or "Columns \d+ and \d+" 32 | // All the other lines are pushed onto the elements array. 33 | private static parseMatrixData(value: string, isComplex: boolean): Array> { 34 | const mutipleColumnsGroup = /Columns? \d+(?: ((?:through)|(?:and)?) \d+)?:/; 35 | const inLines = value.trim().split('\n').filter(line => line); // non-empty lines 36 | const N_lines = inLines.length; 37 | let elements = new Array>(); 38 | 39 | if(mutipleColumnsGroup.test(inLines[0])) { 40 | let line = 1; // skip mutipleColumnsGroup 41 | // Grab the elements of the first columns group, row by row. 42 | for(;line !== N_lines && !mutipleColumnsGroup.test(inLines[line]); ++line) { 43 | // push a new row of elements 44 | elements.push(MatrixParser.split(inLines[line], isComplex)); 45 | } 46 | if(line !== N_lines) { 47 | ++line; // skip mutipleColumnsGroup 48 | } 49 | // If it has more columns groups concatenate row elements with existing rows 50 | for(let row = 0; line !== N_lines; ++line) { 51 | if(mutipleColumnsGroup.test(inLines[line])) { 52 | row = 0; // reached a new column group: reset row index 53 | } else { 54 | // concatenate current row elements with an existing row 55 | Array.prototype.push.apply(elements[row++], MatrixParser.split(inLines[line], isComplex)); 56 | } 57 | } 58 | } else { 59 | for(let line = 0; line !== N_lines; ++line) { 60 | elements.push(MatrixParser.split(inLines[line], isComplex)); 61 | } 62 | } 63 | // We want to return an array that is indexed by column 64 | return MatrixData.transpose(elements); 65 | } 66 | 67 | //************************************************************************** 68 | // We want to to split value into multiple 2D matrix values. 69 | // That'S done by fiding the "ans(:,:) = " in value, e.g.: 70 | // Z = 71 | // 72 | // ans(:,:,1,1) = 73 | // 74 | // Columns 1 through 9: 75 | // 76 | // 0.2513 + 0.2513i 0.5183 + 0.5183i 0.8558 + 0.8558i ... 77 | // 78 | // Column 10: 79 | // ... 80 | // 81 | // ans(:,:,2,1) = 82 | // 83 | // Columns 1 through 9: ... 84 | private static parseMultipleMatrices(value: string, isComplex: boolean): Array { 85 | const valRegex = /\s*ans(\([^\)]+\)) =\s+([^]+?)\s+(ans\([^]+)/; 86 | const matrices = new Array(); 87 | // Split input value by its 2D matrix data: 88 | let tail = value; 89 | let match = tail.match(valRegex); 90 | 91 | while(match !== null && match.length === 4) { 92 | // match[0] is the whole matching expression 93 | const indices = match[1]; // match[1] is the indices of the matrix 94 | const val = match[2]; // match[2] is the value of that matrix 95 | tail = match[3]; // match[3] is the remaining string 96 | matrices.push(new MatrixData(indices, val, MatrixParser.parseMatrixData(val, isComplex))); 97 | match = tail.match(valRegex); 98 | } 99 | // The last matrix can't be matched by the above regex 100 | match = tail.match(/\s*ans(\([^\)]+\)) =\s+([^]+)/); 101 | if(match !== null && match.length === 3) { 102 | const indices = match[1]; // match[1] is the indices of the matrix 103 | const val = match[2]; // match[2] is the value of that matrix 104 | matrices.push(new MatrixData(indices, val, MatrixParser.parseMatrixData(val, isComplex))); 105 | } 106 | 107 | return matrices; 108 | } 109 | 110 | // Entry point for matrix parsing. Can parse any number of matrices: 111 | public static parseMatrices(value: string, isComplex: boolean): Array { 112 | // Do we have a multi-matrix or single matrix: 113 | const MULTI_MATRIX_REGEX = /\s*ans\([^\)]+\) =/; 114 | if(MULTI_MATRIX_REGEX.test(value)) { 115 | return MatrixParser.parseMultipleMatrices(value, isComplex); 116 | } 117 | // We might have only one matrix: 118 | const matrices = new Array(); 119 | matrices.push(new MatrixData("", value, MatrixParser.parseMatrixData(value, isComplex))); 120 | return matrices; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/CommandList.ts: -------------------------------------------------------------------------------- 1 | import * as Constants from './Constants'; 2 | import { Runtime } from './Runtime'; 3 | import { CommandInterface } from './Commands'; 4 | 5 | 6 | //************************************************************************** 7 | class ExecuteCommand { 8 | public id: number; 9 | public command: string; 10 | 11 | public constructor(id: number, cmd: string) { 12 | this.id = id; 13 | this.command = cmd; 14 | } 15 | 16 | public callback(output: string) {} 17 | }; 18 | 19 | 20 | //************************************************************************** 21 | class SingleLineOutputCommand extends ExecuteCommand { 22 | private cb: (output: string) => void; 23 | 24 | public constructor( 25 | id: number, 26 | cmd: string, 27 | cb: (output: string) => void 28 | ) 29 | { 30 | super(id, cmd); 31 | this.cb = cb; 32 | } 33 | 34 | public callback(output: string) { 35 | this.cb(output); 36 | } 37 | }; 38 | 39 | 40 | //************************************************************************** 41 | class MultilineOutputCommand extends ExecuteCommand { 42 | private cb: (output: string[]) => void; 43 | 44 | public constructor( 45 | id: number, 46 | cmd: string, 47 | cb: (output: string[]) => void 48 | ) 49 | { 50 | super(id, cmd); 51 | this.cb = cb; 52 | } 53 | 54 | public callback(output: string) { 55 | this.cb(output.split('\n')); 56 | } 57 | }; 58 | 59 | 60 | export class CommandList implements CommandInterface { 61 | //************************************************************************** 62 | //#region private 63 | //************************************************************************** 64 | private static readonly SYNC = `${Constants.MODULE_NAME}::cl`; 65 | 66 | //************************************************************************** 67 | private _recording = new Array(); 68 | private _executing = new Array(); 69 | private _id = 0; 70 | 71 | 72 | //************************************************************************** 73 | private close(): void { 74 | this._executing = this._recording; 75 | this._recording = new Array(); 76 | } 77 | 78 | 79 | //************************************************************************** 80 | public process(output: string): void { 81 | const lines = output.split(CommandList.SYNC); 82 | const cl = this._executing; 83 | 84 | // In practice we should always have at least as many syncs as commands. 85 | const N = Math.min(cl.length, lines.length); 86 | 87 | for(let i = 0; i !== N; ++i) { 88 | cl[i].callback(lines[i]); 89 | } 90 | } 91 | 92 | //************************************************************************** 93 | //#endregion 94 | //************************************************************************** 95 | 96 | //************************************************************************** 97 | //#region public 98 | //************************************************************************** 99 | public constructor(id: number) { 100 | this._id = id; 101 | } 102 | 103 | 104 | //************************************************************************** 105 | public record(command: ExecuteCommand): void { 106 | command.id = this._recording.length; 107 | this._recording.push(command); 108 | } 109 | 110 | 111 | //************************************************************************** 112 | public evaluateAsLine(expression: string, callback: (line: string) => void) { 113 | this.record(new SingleLineOutputCommand(0, expression, callback)); 114 | } 115 | 116 | 117 | //************************************************************************** 118 | public evaluate(expression: string, callback: (lines: string[]) => void) { 119 | this.record(new MultilineOutputCommand(0, expression, callback)); 120 | } 121 | 122 | 123 | //************************************************************************** 124 | public execute(expression: string) { 125 | this.record(new ExecuteCommand(0, expression)); 126 | } 127 | 128 | 129 | //************************************************************************** 130 | public executeCommandList(runtime: Runtime, callback: (cl: CommandList) => void) { 131 | this.close(); 132 | 133 | const cmds = this.commands(); 134 | 135 | runtime.evaluateAsLine(cmds, (output: string) => { 136 | this.process(output); 137 | callback(this); 138 | }); 139 | } 140 | 141 | 142 | //************************************************************************** 143 | public commands(): string { 144 | let cmds = ''; 145 | 146 | this._executing.forEach(cmd => 147 | cmds += `${cmd.command}\n${Runtime.echo(CommandList.SYNC)}` 148 | ); 149 | 150 | return cmds; 151 | } 152 | 153 | 154 | //************************************************************************** 155 | public empty(): boolean { 156 | return this._recording.length === 0; 157 | } 158 | 159 | 160 | //************************************************************************** 161 | public id(): number { 162 | return this._id; 163 | } 164 | 165 | //************************************************************************** 166 | //#endregion 167 | //************************************************************************** 168 | } 169 | -------------------------------------------------------------------------------- /src/Expression.ts: -------------------------------------------------------------------------------- 1 | import { Variables } from './Variables/Variables'; 2 | import { Variable } from './Variables/Variable'; 3 | import { CommandInterface } from './Commands'; 4 | import * as Constants from './Constants'; 5 | 6 | 7 | //****************************************************************************** 8 | export class Expression { 9 | //************************************************************************** 10 | private static readonly NAME_REGEX = /^([_A-Za-z]\w*).*$/; 11 | 12 | public static evaluate( 13 | expression: string, 14 | runtime: CommandInterface, 15 | ctx: string | undefined, 16 | callback: (info: string | undefined, ref: number) => void 17 | ): void 18 | { 19 | if(ctx === Constants.CTX_CONSOLE) { 20 | // Console just passes through. 21 | runtime.execute(expression); 22 | // We don't send anything back now as any output will be written on the console anyway. 23 | // This also avoids the issue with the pause, input, etc. 24 | callback('', 0); 25 | } else if(Expression.valid(expression)) { 26 | if(expression !== '') { 27 | // some function calls require variable name without indexing 28 | const variableName = Expression.parseName(expression); 29 | Expression.type(variableName, runtime, 30 | (value: string | undefined, type: string | undefined) => { 31 | if(value === undefined && type === undefined) { 32 | if(variableName.includes(":")) { // probably a range 33 | Expression.loadAsVariable(expression, runtime, callback); 34 | } else { 35 | callback(Constants.EVAL_UNDEF, 0); 36 | } 37 | } else if(ctx === Constants.CTX_HOVER || ctx === Constants.CTX_WATCH) { 38 | Expression.handleHover(expression, runtime, value, type, callback); 39 | } else { // and all the rest 40 | Expression.forceEvaluate(expression, runtime, callback); 41 | } 42 | } 43 | ); 44 | } else { 45 | callback(Constants.EVAL_UNDEF, 0); 46 | } 47 | } 48 | } 49 | 50 | 51 | //************************************************************************** 52 | private static valid(expression: string): boolean { 53 | return !expression.startsWith("'") 54 | && expression !== ":"; 55 | } 56 | 57 | 58 | //************************************************************************** 59 | public static handleHover( 60 | expression: string, 61 | runtime: CommandInterface, 62 | val: string | undefined, 63 | type: string | undefined, 64 | callback: (info: string | undefined, ref: number) => void 65 | ): void 66 | { 67 | // TODO: also skip comments 68 | if(type !== undefined && (type === 'file' || type === 'function')) { 69 | callback(val, 0); // Don't evaluate further to avoid side effects 70 | } else { 71 | Expression.loadAsVariable(expression, runtime, callback); 72 | } 73 | } 74 | 75 | 76 | //************************************************************************** 77 | public static loadAsVariable( 78 | expression: string, 79 | runtime: CommandInterface, 80 | callback: (info: string | undefined, ref: number) => void 81 | ): void 82 | { 83 | // Try fetching from cache: 84 | const ref = Variables.getReference(expression); 85 | 86 | if(ref == 0) { 87 | Variables.loadVariable(expression, runtime, (v: Variable | null) => { 88 | if(v === null) { 89 | if(expression.includes(":")) { 90 | callback(Constants.EVAL_UNDEF, 0); 91 | return; // Probably a range, but if it's null it can't be loaded. 92 | } 93 | Variables.getSize(expression, runtime, (size: Array) => { 94 | if(size.length === 0) { 95 | callback(Constants.EVAL_UNDEF, 0); 96 | } else if(Variables.loadable(size)) { 97 | Expression.forceEvaluate(expression, runtime, callback); 98 | } else { 99 | callback(size.join(Constants.SIZE_SEPARATOR), 0); 100 | } 101 | }); 102 | } else { 103 | callback(v.value(), v.indexedVariables() !== 0 ? v.reference() : 0); 104 | } 105 | }); 106 | } else { 107 | callback(Variables.getByReference(ref)?.value(), ref); 108 | } 109 | } 110 | 111 | 112 | //************************************************************************** 113 | public static forceEvaluate( 114 | expression: string, 115 | runtime: CommandInterface, 116 | callback: (info: string | undefined, ref: number) => void 117 | ): void 118 | { 119 | runtime.evaluateAsLine(expression, (output: string) => { 120 | callback(Variables.removeName(expression, output), 0); 121 | }); 122 | } 123 | 124 | 125 | //************************************************************************** 126 | private static parseName(expression: string): string { 127 | const match = expression.match(Expression.NAME_REGEX); 128 | if(match !== null) { 129 | return match[1]; 130 | } 131 | return expression; 132 | } 133 | 134 | //************************************************************************** 135 | public static type( 136 | expression: string, 137 | runtime: CommandInterface, 138 | callback: (val: string | undefined, type: string | undefined) => void 139 | ): void 140 | { 141 | const typeRegex = new RegExp(`^.*'${expression}' is (?:a|the) (?:built-in )?(\\S+).*$`); 142 | let val: string | undefined = undefined; 143 | let type: string | undefined = undefined; 144 | 145 | runtime.evaluate(`which ${expression}`, (output: string[]) => { 146 | output.forEach(line => { 147 | const match = line.match(typeRegex); 148 | 149 | if(match !== null) { 150 | val = line; 151 | type = match[1]; 152 | } else if(val === undefined && line.length !== 0) { 153 | val = ''; 154 | } 155 | }); 156 | 157 | callback(val, type); 158 | }); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Variables/ScalarStruct.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import * as Constants from '../Constants'; 3 | import { Variables } from './Variables'; 4 | import { Variable } from './Variable'; 5 | 6 | 7 | export class ScalarStruct extends Variable { 8 | //************************************************************************** 9 | private _fields: Array; 10 | private _children: Array; 11 | 12 | 13 | //************************************************************************** 14 | constructor( 15 | name: string = '', 16 | fields: Array = [] 17 | ) 18 | { 19 | super(); 20 | this.setFullname(name); 21 | this._value = fields.join(Constants.FIELDS_SEPARATOR); 22 | this._fields = fields; 23 | this._numberOfChildren = this._fields.length; 24 | if(this._numberOfChildren !== 0) { 25 | this._children = new Array(); 26 | Variables.addReferenceTo(this); 27 | } 28 | } 29 | 30 | 31 | //************************************************************************** 32 | public typename(): string { return 'scalar struct'; } 33 | 34 | 35 | //************************************************************************** 36 | public extendedTypename(): string { return this.typename(); } 37 | 38 | 39 | //************************************************************************** 40 | public loads(type: string): boolean { return type === this.typename(); } 41 | 42 | 43 | //************************************************************************** 44 | public createConcreteType( 45 | name: string, 46 | fields: Array 47 | ): ScalarStruct 48 | { 49 | return new ScalarStruct(name, fields); 50 | } 51 | 52 | 53 | //************************************************************************** 54 | public loadNew( 55 | name: string, 56 | type: string, 57 | runtime: CommandInterface, 58 | callback: (ss: ScalarStruct) => void 59 | ): void 60 | { 61 | ScalarStruct.getFields(name, runtime, (fields: Array) => { 62 | const struct = this.createConcreteType(name, fields); 63 | callback(struct); 64 | }); 65 | } 66 | 67 | 68 | //************************************************************************** 69 | public listChildren( 70 | runtime: CommandInterface, 71 | start: number, 72 | count: number, 73 | callback: (vars: Array) => void 74 | ): void 75 | { 76 | const self = this; 77 | this._fields.forEach(name => { 78 | Variables.loadVariable(name, runtime, (v: Variable) => { 79 | self._children.push(v); 80 | 81 | if(self._children.length === self._numberOfChildren) { 82 | callback(self._children.slice(start, start+count)); 83 | } 84 | }); 85 | }); 86 | } 87 | 88 | 89 | //************************************************************************** 90 | public static setSplitStyle(splitFieldnamesOctaveStyle: boolean): void { 91 | if(splitFieldnamesOctaveStyle) { 92 | ScalarStruct.split = ScalarStruct.splitOctaveStyle; 93 | } else { 94 | ScalarStruct.split = ScalarStruct.splitMatlabStyle; 95 | } 96 | } 97 | 98 | 99 | //************************************************************************** 100 | private static split: (value: string) => string[] = ScalarStruct.splitMatlabStyle; 101 | //************************************************************************** 102 | public static getFields( 103 | name: string, 104 | runtime: CommandInterface, 105 | callback: (f: Array) => void 106 | ): void 107 | { 108 | let fieldnames = new Array(); 109 | runtime.evaluateAsLine(`fieldnames(${name})`, (output: string) => { 110 | const fields = ScalarStruct.split(output); 111 | 112 | fields.forEach(field => { 113 | fieldnames.push(`${name}.${field}`); 114 | }); 115 | 116 | callback(fieldnames); 117 | }); 118 | } 119 | 120 | 121 | //************************************************************************** 122 | private static readonly CLEAN_REGEX = /^\{\n((?:.*|\s*)*)\n\}$/; 123 | private static readonly FIELDS_REGEX = /\n? \[\d+,1\] = /; 124 | private static readonly NON_WORD_REGEX = /\W/; 125 | //************************************************************************** 126 | // e.g. value = 127 | //ans = { 128 | // [1,1] = 129 | // [2,1] = b 130 | // [3,1] = 131 | // [4,1] = 132 | // [5,1] = \n 133 | //} 134 | private static splitOctaveStyle(value: string): string[] { 135 | value = Variables.clean(value); // remove ans = 136 | 137 | const match = value.match(ScalarStruct.CLEAN_REGEX); 138 | 139 | if(match !== null && match.length === 2) { 140 | value = match[1]; // remove {\n and }\n 141 | } 142 | 143 | const fields = value.split(ScalarStruct.FIELDS_REGEX); 144 | // Remove the first field because it's empty as there's nothing before [1,1]. 145 | fields.shift(); 146 | 147 | for(let i = 0; i !== fields.length; ++i) { 148 | const field = fields[i]; 149 | 150 | if(field.length === 0 || ScalarStruct.NON_WORD_REGEX.test(field)) { 151 | fields[i] = `('${field}')`; // escape fieldname 152 | } 153 | } 154 | 155 | return fields; 156 | } 157 | 158 | 159 | //************************************************************************** 160 | private static readonly FIELD_REGEX = /^[a-zA-Z]\w*$/; 161 | // Matlab is more restrictive, only \w are valid in fieldnames. 162 | // Furthermore, field names must begin with a letter. 163 | // https://www.mathworks.com/help/matlab/matlab_prog/generate-field-names-from-variables.html 164 | private static splitMatlabStyle(value: string): string[] { 165 | value = Variables.clean(value); // remove ans = 166 | 167 | const match = value.match(ScalarStruct.CLEAN_REGEX); 168 | 169 | if(match !== null && match.length === 2) { 170 | value = match[1]; // remove {\n and }\n 171 | } 172 | 173 | let fields = value.split(ScalarStruct.FIELDS_REGEX); 174 | // Remove the first field because it's empty as there's nothing before [1,1]. 175 | fields.shift(); 176 | 177 | fields = fields.filter(field => 178 | field.length !== 0 && ScalarStruct.FIELD_REGEX.test(field) 179 | ); 180 | 181 | return fields; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsc-octave-debugger", 3 | "displayName": "Octave Debugger", 4 | "version": "0.5.13", 5 | "publisher": "paulosilva", 6 | "description": "Debug Octave and Matlab code in Visual Studio Code.", 7 | "author": { 8 | "name": "paulo silva", 9 | "email": "paulo.fernando.silva@gmail.com" 10 | }, 11 | "keywords": [ 12 | "multi-root ready" 13 | ], 14 | "engines": { 15 | "vscode": "^1.66.0" 16 | }, 17 | "icon": "images/OctaveDebuggerIcon.png", 18 | "categories": [ 19 | "Debuggers" 20 | ], 21 | "private": true, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/paulo-fernando-silva/vscOctaveDebugger.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/paulo-fernando-silva/vscOctaveDebugger/issues" 28 | }, 29 | "dependencies": { 30 | "vscode-debugadapter": "^1.25.0-pre.0", 31 | "vscode-debugprotocol": "^1.25.0-pre.0" 32 | }, 33 | "devDependencies": { 34 | "@vscode/vsce": "^2.19.0", 35 | "@types/glob": "^8.0.0", 36 | "@types/mocha": "^10.0.0", 37 | "@types/node": "^6.14.9", 38 | "@types/vscode": "^1.17.0", 39 | "@vscode/debugadapter-testsupport": "^1.58.0", 40 | "@vscode/test-electron": "^2.2.0", 41 | "mocha": "^10.1.0", 42 | "ts-loader": "^9.4.1", 43 | "tslint": "^5.20.1", 44 | "typescript": "^4.2.3", 45 | "webpack": "^5.94.0", 46 | "webpack-cli": "^4.7.2" 47 | }, 48 | "scripts": { 49 | "prepare": "tsc -p ./", 50 | "compile": "tsc -p ./", 51 | "tslint": "tslint ./src/**/*.ts", 52 | "watch": "tsc -w -p ./", 53 | "package": "vsce package", 54 | "publish": "vsce publish", 55 | "vscode:prepublish": "webpack --mode production", 56 | "webpack": "webpack --mode development", 57 | "webpack-dev": "webpack --mode development --watch", 58 | "test-compile": "tsc -p ./" 59 | }, 60 | "main": "./dist/extension", 61 | "activationEvents": [ 62 | "onDebug", 63 | "onCommand:extension.vsc-octave-debugger.getProgramName" 64 | ], 65 | "contributes": { 66 | "breakpoints": [ 67 | { 68 | "language": "matlab" 69 | }, 70 | { 71 | "language": "octave" 72 | } 73 | ], 74 | "languages": [ 75 | { 76 | "id": "octave", 77 | "aliases": [ 78 | "Octave", 79 | "OCTAVE", 80 | "matlab", 81 | "Matlab", 82 | "MATLAB" 83 | ], 84 | "extensions": [ 85 | ".m" 86 | ] 87 | } 88 | ], 89 | "debuggers": [ 90 | { 91 | "type": "OctaveDebugger", 92 | "label": "Octave Debugger", 93 | "program": "./dist/OctaveDebugger.js", 94 | "runtime": "node", 95 | "languages": [ 96 | "matlab", 97 | "octave" 98 | ], 99 | "configurationAttributes": { 100 | "launch": { 101 | "required": [ 102 | "program" 103 | ], 104 | "properties": { 105 | "program": { 106 | "type": "string", 107 | "description": "Function or file to execute.", 108 | "default": "${command:AskForProgramName}" 109 | }, 110 | "octave": { 111 | "type": "string", 112 | "description": "Path to the octave-cli executable.", 113 | "default": "octave-cli" 114 | }, 115 | "sourceFolder": { 116 | "type": "string", 117 | "description": "Add all source under this folder. (Optional)", 118 | "default": "${workspaceFolder}" 119 | }, 120 | "workingDirectory": { 121 | "type": "string", 122 | "description": "Execute program from this directory. (Optional)", 123 | "default": "" 124 | }, 125 | "verbose": { 126 | "type": "boolean", 127 | "description": "Turn on debug messages. (Optional)", 128 | "default": false 129 | }, 130 | "splitFieldnamesOctaveStyle": { 131 | "type": "boolean", 132 | "description": "Enable fieldnames containing an almost arbitrary format. (Optional)", 133 | "default": false 134 | }, 135 | "logFilename": { 136 | "type": "string", 137 | "description": "Output log to file. Use absolute filename. (Optional)", 138 | "default": "${workspaceFolder}/log" 139 | }, 140 | "autoTerminate": { 141 | "type": "boolean", 142 | "description": "Defaults to true, i.e. Octave process will be terminated after the last program line is executed. Setting this to false will allow the program to continue executing. This is useful if you're running UI elements with callbacks and you want to continue debugging after the end of the program code. (Optional)", 143 | "default": true 144 | }, 145 | "octaveArguments": { 146 | "type": "array", 147 | "description": "Command line arguments to be passed to the octave process. (Optional)", 148 | "default": [] 149 | }, 150 | "octaveEnvironment": { 151 | "type": "object", 152 | "description": "Environment to be set when octave runs. Json key:value pair object, e.g. { 'FOO': 'bar' }. (Optional)", 153 | "default": {} 154 | }, 155 | "shell": { 156 | "type": "boolean", 157 | "description": "Run the octave process in a shell. Allows things like 'octave': 'export FOO=bar; octave-cli'. (Optional)", 158 | "default": true 159 | } 160 | } 161 | } 162 | }, 163 | "initialConfigurations": [ 164 | { 165 | "type": "OctaveDebugger", 166 | "request": "launch", 167 | "name": "Execute selected file.", 168 | "program": "${file}", 169 | "octave": "octave-cli", 170 | "sourceFolder": "${workspaceFolder}" 171 | } 172 | ], 173 | "configurationSnippets": [ 174 | { 175 | "label": "Octave Debugger: Launch", 176 | "description": "A new configuration for launching Octave.", 177 | "body": { 178 | "type": "OctaveDebugger", 179 | "request": "launch", 180 | "name": "Execute selected file.", 181 | "program": "^\"\\${file}\"", 182 | "octave": "octave-cli", 183 | "sourceFolder": "^\"\\${workspaceFolder}\"" 184 | } 185 | } 186 | ], 187 | "variables": { 188 | "AskForProgramName": "extension.vsc-octave-debugger.getProgramName" 189 | } 190 | } 191 | ] 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Variables/SparseMatrix.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import * as Constants from '../Constants'; 3 | import { Variables } from './Variables'; 4 | import { Variable } from './Variable'; 5 | import { Matrix } from './Matrix'; 6 | 7 | /* 8 | * Class that adds support for sparse matrix type. 9 | */ 10 | export class SparseMatrix extends Matrix { 11 | //************************************************************************** 12 | private static readonly TYPENAME_PREFIX: string = 'sparse'; 13 | private static readonly TYPENAME_SUFFIX: string = 'matrix'; 14 | private static readonly TYPENAME: string = 15 | `${SparseMatrix.TYPENAME_PREFIX} ${SparseMatrix.TYPENAME_SUFFIX}`; 16 | private static readonly TYPENAME_REGEX = 17 | new RegExp(`${SparseMatrix.TYPENAME_PREFIX} (?:\\w+ )?${SparseMatrix.TYPENAME_SUFFIX}`); 18 | //************************************************************************** 19 | private _indices: Array; 20 | 21 | 22 | /*************************************************************************** 23 | * @param name the variable name without indices. 24 | * @param value the contents of the variable. 25 | * @param freeIndices the number of elements in each dimension. 26 | * @param fixedIndices if this is a part of a larger matrix, the right-most 27 | * indices that are used to access this submatrix (one based). 28 | * @param validValue actually the variable content and not a placeholder? 29 | **************************************************************************/ 30 | constructor( 31 | name: string = '', 32 | value: string = '', 33 | freeIndices: Array = [], 34 | fixedIndices: Array = [], 35 | validValue: boolean = true, 36 | type: string = SparseMatrix.TYPENAME 37 | ) 38 | { 39 | super(name, value, freeIndices, fixedIndices, validValue, type); 40 | this._indices = new Array(this._numberOfChildren); 41 | } 42 | 43 | 44 | //************************************************************************** 45 | public indices(): Array { return this._indices; } 46 | 47 | 48 | //************************************************************************** 49 | public loads(type: string): boolean { 50 | return SparseMatrix.TYPENAME_REGEX.test(type); 51 | } 52 | 53 | 54 | //************************************************************************** 55 | public createConcreteType( 56 | name: string, 57 | value: string, 58 | freeIndices: Array, 59 | fixedIndices: Array, 60 | validValue: boolean, 61 | type: string 62 | ): SparseMatrix 63 | { 64 | return new SparseMatrix(name, value, freeIndices, fixedIndices, validValue, type); 65 | } 66 | 67 | 68 | //************************************************************************** 69 | public loadNew( 70 | name: string, 71 | type: string, 72 | runtime: CommandInterface, 73 | callback: (sm: SparseMatrix) => void) 74 | { 75 | Variables.getNonZero(name, runtime, (n: number) => { 76 | const size = [n]; 77 | const loadable = Variables.loadable(size); 78 | 79 | const buildWith = (value: string) => { 80 | const matrix = this.createConcreteType(name, value, size, [], loadable, type); 81 | if(loadable) { 82 | matrix.fetchIndices(runtime, 0, 0, () => { callback(matrix); }); 83 | } else { 84 | callback(matrix); 85 | } 86 | }; 87 | 88 | if(loadable) { 89 | Variables.getValue(name, runtime, buildWith); 90 | } else { 91 | buildWith(size.join(Constants.SIZE_SEPARATOR)); 92 | } 93 | }); 94 | } 95 | 96 | 97 | //************************************************************************** 98 | public fetchChildren( 99 | runtime: CommandInterface, 100 | offset: number, 101 | count: number, 102 | callback: (vars: Array) => void 103 | ): void 104 | { 105 | this.fetchIndices(runtime, offset, count, (fetchedIndices: Array) => { 106 | const begin = 1 + offset; 107 | const end = begin + count - 1; 108 | // Matlab indices start at 1 109 | const exp = `${this.name()}(find(${this.name()})(${begin}:${end}))`; 110 | runtime.evaluateAsLine(exp, (value: string) => { 111 | this.parseChildren(value, offset, count, (children: Array) => { 112 | callback(children); 113 | }); 114 | }); 115 | }); 116 | } 117 | 118 | 119 | //************************************************************************** 120 | public fetchIndices( 121 | runtime: CommandInterface, 122 | offset: number, 123 | count: number, 124 | callback: (fetchedIndices: Array) => void 125 | ): void 126 | { 127 | if(count === 0 && count === 0) { 128 | count = this._numberOfChildren; 129 | } 130 | 131 | // Matlab indices start at 1 132 | const begin = 1 + offset; 133 | const end = begin + count - 1; 134 | const idxExp = `find(${this.name()})(${begin}:${end})`; 135 | runtime.evaluateAsLine(idxExp, (output: string) => { 136 | output = Variables.clean(output); 137 | const values = output.split('\n'); 138 | const indices = values.map(i => parseInt(i)); 139 | 140 | for(let i = 0; i !== indices.length; ++i) { 141 | this._indices[offset + i] = indices[i]; 142 | } 143 | 144 | callback(indices); 145 | }); 146 | } 147 | 148 | 149 | //************************************************************************** 150 | public parseChildren( 151 | value: string, 152 | offset: number, 153 | count: number, 154 | callback: (vars: Array) => void 155 | ): void 156 | { 157 | const childFreeIndices = []; 158 | const vars = new Array(count); 159 | const values = SparseMatrix.extractValues(value); 160 | 161 | if(values.length !== count) { 162 | throw `values.length: ${values.length} != ${count}!`; 163 | } 164 | 165 | for(let i = 0; i !== count; ++i) { 166 | const childFixedIndices = [this._indices[i + offset]]; 167 | const name = this.basename(); 168 | const val = ''+values[i]; 169 | vars[i] = this.createConcreteType(name, val, childFreeIndices, childFixedIndices, true, this.typename()); 170 | } 171 | 172 | callback(vars); 173 | } 174 | 175 | 176 | //************************************************************************** 177 | private static readonly VALUES_REGEX = /^\s*\(\d+,\s+\d+\)\s+->\s+(.+)$/; 178 | // Compressed Column Sparse (rows = 3, cols = 1, nnz = 3 [100%]) 179 | // 180 | // (1, 1) -> -10.200 181 | // (2, 1) -> 5 182 | // (3, 1) -> 101 183 | // 184 | public static extractValues(value: string): Array { 185 | const lines = value.split('\n'); 186 | const values = new Array(); 187 | 188 | lines.forEach(line => { 189 | const match = line.match(SparseMatrix.VALUES_REGEX); 190 | if(match !== null && match.length === 2) { 191 | const value = match[1]; 192 | values.push(value); 193 | } 194 | }); 195 | 196 | return values; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Breakpoints.ts: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | import { DebugProtocol } from 'vscode-debugprotocol'; 3 | import { Breakpoint } from 'vscode-debugadapter'; 4 | import { Commands, CommandInterface } from './Commands'; 5 | import { functionFromPath } from './Utils/fsutils'; 6 | import { dirname } from 'path'; 7 | 8 | type ConditionalBreakpoint = DebugProtocol.SourceBreakpoint; 9 | 10 | 11 | //****************************************************************************** 12 | export class Breakpoints { 13 | //************************************************************************** 14 | //#region private 15 | //************************************************************************** 16 | private static readonly BP_REGEX = /^\s*(?:ans =)?\s*((?:\d+\s*)+)$/; 17 | private static readonly BAD_BP_REGEX = /^\s*ans =\s\[\]\(1x0\)$/; 18 | private static setUnconditional( 19 | breakpoints: Array, 20 | confirmedBreakpoints: Array, 21 | fname: string, 22 | runtime: CommandInterface, 23 | callback: (breakpoints: Array) => void 24 | ): void 25 | { 26 | let lines = ''; 27 | breakpoints.forEach(b => lines += `${b.line} `); 28 | 29 | runtime.evaluate(`dbstop ${fname} ${lines}`, (output: string[]) => { 30 | let i = 0; 31 | const badBreakpoint = () => { 32 | const bp = breakpoints[i++]; 33 | const idx = (bp.column? bp.column : 0); 34 | confirmedBreakpoints[idx] = new Breakpoint(false); 35 | } 36 | output.forEach(line => { 37 | const match = line.match(Breakpoints.BP_REGEX); 38 | 39 | if(match !== null && match.length === 2) { 40 | const lines = match[1].split(' ').filter((val) => val); 41 | lines.forEach(l => { 42 | const bp = breakpoints[i++]; 43 | const idx = (bp.column? bp.column : 0); 44 | confirmedBreakpoints[idx] = new Breakpoint(true, parseInt(l)); 45 | }); 46 | } else if(Breakpoints.BAD_BP_REGEX.test(line)) { 47 | badBreakpoint(); 48 | } 49 | }); 50 | 51 | while(i !== breakpoints.length) { 52 | badBreakpoint() 53 | } 54 | 55 | callback(confirmedBreakpoints); 56 | }); 57 | } 58 | 59 | 60 | //************************************************************************** 61 | private static setConditional( 62 | breakpoints: Array, 63 | confirmedBreakpoints: Array, 64 | fname: string, 65 | runtime: CommandInterface, 66 | callback: (breakpoints: Array) => void 67 | ): void 68 | { 69 | let i = 0; 70 | 71 | breakpoints.forEach(b => { 72 | const expression = `dbstop in ${fname} at ${b.line} if ${b.condition}`; 73 | 74 | runtime.evaluate(expression, (output: string[]) => { 75 | output.forEach(line => { 76 | const match = line.match(Breakpoints.BP_REGEX); 77 | 78 | if(match !== null && match.length === 2) { 79 | const l = match[1]; 80 | const bp = breakpoints[i++]; 81 | const idx = (bp.column? bp.column : 0); 82 | confirmedBreakpoints[idx] = new Breakpoint(true, parseInt(l)); 83 | } 84 | }); 85 | 86 | if(i === breakpoints.length) { 87 | callback(confirmedBreakpoints); 88 | } 89 | }); 90 | }); 91 | } 92 | 93 | 94 | //************************************************************************** 95 | private static set( 96 | breakpoints: Array, 97 | path: string, 98 | runtime: CommandInterface, 99 | cb: (breakpoints: Array) => void 100 | ): void 101 | { 102 | const confirmed = new Array(); 103 | if(breakpoints.length !== 0) { 104 | const conditional = new Array(); 105 | const unconditional = new Array(); 106 | // Split breakpoints between conditional and unconditional 107 | breakpoints.forEach(b => { 108 | b.column = confirmed.length; 109 | // Breakpoints start unconfirmed, and the column is their index 110 | confirmed.push(new Breakpoint(false, b.line)); 111 | if(b.condition !== undefined && b.condition.length !== 0) { 112 | conditional.push(b); 113 | } else { 114 | unconditional.push(b); 115 | } 116 | }); 117 | // Set breakpoints by type: 118 | const fname = functionFromPath(path); 119 | if(unconditional.length !== 0) { // any unconditional breakpoints? 120 | if(conditional.length !== 0) { // both breakpoint types? 121 | Breakpoints.setUnconditional(unconditional, confirmed, fname, runtime, 122 | (confirmed: Array) => { 123 | Breakpoints.setConditional(conditional, confirmed, fname, runtime, cb); 124 | }); 125 | } else { // only unconditional breakpoints 126 | Breakpoints.setUnconditional(unconditional, confirmed, fname, runtime, cb); 127 | } 128 | } else { // only conditional breakpoints 129 | Breakpoints.setConditional(conditional, confirmed, fname, runtime, cb); 130 | } 131 | } else { 132 | cb(confirmed); 133 | } 134 | } 135 | 136 | 137 | //************************************************************************** 138 | // e.g. 139 | // debug> dbstatus TestOctaveDebugger 140 | // breakpoints in TestOctaveDebugger at lines 23 27 141 | // breakpoint in TestOctaveDebugger>testNestedFunctionLevel2 at line 37 142 | private static listBreakpointsIn( 143 | path: string, 144 | runtime: CommandInterface, 145 | callback: (lines: string) => void 146 | ): void 147 | { 148 | const fname = functionFromPath(path); 149 | const breakpointRegEx = 150 | new RegExp(`^\\s*breakpoint[s]? in ${fname}(?:>\\w+)*? at line[s]? ((?:\\d+ ?)+)$`); 151 | let lines = ''; 152 | 153 | runtime.evaluate(`dbstatus ${fname}`, (output: string[]) => { 154 | output.forEach(line => { 155 | const match = line.match(breakpointRegEx); 156 | 157 | if(match !== null && match.length === 2) { 158 | lines += match[1]; 159 | } 160 | }); 161 | 162 | callback(lines.trim()); 163 | }); 164 | } 165 | 166 | 167 | //************************************************************************** 168 | private static clearAllBreakpointsIn( 169 | path: string, 170 | ci: CommandInterface, 171 | callback: () => void 172 | ): void 173 | { 174 | Breakpoints.listBreakpointsIn(path, ci, (lines: string) => { 175 | const func = functionFromPath(path); 176 | ci.execute(`dbclear ${func} ${lines}`); 177 | callback(); 178 | }); 179 | } 180 | 181 | //************************************************************************** 182 | //#endregion 183 | //************************************************************************** 184 | 185 | //************************************************************************** 186 | //#region public 187 | //************************************************************************** 188 | public static replaceBreakpoints( 189 | breakpoints: Array, 190 | path: string, 191 | runtime: CommandInterface, 192 | callback: (breakpoints: Array) => void 193 | ): void 194 | { 195 | Commands.pushd(runtime, dirname(path), 196 | (ci: CommandInterface, popd: (ci: CommandInterface) => void) => { 197 | Breakpoints.clearAllBreakpointsIn(path, ci, () => { 198 | Breakpoints.set(breakpoints, path, ci, 199 | (confirmedBreakpoints: Array) => { 200 | popd(ci); 201 | callback(confirmedBreakpoints); 202 | } 203 | ); 204 | }); 205 | } 206 | ); 207 | } 208 | //************************************************************************** 209 | //#endregion 210 | //************************************************************************** 211 | } 212 | -------------------------------------------------------------------------------- /src/Variables/String.ts: -------------------------------------------------------------------------------- 1 | import { CommandInterface } from '../Commands'; 2 | import * as Constants from '../Constants'; 3 | import { Variables } from './Variables'; 4 | import { Variable } from './Variable'; 5 | 6 | 7 | export class String extends Variable { 8 | private static readonly STR_TYPE = 'string'; 9 | private static readonly ESC_STR_TYPE = 'sq_string'; 10 | private _size: Array; 11 | private _validValue: boolean; 12 | private _typename: string; 13 | private _children: Array; 14 | 15 | 16 | //************************************************************************** 17 | constructor( 18 | name: string = '', 19 | value: string = '', 20 | validValue: boolean = true, 21 | size: Array = [], 22 | type: string = String.STR_TYPE 23 | ) 24 | { 25 | super(); 26 | this.setFullname(name); 27 | this._value = value; 28 | this._validValue = validValue; 29 | this._size = size; 30 | this._typename = type; 31 | this._numberOfChildren = 0; 32 | 33 | if(this._size.length !== 0) { 34 | // Unlike other variables, we split strings by rows (lines), and then columns (chars). 35 | // Seems like strings are always 2D arrays: 36 | if(this.isLines()) { 37 | // In this case each child is a line 38 | this._numberOfChildren = this._size[0]; 39 | } else { 40 | // In this case there's only one line, so each child is a char 41 | this._numberOfChildren = this._size[1]; 42 | } 43 | if(!this.isChar()) { 44 | Variables.addReferenceTo(this); 45 | } 46 | } 47 | } 48 | 49 | 50 | //************************************************************************** 51 | public typename(): string { return this._typename; } 52 | 53 | 54 | //************************************************************************** 55 | public loads(type: string): boolean { 56 | return type === String.STR_TYPE || type === String.ESC_STR_TYPE; 57 | } 58 | 59 | 60 | //************************************************************************** 61 | public extendedTypename(): string { return this.typename(); } 62 | 63 | 64 | //************************************************************************** 65 | public createConcreteType( 66 | name: string, 67 | value: string, 68 | validValue: boolean, 69 | size: Array, 70 | type: string 71 | ): String 72 | { 73 | return new String(name, value, validValue, size, type); 74 | } 75 | 76 | 77 | //************************************************************************** 78 | public loadNew( 79 | name: string, 80 | type: string, 81 | runtime: CommandInterface, 82 | callback: (variable: String) => void 83 | ): void 84 | { 85 | Variables.getSize(name, runtime, (size: Array) => { 86 | const loadable = Variables.loadable(size); 87 | 88 | const buildWith = (value: string) => { 89 | const v = this.createConcreteType(name, value, loadable, size, type); 90 | callback(v); 91 | }; 92 | 93 | if(loadable) { 94 | Variables.getValue(name, runtime, buildWith); 95 | } else { 96 | buildWith(size.join(Constants.SIZE_SEPARATOR)); 97 | } 98 | }); 99 | } 100 | 101 | 102 | //************************************************************************** 103 | public listChildren( 104 | runtime: CommandInterface, 105 | start: number, 106 | count: number, 107 | callback: (vars: Array) => void 108 | ): void 109 | { 110 | if(this._numberOfChildren === 0) { 111 | throw "Error: String has no children!"; 112 | } 113 | 114 | if(count === 0 && start === 0) { 115 | count = this._numberOfChildren; 116 | } 117 | 118 | if(this._children === undefined) { 119 | this._children = new Array(this._numberOfChildren); 120 | } 121 | 122 | if(start + count > this._children.length) { 123 | throw `Error: start+count > #children: ${start}+${count} > ${this._children.length}`; 124 | } 125 | 126 | if(this.childrenNeedLoading(start, count)) { 127 | if(this._validValue) { 128 | this.parseChildren(this._value, this._numberOfChildren, start, count, callback); 129 | } else { 130 | // Load by chunks (rows might be huge), use load new for each child... 131 | const childLength = this.childrenLength(); 132 | // If children are individually loadable, then we load them as a block 133 | // even if the block itself wouldn't necessarily be loadable. 134 | const loadable = Variables.loadable([1, 1], childLength); 135 | if(loadable) { 136 | this.loadChildrenBlock(runtime, start, count, callback); 137 | } else { 138 | this.createChildrenPlaceholders(runtime, start, count, callback); 139 | } 140 | } 141 | } 142 | } 143 | 144 | 145 | //************************************************************************** 146 | private createChildrenPlaceholders( 147 | runtime: CommandInterface, 148 | start: number, 149 | count: number, 150 | callback: (vars: Array) => void 151 | ): void 152 | { 153 | const name = this.name(); 154 | const type = this.typename(); 155 | const childLength = this.childrenLength(); 156 | const size = [1, childLength]; 157 | const range = (childLength === 1?'':',:'); 158 | const value = size.join(Constants.SIZE_SEPARATOR) 159 | for(let i = 0; i !== count; ++i) { 160 | const idx = start + i + 1; // matlab indices start at 1 161 | const v = this.createConcreteType(`${name}(${idx}${range})`, value, false, size, type); 162 | this._children[start + i] = v; 163 | 164 | } 165 | callback(this._children.slice(start, start+count)); 166 | } 167 | 168 | 169 | //************************************************************************** 170 | private loadChildrenBlock( 171 | runtime: CommandInterface, 172 | start: number, 173 | count: number, 174 | callback: (vars: Array) => void 175 | ): void 176 | { 177 | const name = this.name(); 178 | const range = `${start + 1}:${start+count}`; // indices start at 1 179 | let rangeName: string; 180 | let ansRegex: RegExp; 181 | if(this.isLine()) { 182 | rangeName = `${name}(${range})`; 183 | ansRegex = Variables.ANS_AND_SPACE_REGEX; 184 | } else { 185 | rangeName = `${name}(${range},:)`; 186 | ansRegex = Variables.ANS_AND_NEW_LINE_REGEX; 187 | } 188 | Variables.getRawValue(rangeName, runtime, (value: string) => { 189 | value = value.replace(ansRegex, ''); 190 | this.parseChildren(value, count, start, count, callback); 191 | }); 192 | } 193 | 194 | 195 | //************************************************************************** 196 | private parseChildren( 197 | value: string, 198 | numVals: number, 199 | start: number, 200 | count: number, 201 | callback: (vars: Array) => void 202 | ): void 203 | { 204 | let values: Array; 205 | // Children should all be the same length: 206 | const childLength = this.childrenLength(); 207 | // Make sure the value makes sense 208 | if(numVals * childLength > value.length || count > value.length) { 209 | throw `String value is invalid`; 210 | } 211 | if(this.isLine()) { 212 | values = value.split(''); 213 | } else { 214 | // Since we have an array of strings, we need to split by string length. 215 | values = new Array(numVals); 216 | for(let i = 0, begin = 0; i !== numVals; ++i) { 217 | values[i] = value.substring(begin, begin + childLength); 218 | // Split value into child values: (+1 skips extra \n) 219 | begin += childLength + 1; 220 | } 221 | } 222 | // This works for both strings, or individual characters, which are the same base type. 223 | const name = this.name(); 224 | const type = this.typename(); 225 | const size = [1, childLength]; 226 | const range = (childLength === 1?'':',:'); 227 | // Assuming that values either contains all children, or just the count of them 228 | const valuesOffset = (values.length < start+count? 0 : start); 229 | // We're assuming that these variables have not been loaded. 230 | for(let i = 0; i !== count; ++i) { 231 | const val = values[valuesOffset + i]; 232 | const idx = start + i + 1; // matlab indices start at 1 233 | const v = this.createConcreteType(`${name}(${idx}${range})`, val, true, size, type); 234 | this._children[start + i] = v; 235 | } 236 | // Return the requested variables: 237 | callback(this._children.slice(start, start+count)); 238 | } 239 | 240 | 241 | //************************************************************************** 242 | private childrenNeedLoading(start: number, count: number): boolean { 243 | // Not checking all chindren in the range have been loaded. 244 | // Assuming that they are loaded as blocks, and block size doesn't change. 245 | return this._children !== undefined 246 | && this._children.length >= start + count 247 | && this._children[start] === undefined; 248 | } 249 | 250 | 251 | //************************************************************************** 252 | private isLine(): boolean { 253 | return this._size.length === 2 254 | && this._size[0] === 1 255 | && this._size[1] > 1; 256 | } 257 | 258 | 259 | //************************************************************************** 260 | private childrenLength(): number { 261 | if(this._size.length === 2) { 262 | // If we have only 1 line, then children are individual characters 263 | // Otherwise, children are strings of length #columns, i.e.: 264 | return (this._size[0] !== 1? this._size[1] : 1); 265 | } 266 | // We don't have a valid size: 267 | return 0; 268 | } 269 | 270 | 271 | //************************************************************************** 272 | private isChar(): boolean { 273 | return this._size.length === 2 274 | && this._size[0] === 1 275 | && this._size[1] === 1; 276 | } 277 | 278 | //************************************************************************** 279 | private isLines(): boolean { 280 | return this._size.length === 2 281 | && this._size[0] > 1; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/Variables/Variables.ts: -------------------------------------------------------------------------------- 1 | import { OctaveLogger } from '../OctaveLogger'; 2 | import { CommandInterface } from '../Commands'; 3 | import * as Constants from '../Constants'; 4 | import { Variable } from './Variable'; 5 | 6 | 7 | export class Variables { 8 | //************************************************************************** 9 | private static readonly _EMPTY_ARRAY = new Array(); 10 | private static readonly _FACTORIES = new Array(); 11 | private static readonly _REFS = new Array(); 12 | private static _CHUNKS_PREFETCH = Constants.CHUNKS_PREFETCH; 13 | private static _FALLBACK: Variable; 14 | 15 | public static evaluateAns = false; 16 | 17 | 18 | //************************************************************************** 19 | public static setChunkPrefetch(n: number) { 20 | if(n > 0) { 21 | Variables._CHUNKS_PREFETCH = n; 22 | } 23 | } 24 | 25 | 26 | //************************************************************************** 27 | public static getMaximumElementsPrefetch(): number { 28 | return Variables._CHUNKS_PREFETCH * Constants.CHUNKS_SIZE; 29 | } 30 | 31 | 32 | //************************************************************************** 33 | // Register concrete type prototype factories 34 | //************************************************************************** 35 | public static register(factory: Variable) { 36 | Variables._FACTORIES.push(factory); 37 | } 38 | 39 | 40 | //************************************************************************** 41 | public static registerFallback(factory: Variable) { 42 | Variables._FALLBACK = factory; 43 | } 44 | 45 | 46 | //************************************************************************** 47 | public static getReference(name: string): number { 48 | for(let i = 0; i != Variables._REFS.length; ++i) { 49 | const v = Variables._REFS[i]; 50 | if(v.name() == name) 51 | return v.reference(); 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | 58 | //************************************************************************** 59 | // This is used to retrive variables with children. 60 | //************************************************************************** 61 | public static addReferenceTo(v: Variable): void { 62 | v.setReference(Variables._REFS.length + 1); // References start at 1; 63 | Variables._REFS.push(v); 64 | } 65 | 66 | 67 | //************************************************************************** 68 | public static getByReference(reference: number): Variable | null { 69 | if(reference > 0) { 70 | const index = reference - 1; // References start at 1; 71 | if(index < Variables._REFS.length) { 72 | return Variables._REFS[index]; 73 | } 74 | } else { 75 | OctaveLogger.error(`Error: getVariable(${reference})!`); 76 | } 77 | 78 | return null; 79 | } 80 | 81 | 82 | //************************************************************************** 83 | public static clearReferences(): void { 84 | Variables._REFS.length = 0; 85 | } 86 | 87 | 88 | //************************************************************************** 89 | // List variables under a parent variable. 90 | //************************************************************************** 91 | public static listByReference( 92 | ref: number, 93 | runtime: CommandInterface, 94 | start: number, 95 | count: number, 96 | callback: (variables: Array) => void 97 | ): void 98 | { 99 | const variable = Variables.getByReference(ref); 100 | if(variable !== null) { 101 | variable.listChildren(runtime, start, count, callback); 102 | } else { 103 | OctaveLogger.error(`Error: listByReference invalid reference ${ref}`); 104 | callback(Variables._EMPTY_ARRAY); 105 | } 106 | } 107 | 108 | 109 | //************************************************************************** 110 | private static skipVariable(name: string): boolean { 111 | return !Variables.evaluateAns && name === 'ans'; 112 | } 113 | 114 | 115 | //************************************************************************** 116 | public static listVariables( 117 | names: Array, 118 | runtime: CommandInterface, 119 | callback: (variables: Array) => void 120 | ): void 121 | { 122 | const variables = new Array(); 123 | let skipped = 0; 124 | 125 | names.forEach(name => { 126 | Variables.loadVariable(name, runtime, (v: Variable | null) => { 127 | if(v === null) { 128 | if(!Variables.skipVariable(name)) { 129 | OctaveLogger.error(`Error: could not load variable '${name}'!`); 130 | } 131 | ++skipped; 132 | } else { 133 | variables.push(v); 134 | } 135 | 136 | if(variables.length + skipped === names.length) { 137 | callback(variables); 138 | } 139 | }); 140 | }); 141 | } 142 | 143 | 144 | //************************************************************************** 145 | public static loadVariable( 146 | name: string, 147 | runtime: CommandInterface, 148 | callback: (v: Variable | null) => void 149 | ): void 150 | { 151 | if(!Variables.skipVariable(name)) { 152 | Variables.getType(name, runtime, (type: string) => { 153 | for(let i = 0; i !== Variables._FACTORIES.length; ++i) { 154 | const factory = Variables._FACTORIES[i]; 155 | 156 | if(factory.loads(type)) { 157 | factory.loadNew(name, type, runtime, callback); 158 | return; 159 | } 160 | } 161 | 162 | if(Variables._FALLBACK !== null && Variables._FALLBACK.loads(type)) { 163 | Variables._FALLBACK.loadNew(name, type, runtime, callback); 164 | } else { 165 | callback(null); 166 | } 167 | }); 168 | } else { 169 | callback(null); 170 | } 171 | } 172 | 173 | 174 | //************************************************************************** 175 | public static setVariable( 176 | name: string, 177 | value: string, 178 | runtime: CommandInterface, 179 | callback: (newValue: string) => void 180 | ): void 181 | { 182 | runtime.evaluateAsLine(`${name} = ${value}`, (result: string) => { 183 | // We could use 'result' here, but for consistency we get the value. 184 | Variables.getValue(name, runtime, callback); 185 | }); 186 | } 187 | 188 | 189 | //************************************************************************** 190 | // Octave seems to use this when the variable has only one row: 191 | public static readonly ANS_AND_SPACE_REGEX = /^\s*ans =\s/; 192 | // Octave seems to use this when the variable has more than one row: 193 | public static readonly ANS_AND_NEW_LINE_REGEX = /^\s*ans =\n/; 194 | // This is here as originally I was removing all white spaces: 195 | private static readonly ANS_AND_WHITE_SPACES_REGEX = /^\s*ans =\s*/; 196 | //************************************************************************** 197 | public static clean(value: string): string { 198 | // removes ans and trims 199 | return value.replace(Variables.ANS_AND_WHITE_SPACES_REGEX, '').trim(); 200 | } 201 | 202 | 203 | //************************************************************************** 204 | public static getType( 205 | variable: string, 206 | runtime: CommandInterface, 207 | callback: (type: string) => void 208 | ): void 209 | { 210 | runtime.evaluateAsLine(`typeinfo(${variable})`, (value: string) => { 211 | callback(Variables.clean(value)); 212 | }); 213 | } 214 | 215 | 216 | //************************************************************************** 217 | public static getValue( 218 | variable: string, 219 | runtime: CommandInterface, 220 | callback: (value: string) => void 221 | ): void 222 | { 223 | this.getRawValue(variable, runtime, (value: string) => { 224 | callback(Variables.removeName(variable, value)); 225 | }); 226 | } 227 | 228 | 229 | //************************************************************************** 230 | public static getRawValue( 231 | variable: string, 232 | runtime: CommandInterface, 233 | callback: (value: string) => void 234 | ): void 235 | { 236 | runtime.evaluateAsLine(variable, callback); 237 | } 238 | 239 | 240 | //************************************************************************** 241 | public static getSize( 242 | variable: string, 243 | runtime: CommandInterface, 244 | callback: (s: Array) => void 245 | ): void 246 | { 247 | runtime.evaluateAsLine(`size(${variable})`, (value: string) => { 248 | const values = Variables.clean(value).split(' ').filter((val) => val); 249 | const size = values.map(i => parseInt(i)); 250 | callback(size); 251 | }); 252 | } 253 | 254 | 255 | //************************************************************************** 256 | public static getNonZero( 257 | variable: string, 258 | runtime: CommandInterface, 259 | callback: (n: number) => void 260 | ): void 261 | { 262 | runtime.evaluateAsLine(`nnz(${variable})`, (value: string) => { 263 | callback(parseInt(Variables.clean(value))); 264 | }); 265 | } 266 | 267 | 268 | //************************************************************************** 269 | private static readonly ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g; 270 | public static escape(str: string): string { 271 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions 272 | return str.replace(Variables.ESCAPE_REGEX, '\\$&'); // $& means the whole matched string 273 | } 274 | 275 | 276 | //************************************************************************** 277 | public static removeName(name: string, value: string): string { 278 | name = Variables.escape(name.trim()); 279 | value = value.replace(new RegExp(`^\\s*(?:ans|${name}) =(?:\n\n)?\\s*`), ''); 280 | return value.trim(); 281 | } 282 | 283 | 284 | //************************************************************************** 285 | public static loadable( 286 | sizes: Array, 287 | count: number = 0 288 | ): boolean 289 | { 290 | if(count === 0) { 291 | count = 1; 292 | } 293 | 294 | const N = sizes.reduce((acc, val) => acc *= val, 1); 295 | 296 | return N * count <= Variables.getMaximumElementsPrefetch(); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/Runtime.ts: -------------------------------------------------------------------------------- 1 | import { OctaveLogger } from './OctaveLogger'; 2 | import { spawn, ChildProcess } from 'child_process'; 3 | import { ReadLine, createInterface } from 'readline'; 4 | import { EventEmitter } from 'events'; 5 | import * as Constants from './Constants'; 6 | import { functionFromPath } from './Utils/fsutils'; 7 | import { Commands, CommandInterface } from './Commands'; 8 | 9 | 10 | //************************************************************************** 11 | enum Status { 12 | NOT_CONSUMING = 0x00000000, 13 | BEGUN_CONSUMING = 0x00000001, 14 | ENDED_CONSUMING = 0x00000002 15 | }; 16 | 17 | //************************************************************************** 18 | enum MatchIndex { 19 | FULL_MATCH, 20 | STATUS, 21 | CMD_NUMBER, 22 | LENGTH 23 | }; 24 | 25 | 26 | export class Runtime extends EventEmitter implements CommandInterface { 27 | //************************************************************************** 28 | //#region private 29 | //************************************************************************** 30 | private static readonly PROMPT = /(?:debug|octave:\d+)> /; 31 | private static readonly SEP = '::'; 32 | private static readonly SYNC = Constants.MODULE_NAME; 33 | private static readonly SYNC_REGEX = 34 | new RegExp(`${Runtime.SYNC}${Runtime.SEP}(\\d+)${Runtime.SEP}(\\d+)`); 35 | private static readonly TERMINATOR = `${Runtime.SYNC}${Runtime.SEP}end`; 36 | private static readonly TERMINATOR_REGEX = new RegExp(Runtime.TERMINATOR); 37 | 38 | //************************************************************************** 39 | private _inputHandler = new Array<(str: string) => number>(); 40 | private _stderrHandler = new Array<(str: string) => boolean>(); 41 | private _commandNumber = 0; 42 | private _processName: string; 43 | private _process: ChildProcess; 44 | private _processStdout: ReadLine; 45 | private _processStderr: ReadLine; 46 | private _program: string; 47 | private _autoTerminate: boolean; 48 | private _arguments: string[]; 49 | private _environment: any; 50 | private _shell: boolean; 51 | 52 | //************************************************************************** 53 | private static createCommand(expression: string, command: number): string { 54 | const vars = new Array(MatchIndex.LENGTH); 55 | // This sequence needs to match the Runtime.SYNC_REGEX pattern above. 56 | vars[MatchIndex.CMD_NUMBER] = `${Runtime.SEP}${command}`; 57 | vars[MatchIndex.STATUS] = `${Runtime.SEP}${Status.BEGUN_CONSUMING}`; 58 | const syncBeginCmd = Runtime.echo(`${Runtime.SYNC}${vars.join('')}`); 59 | vars[MatchIndex.STATUS] = `${Runtime.SEP}${Status.ENDED_CONSUMING}`; 60 | const syncEndCmd = Runtime.echo(`${Runtime.SYNC}${vars.join('')}`); 61 | 62 | return `${syncBeginCmd}${expression}\n${syncEndCmd}`; 63 | } 64 | 65 | 66 | //************************************************************************** 67 | private static split(str: string): Array { 68 | return str.split(this.PROMPT).filter((val) => val); 69 | } 70 | 71 | 72 | //************************************************************************** 73 | private connect() { 74 | OctaveLogger.debug(`Runtime: connecting to '${this._processName}'.`); 75 | 76 | let options = { 77 | env : Object.assign(process.env, this._environment), 78 | shell: this._shell 79 | }; 80 | this._process = spawn(this._processName, this._arguments, options); 81 | 82 | if(this.connected()) { 83 | this._processStdout = createInterface( 84 | { input: this._process.stdout, terminal: false, crlfDelay: Infinity }); 85 | this._processStderr = createInterface( 86 | { input: this._process.stderr, terminal: false, crlfDelay: Infinity }); 87 | this._process.on('close', code => { this.onExit(code); }); 88 | this._process.on('error', err => { this.onError(err); }); 89 | this._processStdout.on('line', data => { this.onStdout(data); }); 90 | this._processStderr.on('line', data => { this.onStderr(data); }); 91 | } 92 | } 93 | 94 | 95 | //************************************************************************** 96 | private isConsumed(data: string): boolean { 97 | let consumeCallbackResult = Status.NOT_CONSUMING; 98 | 99 | while(this._inputHandler.length !== 0) { 100 | consumeCallbackResult = this._inputHandler[0](data); 101 | 102 | if(consumeCallbackResult === Status.ENDED_CONSUMING) { 103 | this._inputHandler.shift(); // Done consuming input, remove handler. 104 | } else { 105 | break; // This handler requires more input so we exit now. 106 | } 107 | } 108 | 109 | return consumeCallbackResult !== Status.NOT_CONSUMING; 110 | } 111 | 112 | 113 | //************************************************************************** 114 | private isSync(data: string): boolean { 115 | return Runtime.SYNC_REGEX.test(data); 116 | } 117 | 118 | 119 | //************************************************************************** 120 | private onStdout(data: string) { 121 | const lines = Runtime.split(data); 122 | 123 | lines.forEach(line => { 124 | if( !this.terminated(line) && 125 | !this.isConsumed(line) && 126 | !this.isSync(line)) 127 | { 128 | // User data, output immediately. 129 | OctaveLogger.log(line); 130 | } 131 | }); 132 | } 133 | 134 | 135 | //************************************************************************** 136 | private onStderr(data: string) { 137 | this._stderrHandler.some(callback => { 138 | return callback(data); 139 | }); 140 | 141 | if(data.startsWith(Constants.ERROR_EXP)) { 142 | OctaveLogger.error(data); 143 | } else { 144 | OctaveLogger.warn(data); 145 | } 146 | } 147 | 148 | 149 | //************************************************************************** 150 | private onExit(code: number) { 151 | const msg = `Runtime: ${this._processName} exited with code: ${code}`; 152 | 153 | if(code !== 0 && code != null) { 154 | OctaveLogger.error(msg); // exited with non-zero code. 155 | } else { 156 | OctaveLogger.debug(msg); 157 | } 158 | 159 | this.emit(Constants.eEXIT); 160 | } 161 | 162 | 163 | //************************************************************************** 164 | private onError(err: Error) { 165 | let msg = err.toString(); 166 | 167 | if(!this._process.connected) { 168 | msg += `\nCould not connect to '${this._processName}'.`; 169 | this.emit(Constants.eEXIT); 170 | } 171 | 172 | if(this._process.killed) { 173 | msg += `\nProcess '${this._processName}' was killed.`; 174 | this.emit(Constants.eEXIT); 175 | } 176 | 177 | OctaveLogger.debug(msg); 178 | 179 | if(this.autoTerminate()) { 180 | this.emit(Constants.eERROR); 181 | } 182 | } 183 | 184 | 185 | //************************************************************************** 186 | private terminated(data: string): boolean { 187 | if(Runtime.TERMINATOR_REGEX.test(data)) { 188 | OctaveLogger.debug(`Runtime: program ${this._program} exited normally.`); 189 | // If we're suppose to terminate when the scrip exists: 190 | if(this.autoTerminate()) { 191 | // We send a end event to exit debug 192 | this.emit(Constants.eEND); 193 | } else { 194 | // Otherwise we send a break event to read octave state 195 | this.emit(Constants.eBREAK); 196 | } 197 | return true; 198 | } 199 | 200 | return false; 201 | } 202 | 203 | 204 | //************************************************************************** 205 | public checkForBreaks(line: string): boolean { 206 | const BREAK_REGEX = /^stopped in .*? at line \d+.*$/; 207 | if(BREAK_REGEX.test(line)) { 208 | this.emit(Constants.eBREAK); 209 | return true; // Event handled. Stop processing. 210 | } 211 | 212 | return false; // Event not handled. Pass the event to the next handler. 213 | } 214 | 215 | 216 | //************************************************************************** 217 | //#endregion 218 | //************************************************************************** 219 | 220 | //************************************************************************** 221 | //#region public 222 | //************************************************************************** 223 | public constructor( 224 | processName: string, 225 | processArguments: string[], 226 | processEnvironment: any, 227 | sourceFolders: string[], 228 | workingDirectory: string, 229 | autoTerminate: boolean, 230 | shell: boolean 231 | ) 232 | { 233 | super(); 234 | this._processName = processName; 235 | this._autoTerminate = autoTerminate; 236 | this._arguments = processArguments; 237 | this._environment = processEnvironment; 238 | this._shell = shell; 239 | 240 | this.connect(); 241 | 242 | if(this.connected()) { 243 | Commands.addFolders(this, sourceFolders); 244 | Commands.cwd(this, workingDirectory); 245 | } 246 | // we always check for break(point) events. 247 | // This call requires an indirection to store the this pointer: 248 | this.addStderrHandler((line: string) => this.checkForBreaks(line)); 249 | } 250 | 251 | 252 | //************************************************************************** 253 | public connected(): boolean { 254 | return this._process.pid !== undefined; 255 | } 256 | 257 | 258 | //************************************************************************** 259 | public disconnect() { 260 | OctaveLogger.debug("Killing Runtime."); 261 | this.execute('quit'); 262 | this._process.kill('SIGKILL'); 263 | } 264 | 265 | 266 | //************************************************************************** 267 | public pause() { 268 | OctaveLogger.debug("Pausing Runtime."); 269 | this._process.kill('SIGINT'); 270 | } 271 | 272 | 273 | //************************************************************************** 274 | public start(program: string) { 275 | this._program = program; 276 | // This is just like a deferred sync command. 277 | // The program executes and then echoes the terminator tag. 278 | const terminator = Runtime.echo(Runtime.TERMINATOR); 279 | const command = functionFromPath(program); 280 | // if command is empty, this will lead to a parse error, so avoid it: 281 | if(command.length != 0) 282 | this.execute(`${functionFromPath(program)};${terminator}`); 283 | else 284 | // Terminates immediately, or enters interactive mode: 285 | this.execute(`${terminator}`); 286 | } 287 | 288 | 289 | //************************************************************************** 290 | public addStderrHandler(callback: (str: string) => boolean) { 291 | this._stderrHandler.push(callback); 292 | } 293 | 294 | 295 | //************************************************************************** 296 | public evaluateAsLine(expression: string, callback: (line: string) => void) { 297 | this.evaluate(expression, (output: string[]) => { 298 | callback(output.join('\n')); 299 | }); 300 | } 301 | 302 | 303 | //************************************************************************** 304 | // Sends the expression through for evaluation, 305 | // and gathers all output until the command number shows up in the output. 306 | public evaluate(expression: string, callback: (lines: string[]) => void) { 307 | let commandNumber: number = this._commandNumber; // cache the current command # 308 | let lines: string[] = []; 309 | let status = Status.NOT_CONSUMING; 310 | // const prefix = `cmd:${commandNumber}> '${expression}'`; // DEBUG 311 | 312 | this._inputHandler.push((line: string) => { 313 | // OctaveLogger.debug(`${prefix} output: ${line}`); // DEBUG 314 | const match = line.match(Runtime.SYNC_REGEX); 315 | // pause, input, errors, etc... can consume sync commands 316 | if(match !== null && match.length === MatchIndex.LENGTH) { 317 | const cmdNum = parseInt(match[MatchIndex.CMD_NUMBER]); 318 | // check if sync cmd# >= the current cmd# 319 | if(cmdNum > commandNumber) { 320 | return Status.ENDED_CONSUMING; 321 | } else if(cmdNum < commandNumber) { 322 | return Status.NOT_CONSUMING; 323 | } 324 | // cmdNum === commandNumber so update status 325 | status = parseInt(match[MatchIndex.STATUS]); 326 | // If we hit the end sync we skip this line on the output 327 | if(status === Status.ENDED_CONSUMING) { 328 | callback(lines); 329 | OctaveLogger.debug(lines.join('\n')); 330 | } 331 | } else if(status === Status.BEGUN_CONSUMING) { 332 | lines.push(line); 333 | } 334 | 335 | return status; 336 | }); 337 | 338 | // The 1st & 3rd cmds don't need \n, as it's included in the echo. 339 | // The user expression needs it as it might not have ; 340 | this.execute(Runtime.createCommand(expression, commandNumber)); 341 | } 342 | 343 | 344 | //************************************************************************** 345 | // All communication with the process goes through here. 346 | public execute(expression: string) { 347 | // And also log them out to the console 348 | OctaveLogger.debug(`${this._processName}:${this._commandNumber}> ${expression}`); 349 | // This actually sends the command to Octave, \n is like pressing enter. 350 | this._process.stdin.write(`${expression}\n`); 351 | // We keep track of the commands sent through 352 | ++this._commandNumber; 353 | } 354 | 355 | 356 | //************************************************************************** 357 | public autoTerminate(): boolean { 358 | return this._autoTerminate; 359 | } 360 | 361 | 362 | //************************************************************************** 363 | public static echo(str: string): string { 364 | // The ' ' space at the beginning unpauses Octave when stepping. 365 | // TODO: this shouldn't be needed. User should be able to use pause, etc... 366 | // The \\n places this output in a new line. 367 | return ` printf("\\n${str}\\n");`; 368 | } 369 | 370 | //************************************************************************** 371 | //#endregion 372 | //************************************************************************** 373 | } 374 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # VS Code Octave Debugger 2 | 3 | This extension provides debugging support for Octave code. This is done by interfacing with `octave-cli` via stdin/stdout. Support for running Matlab code is also done through `octave-cli`. Make sure `octave-cli` is in your path environment variable. Note that `octave-cli` must be installed on your system. You can download it [here](https://www.gnu.org/software/octave/download.html). If you're using windows or scripts with UI elements such as plots and widgets, using `octave-gui` is recommended. For more details on plots check this [page](https://github.com/paulo-fernando-silva/vscOctaveDebugger/issues/45). You can use 4 | 5 | ```matlab 6 | h = figure(); 7 | plot(); 8 | % ... 9 | waitfor(h); 10 | ``` 11 | 12 | to prevent the script from exiting, allowing continuous debugging of plots. Another option is to put `while(waitforbuttonpress()==0) pause(1) end` at the end of your script. See [here](https://github.com/paulo-fernando-silva/vscOctaveDebugger/issues/52#issuecomment-893771137) for more details. 13 | 14 | Do read the changelog to know what's new in this version. This extension has been tested with octave-5.1 and octave-6.1, so everything in between and perhaps more recent versions should also work. If it doesn't, or if a variable is not shown properly, etc, please do let me know over [here](https://github.com/paulo-fernando-silva/vscOctaveDebugger/issues). Additionally, please check the known issues on this page. 15 | 16 | Though the following is not necessary to use this extension, I recommend the [Octave](https://marketplace.visualstudio.com/items?itemName=toasty-technologies.octave) extension for syntax highlighting. I prefer it over [Matlab](https://marketplace.visualstudio.com/items?itemName=Gimly81.matlab). Nicer colors. The following language extension supports code outline for the focused file [Octave Hacking](https://marketplace.visualstudio.com/items?itemName=apjanke.octave-hacking). We're still missing is the F12 "jump to definition" code navigation. If someone has time and wants to implement it [here's](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) a place to start. 17 | 18 | 19 | **Octave Debugger** 20 | This extension supports actions: 21 | * continue, step, step in, step out, 22 | * breakpoints, conditional breakpoints, 23 | * variable inspection, variable editing 24 | * stack navigation and visualization 25 | * expression evaluation, via console input, watch UI, or mouse hover 26 | 27 | The following types are currently supported: 28 | * Scalar: [floats](https://octave.org/doc/v5.1.0/Single-Precision-Data-Types.html) and [ints](https://octave.org/doc/v5.1.0/Integer-Data-Types.html), the default type being double 29 | * [Matrix](https://octave.org/doc/v5.1.0/Matrices.html) of all basic types, includes ComplexMatrix/BoolMatrix 30 | * [DiagonalMatrix/ComplexDiagonalMatrix/PermutationMatrix](https://octave.org/doc/v5.1.0/Diagonal-and-Permutation-Matrices.html) 31 | * [SparseMatrix/SparseComplexMatrix](https://octave.org/doc/v5.1.0/Sparse-Matrices.html) 32 | * [Range](https://octave.org/doc/v5.1.0/Ranges.html) 33 | * [ScalarStruct/Struct](https://octave.org/doc/v5.1.0/Structures.html) 34 | * [ClassDef](https://octave.org/doc/v7.1.0/classdef-Classes.html) 35 | * [Inline functions](https://octave.org/doc/v5.1.0/Inline-Functions.html) and [function handles](https://octave.org/doc/v5.1.0/Function-Handles.html) 36 | * [Cell Arrays](https://octave.org/doc/v5.1.0/Cell-Arrays.html) 37 | * LazyIndex (No docs. This might be an internal type only.) 38 | * [SqString](https://octave.org/doc/v5.1.0/Escape-Sequences-in-String-Constants.html#Escape-Sequences-in-String-Constants) 39 | * [Strings](https://octave.org/doc/v5.1.0/Strings.html) 40 | * UnknownType: represents unknown types as strings. 41 | 42 | If a type isn't supported, request it on the [project repository](https://github.com/paulo-fernando-silva/vscOctaveDebugger.git). 43 | 44 | ![Demo](images/OctaveDebugger.gif) 45 | 46 | 47 | If you want to edit the value of a variable be it scalar, array, or structure, you can double click on it in the `VARIABLES` view, and type in the new value. 48 | That expression will be evaluated and if successful the variable will be updated with the new value. 49 | You can also submit any command you like through the debug console as if you were typing directly into Octave. 50 | 51 | More information about debugging with Octave can be found 52 | [here](https://octave.org/doc/v5.1.0/Debugging.html). 53 | 54 | 55 | ## Using Octave Debugger 56 | 57 | * Open the directory containing the project that you want to debug using everyone's favorite editor, visual studio code. 58 | * In the vsCode debug view click the `DEBUG` drop-down box and select `"Add configuration..."`. See animation above. 59 | * Select `"OctaveDebugger"` from the menu that pops up. 60 | * The following is the default configuration which will debug the current selected file: 61 | 62 | ```json 63 | "type": "OctaveDebugger", 64 | "request": "launch", 65 | "name": "Execute selected file.", 66 | "program": "${file}" 67 | ``` 68 | 69 | * Open the file you wish to debug. 70 | * Set breakpoints as needed. 71 | * Press the `DEBUG` ▷ button or F5 to start debugging. 72 | * Open the `DEBUG CONSOLE` to view any output from your program or to interact with it. Commands will be sent directly to Octave. 73 | 74 | 75 | ## Interactive Mode 76 | 77 | In this mode octave will continue to execute beyond the script execution. Therefore, the `"program"` field can be empty as follows: 78 | 79 | ```json 80 | "type": "OctaveDebugger", 81 | "request": "launch", 82 | "name": "Interactive Mode", 83 | "program": "", 84 | "octave": "octave-cli", 85 | "autoTerminate": false 86 | ``` 87 | 88 | Commands can be sent via the `DEBUG CONSOLE`. To enter this mode set `"autoTerminate": false` in the launch configuration. Note that octave will terminate only when either the stop □ button is pressed, or when an error occurs after the end of the script. To continue executing even on error, use the octave option `"octaveArguments": [ "--interactive" ],`. The main point of this mode is that variables will continue to be displayed in the `VARIABLES` and `WATCHES` view beyond the end of the script. 89 | 90 | 91 | ## Debug Session Configuration Variables 92 | 93 | * Example: 94 | 95 | ```json 96 | "type": "OctaveDebugger", 97 | "request": "launch", 98 | "name": "My Debug Config - free text", 99 | "program": "${file}", 100 | "octave": "/path/to/octave-cli", 101 | "sourceFolder": "${workspaceFolder}:/usr/local/matlab/code:/opt/local/matlab/more_code", 102 | "workingDirectory": "${workspaceFolder}" 103 | ``` 104 | 105 | * `"program"` can be anything that can be evaluated, e.g. `"path/to/file.m"`, or `"functionName(value)"`. It defaults to `${file}` which corresponds to the currently focused file. This is a vscode variable - see [variables-reference](https://code.visualstudio.com/docs/editor/variables-reference). However, something like `"functionName('/some/path/file.m')"` probably won't work, as the plugin tries to parse the program directory from this value. 106 | * `"octave"` must point to the location where `"octave-cli"` or `"octave-gui"` is installed. This parameter is optional, and defaults to `"octave-cli"` which assumes that `"octave-cli"` is in your path. If that's not the case make sure to provide the full installation path. 107 | * `"sourceFolder"` is an optional parameter that defaults to `"${workspaceFolder}"`. Basically it is added using `"addpath()"` before starting the `"program"`. More than one directory can be added by separating them with `pathsep()` which defaults to `:`. 108 | 109 | For example: 110 | 111 | ```json 112 | "program": "foo", 113 | "sourceFolder": "${workspaceFolder}/A/B/C/" 114 | ``` 115 | 116 | is equivalent to 117 | 118 | > 119 | "program": "${workspaceFolder}/A/B/C/foo.m" 120 | 121 | * `"workingDirectory"` is another optional parameter. Octave will switch to this directory before running `"program"`. In the following example program `"foo"` can exist anywhere under `"${workspaceFolder}"`, but will be executed from `"${workspaceFolder}/A/B/C/"`: 122 | 123 | ```json 124 | "program": "foo", 125 | "sourceFolder": "${workspaceFolder}" 126 | "workingDirectory": "${workspaceFolder}/A/B/C/" 127 | ``` 128 | 129 | * `"splitFieldnamesOctaveStyle"` this allows struct field names to be almost arbitrary ([details](https://octave.org/doc/v5.1.0/Creating-Structures.html)). This option is not compatible with Matlab and so it's off by default ([details](https://www.mathworks.com/help/matlab/matlab_prog/generate-field-names-from-variables.html)). 130 | 131 | ## Environment Variables and Octave Arguments 132 | 133 | * Consider the following configuration: 134 | 135 | ```json 136 | "type": "OctaveDebugger", 137 | "request": "launch", 138 | "name": "Environment Check", 139 | "program": "env.m", 140 | "octave": "octave-cli", 141 | "octaveEnvironment": { "FOO": "bar", "VAR": "XPTO" }, 142 | "workingDirectory": "${workspaceFolder}" 143 | ``` 144 | 145 | * `"octaveEnvironment"` is added to the environment when the octave process is created. We can print those environment variables by running the program `env.m` defined as: 146 | 147 | ```matlab 148 | printf('FOO: "%s"\n', getenv('FOO')); 149 | printf('VAR: "%s"\n', getenv('VAR')); 150 | printf('PATH: "%s"\n', getenv('PATH')); 151 | ``` 152 | 153 | * The same can be achieved using: 154 | 155 | ```json 156 | "type": "OctaveDebugger", 157 | "request": "launch", 158 | "name": "Environment Check", 159 | "program": "env.m", 160 | "octave": "export FOO=bar; octave-cli", 161 | "shell": true, 162 | "workingDirectory": "${workspaceFolder}" 163 | ``` 164 | 165 | * `"shell": true,` is this variable's default value, so it can be omitted. If you set it to false, the above configuration will not run as `export FOO=bar; octave-cli` is a shell command, and is not a valid process name. 166 | 167 | * Another option is `"octaveArguments": [ "FOO", "bar" ],`. This passes `"FOO"` and `"bar"` as parameters to the octave process. For example, here's another way to manipulate paths: 168 | 169 | ```json 170 | "type": "OctaveDebugger", 171 | "request": "launch", 172 | "name": "Execute foo in bar", 173 | "program": "foo.m", 174 | "octave": "octave-cli", 175 | "octaveArguments": [ "--path", "bar" ], 176 | "workingDirectory": "${workspaceFolder}" 177 | ``` 178 | 179 | 180 | ## Project Homepage 181 | Source available [here](https://github.com/paulo-fernando-silva/vscOctaveDebugger.git). 182 | Please submit bugs there too. 183 | 184 | 185 | ## Known Issues 186 | 187 | The following issues will likely not be fixed. For other open issues see [here](https://github.com/paulo-fernando-silva/vscOctaveDebugger/issues). 188 | 189 | * ans: Is not displayed in the variables view by default. You can still output it in the console or watch view. 190 | * stdinput when using octave version 5.1 or older: if you're stepping you can't rely on stdinput from your Matlab/Octave code. For example, you can use functions like `pause()`, `input()`, `keyboard()`, etc, as long as it's not during a step over, step into, or step out. To workaround this you can press F5 (continue), and `pause()` will wait for your input in the `DEBUG CONSOLE`. The issue comes from the communication that the plugin does with Octave in order to control execution. When using the console or continuing the execution no such communication exists. So you can step over/into/out using the `DEBUG CONSOLE`, by typing `dbstep` and pressing the `RETURN` key (see [here](https://octave.org/doc/v5.1.0/Debug-Mode.html) for details). Then each new `RETURN` should work as a step directly. This is the way `octave-cli` works by default. Since the `DEBUG CONSOLE` just forwards your commands to `octave-cli` you can interact with it as if it was a normal terminal. 191 | * stdinput when using octave version 5.2 or newer: in this case `pause()`, `kbhit()`, and friends block octave waiting for direct keyboard input. Unfortunately the plugin can't send keypresses directly to the octave process. The plan is to implement a switch to open a background window to run octave-cli so the user can send direct keyboard input to that window. This is tracked in issue [#34](https://github.com/paulo-fernando-silva/vscOctaveDebugger/issues/34). For now if you're using octave 5.2 or newer you should avoid these keyboard functions. 192 | * plotting with qt or other plot frameworks might require you to add an infinite loop at the end of the program being debugged. For example, as mentioned at the top of this page, you can use `h = figure(); plot(); waitfor(h);` or `while(waitforbuttonpress()==0) pause(1) end` which hopefully exits when you press a key but not when you press a mouse button. Using either method will prevent the program from terminating, allowing the debug session to stay alive and the plot to update. The first method will exit when you close the plot, the second method will exit on key press. If the plot doesn't require an update, i.e. no interactive elements such as sliders, then setting a breakpoint on the last program instruction might be enough - just make sure that instruction is after the plot command. This seems to be an issue even when running plots from the cli, see [Getting octave to plot when invoking a function from the command line](https://stackoverflow.com/questions/6843014/getting-octave-to-plot-when-invoking-a-function-from-the-command-line/62340602#62340602) and [Octave: How to prevent plot window from closing itself?](https://stackoverflow.com/questions/52569584/octave-how-to-prevent-plot-window-from-closing-itself). 193 | * Octave will accept arbitrary strings as struct field names. When Octave field names are enabled using `"splitFieldnamesOctaveStyle": true` in the launch options, the only strings that can't be used as struct field names will be strings that match `/\n? \[\d+,1\] = /`. Using this kind of struct field names with this plugin is not recommended as it might lead to communication issues between the plugin and octave. 194 | * Support for setting breakpoints in classdefs is ongoing. The plugin includes support, but currently you need to build octave from source to get that functionality. This should be fixed soon. 195 | 196 | 197 | 198 | ## History and Acknowledgements 199 | 200 | I started this project back in December 2017 or January 2018, not quite sure anymore, when I was going through the exercises from the [Andrew Ng's machine learning class](http://openclassroom.stanford.edu/MainFolder/CoursePage.php?course=MachineLearning), and other ML resources such as [Stanford Machine Learning](https://www.youtube.com/watch?v=UzxYlbK2c7E&list=PLA89DCFA6ADACE599), [Caltech Learning from Data](https://www.youtube.com/watch?v=VeKeFIepJBU&list=PLCA2C1469EA777F9A), [Deep Learning tutorial](http://ufldl.stanford.edu/tutorial/), and more from MIT and others. 201 | 202 | Since vscode is the best editor around, but unfortunately there was no Octave debugger at the time, and since I had a long commute to work, I decided to use that time to develop this plugin. 203 | It was an on and off development process, but I would say that about 80% of it was done on the train while commuting to work or flying around the world. I really would like to thank Andrew and all the openclassroom and other similar projects (e.g. OpenCourseWare), and of course the people behind Octave and vscode. 204 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.13 2 | * Bump webpack from 5.76.0 to 5.94.0. 3 | 4 | ## 0.5.12 5 | * Bump braces from 3.0.2 to 3.0.3. 6 | 7 | ## 0.5.11 8 | * Added support for classdefs inside packages like: +MYPakcage/MyClassdef.m 9 | * Minor readme tweak. 10 | * Minor cleanup in the package.json. 11 | 12 | ## 0.5.10 13 | * Bump webpack from 5.38.1 to 5.76.0 14 | 15 | ## 0.5.9 16 | * Fix classdef name parsing for classes in +packages, e.g. package.MyType 17 | 18 | ## 0.5.8 19 | * Fix sorting VARIABLES view. Only name is used, not indices. Assumes stable sort. 20 | * Fix error on hover over a : 21 | 22 | ## 0.5.7 23 | * Added sorting to the VARIABLES view. 24 | 25 | ## 0.5.6 26 | * Fix stack fetch - lambdas are now supported. 27 | 28 | ## 0.5.5 29 | * Tweaked hover evaluation to use current selected stack frame. 30 | 31 | ## 0.5.4 32 | * Bump terser from 5.7.0 to 5.14.2 33 | 34 | ## 0.5.3 35 | * Fix debugger not starting because of more than 1 invalid breakpoint (again). 36 | 37 | ## 0.5.2 38 | * Fix debugger not starting because of invalid breakpoints. 39 | 40 | ## 0.5.1 41 | * Added support for loading "long" strings by chunks. 42 | 43 | ## 0.5.0 44 | * Added support for classdef types (breakpoints are not supported yet in octave) 45 | 46 | ## 0.4.29 47 | * Fixed Cell loading. 48 | 49 | ## 0.4.28 50 | * Fixed performance of HOVER and WATCH variable fetch. Now it'll fetch large variables by chunks. 51 | 52 | ## 0.4.27 53 | * Fixed hover over 1:variable, now it'll display the value of the whole expression. Still outputs error to console on evaluation of non defined ranges. 54 | * Updated packages due to CVE-2021-44906 55 | 56 | ## 0.4.26 57 | * Added support to load multi-dimensional matrices. 58 | 59 | ## 0.4.25 60 | * Added matrix/array visualization in WATCH view. 61 | * Fixed free indices that are 1 in matrix types. 62 | * Changed the vector size visualization to display 1xN or Nx1 instead of just N. 63 | 64 | ## 0.4.24 65 | * Fixed addFolders to use pathsep instead of a hardcoded :. 66 | * Updated mocha used in unit tests to remove depend-bot warning. 67 | 68 | ## 0.4.23 69 | * Added some language aliases, to take into consideration other ways of writing octave. 70 | 71 | ## 0.4.22 72 | * Removed vsce from development dependencies, 73 | * Added support for --interactive mode. Allows octave to continue executing when the script exited even on error. Rigth now it still needs to be set manually via octave arguments. See readme for details. 74 | 75 | ## 0.4.21 76 | * Extended autoTerminate to support interaction beyond script termination. Now using program:"" and autoTerminate:false enters a sort of interactive mode, where commands can be sent to octave via de DEBUG CONSOLE, and the internal octave state will still show in the VARIABLES, and WATCH views. 77 | 78 | ## 0.4.20 79 | * Fixed readme.md typos. 80 | 81 | ## 0.4.19 82 | * Fixed issue with breakpoints set in files contained in the "workingDirectory". 83 | * Tweaked the readme.md 84 | * Updated a couple of packages. 85 | 86 | ## 0.4.18 87 | * Removed tar dependency. 88 | * Set ${file} as the default program to run. 89 | * Tweaked the readme a bit. 90 | 91 | ## 0.4.17 92 | * Updated vsce, typescript, ts-loader, webpack, and webpack-cli. 93 | 94 | ## 0.4.16 95 | * Bump css-what from 5.0.0 to 5.0.1 96 | 97 | ## 0.4.15 98 | * Added support for passing environment variables and program arguments to octave. 99 | 100 | ## 0.4.14 101 | * Bumped "elliptic": ">=6.5.4", "lodash": "^4.17.21", "vsce": "^1.88.0", "webpack-cli": "^3.3.10" 102 | 103 | ## 0.4.13 104 | * Small readme tweak. 105 | * Bump ssri from 6.0.1 to 6.0.2 106 | 107 | ## 0.4.12 108 | * Tested octave 6.2.0 and updated the readme file with some more information on how to properly run the plugin when using plots. 109 | 110 | ## 0.4.11 111 | * Updated y18n dependency. 112 | 113 | ## 0.4.10 114 | * Updated "elliptic": ">=6.5.4" 115 | * Updated readme. 116 | 117 | ## 0.4.9 118 | * Fix issue #40, i.e. now the plugin works with octave >5.1. Though pause() and kbhit() are still issues. 119 | 120 | ## 0.4.8 121 | * Fix for issue #37 gave origin to issue #38, hopefully, this fixes it. ^^ 122 | 123 | ## 0.4.7 124 | * Fixed issue #37, escaped paths. 125 | 126 | ## 0.4.6 127 | * Update "serialize-javascript": ">=3.1.0" 128 | 129 | ## 0.4.5 130 | * Update elliptic package to 6.5.3 (indirect dependency) 131 | 132 | ## 0.4.4 133 | * Bump lodash from 4.17.14 to 4.17.19. 134 | 135 | ## 0.4.3 136 | * Fix remove_all_breakpoint_in_file: unable to find function ex1 #33 137 | 138 | ## 0.4.2 139 | * Fixed security warnings given by the github bot. 140 | * Updated the octave language suggestions in readme. 141 | 142 | ## 0.4.1 143 | * Added support for long strings. 144 | 145 | ## 0.4.0 146 | * Added CommandLists which allow to buffers commands before sending them to Octave. This costs more than immediate mode for small commands, but for scopes with lots of variables provides gains of about 40~50% in variable fetching. Tested with scopes of about 30 variables. 147 | * Removed log messages that I believe aren't necessary. 148 | * Tweaked I/O between Octave and vsc. 149 | * Fixed "which" called without parameters bug when evaluating expressions. 150 | * Added confirmation for conditional breakpoints. 151 | * Improved struct field name parsing. Now it supports Matlab and Octave styles. Matlab is used by default because Octave allows for arbitrary strings which is more generic so less compatible. Also, completely arbitrary strings are not supported. 152 | * Updated readme, and fixed typos. 153 | * Improved String support. Before only sq_strings were supported, now both string and sq_string are supported. Still needs support for lazy loading of large strings. 154 | * Made UnknownType load when possible. 155 | * Extension changed enough to deserve a minor version bump. Completely arbitrary. :) 156 | 157 | ## 0.3.19 158 | * Removed the theme color. Doesn't look as I imagined. Have to figure out how to use it. 159 | * Webpacked the project. Should load faster, and maybe be a bit snappier. 160 | * Fixed WATCH errors for undefined variables. 161 | * Removed leading spaces in matrix hover evaluation. 162 | * Other smaller misc changes. 163 | 164 | ## 0.3.18 165 | * Tweaked the .vscodeignore. This removes unnecessary files, and should make the extension smaller and allow faster loading. 166 | * Pressing return/enter in the DEBUG CONSOLE no longer prints synchronization tags. 167 | * Fix a bug that would prevent stdio from the script, e.g. printf("foo"), from being output to the DEBUG CONSOLE as it would be mixed in the variable fetching and therefore silently ignored. Now commands have a begin and end to separate their output from other output. 168 | * Fixed Permutation, Complex Diagonal, and Diagonal Matrix types parsing. It has been broken for over 1 year, but apparently noone noticed. 169 | * Fixed bug with wrong HOVER evaluation, i.e. if you hover 'var', evaluate would eval "'var". 170 | * Fixed bug where evaluate errors would consume begin/end tags. Now if an error occurs the input handlers will skip their capture if it's passed their command number. 171 | 172 | ## 0.3.17 173 | * Updated readme. 174 | * Fixed support for multiple source directories. 175 | 176 | ## 0.3.16 177 | * Updated readme. 178 | * Added autoTerminate to launch.json to control if Octave terminates automatically when the last program instruction is executed. Default value is true, i.e. Octave process will be terminated after the last program line is executed. Setting this to false will allow the program to continue executing. 179 | * Fixes issue #25 basename fails when path is not a path, but includes a path-like structure. 180 | * Fixes issue with https-proxy-agent. 181 | 182 | ## 0.3.15 183 | * Updated readme. 184 | * Fixed a long-standing issue between stepping and pause, input, keyboard, and other input commands. Now pause can be used, but one needs to "continue/F5" the execution over those commands, or dbstep and multiple enters in the "DEBUG CONSOLE" as this bypasses the vsc-octave-debugger control commands. 185 | * Added option "logFilename" for debugging the extension. 186 | * Added option "verbose" for debugging the extension. 187 | * Simplified the configuration. Now a minimal config is only the "program" and "name". 188 | * Refactored the logger. Now user commands show immediately in the DEBUG CONSOLE. Stepping still shows the output only at the end. Will likely change this in the near future. 189 | 190 | ## 0.3.14 191 | * Security update. Upgraded lodash to version 4.17.13 or later. 192 | 193 | ## 0.3.13 194 | * Upgrade querystringify to version 2.0.0 195 | 196 | ## 0.3.12 197 | * Updated packages that had vulnerabilities. 198 | 199 | ## 0.3.11 200 | * Escaped variable names as these might contain special characters. 201 | * Avoided loading type again when loading new variable instances. 202 | 203 | ## 0.3.10 204 | * Small fix to the variable response. Now it wont stop if you step before the variable listing is complete. Needs refactoring as it's way too slow for certain complex codebases. 205 | * Added a bit of extra error logging. 206 | 207 | ## 0.3.9 208 | * Added Support for "pause" execution. 209 | * Fixed pausing issue that would break debugging session. 210 | 211 | ## 0.3.8 212 | * Now UnknownType never loads the value to prevent loading large data types. 213 | * Added support for cell type. 214 | 215 | ## 0.3.7 216 | * Fixed loading large structs. 217 | 218 | ## 0.3.6 219 | * Added support for function handles. 220 | * Added a variable workingDirectory to the program arguments. If not set defaults to program directory, if no program directory is given defaults to sourceDirectory, otherwise defaults to the filesystem root. 221 | * Fixed scalar struct loading children. Now children are loaded as variables which avoids loading very large matrices. Struct has the same issue but will require more work to fix. Next release. 222 | 223 | ## 0.3.5 224 | * Extended matrix support to all basic types. 225 | * Extended sparse matrix support to extra basic types. 226 | 227 | ## 0.3.4 228 | * Fixed readme. 229 | 230 | ## 0.3.3 231 | * Created an UnknownType, and make sure of its size allows loading. 232 | * Replaced the old evaluate and waitSend by a new execute, and evaluate respectively. 233 | * Added support for uint8 matrix type. 234 | * Added support for int and float (complex) scalar types. 235 | * Added support for bool type. 236 | * Added support for permutation matrices. 237 | * Added support for inline functions. 238 | 239 | ## 0.3.2 240 | * Added Range support. 241 | * Fixed a bug by which children of subclasses of matrix would be matrices instead of the subclass. 242 | 243 | ## 0.3.1 244 | * Added a new line to lines output to the console. 245 | 246 | ## 0.3.0 247 | * Added support for lazy_index (e.g. find(x)) 248 | * Added support for sq_string (e.g. argn) 249 | * Added support for sparse and sparse complex matrices. 250 | * Added support for diagonal and complex diagonal matrices. 251 | * Added support for bool matrix. 252 | * Added logging for output when stepping (e.g. printf). 253 | * Added buffering to very long program output. 254 | * Loading hover expressions as variables when possible. 255 | * Watch expressions now also load as variables if possible. 256 | 257 | ## 0.2.20 258 | * Fix for launchRequest before configurationDoneRequest. 259 | * Made expression handling more robust. 260 | 261 | ## 0.2.19 262 | * Extra fix for the terminator string when the last command is a print with no new line. 263 | * Added a separator between the sync command and previous Matlab commands. 264 | * Added filtering for 'debug> ' prompt. 265 | 266 | ## 0.2.18 267 | * Fixed stopping when no breakpoint is set. The runtime wouldn't catch the termination event. 268 | * Made console commands pass through directly to Octave. 269 | * Updated extension name in several locations. 270 | * Other minor aesthetic code changes. 271 | * Minor readme changes. 272 | * Set kill to SIGKILL to really kill Octave. Just in case... 273 | 274 | ## 0.2.17 275 | * Fixed bug when clearing breakpoints in file. It would set a single breakpoint. 276 | * Return "undefined" if the watched expression doesn't exist in the current context. 277 | 278 | ## 0.2.16 279 | * Added matrix and struct size to typename. Displayed when hovering over the name on the variables column. 280 | * Removed the name from the value of the watch variables content. 281 | 282 | ## 0.2.15 283 | * Renamed the extension. New commits will be done here. 284 | 285 | ## 0.2.14 286 | * Fixed a bug in the parsing of multi-column vectors/matrices. 287 | 288 | ## 0.2.13 289 | * Truly fixed the parsing of matrix/vector rows. 290 | * Partially fixed the evaluation of comments. For the time being I'm skipping expressions that don't exist. 291 | 292 | ## 0.2.12 293 | * Reverted a change that blocks the extension on the new vsc update. 294 | 295 | ## 0.2.11 296 | * Improved feedback on error. Now a message will be displayed in the console if the extension fails to connect to the Octave executable. Syntax errors are also shown in the console. 297 | 298 | ## 0.2.10 299 | * Made mouse hover expression evaluation always on. 300 | * Fixed the console function evaluation. It was still ignoring the first character. 301 | * Replaced sending quit message by actually killing the process as it sometimes stays spinning behind. 302 | 303 | ## 0.2.9 304 | * Removed the need for the / in console function evaluation. 305 | * Added a flag to allow visualization of ans. 306 | 307 | ## 0.2.8 308 | * Fixed a bug that made the vsc UI get stuck if a step response was sent in the middle of a stack request. 309 | 310 | ## 0.2.7 311 | * Added / to evaluate functions given via debug console. 312 | * Added extra logging, and a new verbose level for extension debug. 313 | * Other minor bug fixes. 314 | 315 | ## 0.2.6 316 | * Fixed bug that was preventing parsing or matrices with negative values. 317 | 318 | ## 0.2.5 319 | * Added support for both Matlab and Octave languages. 320 | * Made arbitrary expression evaluation on by default. Everything is evaluated except functions because of potential side effects. 321 | * Added a new icon. 322 | * Other misc code changes. 323 | 324 | ## 0.2.3 325 | * Fixed multiroot breakpoints issue. 326 | 327 | ## 0.2.2 328 | * Fixed the bug that was preventing the debug session from terminating on step. 329 | 330 | ## 0.2.1 331 | * Added logging for output from the program. I only noticed now that it wasn't supported. Output in orange. 332 | * Add logging for debug communications with Octave. This is output in white. Set "trace": true in your launch.json. 333 | * Set server side logging to off by default as this is only useful for development, e.g. unit tests. 334 | 335 | 336 | ## 0.2.0 337 | * Implemented variable fetching in chunks of 100 elements, e.g. 10x10 matrix will be completely fetched. while a 1000x1 will be fetched for selected ranges of elements. Matrices 2D or less with more than 100 elements can still be fetched in a single operation by setting prefetchCount in launch.json. A prefetchCount of 10 would load any 2D matrix up to 1000 elements (10 chunks of 100). The default is 1. 338 | 339 | ## 0.1.7 340 | * Refactored the matrix code. Now it retrieves the value once, and then parses the children from that value. This is only done for two (#indices) dimensional matrices, independent of their size in each dimension. 341 | * struct has also been separated from matrix. Now it uses the old matrix code. I'm assuming it'll never have as many elements as a matrix. 342 | 343 | 344 | ## 0.1.6 345 | * Fix for issue #7 "scope list children fails when answer contains new lines" 346 | 347 | ## 0.1.5 348 | * Refactored the matrix fetch to allow only fetching a predefined number of elements. That way the amount of time taken to obtain the contents of a matrix or sub-matrix can be somewhat controlled. A prefetchCount = 1 is the fastest, but also only shows elements when at the leaf level. A prefetchCount = 2 ~ 99 costs about the same as the cost is mainly dependent on the pipe communication. 349 | 350 | ## 0.1.4 351 | * Added a variable to launch.json to set the source directory so to add to Octave's path every time you debug. That way the source can be located anywhere on disk. Also added an option to set Octave's executable, in case you don't have it on your path. Updated documentation. 352 | 353 | ## 0.1.3 354 | * Updated demo animation. 355 | 356 | ## 0.1.2 357 | * Fixed stack navigation (update locals). 358 | 359 | ## 0.1.1 360 | * Updated documentation. 361 | 362 | ## 0.1.0 363 | * Created project. 364 | -------------------------------------------------------------------------------- /src/Variables/Matrix.ts: -------------------------------------------------------------------------------- 1 | import { Variable } from './Variable'; 2 | import { Variables } from './Variables'; 3 | import * as Constants from '../Constants'; 4 | import { Interval } from '../Utils/Interval'; 5 | import { CommandInterface } from '../Commands'; 6 | import { MatrixParser } from './Matrix/MatrixParser'; 7 | 8 | /* 9 | * Class that adds support for number based matrices. 10 | * This doesn't support string or character matrices. 11 | * 1D matrices are column vectors. 12 | */ 13 | export class Matrix extends Variable { 14 | //************************************************************************** 15 | private static MATRIX_TYPENAME: string = 'matrix'; 16 | // _typename caches type passed to the constructor, and is returned by typename() 17 | // However, for certain types this might differ from the overriden typename. 18 | // This is because mainly because in Octave typesnames might come in two formats. 19 | // e.g. "complex diagonal matrix" uses "diagonal matrix" in certain places... 20 | // typename used by Octave when printing the value 21 | private _typename: string; // typename used by Octave when printing the value 22 | private _fixedIndices: Array; // subvariable indices 23 | private _freeIndices: Array; // children indices 24 | private _children: Array; // the childen parsed so far 25 | private _availableChildrenRange: Array; // children parsed? 26 | private _validValue: boolean; // is the value parsable? 27 | private _parsedValue: boolean; // has the value been parsed? 28 | private _extendedTypename: string; // used for displaying when value is not valid. 29 | private _typeRegex: RegExp; // used to clean the value from pesky _typenames 30 | 31 | 32 | /*************************************************************************** 33 | * @param name the variable name without indices. 34 | * @param value the contents of the variable. 35 | * @param freeIndices the number of elements in each dimension. 36 | * @param fixedIndices if this is a part of a larger matrix, the right-most 37 | * indices that are used to access this submatrix (one based). 38 | * @param validValue actually the variable content and not a placeholder? 39 | **************************************************************************/ 40 | constructor( 41 | name: string = '', 42 | value: string = '', 43 | freeIndices: Array = [], 44 | fixedIndices: Array = [], 45 | validValue: boolean = true, 46 | type: string = Matrix.MATRIX_TYPENAME, 47 | ) 48 | { 49 | super(); 50 | // These need to be set before anything else is set. 51 | this._typeRegex = new RegExp(type, 'i') 52 | this.setBasename(name); 53 | this._typename = type; 54 | // These perform a more complex setting with some bookkeeping 55 | this.setIndices(freeIndices, fixedIndices); 56 | this.setValue(value, validValue); 57 | } 58 | 59 | 60 | //************************************************************************** 61 | public static cleanComplex(value: string): string { 62 | return value.replace(/(?:\s+([\+\-])\s+)/g, "$1"); 63 | } 64 | 65 | 66 | //************************************************************************** 67 | public numberOfFixableIndices(indices: Array): number { 68 | let fixableIndices = 0; 69 | 70 | if(indices.length !== 0) { 71 | for(let i = indices.length - 1; i != -1 && indices[i] === 1; --i) { 72 | ++fixableIndices; 73 | } 74 | } 75 | 76 | return fixableIndices; 77 | } 78 | 79 | 80 | //************************************************************************** 81 | public fixIndices(free: Array, fixed: Array): 82 | { free: Array, fixed: Array } 83 | { 84 | let fixableIndices = this.numberOfFixableIndices(free); 85 | // Move the 1's to the fixed indices 86 | if(fixableIndices !== 0) { 87 | free = free.slice(0, free.length - fixableIndices); 88 | fixed = Array(fixableIndices).fill(1).concat(fixed); 89 | } 90 | return { free: free, fixed: fixed }; 91 | } 92 | 93 | 94 | //************************************************************************** 95 | public setIndices(free: Array, fixed: Array): void { 96 | // Here, only non-1 free indices exist. Other indices are fixed. 97 | const idx = this.fixIndices(free, fixed); 98 | this._freeIndices = idx.free; 99 | this._fixedIndices = idx.fixed; 100 | // Variable name may include indices as needed to get its value. 101 | this.setIndexing(this.makeIndexing(this._freeIndices, this._fixedIndices)); 102 | // If we have free indices we have children. 103 | if(this._freeIndices.length !== 0) { 104 | this._numberOfChildren = this._freeIndices[this._freeIndices.length - 1]; 105 | // Sanity check only. Number of children should never be 0 if we have free indices. 106 | if(this._numberOfChildren !== 0) { 107 | // If we have children, add a reference to this variable so they can be fetched. 108 | Variables.addReferenceTo(this); 109 | } 110 | // Matrices have their size as part of their typename. 111 | const size = Matrix.getSizeString(this._freeIndices); 112 | this._extendedTypename = `${this.typename()} ${size}`; 113 | } 114 | } 115 | 116 | 117 | //************************************************************************** 118 | private static getSizeString(size: Array): string { 119 | if(size.length === 1) { 120 | return `${size}${Constants.SIZE_SEPARATOR}1`; 121 | 122 | } 123 | 124 | return size.join(Constants.SIZE_SEPARATOR); 125 | } 126 | 127 | 128 | //************************************************************************** 129 | public setValue(val: string, isValid: boolean): void { 130 | this._validValue = isValid; 131 | 132 | if(isValid) { 133 | // "this cleanup is done really just for displaying." 134 | val = val.replace(this._typeRegex, '').replace(/\n\s+/g, '\n').trim(); 135 | 136 | if(this.isComplex()) { 137 | val = MatrixParser.cleanComplex(val); 138 | } 139 | } 140 | 141 | this._value = val; 142 | } 143 | 144 | 145 | //************************************************************************** 146 | public typename(): string { return this._typename; } 147 | 148 | 149 | //************************************************************************** 150 | public extendedTypename(): string { return this._extendedTypename; } 151 | 152 | 153 | //************************************************************************** 154 | public fixedIndices(): Array { return this._fixedIndices; } 155 | 156 | 157 | //************************************************************************** 158 | public freeIndices(): Array { return this._freeIndices; } 159 | 160 | 161 | //************************************************************************** 162 | public loads(type: string): boolean { 163 | return type.endsWith(this.typename()); 164 | } 165 | 166 | 167 | //************************************************************************** 168 | public isComplex(): boolean { 169 | // TODO: typename() can be different from _typename 170 | return this.typename().includes("complex"); 171 | } 172 | 173 | 174 | //************************************************************************** 175 | public createConcreteType( 176 | name: string, 177 | value: string, 178 | freeIndices: Array, 179 | fixedIndices: Array, 180 | validValue: boolean, 181 | type: string 182 | ): Matrix 183 | { 184 | return new Matrix(name, value, freeIndices, fixedIndices, validValue, type); 185 | } 186 | 187 | 188 | //************************************************************************** 189 | public createChildType( 190 | value: string, 191 | freeIndices: Array, 192 | fixedIndices: Array, 193 | validValue: boolean 194 | ): Variable 195 | { 196 | return new Matrix(this.basename(), value, freeIndices, fixedIndices, validValue, this.typename()); 197 | } 198 | 199 | 200 | //************************************************************************** 201 | public loadNew( 202 | name: string, 203 | type: string, 204 | runtime: CommandInterface, 205 | callback: (m: Matrix) => void) 206 | { 207 | Variables.getSize(name, runtime, (size: Array) => { 208 | const loadable = Variables.loadable(size); 209 | 210 | const buildWith = (value: string) => { 211 | const matrix = this.createConcreteType(name, value, size, [], loadable, type); 212 | callback(matrix); 213 | }; 214 | 215 | if(loadable) { 216 | Variables.getValue(name, runtime, buildWith); 217 | } else { 218 | buildWith(Matrix.getSizeString(size)); 219 | } 220 | }); 221 | } 222 | 223 | 224 | //************************************************************************** 225 | public listChildren( 226 | runtime: CommandInterface, 227 | start: number, 228 | count: number, 229 | callback: (vars: Array) => void 230 | ): void 231 | { 232 | if(this._numberOfChildren === 0) { 233 | throw "Error: matrix has no children!"; 234 | } 235 | 236 | if(count === 0 && start === 0) { 237 | count = this._numberOfChildren; 238 | } 239 | 240 | const interval = new Interval(start, start+count); 241 | const self = this; 242 | 243 | this.makeChildrenAvailable(runtime, interval, () => { 244 | if(self._numberOfChildren !== self._children.length) { 245 | throw `Error: listChildren ${self._numberOfChildren} !== ${self._children.length}!`; 246 | } 247 | 248 | if(count === self._children.length) { 249 | callback(self._children); 250 | } else { 251 | callback(self._children.slice(start, start+count)); 252 | } 253 | }); 254 | } 255 | 256 | 257 | //************************************************************************** 258 | public makeChildrenAvailable( 259 | runtime: CommandInterface, 260 | interval: Interval, 261 | callback: () => void 262 | ): void 263 | { 264 | const self = this; 265 | if(this._validValue) { 266 | if(!this._parsedValue) { 267 | this.parseAllChildren((children: Array) => { 268 | self._children = children; 269 | callback(); 270 | }); 271 | } else { 272 | callback(); 273 | } 274 | } else { 275 | const rangesToFetch = this.unavailableOverlappingRanges(interval); 276 | 277 | if(rangesToFetch.length !== 0) { 278 | this.fetchRanges(rangesToFetch, runtime, callback); 279 | } else { 280 | callback(); 281 | } 282 | } 283 | } 284 | 285 | 286 | //************************************************************************** 287 | public fetchRanges( 288 | ranges: Array, 289 | runtime: CommandInterface, 290 | callback: () => void 291 | ): void 292 | { 293 | let fetchedRanges = 0; 294 | const self = this; 295 | 296 | ranges.forEach(interval => { 297 | this.fetchChildrenRange(interval, runtime, (children: Array) => { 298 | self.addChildrenFrom(interval, children); 299 | ++fetchedRanges; 300 | 301 | if(fetchedRanges === ranges.length) { 302 | callback(); 303 | } 304 | }) 305 | }); 306 | } 307 | 308 | 309 | //************************************************************************** 310 | public addChildrenFrom( 311 | interval: Interval, 312 | children: Array 313 | ): void 314 | { 315 | if(this._children === undefined) { 316 | this._children = new Array(this._numberOfChildren); 317 | } 318 | 319 | if(this._children.length < interval.min() + children.length) { 320 | throw `Matrix::addChildrenFrom dst ${this._children.length} < src ${interval.min()} + ${children.length}`; 321 | } 322 | 323 | for(let i = 0; i !== children.length; ++i) { 324 | this._children[i + interval.min()] = children[i]; 325 | } 326 | 327 | const a = Math.trunc(interval.min() / Constants.CHUNKS_SIZE); 328 | const b = Math.ceil(interval.max() / Constants.CHUNKS_SIZE); 329 | 330 | for(let i = a; i !== b; ++i) { 331 | this._availableChildrenRange[i] = true; 332 | } 333 | } 334 | 335 | 336 | //************************************************************************** 337 | public unavailableOverlappingRanges(interval: Interval): Array { 338 | if(this._availableChildrenRange === undefined) { 339 | const rangeCount = Math.ceil(this._numberOfChildren / Constants.CHUNKS_SIZE); 340 | this._availableChildrenRange = new Array(rangeCount); 341 | } 342 | 343 | const a = Math.trunc(interval.min() / Constants.CHUNKS_SIZE); 344 | const b = Math.ceil(interval.max() / Constants.CHUNKS_SIZE); 345 | let unavailable = new Array(); 346 | 347 | for(let i = a; i !== b; ++i) { 348 | if(!this._availableChildrenRange[i]) { 349 | const min = i * Constants.CHUNKS_SIZE; 350 | const max = Math.min(min + Constants.CHUNKS_SIZE, this._numberOfChildren); 351 | const interval = new Interval(min, max); 352 | 353 | if(unavailable.length !== 0) { 354 | const last = unavailable[unavailable.length - 1]; 355 | if(last.contigous(interval)) { 356 | last.expandWith(interval); 357 | } else { 358 | unavailable.push(interval); 359 | } 360 | } else { 361 | unavailable.push(interval); 362 | } 363 | } 364 | } 365 | 366 | return unavailable; 367 | } 368 | 369 | 370 | //************************************************************************** 371 | public fetchChildrenRange( 372 | interval: Interval, 373 | runtime: CommandInterface, 374 | callback: (vars: Array) => void 375 | ): void 376 | { 377 | const offset = interval.min(); 378 | const count = interval.size(); 379 | this.fetchChildren(runtime, offset, count, callback); 380 | } 381 | 382 | 383 | //************************************************************************** 384 | public parseChildren( 385 | value: string, 386 | offset: number, 387 | count: number, 388 | callback: (vars: Array) => void 389 | ): void 390 | { 391 | // We only unfold one index each time, the size of that index is "count": 392 | const childFreeIndices = this._freeIndices.length <= 1 ? [] : 393 | this._freeIndices.slice(0, this._freeIndices.length - 1); 394 | // The value must to contain data for offset/count range of children: 395 | const matrices = MatrixParser.parseMatrices(value, this.isComplex()); 396 | // Independent of what the children are, they'll be variables: 397 | const vars = new Array(count); 398 | if(matrices.length > 1) { 399 | // If we have many matrices, each child will be a matrix: 400 | for(let i = 0; i !== count; ++i) { 401 | const childFixedIndices = [i + offset + 1].concat(this._fixedIndices); 402 | const value = matrices[i].value(); 403 | vars[i] = this.createChildType(value, childFreeIndices, childFixedIndices, true); 404 | } 405 | } else if(matrices.length === 1 && matrices[0].size() > 1) { 406 | // If we have only one matrix, but many vectors, each child will be a vector: 407 | const matrix = matrices[0]; 408 | for(let i = 0; i !== count; ++i) { 409 | const childFixedIndices = [i + offset + 1].concat(this._fixedIndices); 410 | const vector = matrix.vector(i); 411 | // TODO: we're converting back the parsed data into string. Seems like a waste. 412 | const childValue = vector.join(Constants.COLUMN_ELEMENTS_SEPARATOR); 413 | vars[i] = this.createChildType(childValue, childFreeIndices, childFixedIndices, true); 414 | } 415 | } else if(matrices.length === 1 && matrices[0].size() === 1) { 416 | // If we have only one vector, each child will be a vector entry: 417 | const vector = matrices[0].vector(0); 418 | for(let i = 0; i !== count; ++i) { 419 | const childFixedIndices = [i + offset + 1].concat(this._fixedIndices); 420 | vars[i] = this.createChildType(vector[i], childFreeIndices, childFixedIndices, true); 421 | } 422 | } 423 | // return the children variables: 424 | callback(vars); 425 | } 426 | 427 | 428 | //************************************************************************** 429 | public parseAllChildren(callback: (vars: Array) => void): void { 430 | const value = this._value; 431 | 432 | if(this._freeIndices.length < 1) { 433 | throw `freeIndices.length: ${this._freeIndices.length}, expected >= 1!`; 434 | } 435 | 436 | const count = this._freeIndices[this._freeIndices.length - 1]; 437 | const self = this; 438 | 439 | this.parseChildren(value, 0, count, (vars: Array) => { 440 | self._parsedValue = true; 441 | callback(vars); 442 | }); 443 | } 444 | 445 | 446 | //************************************************************************** 447 | public fetchChildren( 448 | runtime: CommandInterface, 449 | offset: number, 450 | count: number, 451 | callback: (vars: Array) => void 452 | ): void 453 | { 454 | if(this._freeIndices.length === 0) { 455 | throw `fetchChildren::freeIndices.length: ${this._freeIndices.length} === 0`; 456 | } 457 | 458 | const childrenFreeIndices = this._freeIndices.slice(0, this._freeIndices.length - 1); 459 | const loadable = Variables.loadable(childrenFreeIndices, count); 460 | 461 | if(loadable) { 462 | this.loadChildrenRange(runtime, offset, count, callback); 463 | } else { 464 | const value = Matrix.getSizeString(childrenFreeIndices); 465 | const vars = new Array(count); 466 | for(let i = 0; i !== count; ++i) { 467 | const childrenFixedIndices = [offset + i + 1].concat(this._fixedIndices); 468 | vars[i] = this.createChildType(value, childrenFreeIndices, childrenFixedIndices, false); 469 | } 470 | callback(vars); 471 | } 472 | } 473 | 474 | 475 | //************************************************************************** 476 | public loadChildrenRange( 477 | runtime: CommandInterface, 478 | offset: number, 479 | count: number, 480 | callback: (vars: Array) => void 481 | ): void 482 | { 483 | const rangeName = this.makeRangeName(offset, count); 484 | Variables.getValue(rangeName, runtime, (value: string) => { 485 | // The "value" is not cached, as there might be ranges that aren't loaded. 486 | // We still need to potentially remove the typename from certain types. 487 | value = value.replace(this._typeRegex, '').trim(); 488 | this.parseChildren(value, offset, count, callback); 489 | }); 490 | } 491 | 492 | 493 | //************************************************************************** 494 | public fetchAllChildren( 495 | runtime: CommandInterface, 496 | callback: (vars: Array) => void 497 | ): void 498 | { 499 | const Nchildren = this._freeIndices[this._freeIndices.length - 1]; // #children 500 | this.fetchChildren(runtime, 0, Nchildren, callback); 501 | } 502 | 503 | 504 | //************************************************************************** 505 | public makeIndexing( 506 | freeIndices: Array, 507 | fixedIndices: Array, 508 | left: string = Constants.DEFAULT_LEFT, 509 | right: string = Constants.DEFAULT_RIGHT 510 | ): string 511 | { 512 | let freeIndicesStr = '', fixedIndicesStr = ''; 513 | 514 | if(fixedIndices.length !== 0) { 515 | fixedIndicesStr = fixedIndices.join(','); 516 | } else { 517 | return ''; 518 | } 519 | 520 | if(freeIndices.length !== 0) { 521 | freeIndicesStr = ':,'.repeat(freeIndices.length); 522 | } 523 | 524 | return `${left}${freeIndicesStr}${fixedIndicesStr}${right}`; 525 | } 526 | 527 | 528 | //************************************************************************** 529 | public makeRangeName( 530 | offset: number, 531 | count: number 532 | ): string 533 | { 534 | const name: string = this.basename(); 535 | const freeIndices: Array = this._freeIndices; 536 | const fixedIndices: Array = this._fixedIndices; 537 | 538 | let freeIndicesStr = '', fixedIndicesStr = ''; 539 | 540 | if(fixedIndices.length !== 0) { 541 | fixedIndicesStr = ',' + fixedIndices.join(','); 542 | } 543 | 544 | if(freeIndices.length !== 0) { 545 | freeIndicesStr = ':,'.repeat(freeIndices.length - 1); 546 | const a = offset + 1; // Indices are 1-based 547 | const b = offset + count; // Last index is inclusive 548 | freeIndicesStr += `${a}:${b}`; 549 | } 550 | 551 | return `${name}(${freeIndicesStr}${fixedIndicesStr})`; 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/tests/Matrix.test.ts: -------------------------------------------------------------------------------- 1 | import { OctaveLogger } from '../OctaveLogger'; 2 | import { Variable } from '../Variables/Variable'; 3 | import { Variables } from '../Variables/Variables'; 4 | import { Matrix } from '../Variables/Matrix'; 5 | import { SparseMatrix } from '../Variables/SparseMatrix'; 6 | import * as assert from 'assert'; 7 | import * as Constants from '../Constants'; 8 | import { Runtime } from '../Runtime'; 9 | import { MatrixParser } from '../Variables/Matrix/MatrixParser'; 10 | 11 | 12 | describe('Test Matrix', function() { 13 | // ******************************************************************************** 14 | describe('Variables.loadable empty', function() { 15 | it(`Should return true`, function() { 16 | const emptySize = []; 17 | assert.ok(Variables.loadable(emptySize)); 18 | }); 19 | }); 20 | 21 | 22 | // ******************************************************************************** 23 | describe('Matrix.makeName', function() { 24 | const name = 'm0D'; 25 | it(`Should return ${name}`, function() { 26 | assert.equal((new Matrix(name)).name(), name); 27 | }); 28 | const freeIndices = [2]; 29 | const fixedIndices = [3,4,5]; 30 | const expectedName = `${name}(:,3,4,5)`; 31 | it(`Should return ${expectedName}`, function() { 32 | assert.equal((new Matrix(name, '', freeIndices, fixedIndices, false)).name(), expectedName); 33 | }); 34 | }); 35 | 36 | 37 | // ******************************************************************************** 38 | describe('Matrix.parseChildren short', function() { 39 | const name = 'm1D'; 40 | const values = [0.59733, 0.48898, 0.97283]; 41 | const value = ` 42 | ${values[0]} 43 | ${values[1]} 44 | ${values[2]}`; 45 | const freeIndices = [values.length]; 46 | const fixedIndices = [2,4,5]; 47 | const count = freeIndices[freeIndices.length - 1]; 48 | const matrix = new Matrix(name, '', freeIndices, fixedIndices, false); 49 | matrix.parseChildren(value, 0, count, (children: Array) => { 50 | const expectedChildCount = values.length; 51 | 52 | it(`Should create ${expectedChildCount} child variables`, function() { 53 | assert.equal(children.length, expectedChildCount); 54 | }); 55 | 56 | for(let i = 0; i !== expectedChildCount; ++i) { 57 | const val = values[i]; 58 | const child = children[i]; 59 | const expectedName = `${name}(${i+1},${fixedIndices.join(',')})`; 60 | it(`Should match ${i}-th child value ${val}`, function() { 61 | assert.equal(child.value(), val); 62 | }); 63 | it(`Should match name ${expectedName}`, function() { 64 | assert.equal(child.name(), expectedName); 65 | }); 66 | } 67 | } 68 | ); 69 | }); 70 | 71 | 72 | // ******************************************************************************** 73 | describe('Matrix.parseChildren imaginary', function() { 74 | const name = 'm1D'; 75 | const values = ['0.0720969 + 0.0720969i', '0.8437697 + 0.8437697i', '0.4532340 + 0.4532340i']; 76 | const value = ` 77 | ${values[0]} 78 | ${values[1]} 79 | ${values[2]}`; 80 | const freeIndices = [values.length]; 81 | const fixedIndices = [2,4,5]; 82 | const count = freeIndices[freeIndices.length - 1]; 83 | const matrix = new Matrix(name, '', freeIndices, fixedIndices, false, "complex matrix"); 84 | matrix.parseChildren(value, 0, count, (children: Array) => { 85 | const expectedChildCount = values.length; 86 | 87 | it(`Should create ${expectedChildCount} child variables`, function() { 88 | assert.equal(children.length, expectedChildCount); 89 | }); 90 | 91 | for(let i = 0; i !== expectedChildCount; ++i) { 92 | const val = values[i].replace(/\s+/g, ''); // complex numbers have no spaces. 93 | const child = children[i]; 94 | const expectedName = `${name}(${i+1},${fixedIndices.join(',')})`; 95 | it(`Should match ${i}-th child value ${val}`, function() { 96 | assert.equal(child.value(), val); 97 | }); 98 | it(`Should match name ${expectedName}`, function() { 99 | assert.equal(child.name(), expectedName); 100 | }); 101 | } 102 | } 103 | ); 104 | }); 105 | 106 | 107 | // ******************************************************************************** 108 | describe('Matrix.parseAllChildren multi-column imaginary', function() { 109 | const name = 'm2D'; 110 | const values = [ // Add some extra spaces to test if those are correctly handled. 111 | ['0.0720969 + 0.0720969i', '0.8437697 + 0.8437697i', '0.4532340 + 0.4532340i'], 112 | ['0.3728132 + 0.3728132i', '0.4578918 + 0.4578918i', '0.2673617 + 0.2673617i'], 113 | ['0.3595750 + 0.3595750i', '0.4374822 + 0.4374822i', '0.2433045 + 0.2433045i'], 114 | ['0.2571942 + 0.2571942i', '0.8806884 + 0.8806884i', '0.6366056 + 0.6366056i'], 115 | ['0.0903121 + 0.0903121i', '0.0477089 + 0.0477089i', '0.7309768 + 0.7309768i'], 116 | ['0.0014517 + 0.0014517i', '0.5637025 + 0.5637025i', '0.7037470 + 0.7037470i'], 117 | ['0.8455747 + 0.8455747i', '0.0913287 + 0.0913287i', '0.0333014 + 0.0333014i'], 118 | ['0.3085562 + 0.3085562i', '0.3474878 + 0.3474878i', '0.1476501 + 0.1476501i'], 119 | ['0.2173087 + 0.2173087i', '0.2924523 + 0.2924523i', '0.4891809 + 0.4891809i'] 120 | ]; 121 | const value = 122 | `Columns 1 through 3: 123 | 124 | ${values[0][0]} ${values[1][0]} ${values[2][0]} 125 | ${values[0][1]} ${values[1][1]} ${values[2][1]} 126 | ${values[0][2]} ${values[1][2]} ${values[2][2]} 127 | 128 | Columns 4 through 6: 129 | 130 | ${values[3][0]} ${values[4][0]} ${values[5][0]} 131 | ${values[3][1]} ${values[4][1]} ${values[5][1]} 132 | ${values[3][2]} ${values[4][2]} ${values[5][2]} 133 | 134 | Columns 7 through 9: 135 | 136 | ${values[6][0]} ${values[7][0]} ${values[8][0]} 137 | ${values[6][1]} ${values[7][1]} ${values[8][1]} 138 | ${values[6][2]} ${values[7][2]} ${values[8][2]} 139 | `; 140 | const exp = /(?:\s+([\+\-])\s+)/g; // no spaces in complex values 141 | const columns = [ 142 | values[0].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 143 | values[1].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 144 | values[2].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 145 | values[3].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 146 | values[4].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 147 | values[5].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 148 | values[6].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 149 | values[7].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1"), 150 | values[8].join(Constants.COLUMN_ELEMENTS_SEPARATOR).replace(exp, "$1") 151 | ]; 152 | // freeIndices.size == two free indices 153 | const freeIndices = [columns[0].length, columns.length]; 154 | const fixedIndices = [4,5]; // 4 and 5 are actually 1-based indices 155 | const matrix = new Matrix(name, value, freeIndices, fixedIndices, true, "complex matrix"); 156 | matrix.parseAllChildren((children: Array) => { 157 | const expectedChildCount = columns.length; 158 | 159 | it(`Should create ${expectedChildCount} child variables`, function() { 160 | assert.equal(children.length, expectedChildCount); 161 | }); 162 | 163 | for(let i = 0; i !== expectedChildCount; ++i) { 164 | const val = columns[i]; 165 | const child = children[i]; 166 | const expectedName = `${name}(:,${i+1},${fixedIndices.join(',')})`; 167 | it(`Should match ${i}-th child value ${val}`, function() { 168 | assert.equal(child.value(), val); 169 | }); 170 | it(`Should match name ${expectedName}`, function() { 171 | assert.equal(child.name(), expectedName); 172 | }); 173 | } 174 | } 175 | ); 176 | }); 177 | 178 | 179 | // ******************************************************************************** 180 | describe('Matrix.fetchAllChildren not loadable', function() { 181 | const name = 'mND'; 182 | const freeIndices = [1, 2, 2, 2]; 183 | const fixedIndices = []; 184 | OctaveLogger.logToConsole = false; 185 | const runtime = new Runtime(Constants.DEFAULT_EXECUTABLE, [], {}, ['.'], '.', true, false); 186 | 187 | const matrix = new Matrix(name, '', freeIndices, fixedIndices, false); 188 | matrix.fetchAllChildren(runtime, (children: Array) => { 189 | runtime.disconnect(); 190 | const consumedIndex = freeIndices.length - 1; 191 | const expectedfreeIndices = freeIndices.slice(0, consumedIndex); 192 | const expectedChildCount = freeIndices[consumedIndex]; 193 | const expectedValue = expectedfreeIndices.join(Constants.SIZE_SEPARATOR); 194 | const prefix = (expectedfreeIndices.length !== 0? 195 | ':,'.repeat(expectedfreeIndices.length) : ''); 196 | const suffix = (fixedIndices.length !== 0? ',' + fixedIndices.join(',') : ''); 197 | 198 | it(`Should create ${expectedChildCount} child variables`, function() { 199 | assert.equal(children.length, expectedChildCount); 200 | }); 201 | 202 | for(let i = 0; i !== expectedChildCount; ++i) { 203 | const child = children[i]; 204 | const expectedName = `${name}(${prefix}${i+1}${suffix})`; 205 | it(`Should match name ${expectedName}`, function() { 206 | assert.equal(child.name(), expectedName); 207 | }); 208 | it(`Should match ${i}-th child value ${expectedValue}`, function() { 209 | assert.equal(child.value(), expectedValue); 210 | }); 211 | } 212 | } 213 | ); 214 | }); 215 | 216 | 217 | // ******************************************************************************** 218 | describe('Matrix.fetchAllChildren', async function() { 219 | const name = 'm3D'; 220 | // TODOing: make this a 2D matrix test 221 | const freeIndices = [2, 2]; 222 | const fixedIndices = []; 223 | const values = [['0.71780', '0.62359'], ['0.57914', '0.98442']]; 224 | const precision = values[0][0].length - 1; 225 | const vectors = [ 226 | `[${values[0].join(Constants.ROW_ELEMENTS_SEPARATOR)}]'`, 227 | `[${values[1].join(Constants.ROW_ELEMENTS_SEPARATOR)}]'`]; 228 | const consumedIndex = freeIndices.length - 1; 229 | const expectedFreeIndices = freeIndices.slice(0, consumedIndex); 230 | const expectedGranchildFreeIndices = 231 | (consumedIndex !== 0? freeIndices.slice(0, consumedIndex - 1) : []); 232 | const expectedChildCount = freeIndices[consumedIndex]; 233 | const prefix = (expectedFreeIndices.length !== 0? 234 | ':,'.repeat(expectedFreeIndices.length) : ''); 235 | const granchilPrefix = (expectedGranchildFreeIndices.length !== 0? 236 | ':,'.repeat(expectedGranchildFreeIndices.length) : ''); 237 | const suffix = (fixedIndices.length !== 0? ',' + fixedIndices.join(',') : ''); 238 | 239 | let runtime; 240 | let children; 241 | let grandchildren: Array> = []; 242 | 243 | before((done) => { 244 | OctaveLogger.logToConsole = false; 245 | runtime = new Runtime(Constants.DEFAULT_EXECUTABLE, [], {}, ['.'], '.', true, false); 246 | const cmd = `${name} = [${vectors[0]},${vectors[1]}];`; 247 | runtime.execute(`output_precision(${precision});`); 248 | runtime.evaluateAsLine(cmd, (output: string) => { 249 | const matrix = new Matrix(name, '', freeIndices, fixedIndices); 250 | matrix.fetchAllChildren(runtime, (parsedChildren: Array) => { 251 | children = parsedChildren; 252 | for(let i = 0; i !== children.length; ++i) { 253 | const child = children[i]; 254 | child.listChildren(runtime, 0, 0, (vars: Array) => { 255 | grandchildren.push(vars); 256 | if(grandchildren.length === children.length) { 257 | done(); 258 | } 259 | }); 260 | } 261 | } 262 | ); 263 | }); 264 | }); 265 | 266 | describe('Matrix.fetchAllChildren load from runtime', function() { 267 | it(`Should create ${expectedChildCount} child variables`, function() { 268 | assert.equal(children.length, expectedChildCount); 269 | }); 270 | 271 | for(let i = 0; i !== expectedChildCount; ++i) { 272 | const val = values[i].join(Constants.COLUMN_ELEMENTS_SEPARATOR); 273 | const expectedName = `${name}(${prefix}${i+1}${suffix})`; 274 | it(`Should match name ${expectedName}`, function() { 275 | const child = children[i]; 276 | assert.equal(child.name(), expectedName); 277 | }); 278 | it(`Should match ${i}-th child value ${val}`, function() { 279 | const child = children[i]; 280 | assert.equal(child.value(), val); 281 | }); 282 | } 283 | }); 284 | 285 | describe('Matrix.listChildren', function() { 286 | for(let col = 0; col !== expectedChildCount; ++col) { 287 | for(let row = 0; row !== values.length; ++row) { 288 | const val = values[col][row]; 289 | const expectedName = `${name}(${granchilPrefix}${row+1},${col+1}${suffix})`; 290 | it(`Should match name ${expectedName}`, function() { 291 | const grandchild = grandchildren[col][row]; 292 | assert.equal(grandchild.name(), expectedName); 293 | }); 294 | it(`Should match child[${col}] grandchild[${row}] value ${val}`, function() { 295 | const grandchild = grandchildren[col][row]; 296 | assert.equal(grandchild.value(), val); 297 | }); 298 | } 299 | } 300 | }); 301 | 302 | after(() => { 303 | runtime.disconnect(); 304 | }); 305 | }); 306 | 307 | 308 | // ******************************************************************************** 309 | describe('SparseMatrix tests', async function() { 310 | const name = "sm"; 311 | const rows = [1, 2, 3]; 312 | const columns = [4, 5, 6]; 313 | const values = ['7', '8', '9']; 314 | const expectedIndices = [10, 14, 18]; // find(s) 315 | 316 | let runtime: Runtime; 317 | let matrix: SparseMatrix; 318 | let indices: Array; 319 | let children: Array; 320 | 321 | before((done) => { 322 | OctaveLogger.logToConsole = false; 323 | runtime = new Runtime(Constants.DEFAULT_EXECUTABLE, [], {}, ['.'], '.', true, false); 324 | const cmd = `${name} = sparse([${rows.join(' ')}], [${columns.join(' ')}], [${values.join(' ')}]);`; 325 | runtime.evaluateAsLine(cmd, (output: string) => { 326 | const factory = new SparseMatrix(); 327 | factory.loadNew(name, factory.typename(), runtime, (sm: SparseMatrix) => { 328 | matrix = sm; 329 | matrix.listChildren(runtime, 0, values.length, (vars: Array) => { 330 | indices = matrix.indices(); 331 | children = vars; 332 | done(); 333 | }); 334 | }); 335 | }); 336 | }); 337 | 338 | describe('Matrix Instance Tests', function() { 339 | it(`Should match name ${name}`, function() { 340 | assert.equal(matrix.name(), name); 341 | }); 342 | it(`Should match indices`, function() { 343 | for(let index = 0; index !== indices.length; ++index) { 344 | assert.equal(indices[index], expectedIndices[index]); 345 | } 346 | }); 347 | }); 348 | 349 | describe('Matrix Children Tests', function() { 350 | for(let i = 0; i !== values.length; ++i) { 351 | const val = values[i]; 352 | const expectedName = `${name}(${expectedIndices[i]})`; 353 | it(`Should match name ${expectedName}`, function() { 354 | const child = children[i]; 355 | const childName = child.name(); 356 | assert.equal(childName, expectedName); 357 | }); 358 | it(`Should match ${expectedName} value ${values[i]}`, function() { 359 | const child = children[i]; 360 | const childValue = child.value(); 361 | assert.equal(childValue, val); 362 | }); 363 | } 364 | }); 365 | 366 | after(() => { 367 | runtime.disconnect(); 368 | }); 369 | }); 370 | 371 | 372 | // ******************************************************************************** 373 | describe('MatrixParser.parseMatrices', function() { 374 | const values = [ 375 | [ 376 | ['0.0720969+0.0720969i', '0.8437697+0.8437697i' ], 377 | ['0.3728132+0.3728132i', '0.4578918+0.4578918i' ], 378 | ['0.3595750+0.3595750i', '0.4374822+0.4374822i' ], 379 | 380 | ['0.2571942+0.2571942i', '0.8806884+0.8806884i' ], 381 | ['0.0903121+0.0903121i', '0.0477089+0.0477089i' ] 382 | ], 383 | [ 384 | ['0.0014517+0.0014517i', '0.5637025+0.5637025i' ], 385 | ['0.8455747+0.8455747i', '0.0913287+0.0913287i' ], 386 | ['0.3085562+0.3085562i', '0.3474878+0.3474878i' ], 387 | 388 | ['0.2173087+0.2173087i', '0.2924523+0.2924523i' ], 389 | ['0.2173087+0.2173087i', '0.2924523+0.2924523i' ] 390 | ] 391 | ]; 392 | const value = 393 | `ans(:,:,1,1) = 394 | 395 | Columns 1 through 3: 396 | 397 | ${values[0][0][0]} ${values[0][1][0]} ${values[0][2][0]} 398 | ${values[0][0][1]} ${values[0][1][1]} ${values[0][2][1]} 399 | 400 | Columns 4 and 5: 401 | 402 | ${values[0][3][0]} ${values[0][4][0]} 403 | ${values[0][3][1]} ${values[0][4][1]} 404 | 405 | ans(:,:,1,2) = 406 | 407 | Columns 1 through 3: 408 | 409 | ${values[1][0][0]} ${values[1][1][0]} ${values[1][2][0]} 410 | ${values[1][0][1]} ${values[1][1][1]} ${values[1][2][1]} 411 | 412 | Columns 4 and 5: 413 | 414 | ${values[1][3][0]} ${values[1][4][0]} 415 | ${values[1][3][1]} ${values[1][4][1]} 416 | `; 417 | const matrices = MatrixParser.parseMatrices(value, true); 418 | 419 | const expectedChildCount = values.length; 420 | const actualChildCount = matrices.length; 421 | // Check if we have the expected amount of parsed matrices: 422 | it(`Should create ${expectedChildCount} child variables`, function() { 423 | assert.equal(actualChildCount, expectedChildCount); 424 | }); 425 | // Check every value to make sure they were parsed correctly: 426 | for(let m = 0; m !== expectedChildCount; ++m) { 427 | const srcMatrix = values[m]; 428 | const dstMatrix = matrices[m]; 429 | 430 | const expectedName = `(:,:,1,${m+1})`; 431 | it(`Should match matrix indices ${expectedName}`, function() { 432 | assert.equal(dstMatrix.indices(), expectedName); 433 | }); 434 | 435 | for(let col = 0; col !== srcMatrix.length; ++col) { 436 | const srcVector = srcMatrix[col]; 437 | const dstVector = dstMatrix.vector(col); 438 | 439 | const expectedVectorSize = srcVector.length; 440 | const actualVectorSize = dstVector.length; 441 | it(`Should match vector size ${expectedVectorSize}`, function() { 442 | assert.equal(actualVectorSize, expectedVectorSize); 443 | }); 444 | 445 | for(let row = 0; row !== srcVector.length; ++row) { 446 | const srcVal = srcVector[row]; 447 | const dstVal = dstVector[row]; 448 | 449 | it(`Should match ${row}-th vector value ${srcVal}`, function() { 450 | assert.equal(dstVal, srcVal); 451 | }); 452 | } 453 | } 454 | } 455 | }); 456 | 457 | // ******************************************************************************** 458 | describe('MatrixParser.parseAllChildren multi-matrix imaginary', function() { 459 | const values = [ 460 | [ 461 | ['0.0720969+0.0720969i', '0.8437697+0.8437697i' ], 462 | ['0.3728132+0.3728132i', '0.4578918+0.4578918i' ], 463 | ['0.3595750+0.3595750i', '0.4374822+0.4374822i' ], 464 | 465 | ['0.2571942+0.2571942i', '0.8806884+0.8806884i' ], 466 | ['0.0903121+0.0903121i', '0.0477089+0.0477089i' ] 467 | ], 468 | [ 469 | ['0.0014517+0.0014517i', '0.5637025+0.5637025i' ], 470 | ['0.8455747+0.8455747i', '0.0913287+0.0913287i' ], 471 | ['0.3085562+0.3085562i', '0.3474878+0.3474878i' ], 472 | 473 | ['0.2173087+0.2173087i', '0.2924523+0.2924523i' ], 474 | ['0.2173087+0.2173087i', '0.2924523+0.2924523i' ] 475 | ] 476 | ]; 477 | const valuesRegexes = [ 478 | `Columns 1 through 3: 479 | ${values[0][0][0]} ${values[0][1][0]} ${values[0][2][0]} 480 | ${values[0][0][1]} ${values[0][1][1]} ${values[0][2][1]} 481 | Columns 4 and 5: 482 | ${values[0][3][0]} ${values[0][4][0]} 483 | ${values[0][3][1]} ${values[0][4][1]}`, 484 | `Columns 1 through 3: 485 | ${values[1][0][0]} ${values[1][1][0]} ${values[1][2][0]} 486 | ${values[1][0][1]} ${values[1][1][1]} ${values[1][2][1]} 487 | Columns 4 and 5: 488 | ${values[1][3][0]} ${values[1][4][0]} 489 | ${values[1][3][1]} ${values[1][4][1]}` 490 | ]; 491 | const value = 492 | `ans(:,:,1,1) = 493 | 494 | Columns 1 through 3: 495 | 496 | ${values[0][0][0]} ${values[0][1][0]} ${values[0][2][0]} 497 | ${values[0][0][1]} ${values[0][1][1]} ${values[0][2][1]} 498 | 499 | Columns 4 and 5: 500 | 501 | ${values[0][3][0]} ${values[0][4][0]} 502 | ${values[0][3][1]} ${values[0][4][1]} 503 | 504 | ans(:,:,1,2) = 505 | 506 | Columns 1 through 3: 507 | 508 | ${values[1][0][0]} ${values[1][1][0]} ${values[1][2][0]} 509 | ${values[1][0][1]} ${values[1][1][1]} ${values[1][2][1]} 510 | 511 | Columns 4 and 5: 512 | 513 | ${values[1][3][0]} ${values[1][4][0]} 514 | ${values[1][3][1]} ${values[1][4][1]} 515 | `; 516 | 517 | const name = "mm"; 518 | const freeIndices = [values[0][0].length, values[0].length, 1, values.length]; 519 | const fixedIndices = []; 520 | const matrix = new Matrix(name, value, freeIndices, fixedIndices, true, "complex matrix"); 521 | matrix.parseAllChildren((matrices: Array) => { 522 | const expectedChildCount = values.length; 523 | const actualChildCount = matrices.length; 524 | // Check if we have the expected amount of parsed matrices: 525 | it(`Should create ${expectedChildCount} child variables`, function() { 526 | assert.equal(actualChildCount, expectedChildCount); 527 | }); 528 | // Check every value to make sure they were parsed correctly: 529 | for(let i = 0; i !== expectedChildCount; ++i) { 530 | const val = valuesRegexes[i]; 531 | const child = matrices[i]; 532 | const expectedName = `${name}(:,:,1,${i+1})`; 533 | it(`Should match ${i}-th child value ${val}`, function() { 534 | assert.equal(child.value(), val); 535 | }); 536 | it(`Should match name ${expectedName}`, function() { 537 | assert.equal(child.name(), expectedName); 538 | }); 539 | } 540 | }); 541 | }); 542 | }); 543 | --------------------------------------------------------------------------------