Skip to content

Commit c0faf44

Browse files
committed
feat: Implement core compiler, VM, standard library, and package manager infrastructure with VS Code extension support.
1 parent ebc04bf commit c0faf44

11 files changed

Lines changed: 431 additions & 119 deletions

File tree

extension/snippets/proxpl-snippets.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,61 @@
2929
"}"
3030
],
3131
"description": "If block"
32+
},
33+
"For Loop": {
34+
"prefix": "for",
35+
"body": [
36+
"for (var ${1:i} = 0; ${1:i} < ${2:count}; ${1:i} = ${1:i} + 1) {",
37+
"\t${3:// code}",
38+
"}"
39+
],
40+
"description": "For loop"
41+
},
42+
"While Loop": {
43+
"prefix": "while",
44+
"body": [
45+
"while (${1:condition}) {",
46+
"\t${2:// code}",
47+
"}"
48+
],
49+
"description": "While loop"
50+
},
51+
"Function": {
52+
"prefix": "func",
53+
"body": [
54+
"func ${1:name}(${2:params}) {",
55+
"\t${3:// code}",
56+
"}"
57+
],
58+
"description": "Function declaration"
59+
},
60+
"Class": {
61+
"prefix": "class",
62+
"body": [
63+
"class ${1:Name} {",
64+
"\tfunc ${1:Name}(${2:params}) {",
65+
"\t\t${3:// constructor}",
66+
"\t}",
67+
"}"
68+
],
69+
"description": "Class declaration"
70+
},
71+
"Print": {
72+
"prefix": "print",
73+
"body": [
74+
"print ${1:value};"
75+
],
76+
"description": "Print statement"
77+
},
78+
"Try-Catch": {
79+
"prefix": "try",
80+
"body": [
81+
"try {",
82+
"\t${1:// try code}",
83+
"} catch (err) {",
84+
"\t${2:// handle error}",
85+
"}"
86+
],
87+
"description": "Try-Catch block"
3288
}
3389
}

extension/src/extension.ts

Lines changed: 171 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function activate(context: vscode.ExtensionContext) {
3535

3636
// Save file before running
3737
editor.document.save().then(() => {
38-
let terminal = vscode.window.terminals.find(t => t.name === 'ProXPL');
38+
let terminal = vscode.window.terminals.find(t => t.name === 'ProXPL');
3939
if (!terminal) {
4040
terminal = vscode.window.createTerminal('ProXPL');
4141
}
@@ -66,121 +66,191 @@ export function activate(context: vscode.ExtensionContext) {
6666
});
6767
*/
6868
});
69-
});
7069
});
7170
});
72-
context.subscriptions.push(runCommand);
73-
74-
// 2. Watch Mode
75-
let isWatchMode = false;
76-
let watchStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
77-
watchStatusBarItem.command = 'proxpl.startWatchMode';
78-
context.subscriptions.push(watchStatusBarItem);
79-
80-
const updateStatusBar = () => {
81-
if (isWatchMode) {
82-
watchStatusBarItem.text = '$(eye) ProXPL Watch: ON';
83-
watchStatusBarItem.tooltip = 'Stop Watching ProXPL Files';
84-
watchStatusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
85-
} else {
86-
watchStatusBarItem.text = '$(eye-closed) ProXPL Watch: OFF';
87-
watchStatusBarItem.tooltip = 'Start Watching ProXPL Files';
88-
watchStatusBarItem.backgroundColor = undefined;
89-
}
90-
watchStatusBarItem.show();
91-
};
92-
updateStatusBar();
71+
});
72+
context.subscriptions.push(runCommand);
9373

94-
let watchCommand = vscode.commands.registerCommand('proxpl.startWatchMode', () => {
95-
isWatchMode = !isWatchMode;
96-
updateStatusBar();
97-
vscode.window.showInformationMessage(isWatchMode ? 'ProXPL Watch Mode Started' : 'ProXPL Watch Mode Stopped');
98-
});
99-
context.subscriptions.push(watchCommand);
74+
// 2. Watch Mode
75+
let isWatchMode = false;
76+
let watchStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
77+
watchStatusBarItem.command = 'proxpl.startWatchMode';
78+
context.subscriptions.push(watchStatusBarItem);
10079

101-
vscode.workspace.onDidSaveTextDocument((document) => {
102-
if (isWatchMode && (document.fileName.endsWith('.prox') || document.fileName.endsWith('.pxpl'))) {
103-
const terminalName = 'ProXPL Debugger';
104-
let terminal = vscode.window.terminals.find(t => t.name === terminalName);
80+
const updateStatusBar = () => {
81+
if (isWatchMode) {
82+
watchStatusBarItem.text = '$(eye) ProXPL Watch: ON';
83+
watchStatusBarItem.tooltip = 'Stop Watching ProXPL Files';
84+
watchStatusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
85+
} else {
86+
watchStatusBarItem.text = '$(eye-closed) ProXPL Watch: OFF';
87+
watchStatusBarItem.tooltip = 'Start Watching ProXPL Files';
88+
watchStatusBarItem.backgroundColor = undefined;
89+
}
90+
watchStatusBarItem.show();
91+
};
92+
updateStatusBar();
10593

106-
if (!terminal) {
107-
terminal = vscode.window.createTerminal(terminalName);
108-
}
94+
let watchCommand = vscode.commands.registerCommand('proxpl.startWatchMode', () => {
95+
isWatchMode = !isWatchMode;
96+
updateStatusBar();
97+
vscode.window.showInformationMessage(isWatchMode ? 'ProXPL Watch Mode Started' : 'ProXPL Watch Mode Stopped');
98+
});
99+
context.subscriptions.push(watchCommand);
100+
101+
vscode.workspace.onDidSaveTextDocument((document) => {
102+
if (isWatchMode && (document.fileName.endsWith('.prox') || document.fileName.endsWith('.pxpl'))) {
103+
const terminalName = 'ProXPL Debugger';
104+
let terminal = vscode.window.terminals.find(t => t.name === terminalName);
109105

110-
terminal.show(); // Focus the terminal as requested
111-
// Clear previous output
112-
vscode.commands.executeCommand('workbench.action.terminal.clear');
113-
terminal.sendText(`proxpl "${document.fileName}"`);
106+
if (!terminal) {
107+
terminal = vscode.window.createTerminal(terminalName);
114108
}
115-
});
116109

117-
// 3. Formatter Provider
118-
const formattingProvider = vscode.languages.registerDocumentFormattingEditProvider('proxpl', {
119-
provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] {
120-
const edits: vscode.TextEdit[] = [];
121-
let lastLineWasEmpty = false;
122-
123-
for (let i = 0; i < document.lineCount; i++) {
124-
const line = document.lineAt(i);
125-
const text = line.text;
126-
127-
// 1. Remove extra empty lines (consecutive empty lines)
128-
if (text.trim() === '') {
129-
if (lastLineWasEmpty) {
130-
// Delete this extra empty line
131-
edits.push(vscode.TextEdit.delete(line.rangeIncludingLineBreak));
132-
continue;
133-
}
134-
lastLineWasEmpty = true;
135-
} else {
136-
lastLineWasEmpty = false;
137-
}
110+
terminal.show(); // Focus the terminal as requested
111+
// Clear previous output
112+
vscode.commands.executeCommand('workbench.action.terminal.clear');
113+
terminal.sendText(`proxpl "${document.fileName}"`);
114+
}
115+
});
116+
117+
// 3. Formatter Provider
118+
const formattingProvider = vscode.languages.registerDocumentFormattingEditProvider('proxpl', {
119+
provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] {
120+
const edits: vscode.TextEdit[] = [];
121+
let lastLineWasEmpty = false;
122+
123+
for (let i = 0; i < document.lineCount; i++) {
124+
const line = document.lineAt(i);
125+
const text = line.text;
138126

139-
// 2. Remove trailing whitespace
140-
if (text.endsWith(' ') || text.endsWith('\t')) {
141-
edits.push(vscode.TextEdit.delete(new vscode.Range(i, text.trimEnd().length, i, text.length)));
127+
// 1. Remove extra empty lines (consecutive empty lines)
128+
if (text.trim() === '') {
129+
if (lastLineWasEmpty) {
130+
// Delete this extra empty line
131+
edits.push(vscode.TextEdit.delete(line.rangeIncludingLineBreak));
132+
continue;
142133
}
134+
lastLineWasEmpty = true;
135+
} else {
136+
lastLineWasEmpty = false;
137+
}
143138

144-
// 3. Basic Indentation (Fix to 4 spaces)
145-
const indentMatch = text.match(/^(\s+)/);
146-
if (indentMatch) {
147-
const oldIndent = indentMatch[1];
148-
const newIndent = oldIndent.replace(/\t/g, ' ');
149-
if (oldIndent !== newIndent) {
150-
edits.push(vscode.TextEdit.replace(new vscode.Range(i, 0, i, oldIndent.length), newIndent));
151-
}
139+
// 2. Remove trailing whitespace
140+
if (text.endsWith(' ') || text.endsWith('\t')) {
141+
edits.push(vscode.TextEdit.delete(new vscode.Range(i, text.trimEnd().length, i, text.length)));
142+
}
143+
144+
// 3. Basic Indentation (Fix to 4 spaces)
145+
const indentMatch = text.match(/^(\s+)/);
146+
if (indentMatch) {
147+
const oldIndent = indentMatch[1];
148+
const newIndent = oldIndent.replace(/\t/g, ' ');
149+
if (oldIndent !== newIndent) {
150+
edits.push(vscode.TextEdit.replace(new vscode.Range(i, 0, i, oldIndent.length), newIndent));
152151
}
153152
}
154-
return edits;
155153
}
156-
});
157-
context.subscriptions.push(formattingProvider);
158-
159-
// 4. Hover Support
160-
const hoverProvider = vscode.languages.registerHoverProvider('proxpl', {
161-
provideHover(document: vscode.TextDocument, position: vscode.Position) {
162-
const range = document.getWordRangeAtPosition(position);
163-
if (!range) return null;
164-
const word = document.getText(range);
165-
166-
const descriptions: { [key: string]: string } = {
167-
'func': 'Defines a new function in ProXPL. Syntax: `func name(params) { ... }`',
168-
'var': 'Declares a new variable. ProXPL is dynamically typed but variables must be declared.',
169-
'if': 'Conditional statement. Executes a block if the condition is true.',
170-
'else': 'Defines an alternative block for an `if` statement.',
171-
'while': 'Loop that continues as long as a condition is true.',
172-
'return': 'Exits a function and optionally returns a value.',
173-
'print': 'Built-in function to output values to the terminal.',
174-
'import': 'Incorporates external modules into the current script.'
175-
};
176-
177-
if (descriptions[word]) {
178-
return new vscode.Hover(new vscode.MarkdownString(descriptions[word]));
179-
}
180-
return null;
154+
return edits;
155+
}
156+
});
157+
context.subscriptions.push(formattingProvider);
158+
159+
// 4. Hover Support
160+
const hoverProvider = vscode.languages.registerHoverProvider('proxpl', {
161+
provideHover(document: vscode.TextDocument, position: vscode.Position) {
162+
const range = document.getWordRangeAtPosition(position);
163+
if (!range) return null;
164+
const word = document.getText(range);
165+
166+
const descriptions: { [key: string]: string } = {
167+
'func': 'Defines a new function in ProXPL. Syntax: `func name(params) { ... }`',
168+
'var': 'Declares a new variable. ProXPL is dynamically typed but variables must be declared (using `let` or `const` preferred).',
169+
'let': 'Declares a mutable variable.',
170+
'const': 'Declares an immutable constant.',
171+
'if': 'Conditional statement. Executes a block if the condition is true.',
172+
'else': 'Defines an alternative block for an `if` statement.',
173+
'while': 'Loop that continues as long as a condition is true.',
174+
'for': 'Loop with initializer, condition, and increment.',
175+
'return': 'Exits a function and optionally returns a value.',
176+
'print': 'Built-in statement/function to output values to the terminal.',
177+
'import': 'Incorporates external modules into the current script.',
178+
'class': 'Defines a new class with methods and properties.',
179+
'this': 'Refers to the current instance of the class.',
180+
'super': 'Refers to the superclass.',
181+
'true': 'Boolean true literal.',
182+
'false': 'Boolean false literal.',
183+
'null': 'Represents the absence of value.',
184+
'len': 'Built-in function. Returns the length of a string or list.',
185+
'str': 'Built-in function. Converts a value to a string.',
186+
'clock': 'Built-in function. Returns the current time in seconds.',
187+
'input': 'Built-in function. Reads a line of input from stdin.',
188+
'type': 'Built-in function. Returns the type of a value.',
189+
'try': 'Starts a block of code to test for errors.',
190+
'catch': 'Handles errors thrown in the try block.',
191+
'throw': 'Throws an error/exception.'
192+
};
193+
194+
if (descriptions[word]) {
195+
return new vscode.Hover(new vscode.MarkdownString(descriptions[word]));
181196
}
182-
});
183-
context.subscriptions.push(hoverProvider);
197+
return null;
198+
}
199+
});
200+
context.subscriptions.push(hoverProvider);
201+
202+
// 5. Definition Provider (Basic "Go to Definition")
203+
const definitionProvider = vscode.languages.registerDefinitionProvider('proxpl', {
204+
provideDefinition(document: vscode.TextDocument, position: vscode.Position) {
205+
const range = document.getWordRangeAtPosition(position);
206+
if (!range) return null;
207+
const word = document.getText(range);
208+
209+
const text = document.getText();
210+
// Regex to find 'func word' or 'class word'
211+
const funcRegex = new RegExp(`func\\s+${word}\\s*\\(`, 'g');
212+
const classRegex = new RegExp(`class\\s+${word}\\s*\\{`, 'g');
213+
214+
const results: vscode.Location[] = [];
215+
216+
let match;
217+
while ((match = funcRegex.exec(text)) !== null) {
218+
const pos = document.positionAt(match.index);
219+
results.push(new vscode.Location(document.uri, new vscode.Range(pos, pos)));
220+
}
221+
while ((match = classRegex.exec(text)) !== null) {
222+
const pos = document.positionAt(match.index);
223+
results.push(new vscode.Location(document.uri, new vscode.Range(pos, pos)));
224+
}
225+
226+
return results;
227+
}
228+
});
229+
context.subscriptions.push(definitionProvider);
230+
231+
// 6. Completion Item Provider
232+
const completionProvider = vscode.languages.registerCompletionItemProvider('proxpl', {
233+
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
234+
const keywords = [
235+
'func', 'class', 'if', 'else', 'while', 'for', 'return', 'print',
236+
'var', 'let', 'const', 'true', 'false', 'null', 'import', 'export',
237+
'from', 'as', 'try', 'catch', 'throw', 'async', 'await'
238+
];
239+
const builtins = ['len', 'str', 'clock', 'input', 'type'];
240+
241+
const items: vscode.CompletionItem[] = [];
242+
243+
keywords.forEach(word => {
244+
items.push(new vscode.CompletionItem(word, vscode.CompletionItemKind.Keyword));
245+
});
246+
builtins.forEach(word => {
247+
items.push(new vscode.CompletionItem(word, vscode.CompletionItemKind.Function));
248+
});
249+
250+
return items;
251+
}
252+
});
253+
context.subscriptions.push(completionProvider);
184254
}
185255

186256
function mapLineNumber(lineStr: string): number {

include/parser.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ typedef struct {
1919
int current;
2020
bool panicMode;
2121
bool hadError;
22+
const char *source;
2223
} Parser;
2324

24-
// Initialize parser with token array
25-
void initParser(Parser *parser, Token *tokens, int count);
25+
// Initialize parser with token array and source code
26+
void initParser(Parser *parser, Token *tokens, int count, const char *source);
2627

2728
// Main parse function - returns list of statements (program)
2829
StmtList *parse(Parser *parser);

0 commit comments

Comments
 (0)