Skip to content

Commit 594dab6

Browse files
committed
time to merge into main try 2
1 parent 6eb8096 commit 594dab6

5 files changed

Lines changed: 75 additions & 202 deletions

File tree

src/components/InfoComponent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { generatePlanetLanguage } from "../language/generator/generatePlanetLang
55
import { parsePythonWithTreeSitter } from "../language/parse/parsePythonWithTreeSitter";
66
import type { ProgramNode } from "../language/types";
77

8-
const InfoComponent = () => {
8+
const InfoComponent = ({ seed }: { seed: number }) => {
99
const [exampleAst, setExampleAst] = useState<ProgramNode | null>(null);
1010
const [exampleAlien, setExampleAlien] = useState("");
1111
const [exampleError, setExampleError] = useState("");
@@ -18,7 +18,7 @@ const InfoComponent = () => {
1818
`.trim();
1919

2020
// Use a fixed seed for this level for now
21-
const lang = useMemo(() => generatePlanetLanguage(2), []);
21+
const lang = useMemo(() => generatePlanetLanguage(seed), []);
2222

2323
useEffect(() => {
2424
let cancelled = false;

src/components/LevelComponent.tsx

Lines changed: 7 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -2,159 +2,17 @@ import React, { useState, useEffect, useRef, useMemo } from "react";
22
import OutputComponent from './OutputComponent'
33
import InfoComponent from './InfoComponent'
44

5-
export default function LevelComponent() {
6-
const [code, setCode] = useState("")
7-
const [output, setOutput] = useState("")
8-
const [translatedPython, setTranslatedPython] = useState("");
9-
const [exampleAst, setExampleAst] = useState<ProgramNode | null>(null);
10-
const [exampleAlien, setExampleAlien] = useState("");
11-
const [exampleError, setExampleError] = useState("");
12-
const [pyodideReady, setPyodideReady] = useState(false)
13-
const pyodideRef = useRef<any>(null)
14-
const sourcePython = `
15-
nums = [1,2,3]
16-
sum = 0
17-
for i in nums:
18-
sum += i
19-
print(sum)
20-
`.trim();
21-
22-
23-
// Use a fixed seed for this level for now
24-
const lang = useMemo(() => generatePlanetLanguage(2), []);
25-
26-
useEffect(() => {
27-
const loadPyodide = async () => {
28-
// @ts-ignore
29-
pyodideRef.current = await window.loadPyodide()
30-
setPyodideReady(true)
31-
}
32-
loadPyodide()
33-
}, [])
34-
35-
useEffect(() => {
36-
let cancelled = false;
37-
38-
const parseExample = async () => {
39-
try {
40-
setExampleError("");
41-
42-
const ast = await parsePythonWithTreeSitter(sourcePython);
43-
if (cancelled) return;
44-
45-
setExampleAst(ast);
46-
setExampleAlien(renderAlien(ast, lang));
47-
} catch (err) {
48-
if (cancelled) return;
49-
setExampleError(err instanceof Error ? err.message : String(err));
50-
}
51-
};
52-
53-
parseExample();
54-
55-
return () => {
56-
cancelled = true;
57-
};
58-
}, [sourcePython, lang]);
59-
60-
const runCode = async () => {
61-
if (!pyodideRef.current) return
62-
if (!pyodideRef.current) return;
63-
64-
const validation = validateAlienSource(code, lang);
65-
66-
if (!validation.isValid) {
67-
setTranslatedPython("");
68-
setOutput(
69-
validation.issues
70-
.map(
71-
(issue) =>
72-
`Line ${issue.line}, Col ${issue.column}: ${issue.message}`,
73-
)
74-
.join("\n"),
75-
);
76-
return;
77-
}
78-
try {
79-
// 1. Alien -> AST
80-
const ast = parseAlien(code, lang);
81-
console.log(ast);
82-
// 2. AST -> Python
83-
const pythonCode = renderPython(ast);
84-
setTranslatedPython(pythonCode);
85-
86-
87-
// 3. Reset stdout
88-
await pyodideRef.current.runPythonAsync(`
89-
import sys, io
90-
sys.stdout = io.StringIO()
91-
`);
92-
93-
// 4. Run translated Python
94-
await pyodideRef.current.runPythonAsync(pythonCode);
95-
96-
// 5. Get output
97-
const result = await pyodideRef.current.runPythonAsync(`sys.stdout.getvalue()`);
98-
setOutput(String(result));
99-
} catch (err: any) {
100-
setOutput(err?.message ?? String(err));
101-
}
102-
};
103-
104-
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
105-
if (e.key === "Tab") {
106-
e.preventDefault()
107-
const el = e.target as HTMLTextAreaElement
108-
const { selectionStart, selectionEnd } = el
109-
const newValue = code.slice(0, selectionStart) + " " + code.slice(selectionEnd)
110-
setCode(newValue)
111-
requestAnimationFrame(() => {
112-
el.selectionStart = el.selectionEnd = selectionStart + 4
113-
})
114-
}
115-
}
116-
5+
export default function LevelComponent({ lvl }: { lvl: number }) {
6+
const seed = lvl*71 - 10;
1177
return (
118-
//<div className="w-screen h-screen flex gap-10 p-4 font-mono overflow-hidden">
119-
// <div className="flex gap-4 p-4 w-6/7 mx-auto">
8+
<div className="w-screen h-screen flex gap-10 p-4 font-mono overflow-hidden">
9+
<div className="flex gap-4 p-4 w-6/7 mx-auto">
12010

121-
// <div className="flex-1 bg-[#2a2a2a] rounded-2xl p-3 flex flex-col gap-3">
122-
// <InfoComponent />
123-
<div className="w-screen h-screen flex gap-4 p-4 bg-[#0a0f2e] font-mono overflow-hidden">
124-
125-
{/* Left panel */}
126-
<div className="flex-1 bg-[#2a2a2a] rounded-2xl p-5 flex flex-col gap-4">
127-
128-
{/* Title */}
129-
<h1 className="text-center text-2xl font-black tracking-widest text-green-400"
130-
style={{ textShadow: "2px 2px 0px #166534, -1px -1px 0px #166534, 1px -1px 0px #166534, -1px 1px 0px #166534" }}>
131-
ALIEN PYTHON
132-
</h1>
133-
134-
{/* Code panels */}
135-
<div className="flex gap-3">
136-
{/* Python */}
137-
<div className="flex-1 bg-[#3a7bd5] rounded-2xl overflow-hidden">
138-
<div className="bg-[#2d5fa8] text-white text-xs text-center py-2 tracking-widest font-bold">
139-
*** PYTHON CODE ***
140-
</div>
141-
<div className="bg-[#4a8be0] m-2 rounded-xl p-3">
142-
<pre className="text-white text-xs leading-relaxed m-0">{sourcePython}</pre>
143-
</div>
144-
</div>
145-
146-
{/* Alien */}
147-
<div className="flex-1 bg-[#2d7a3a] rounded-2xl overflow-hidden">
148-
<div className="bg-[#1f5c29] text-white text-xs text-center py-2 tracking-widest font-bold">
149-
*** ALIEN CODE ***
150-
</div>
151-
<div className="bg-[#3a9447] m-2 rounded-xl p-3">
152-
<pre className="text-white text-xs leading-relaxed m-0">{exampleError ? `Error: ${exampleError}` : exampleAlien || "Loading..."}</pre>
153-
</div>
154-
</div>
11+
<div className="flex-1 bg-[#2a2a2a] rounded-2xl p-3 flex flex-col gap-3">
12+
<InfoComponent seed={seed}/>
15513
</div>
15614
<div className="flex-1 bg-[#2a2a2a] rounded-2xl p-3 flex flex-col gap-3">
157-
<OutputComponent />
15+
<OutputComponent seed={seed} />
15816
</div>
15917
</div>
16018

src/components/OutputComponent.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import { parseAlien } from "../language/parse/parseAlien";
44
import { renderPython } from "../language/render/renderPython";
55
import { validateAlienSource } from "../language/validate/validateAlienSource";
66

7-
const OutputComponent = () => {
7+
const OutputComponent = ({ seed }: { seed: number }) => {
88
const [code, setCode] = useState("")
99
const [output, setOutput] = useState("")
1010
const [translatedPython, setTranslatedPython] = useState("");
1111
const [pyodideReady, setPyodideReady] = useState(false)
1212
const pyodideRef = useRef<any>(null)
13-
const outputExpected = "5"
14-
13+
const outputExpected = "9"
14+
1515
// Use a fixed seed for this level for now
16-
const lang = useMemo(() => generatePlanetLanguage(2), []);
16+
const lang = useMemo(() => generatePlanetLanguage(seed), []);
1717

1818
useEffect(() => {
1919
const loadPyodide = async () => {
@@ -55,6 +55,7 @@ const OutputComponent = () => {
5555
await pyodideRef.current.runPythonAsync(`
5656
import sys, io
5757
sys.stdout = io.StringIO()
58+
nums = [1, 2, 3]
5859
`);
5960

6061
// 4. Run translated Python

src/language/validate/validateAlienSource.ts

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ function isWordLikeIdentifier(value: string): boolean {
9191
return /^[A-Za-z_~!?#^:-][A-Za-z0-9_~!?#^:-]*$/.test(value);
9292
}
9393

94+
function getLineBounds(tokens: AlienToken[], index: number): { start: number; end: number } {
95+
let start = index;
96+
let end = index;
97+
98+
while (start > 0 && tokens[start - 1].type !== "newline") {
99+
start--;
100+
}
101+
102+
while (end < tokens.length - 1 && tokens[end + 1].type !== "newline") {
103+
end++;
104+
}
105+
106+
return { start, end };
107+
}
108+
94109
function looksLikeDefinitionContext(
95110
tokens: AlienToken[],
96111
index: number,
@@ -105,70 +120,69 @@ function looksLikeDefinitionContext(
105120
return true;
106121
}
107122

108-
// Assignment target patterns
109123
switch (lang.syntax.assignmentStyle) {
110124
case "equals":
125+
return !!(
126+
next &&
127+
next.type === "operator" &&
128+
next.value === "="
129+
);
130+
111131
case "arrow":
112-
case "word_infix":
113-
if (next && next.type === "operator") {
114-
return false;
115-
}
116-
if (
132+
return !!(
117133
next &&
118-
(
119-
(lang.syntax.assignmentStyle === "equals" && next.value === "=") ||
120-
(lang.syntax.assignmentStyle === "arrow" && next.value === (lang.symbols.assignmentToken ?? "<-"))
121-
)
122-
) {
123-
return true;
124-
}
125-
if (
126-
lang.syntax.assignmentStyle === "word_infix" &&
134+
next.type === "operator" &&
135+
next.value === (lang.symbols.assignmentToken ?? "<-")
136+
);
137+
138+
case "word_infix":
139+
return !!(
127140
next &&
128141
next.type === "identifier" &&
129142
next.value === lang.symbols.assignmentWord
130-
) {
131-
return true;
132-
}
133-
break;
143+
);
134144

135145
case "word_prefix":
136-
if (
146+
return !!(
137147
prev &&
138148
prev.type === "identifier" &&
139149
prev.value === lang.symbols.assignmentWord
140-
) {
141-
return true;
142-
}
143-
break;
150+
);
144151

145-
case "word_suffix":
146-
if (
147-
next &&
148-
next.type !== "newline" &&
149-
tokens[index + 2] &&
150-
tokens[index + 2].type === "identifier" &&
151-
tokens[index + 2].value === lang.symbols.assignmentWord
152-
) {
153-
return true;
152+
case "word_suffix": {
153+
const { start, end } = getLineBounds(tokens, index);
154+
155+
// Must be the first token on the line
156+
if (index !== start) {
157+
return false;
154158
}
155-
break;
159+
160+
// Last token on the line must be the assignment word
161+
const lastToken = tokens[end];
162+
return !!(
163+
lastToken &&
164+
lastToken.type === "identifier" &&
165+
lastToken.value === lang.symbols.assignmentWord
166+
);
167+
}
156168

157169
case "set_prefix":
158-
if (prev && prev.type === "identifier" && prev.value === "set") {
159-
return true;
160-
}
161-
break;
170+
return !!(
171+
prev &&
172+
prev.type === "identifier" &&
173+
prev.value === "set"
174+
);
162175

163176
case "put_in":
164-
// target comes after "in"
165-
if (prev && prev.type === "identifier" && prev.value === "in") {
166-
return true;
167-
}
168-
break;
169-
}
177+
return !!(
178+
prev &&
179+
prev.type === "identifier" &&
180+
prev.value === "in"
181+
);
170182

171-
return false;
183+
default:
184+
return false;
185+
}
172186
}
173187

174188
export function validateAlienSource(

src/pages/home/Home.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import LevelComponent from "../../components/LevelComponent";
55

66

77
function Home() {
8-
8+
const lvlNum = 4;
99
return (
1010
<div>
11-
<LevelComponent />
11+
<LevelComponent lvl={lvlNum} />
1212
</div>
1313

1414
);

0 commit comments

Comments
 (0)