@circuit-forge/eda-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/erc/checker.d.ts +15 -0
  4. package/dist/erc/checker.d.ts.map +1 -0
  5. package/dist/erc/checker.js +267 -0
  6. package/dist/erc/checker.js.map +1 -0
  7. package/dist/erc/codes.d.ts +15 -0
  8. package/dist/erc/codes.d.ts.map +1 -0
  9. package/dist/erc/codes.js +48 -0
  10. package/dist/erc/codes.js.map +1 -0
  11. package/dist/erc/index.d.ts +7 -0
  12. package/dist/erc/index.d.ts.map +1 -0
  13. package/dist/erc/index.js +14 -0
  14. package/dist/erc/index.js.map +1 -0
  15. package/dist/index.d.ts +20 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +79 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/netlist/generator.d.ts +32 -0
  20. package/dist/netlist/generator.d.ts.map +1 -0
  21. package/dist/netlist/generator.js +183 -0
  22. package/dist/netlist/generator.js.map +1 -0
  23. package/dist/netlist/index.d.ts +6 -0
  24. package/dist/netlist/index.d.ts.map +1 -0
  25. package/dist/netlist/index.js +20 -0
  26. package/dist/netlist/index.js.map +1 -0
  27. package/dist/netlist/sanitizer.d.ts +45 -0
  28. package/dist/netlist/sanitizer.d.ts.map +1 -0
  29. package/dist/netlist/sanitizer.js +145 -0
  30. package/dist/netlist/sanitizer.js.map +1 -0
  31. package/dist/parser/csv-parser.d.ts +27 -0
  32. package/dist/parser/csv-parser.d.ts.map +1 -0
  33. package/dist/parser/csv-parser.js +198 -0
  34. package/dist/parser/csv-parser.js.map +1 -0
  35. package/dist/parser/index.d.ts +6 -0
  36. package/dist/parser/index.d.ts.map +1 -0
  37. package/dist/parser/index.js +15 -0
  38. package/dist/parser/index.js.map +1 -0
  39. package/dist/parser/netlist-parser.d.ts +25 -0
  40. package/dist/parser/netlist-parser.d.ts.map +1 -0
  41. package/dist/parser/netlist-parser.js +260 -0
  42. package/dist/parser/netlist-parser.js.map +1 -0
  43. package/dist/schemas/analysis.schema.d.ts +298 -0
  44. package/dist/schemas/analysis.schema.d.ts.map +1 -0
  45. package/dist/schemas/analysis.schema.js +91 -0
  46. package/dist/schemas/analysis.schema.js.map +1 -0
  47. package/dist/schemas/circuit.schema.d.ts +405 -0
  48. package/dist/schemas/circuit.schema.d.ts.map +1 -0
  49. package/dist/schemas/circuit.schema.js +121 -0
  50. package/dist/schemas/circuit.schema.js.map +1 -0
  51. package/dist/types/analysis.d.ts +61 -0
  52. package/dist/types/analysis.d.ts.map +1 -0
  53. package/dist/types/analysis.js +119 -0
  54. package/dist/types/analysis.js.map +1 -0
  55. package/dist/types/circuit.d.ts +94 -0
  56. package/dist/types/circuit.d.ts.map +1 -0
  57. package/dist/types/circuit.js +32 -0
  58. package/dist/types/circuit.js.map +1 -0
  59. package/dist/types/erc.d.ts +68 -0
  60. package/dist/types/erc.d.ts.map +1 -0
  61. package/dist/types/erc.js +33 -0
  62. package/dist/types/erc.js.map +1 -0
  63. package/dist/types/simulation.d.ts +61 -0
  64. package/dist/types/simulation.d.ts.map +1 -0
  65. package/dist/types/simulation.js +43 -0
  66. package/dist/types/simulation.js.map +1 -0
  67. package/dist/utils/index.d.ts +5 -0
  68. package/dist/utils/index.d.ts.map +1 -0
  69. package/dist/utils/index.js +14 -0
  70. package/dist/utils/index.js.map +1 -0
  71. package/dist/utils/unit-parser.d.ts +47 -0
  72. package/dist/utils/unit-parser.d.ts.map +1 -0
  73. package/dist/utils/unit-parser.js +229 -0
  74. package/dist/utils/unit-parser.js.map +1 -0
  75. package/package.json +58 -0
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateNetlist = generateNetlist;
4
+ exports.getNodeNames = getNodeNames;
5
+ exports.validateNetlist = validateNetlist;
6
+ const circuit_1 = require("../types/circuit");
7
+ const analysis_1 = require("../types/analysis");
8
+ const sanitizer_1 = require("./sanitizer");
9
+ /**
10
+ * Default diode model for circuits without explicit model
11
+ */
12
+ const DEFAULT_DIODE_MODEL = `.model DDEFAULT D(IS=1e-14 N=1.05 RS=10 BV=100 IBV=1e-10)`;
13
+ /**
14
+ * Generate a complete SPICE netlist from circuit JSON
15
+ */
16
+ function generateNetlist(circuit, analysis, options = {}) {
17
+ const lines = [];
18
+ // Title
19
+ const title = options.title || circuit.metadata?.name || 'Untitled Circuit';
20
+ lines.push(`* ${title}`);
21
+ lines.push(`* Generated by eda-core`);
22
+ lines.push(`* ${new Date().toISOString()}`);
23
+ lines.push('');
24
+ // Build node map
25
+ const nodeMap = buildNodeMap(circuit.nets);
26
+ // Check if we need default diode model
27
+ const needsDiodeModel = circuit.components.some((c) => c.type === 'diode' && !c.model);
28
+ if (needsDiodeModel) {
29
+ lines.push('* Default diode model');
30
+ lines.push(DEFAULT_DIODE_MODEL);
31
+ lines.push('');
32
+ }
33
+ // Include files (with validation)
34
+ if (options.includeFiles && options.includeFiles.length > 0) {
35
+ if (options.jobDir) {
36
+ (0, sanitizer_1.validateIncludePaths)(options.includeFiles, options.jobDir);
37
+ }
38
+ lines.push('* Include files');
39
+ for (const file of options.includeFiles) {
40
+ lines.push(`.include "${file}"`);
41
+ }
42
+ lines.push('');
43
+ }
44
+ // Components
45
+ lines.push('* Components');
46
+ for (const component of circuit.components) {
47
+ const spiceLine = componentToSpice(component, nodeMap);
48
+ if (spiceLine) {
49
+ lines.push(spiceLine);
50
+ }
51
+ }
52
+ lines.push('');
53
+ // Analysis
54
+ lines.push('* Analysis');
55
+ lines.push((0, analysis_1.analysisToSpice)(analysis));
56
+ lines.push('');
57
+ // Control block for output
58
+ const probes = options.probes || generateDefaultProbes(circuit, nodeMap);
59
+ lines.push('* Control block');
60
+ lines.push('.control');
61
+ lines.push(' set filetype=ascii');
62
+ lines.push(' run');
63
+ if (probes.length > 0) {
64
+ const probeList = probes.join(' ');
65
+ lines.push(` wrdata output.csv ${probeList}`);
66
+ }
67
+ lines.push(' quit');
68
+ lines.push('.endc');
69
+ lines.push('');
70
+ lines.push('.end');
71
+ return lines.join('\n');
72
+ }
73
+ /**
74
+ * Build a map from net IDs to SPICE node names
75
+ */
76
+ function buildNodeMap(nets) {
77
+ const nodeMap = new Map();
78
+ for (const net of nets) {
79
+ if (net.isGround) {
80
+ nodeMap.set(net.id, '0');
81
+ }
82
+ else {
83
+ nodeMap.set(net.id, (0, sanitizer_1.sanitizeNodeName)(net.id));
84
+ }
85
+ }
86
+ return nodeMap;
87
+ }
88
+ /**
89
+ * Convert a component to a SPICE line
90
+ */
91
+ function componentToSpice(component, nodeMap) {
92
+ const { type, designator, value, model, pins } = component;
93
+ // Ground is not a real component
94
+ if (type === 'ground') {
95
+ return null;
96
+ }
97
+ const prefix = circuit_1.SPICE_PREFIXES[type];
98
+ if (!prefix) {
99
+ throw new Error(`Unknown component type: ${type}`);
100
+ }
101
+ // Get node names for pins
102
+ const nodes = pins.map((pin) => {
103
+ const node = nodeMap.get(pin.netId);
104
+ if (!node) {
105
+ throw new Error(`Net not found: ${pin.netId} for component ${designator}`);
106
+ }
107
+ return node;
108
+ });
109
+ // Generate SPICE line based on component type
110
+ switch (type) {
111
+ case 'resistor':
112
+ case 'capacitor':
113
+ case 'inductor':
114
+ return `${designator} ${nodes.join(' ')} ${value || '0'}`;
115
+ case 'voltage_source':
116
+ case 'current_source':
117
+ return `${designator} ${nodes.join(' ')} ${value || 'DC 0'}`;
118
+ case 'diode':
119
+ return `${designator} ${nodes.join(' ')} ${model || 'DDEFAULT'}`;
120
+ default:
121
+ throw new Error(`Unsupported component type: ${type}`);
122
+ }
123
+ }
124
+ /**
125
+ * Generate default probes (all node voltages)
126
+ */
127
+ function generateDefaultProbes(circuit, nodeMap) {
128
+ const probes = [];
129
+ for (const net of circuit.nets) {
130
+ if (!net.isGround) {
131
+ const nodeName = nodeMap.get(net.id);
132
+ if (nodeName) {
133
+ probes.push(`v(${nodeName})`);
134
+ }
135
+ }
136
+ }
137
+ return probes;
138
+ }
139
+ /**
140
+ * Get all unique node names from a circuit
141
+ */
142
+ function getNodeNames(circuit) {
143
+ const nodeMap = buildNodeMap(circuit.nets);
144
+ return Array.from(nodeMap.values());
145
+ }
146
+ /**
147
+ * Validate that a netlist is syntactically correct (basic check)
148
+ */
149
+ function validateNetlist(netlist) {
150
+ const errors = [];
151
+ const lines = netlist.split('\n');
152
+ let hasEnd = false;
153
+ let hasAnalysis = false;
154
+ for (let i = 0; i < lines.length; i++) {
155
+ const line = lines[i]?.trim() || '';
156
+ // Skip comments and empty lines
157
+ if (line.startsWith('*') || line === '') {
158
+ continue;
159
+ }
160
+ // Check for .end
161
+ if (line.toLowerCase() === '.end') {
162
+ hasEnd = true;
163
+ }
164
+ // Check for analysis
165
+ if (line.toLowerCase().startsWith('.tran') ||
166
+ line.toLowerCase().startsWith('.ac') ||
167
+ line.toLowerCase().startsWith('.dc') ||
168
+ line.toLowerCase().startsWith('.op')) {
169
+ hasAnalysis = true;
170
+ }
171
+ }
172
+ if (!hasEnd) {
173
+ errors.push('Netlist missing .end statement');
174
+ }
175
+ if (!hasAnalysis) {
176
+ errors.push('Netlist missing analysis command (.tran, .ac, .dc, or .op)');
177
+ }
178
+ return {
179
+ valid: errors.length === 0,
180
+ errors,
181
+ };
182
+ }
183
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/netlist/generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Netlist Module Exports
3
+ */
4
+ export { generateNetlist, getNodeNames, validateNetlist, type NetlistOptions, } from './generator';
5
+ export { sanitizeNodeName, validateIncludePaths, validateIncludePath, sanitizeNetlist, sanitizeValue, validateDesignator, hasShellMetacharacters, SecurityError, } from './sanitizer';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/netlist/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACH,eAAe,EACf,YAAY,EACZ,eAAe,EACf,KAAK,cAAc,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACH,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,GAChB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SecurityError = exports.hasShellMetacharacters = exports.validateDesignator = exports.sanitizeValue = exports.sanitizeNetlist = exports.validateIncludePath = exports.validateIncludePaths = exports.sanitizeNodeName = exports.validateNetlist = exports.getNodeNames = exports.generateNetlist = void 0;
4
+ /**
5
+ * Netlist Module Exports
6
+ */
7
+ var generator_1 = require("./generator");
8
+ Object.defineProperty(exports, "generateNetlist", { enumerable: true, get: function () { return generator_1.generateNetlist; } });
9
+ Object.defineProperty(exports, "getNodeNames", { enumerable: true, get: function () { return generator_1.getNodeNames; } });
10
+ Object.defineProperty(exports, "validateNetlist", { enumerable: true, get: function () { return generator_1.validateNetlist; } });
11
+ var sanitizer_1 = require("./sanitizer");
12
+ Object.defineProperty(exports, "sanitizeNodeName", { enumerable: true, get: function () { return sanitizer_1.sanitizeNodeName; } });
13
+ Object.defineProperty(exports, "validateIncludePaths", { enumerable: true, get: function () { return sanitizer_1.validateIncludePaths; } });
14
+ Object.defineProperty(exports, "validateIncludePath", { enumerable: true, get: function () { return sanitizer_1.validateIncludePath; } });
15
+ Object.defineProperty(exports, "sanitizeNetlist", { enumerable: true, get: function () { return sanitizer_1.sanitizeNetlist; } });
16
+ Object.defineProperty(exports, "sanitizeValue", { enumerable: true, get: function () { return sanitizer_1.sanitizeValue; } });
17
+ Object.defineProperty(exports, "validateDesignator", { enumerable: true, get: function () { return sanitizer_1.validateDesignator; } });
18
+ Object.defineProperty(exports, "hasShellMetacharacters", { enumerable: true, get: function () { return sanitizer_1.hasShellMetacharacters; } });
19
+ Object.defineProperty(exports, "SecurityError", { enumerable: true, get: function () { return sanitizer_1.SecurityError; } });
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/netlist/index.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,yCAKqB;AAJjB,4GAAA,eAAe,OAAA;AACf,yGAAA,YAAY,OAAA;AACZ,4GAAA,eAAe,OAAA;AAGnB,yCASqB;AARjB,6GAAA,gBAAgB,OAAA;AAChB,iHAAA,oBAAoB,OAAA;AACpB,gHAAA,mBAAmB,OAAA;AACnB,4GAAA,eAAe,OAAA;AACf,0GAAA,aAAa,OAAA;AACb,+GAAA,kBAAkB,OAAA;AAClB,mHAAA,sBAAsB,OAAA;AACtB,0GAAA,aAAa,OAAA"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Netlist Sanitization Utilities
3
+ * Security-focused input validation and path sanitization
4
+ */
5
+ /**
6
+ * Sanitize a node name for SPICE compatibility
7
+ * - Removes special characters
8
+ * - Ensures it doesn't start with a number
9
+ * - Avoids reserved words
10
+ */
11
+ export declare function sanitizeNodeName(name: string): string;
12
+ /**
13
+ * Validate include paths to prevent path traversal attacks
14
+ * All paths must be relative and within the job directory
15
+ */
16
+ export declare function validateIncludePaths(paths: string[], jobDir: string): void;
17
+ /**
18
+ * Validate a single include path
19
+ */
20
+ export declare function validateIncludePath(includePath: string, _jobDir: string): void;
21
+ /**
22
+ * Sanitize netlist content for dangerous patterns
23
+ */
24
+ export declare function sanitizeNetlist(netlist: string, jobDir: string): string;
25
+ /**
26
+ * Security error class
27
+ */
28
+ export declare class SecurityError extends Error {
29
+ readonly code: string;
30
+ constructor(message: string, code: string);
31
+ }
32
+ /**
33
+ * Sanitize a component value string
34
+ * Removes potentially dangerous characters while keeping valid SPICE syntax
35
+ */
36
+ export declare function sanitizeValue(value: string): string;
37
+ /**
38
+ * Validate a designator format
39
+ */
40
+ export declare function validateDesignator(designator: string): boolean;
41
+ /**
42
+ * Check if a string contains shell metacharacters
43
+ */
44
+ export declare function hasShellMetacharacters(str: string): boolean;
45
+ //# sourceMappingURL=sanitizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/netlist/sanitizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiBH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBrD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAI1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgC9E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA2BvE;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAK5C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAInD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAI3D"}
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * Netlist Sanitization Utilities
4
+ * Security-focused input validation and path sanitization
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SecurityError = void 0;
8
+ exports.sanitizeNodeName = sanitizeNodeName;
9
+ exports.validateIncludePaths = validateIncludePaths;
10
+ exports.validateIncludePath = validateIncludePath;
11
+ exports.sanitizeNetlist = sanitizeNetlist;
12
+ exports.sanitizeValue = sanitizeValue;
13
+ exports.validateDesignator = validateDesignator;
14
+ exports.hasShellMetacharacters = hasShellMetacharacters;
15
+ /**
16
+ * Reserved SPICE words that cannot be used as node names
17
+ */
18
+ const RESERVED_WORDS = new Set([
19
+ 'all',
20
+ 'none',
21
+ 'in',
22
+ 'out',
23
+ 'vcc',
24
+ 'vdd',
25
+ 'vss',
26
+ 'gnd',
27
+ 'ground',
28
+ ]);
29
+ /**
30
+ * Sanitize a node name for SPICE compatibility
31
+ * - Removes special characters
32
+ * - Ensures it doesn't start with a number
33
+ * - Avoids reserved words
34
+ */
35
+ function sanitizeNodeName(name) {
36
+ // Remove special characters, keep alphanumeric and underscore
37
+ let sanitized = name.replace(/[^a-zA-Z0-9_]/g, '_');
38
+ // Ensure it doesn't start with a number
39
+ if (/^[0-9]/.test(sanitized)) {
40
+ sanitized = `n${sanitized}`;
41
+ }
42
+ // Prefix if it's a reserved word
43
+ if (RESERVED_WORDS.has(sanitized.toLowerCase())) {
44
+ sanitized = `x_${sanitized}`;
45
+ }
46
+ // Ensure not empty
47
+ if (sanitized === '') {
48
+ sanitized = 'node';
49
+ }
50
+ // Prefix with 'n' for clarity
51
+ if (!sanitized.startsWith('n') && !sanitized.startsWith('x_')) {
52
+ sanitized = `n${sanitized}`;
53
+ }
54
+ return sanitized;
55
+ }
56
+ /**
57
+ * Validate include paths to prevent path traversal attacks
58
+ * All paths must be relative and within the job directory
59
+ */
60
+ function validateIncludePaths(paths, jobDir) {
61
+ for (const includePath of paths) {
62
+ validateIncludePath(includePath, jobDir);
63
+ }
64
+ }
65
+ /**
66
+ * Validate a single include path
67
+ */
68
+ function validateIncludePath(includePath, _jobDir) {
69
+ // Reject absolute paths (Unix or Windows style)
70
+ if (includePath.startsWith('/') || /^[A-Za-z]:/.test(includePath)) {
71
+ throw new SecurityError(`Absolute include path not allowed: ${includePath}`, 'ABSOLUTE_PATH');
72
+ }
73
+ // Reject path traversal
74
+ if (includePath.includes('..')) {
75
+ throw new SecurityError(`Path traversal not allowed: ${includePath}`, 'PATH_TRAVERSAL');
76
+ }
77
+ // Reject paths starting with special characters
78
+ if (includePath.startsWith('~') || includePath.startsWith('$')) {
79
+ throw new SecurityError(`Special path prefix not allowed: ${includePath}`, 'SPECIAL_PREFIX');
80
+ }
81
+ // Validate characters (alphanumeric, underscore, dash, dot, slash)
82
+ if (!/^[a-zA-Z0-9_\-./]+$/.test(includePath)) {
83
+ throw new SecurityError(`Invalid characters in include path: ${includePath}`, 'INVALID_CHARS');
84
+ }
85
+ }
86
+ /**
87
+ * Sanitize netlist content for dangerous patterns
88
+ */
89
+ function sanitizeNetlist(netlist, jobDir) {
90
+ const lines = netlist.split('\n');
91
+ const sanitized = [];
92
+ for (const line of lines) {
93
+ const trimmed = line.trim().toLowerCase();
94
+ // Validate .include statements
95
+ if (trimmed.startsWith('.include')) {
96
+ const match = line.match(/\.include\s+["']?([^"'\s]+)["']?/i);
97
+ if (match && match[1]) {
98
+ validateIncludePath(match[1], jobDir);
99
+ }
100
+ }
101
+ // Block potentially dangerous directives
102
+ if (trimmed.startsWith('.shell') || trimmed.startsWith('.system')) {
103
+ throw new SecurityError('Shell commands not allowed in netlist', 'SHELL_COMMAND');
104
+ }
105
+ sanitized.push(line);
106
+ }
107
+ return sanitized.join('\n');
108
+ }
109
+ /**
110
+ * Security error class
111
+ */
112
+ class SecurityError extends Error {
113
+ code;
114
+ constructor(message, code) {
115
+ super(message);
116
+ this.name = 'SecurityError';
117
+ this.code = code;
118
+ }
119
+ }
120
+ exports.SecurityError = SecurityError;
121
+ /**
122
+ * Sanitize a component value string
123
+ * Removes potentially dangerous characters while keeping valid SPICE syntax
124
+ */
125
+ function sanitizeValue(value) {
126
+ // Allow: numbers, letters (for units), spaces, parentheses (for sources), +/-/.
127
+ const sanitized = value.replace(/[^a-zA-Z0-9\s()+\-.,_]/g, '');
128
+ return sanitized.trim();
129
+ }
130
+ /**
131
+ * Validate a designator format
132
+ */
133
+ function validateDesignator(designator) {
134
+ // Must start with a letter, followed by alphanumeric, ending with a number
135
+ return /^[A-Za-z][A-Za-z0-9]*[0-9]+$/.test(designator);
136
+ }
137
+ /**
138
+ * Check if a string contains shell metacharacters
139
+ */
140
+ function hasShellMetacharacters(str) {
141
+ // Characters that could be used for shell injection
142
+ const shellMeta = /[;&|`$<>\\!#{}[\]*?'"]/;
143
+ return shellMeta.test(str);
144
+ }
145
+ //# sourceMappingURL=sanitizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizer.js","sourceRoot":"","sources":["../../src/netlist/sanitizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * CSV Parser for ngspice wrdata output
3
+ * Parses simulation output into structured JSON format
4
+ */
5
+ import type { SimulationResult } from '../types/simulation';
6
+ /**
7
+ * Parse ngspice wrdata CSV output to SimulationResult
8
+ *
9
+ * wrdata format:
10
+ * - Whitespace-separated values
11
+ * - First column is X axis (time, frequency, voltage depending on analysis)
12
+ * - Subsequent columns are Y values for each probe
13
+ */
14
+ export declare function parseCsv(csvContent: string, probeNames: string[], analysisType?: string): SimulationResult;
15
+ /**
16
+ * Parse ngspice raw ASCII format (alternative parser)
17
+ */
18
+ export declare function parseRawAscii(rawContent: string, analysisType?: string): SimulationResult;
19
+ /**
20
+ * Detect output format from content
21
+ */
22
+ export declare function detectOutputFormat(content: string): 'csv' | 'raw' | 'unknown';
23
+ /**
24
+ * Auto-parse output based on detected format
25
+ */
26
+ export declare function parseSimulationOutput(content: string, probeNames: string[], analysisType?: string): SimulationResult;
27
+ //# sourceMappingURL=csv-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv-parser.d.ts","sourceRoot":"","sources":["../../src/parser/csv-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAqC,MAAM,qBAAqB,CAAC;AAE/F;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAAE,EACpB,YAAY,GAAE,MAAe,GAC9B,gBAAgB,CA6ClB;AAoDD;;GAEG;AACH,wBAAgB,aAAa,CACzB,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,MAAe,GAC9B,gBAAgB,CAqElB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAkB7E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAAE,EACpB,YAAY,GAAE,MAAe,GAC9B,gBAAgB,CAWlB"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCsv = parseCsv;
4
+ exports.parseRawAscii = parseRawAscii;
5
+ exports.detectOutputFormat = detectOutputFormat;
6
+ exports.parseSimulationOutput = parseSimulationOutput;
7
+ /**
8
+ * Parse ngspice wrdata CSV output to SimulationResult
9
+ *
10
+ * wrdata format:
11
+ * - Whitespace-separated values
12
+ * - First column is X axis (time, frequency, voltage depending on analysis)
13
+ * - Subsequent columns are Y values for each probe
14
+ */
15
+ function parseCsv(csvContent, probeNames, analysisType = 'tran') {
16
+ const lines = csvContent.trim().split('\n');
17
+ if (lines.length === 0) {
18
+ return createEmptyResult(analysisType);
19
+ }
20
+ // Initialize series for each probe
21
+ const series = probeNames.map((name) => ({
22
+ name,
23
+ points: [],
24
+ }));
25
+ // Parse each line.
26
+ // ngspice `wrdata` (ascii) repeats the scale (x) column BEFORE each vector, so a row
27
+ // for N probes is laid out as: x y1 x y2 x y3 ... (2 columns per probe). We detect
28
+ // that interleaved layout by width and fall back to a single leading x column
29
+ // (x y1 y2 y3 ...) for other producers.
30
+ for (const line of lines) {
31
+ const trimmed = line.trim();
32
+ if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith('*')) {
33
+ continue; // Skip empty lines and comments
34
+ }
35
+ const values = trimmed.split(/\s+/).map(parseFloat);
36
+ if (values.length < 2) {
37
+ continue; // Need at least X and one Y value
38
+ }
39
+ const interleaved = values.length >= 2 * probeNames.length;
40
+ for (let i = 0; i < probeNames.length; i++) {
41
+ const x = interleaved ? values[2 * i] : values[0];
42
+ const y = interleaved ? values[2 * i + 1] : values[i + 1];
43
+ if (x !== undefined && y !== undefined && !isNaN(x) && !isNaN(y)) {
44
+ series[i]?.points.push({ x, y });
45
+ }
46
+ }
47
+ }
48
+ // Build metadata
49
+ const meta = buildMeta(analysisType, series);
50
+ return { meta, series };
51
+ }
52
+ /**
53
+ * Build result metadata
54
+ */
55
+ function buildMeta(analysisType, series) {
56
+ const pointsCount = series[0]?.points.length || 0;
57
+ let xLabel = 'time';
58
+ let xUnit = 's';
59
+ switch (analysisType) {
60
+ case 'tran':
61
+ xLabel = 'time';
62
+ xUnit = 's';
63
+ break;
64
+ case 'ac':
65
+ xLabel = 'frequency';
66
+ xUnit = 'Hz';
67
+ break;
68
+ case 'dc':
69
+ xLabel = 'voltage';
70
+ xUnit = 'V';
71
+ break;
72
+ case 'op':
73
+ xLabel = 'point';
74
+ xUnit = undefined;
75
+ break;
76
+ }
77
+ return {
78
+ analysisType,
79
+ xLabel,
80
+ xUnit,
81
+ pointsCount,
82
+ };
83
+ }
84
+ /**
85
+ * Create an empty result
86
+ */
87
+ function createEmptyResult(analysisType) {
88
+ return {
89
+ meta: {
90
+ analysisType,
91
+ xLabel: 'time',
92
+ pointsCount: 0,
93
+ },
94
+ series: [],
95
+ };
96
+ }
97
+ /**
98
+ * Parse ngspice raw ASCII format (alternative parser)
99
+ */
100
+ function parseRawAscii(rawContent, analysisType = 'tran') {
101
+ const lines = rawContent.split('\n');
102
+ let inData = false;
103
+ let variables = [];
104
+ const dataBlocks = [];
105
+ for (const line of lines) {
106
+ const trimmed = line.trim();
107
+ // Parse header
108
+ if (trimmed.startsWith('No. Variables:')) {
109
+ // Number of variables
110
+ }
111
+ else if (trimmed.startsWith('No. Points:')) {
112
+ // Parse number of points (for reference, not used directly)
113
+ }
114
+ else if (trimmed.startsWith('Variables:')) {
115
+ inData = false;
116
+ // Next lines will be variable names
117
+ }
118
+ else if (trimmed.match(/^\d+\s+[\w()]+/)) {
119
+ // Variable definition line: "0 time time"
120
+ const parts = trimmed.split(/\s+/);
121
+ if (parts[1]) {
122
+ variables.push(parts[1]);
123
+ }
124
+ }
125
+ else if (trimmed === 'Values:' || trimmed === 'Binary:') {
126
+ inData = true;
127
+ }
128
+ else if (inData && trimmed !== '') {
129
+ // Parse data values
130
+ const values = trimmed.split(/\s+/).map((v) => {
131
+ // Handle complex numbers (take real part)
132
+ if (v.includes(',')) {
133
+ return parseFloat(v.split(',')[0] || '0');
134
+ }
135
+ return parseFloat(v);
136
+ });
137
+ if (values.length > 0 && !values.some(isNaN)) {
138
+ dataBlocks.push(values);
139
+ }
140
+ }
141
+ }
142
+ // Convert to series format
143
+ const series = [];
144
+ if (variables.length > 1 && dataBlocks.length > 0) {
145
+ // First variable is usually time/frequency
146
+ for (let i = 1; i < variables.length; i++) {
147
+ const points = [];
148
+ for (const block of dataBlocks) {
149
+ const x = block[0];
150
+ const y = block[i];
151
+ if (x !== undefined && y !== undefined) {
152
+ points.push({ x, y });
153
+ }
154
+ }
155
+ series.push({
156
+ name: variables[i] || `var${i}`,
157
+ points,
158
+ });
159
+ }
160
+ }
161
+ return {
162
+ meta: buildMeta(analysisType, series),
163
+ series,
164
+ };
165
+ }
166
+ /**
167
+ * Detect output format from content
168
+ */
169
+ function detectOutputFormat(content) {
170
+ const firstLines = content.split('\n').slice(0, 10).join('\n');
171
+ if (firstLines.includes('Plotname:') || firstLines.includes('No. Variables:')) {
172
+ return 'raw';
173
+ }
174
+ // CSV-like format (just numbers separated by whitespace)
175
+ const firstDataLine = content.split('\n').find((l) => {
176
+ const trimmed = l.trim();
177
+ return trimmed !== '' && !trimmed.startsWith('*') && !trimmed.startsWith('#');
178
+ });
179
+ if (firstDataLine && /^[\d.eE+\-\s]+$/.test(firstDataLine.trim())) {
180
+ return 'csv';
181
+ }
182
+ return 'unknown';
183
+ }
184
+ /**
185
+ * Auto-parse output based on detected format
186
+ */
187
+ function parseSimulationOutput(content, probeNames, analysisType = 'tran') {
188
+ const format = detectOutputFormat(content);
189
+ switch (format) {
190
+ case 'csv':
191
+ return parseCsv(content, probeNames, analysisType);
192
+ case 'raw':
193
+ return parseRawAscii(content, analysisType);
194
+ default:
195
+ throw new Error(`Unknown output format. Content starts with: ${content.substring(0, 100)}`);
196
+ }
197
+ }
198
+ //# sourceMappingURL=csv-parser.js.map