Skip to content

Commit 634ceb9

Browse files
feat: implement type annotations and return statements in parser
1 parent 76125c8 commit 634ceb9

File tree

13 files changed

+746
-131
lines changed

13 files changed

+746
-131
lines changed

deno.lock

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export default defineConfig([
1414
{
1515
rules: {
1616
"@typescript-eslint/no-explicit-any": "off",
17+
"@typescript-eslint/no-unused-vars": "warn",
18+
"@typescript-eslint/no-empty-object-type": "warn",
19+
"@typescript-eslint/prefer-as-const": "warn",
1720
},
1821
},
1922
]);

main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ async function repl() {
1212
const input = prompt("> ");
1313
if (!input || input.includes("exit")) {
1414
process.exit(1);
15+
// Deno.exit(1);
1516
}
1617
const program = parser.proTypeAsst(input);
1718
const results = evaluate(program, env);

src/cli/jistc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { parseArgs } from "node:util";
22
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
3-
import { join, dirname, relative, resolve } from "node:path";
3+
import { join, dirname, relative } from "node:path";
44
import { glob } from "glob";
55
import Parser from "../parser/parser";
66
import { transpileToJS } from "../compiler/transpiler";

src/compiler/transpiler.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ObjectLiteral,
1111
ArrayLiteral,
1212
AssignmentExpr,
13+
TypeAnnotation,
1314
} from "../parser/typeAst";
1415

1516
interface TranspileOptions {
@@ -23,13 +24,60 @@ interface TranspileOptions {
2324
export function transpileToJS(ast: Program, options: TranspileOptions = {}): string {
2425
const ctx = new TranspileContext(options);
2526
const programCode = transpileProgram(ast, ctx);
26-
// Mark function runtime //
27+
28+
// State management runtime //
2729
const runtime =
2830
options.injectRuntime !== false
2931
? `// JistScript Runtime
3032
const mark = typeof window !== 'undefined' && window.console
3133
? (...args) => console.log('[JistScript]', ...args)
3234
: (...args) => console.log('[JistScript]', ...args);
35+
36+
// State management system
37+
const __jist_state_counter = { value: 0 };
38+
const __jist_state_store = new Map();
39+
const __jist_component_states = new Map();
40+
41+
function useState(initialValue, typeInfo) {
42+
const stateId = __jist_state_counter.value++;
43+
44+
if (!__jist_state_store.has(stateId)) {
45+
__jist_state_store.set(stateId, initialValue);
46+
}
47+
48+
const currentValue = __jist_state_store.get(stateId);
49+
50+
const setState = (newValue) => {
51+
if (typeInfo && !validateType(newValue, typeInfo)) {
52+
console.warn(\`[JistScript] Type mismatch: expected \${typeInfo.type}, got \${typeof newValue}\`);
53+
}
54+
__jist_state_store.set(stateId, newValue);
55+
return newValue;
56+
};
57+
58+
return [currentValue, setState];
59+
}
60+
61+
function validateType(value, typeInfo) {
62+
switch (typeInfo.type) {
63+
case 'String':
64+
return typeof value === 'string';
65+
case 'Number':
66+
return typeof value === 'number';
67+
case 'Boolean':
68+
return typeof value === 'boolean';
69+
case 'Array':
70+
if (!Array.isArray(value)) return false;
71+
if (typeInfo.elementType) {
72+
return value.every(item => validateType(item, { type: typeInfo.elementType }));
73+
}
74+
return true;
75+
case 'Object':
76+
return typeof value === 'object' && value !== null && !Array.isArray(value);
77+
default:
78+
return true;
79+
}
80+
}
3381
`
3482
: "";
3583

@@ -61,6 +109,11 @@ function transpileStatement(stmt: Statement, ctx: TranspileContext): string {
61109
case "VarDeclaration": {
62110
const decl = stmt as VarDeclaration;
63111
const keyword = decl.constant ? "const" : "let";
112+
if (decl.destructuring && decl.value) {
113+
const elements = decl.destructuring.elements.map(e => e.symbol).join(", ");
114+
const value = transpileExpression(decl.value, ctx);
115+
return `${indent}${keyword} [${elements}] = ${value};`;
116+
}
64117
const value = decl.value ? transpileExpression(decl.value, ctx) : "undefined";
65118
return `${indent}${keyword} ${decl.identifier} = ${value};`;
66119
}
@@ -118,6 +171,11 @@ function transpileExpression(expr: Expression, ctx: TranspileContext): string {
118171
case "CallExpr": {
119172
const call = expr as CallExpr;
120173
const caller = transpileExpression(call.caller, ctx);
174+
if (caller === "useState" && call.typeAnnotation) {
175+
const typeInfo = transpileTypeAnnotation(call.typeAnnotation);
176+
const args = call.args.map(arg => transpileExpression(arg, ctx)).join(", ");
177+
return `useState(${args}, ${typeInfo})`;
178+
}
121179
const args = call.args.map(arg => transpileExpression(arg, ctx)).join(", ");
122180
return `${caller}(${args})`;
123181
}
@@ -153,3 +211,14 @@ function transpileExpression(expr: Expression, ctx: TranspileContext): string {
153211
return `/* Unknown: ${expr.kind} */`;
154212
}
155213
}
214+
215+
function transpileTypeAnnotation(typeAnnotation: TypeAnnotation): string {
216+
let typeInfo = `{ type: "${typeAnnotation.typeName}"`;
217+
if (typeAnnotation.genericTypes && typeAnnotation.genericTypes.length > 0) {
218+
const elementType = typeAnnotation.genericTypes[0].typeName;
219+
typeInfo += `, elementType: "${elementType}"`;
220+
}
221+
222+
typeInfo += " }";
223+
return typeInfo;
224+
}

src/parser/lexer.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export enum TokenType {
1717
Colon,
1818
Dot,
1919
StringLiteral,
20+
TypeAnnotation,
2021
EOF,
2122
}
2223

@@ -26,6 +27,9 @@ const ReservedKeywords: Record<string, TokenType> = {
2627
function: TokenType.Function,
2728
};
2829

30+
// Built-in type keywords //
31+
const TypeKeywords = new Set(["String", "Number", "Boolean", "Array", "Object", "Any", "Void"]);
32+
2933
export interface Token {
3034
value: string;
3135
type: TokenType;
@@ -37,7 +41,7 @@ function createToken(value: string, type: TokenType): Token {
3741

3842
function isAlphabetic(char: string): boolean {
3943
const code = char.charCodeAt(0);
40-
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
44+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || char === "_";
4145
}
4246

4347
function isSkippable(char: string): boolean {
@@ -48,6 +52,10 @@ function isInt(char: string): boolean {
4852
return /^[0-9]+$/.test(char);
4953
}
5054

55+
function isAlphaNumeric(char: string): boolean {
56+
return isAlphabetic(char) || isInt(char);
57+
}
58+
5159
export function tokenize(sourceCode: string): Token[] {
5260
const tokens: Token[] = [];
5361
const src: string[] = sourceCode.split("");
@@ -108,26 +116,31 @@ export function tokenize(sourceCode: string): Token[] {
108116
default:
109117
if (isInt(char)) {
110118
let num = char;
111-
while (src.length > 0 && isInt(src[0])) {
119+
while (src.length > 0 && (isInt(src[0]) || src[0] === ".")) {
112120
num += src.shift()!;
113121
}
114122
tokens.push(createToken(num, TokenType.Number));
115123
} else if (isAlphabetic(char)) {
116124
let ident = char;
117-
while (src.length > 0 && isAlphabetic(src[0])) {
125+
while (src.length > 0 && isAlphaNumeric(src[0])) {
118126
ident += src.shift()!;
119127
}
120-
const reserved = ReservedKeywords[ident];
121-
if (reserved !== undefined) {
122-
tokens.push(createToken(ident, reserved));
128+
if (TypeKeywords.has(ident)) {
129+
tokens.push(createToken(ident, TokenType.TypeAnnotation));
123130
} else {
124-
tokens.push(createToken(ident, TokenType.Identifier));
131+
const reserved = ReservedKeywords[ident];
132+
if (reserved !== undefined) {
133+
tokens.push(createToken(ident, reserved));
134+
} else {
135+
tokens.push(createToken(ident, TokenType.Identifier));
136+
}
125137
}
126138
} else if (isSkippable(char)) {
127139
continue;
128140
} else {
129141
console.error("Unrecognized character found in source:", char.charCodeAt(0), char);
130142
process.exit(1);
143+
// Deno.exit(1);
131144
}
132145
}
133146
}

0 commit comments

Comments
 (0)