11import { useState } from "react" ;
22
3- // ---- types shared between executor and props ----
43export type ExpectedStep =
54 | { kind : "assign" ; var : string ; val : number }
65 | { kind : "print" ; val : number } ;
@@ -11,29 +10,123 @@ interface CodeboxProps {
1110 headerimage : string ;
1211 onOutput : ( output : string , isError : boolean ) => void ;
1312 expectedTrace ?: ExpectedStep [ ] ;
13+ expectedOutputStr ?: string ;
14+ level : number ;
1415}
1516
16- // ---- expression validator ----
17- // Allowed: single-char vars x/y/z, literal 1 only, operators +-*^, parens.
18- // Spaces are ignored. :) and :( must be together (enforced by caller regex).
19- function validateExpression ( expr : string ) : boolean {
20- const s = expr . replace ( / \s / g, "" ) ;
21- if ( s . length === 0 ) return false ;
22- if ( s . includes ( "**" ) ) return false ;
17+ function approxEqual ( a : number , b : number ) : boolean {
18+ return Math . abs ( a - b ) < 1e-9 ;
19+ }
20+
21+ type AlienStep =
22+ | { kind : "assign" ; var : string ; val : number ; lineNum : number }
23+ | { kind : "print" ; val : number ; lineNum : number } ;
24+
25+ function checkTrace (
26+ alienTrace : AlienStep [ ] ,
27+ expectedTrace : ExpectedStep [ ] | undefined
28+ ) : string | null {
29+ if ( ! expectedTrace || expectedTrace . length === 0 ) return null ;
30+ if ( alienTrace . length !== expectedTrace . length )
31+ return "Alien Error: Translation incorrect" ;
32+
33+ for ( let i = 0 ; i < alienTrace . length ; i ++ ) {
34+ const a = alienTrace [ i ] ;
35+ const e = expectedTrace [ i ] ;
36+ if ( a . kind !== e . kind )
37+ return `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` ;
38+ if ( a . kind === "assign" && e . kind === "assign" ) {
39+ if ( a . var !== e . var || ! approxEqual ( a . val , e . val ) )
40+ return `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` ;
41+ }
42+ if ( a . kind === "print" && e . kind === "print" ) {
43+ if ( ! approxEqual ( a . val , e . val ) )
44+ return `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` ;
45+ }
46+ }
47+ return null ;
48+ }
2349
50+ // ---- Level 1: HI / YAY / NO ----
51+ // HI var, value → var = value
52+ // YAY var, value → var += value
53+ // NO var, value → var -= value
54+ // value: integer literal or single variable x/y/z
55+ // Implicit print z at the end if z is defined.
56+ function runLevel1 (
57+ code : string ,
58+ expectedTrace ?: ExpectedStep [ ]
59+ ) : { output : string ; error : boolean } {
60+ const lines = code
61+ . split ( "\n" )
62+ . map ( ( text , i ) => ( { text : text . trim ( ) , lineNum : i + 1 } ) )
63+ . filter ( ( l ) => l . text . length > 0 ) ;
64+
65+ if ( lines . length === 0 )
66+ return { output : "Alien Error [Line 1]: Syntax Error" , error : true } ;
67+
68+ const STMT_RE = / ^ ( H I | Y A Y | N O ) \s + ( [ x y z ] ) \s * , \s * ( [ x y z ] | [ 0 - 9 ] + ) $ / ;
69+ const vars : Record < string , number > = { } ;
70+ const alienTrace : AlienStep [ ] = [ ] ;
71+
72+ for ( const { text : line , lineNum } of lines ) {
73+ const m = line . match ( STMT_RE ) ;
74+ if ( ! m )
75+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
76+
77+ const [ , op , varName , valToken ] = m ;
78+ const isRef = / ^ [ x y z ] $ / . test ( valToken ) ;
79+
80+ if ( isRef && vars [ valToken ] === undefined )
81+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
82+
83+ const val = isRef ? vars [ valToken ] : Number ( valToken ) ;
84+
85+ let result : number ;
86+ if ( op === "HI" ) {
87+ result = val ;
88+ } else if ( op === "YAY" ) {
89+ if ( vars [ varName ] === undefined )
90+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
91+ result = vars [ varName ] + val ;
92+ } else {
93+ if ( vars [ varName ] === undefined )
94+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
95+ result = vars [ varName ] - val ;
96+ }
97+
98+ vars [ varName ] = result ;
99+ alienTrace . push ( { kind : "assign" , var : varName , val : result , lineNum } ) ;
100+ }
101+
102+ if ( vars [ "z" ] === undefined )
103+ return { output : "No output" , error : true } ;
104+
105+ const lastLine = lines [ lines . length - 1 ] . lineNum ;
106+ alienTrace . push ( { kind : "print" , val : vars [ "z" ] , lineNum : lastLine } ) ;
107+
108+ const traceErr = checkTrace ( alienTrace , expectedTrace ) ;
109+ if ( traceErr ) return { output : traceErr , error : true } ;
110+
111+ return { output : String ( vars [ "z" ] ) , error : false } ;
112+ }
113+
114+ // ---- Level 2: :) assignment, :( print ----
115+ // Expressions: only literal 1, vars x/y/z, operators +-*^, parens.
116+ function validateExprL2 ( expr : string ) : boolean {
117+ const s = expr . replace ( / \s / g, "" ) ;
118+ if ( s . length === 0 || s . includes ( "**" ) ) return false ;
24119 let i = 0 ;
25120 while ( i < s . length ) {
26121 const ch = s [ i ] ;
27122 if ( "xyz" . includes ( ch ) ) {
28- // no multi-char identifiers
29123 if ( i + 1 < s . length && / [ a - z A - Z ] / . test ( s [ i + 1 ] ) ) return false ;
30124 i ++ ;
31125 } else if ( ch === "1" ) {
32- // only 1 is a valid literal — 11, 12, etc. are invalid
33126 if ( i + 1 < s . length && / [ 0 - 9 ] / . test ( s [ i + 1 ] ) ) return false ;
34127 i ++ ;
35128 } else if ( / [ 0 - 9 ] / . test ( ch ) ) {
36- return false ; // 2, 3, … all forbidden
129+ return false ;
37130 } else if ( "+-*^()" . includes ( ch ) ) {
38131 i ++ ;
39132 } else {
@@ -43,75 +136,56 @@ function validateExpression(expr: string): boolean {
43136 return true ;
44137}
45138
46- function approxEqual ( a : number , b : number ) : boolean {
47- return Math . abs ( a - b ) < 1e-9 ;
48- }
49-
50- // ---- interpreter ----
51- type AlienStep =
52- | { kind : "assign" ; var : string ; val : number ; lineNum : number }
53- | { kind : "print" ; val : number ; lineNum : number } ;
54-
55- function runAlienCode ( code : string , expectedTrace ?: ExpectedStep [ ] ) : { output : string ; error : boolean } {
56- // Trim every line, drop blanks, keep original line numbers
57- const lines : Array < { text : string ; lineNum : number } > = code
139+ function runLevel2 (
140+ code : string ,
141+ expectedTrace ?: ExpectedStep [ ]
142+ ) : { output : string ; error : boolean } {
143+ const lines = code
58144 . split ( "\n" )
59145 . map ( ( text , i ) => ( { text : text . trim ( ) , lineNum : i + 1 } ) )
60146 . filter ( ( l ) => l . text . length > 0 ) ;
61147
62- if ( lines . length === 0 ) {
148+ if ( lines . length === 0 )
63149 return { output : "Alien Error [Line 1]: Syntax Error" , error : true } ;
64- }
65150
66- // :) assignment — space allowed around :) but NOT inside it
67- const ASSIGNMENT_RE = / ^ ( [ x y z ] ) \s * : \) \s * ( .+ ) $ / ;
68- // :( print — any of x/y/z, space allowed before :(, NOT inside it
151+ const ASSIGN_RE = / ^ ( [ x y z ] ) \s * : \) \s * ( .+ ) $ / ;
69152 const PRINT_RE = / ^ ( [ x y z ] ) \s * : \( $ / ;
70-
71153 const vars : Record < string , number > = { } ;
72154 const alienTrace : AlienStep [ ] = [ ] ;
73155 const outputLines : string [ ] = [ ] ;
74156
75157 for ( const { text : line , lineNum } of lines ) {
76- // --- print ---
77158 const printMatch = line . match ( PRINT_RE ) ;
78159 if ( printMatch ) {
79160 const v = printMatch [ 1 ] ;
80- if ( vars [ v ] === undefined ) {
161+ if ( vars [ v ] === undefined )
81162 return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
82- }
83163 const val = vars [ v ] ;
84164 outputLines . push ( String ( val ) ) ;
85165 alienTrace . push ( { kind : "print" , val, lineNum } ) ;
86166 continue ;
87167 }
88168
89- // --- assignment ---
90- const assignMatch = line . match ( ASSIGNMENT_RE ) ;
169+ const assignMatch = line . match ( ASSIGN_RE ) ;
91170 if ( assignMatch ) {
92171 const varName = assignMatch [ 1 ] ;
93172 const expr = assignMatch [ 2 ] . trim ( ) ;
94-
95- if ( ! validateExpression ( expr ) ) {
173+ if ( ! validateExprL2 ( expr ) )
96174 return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
97- }
98175
99- // every referenced variable must be defined
100176 const exprClean = expr . replace ( / \s / g, "" ) ;
101177 for ( const v of [ "x" , "y" , "z" ] ) {
102- if ( exprClean . includes ( v ) && vars [ v ] === undefined ) {
178+ if ( exprClean . includes ( v ) && vars [ v ] === undefined )
103179 return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
104- }
105180 }
106181
107182 try {
108183 const jsExpr = expr . replace ( / \^ / g, "**" ) ;
109184 const result = new Function ( "x" , "y" , "z" , `"use strict"; return (${ jsExpr } )` ) (
110185 vars [ "x" ] , vars [ "y" ] , vars [ "z" ]
111186 ) ;
112- if ( typeof result !== "number" || ! isFinite ( result ) ) {
187+ if ( typeof result !== "number" || ! isFinite ( result ) )
113188 return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
114- }
115189 vars [ varName ] = result ;
116190 alienTrace . push ( { kind : "assign" , var : varName , val : result , lineNum } ) ;
117191 } catch {
@@ -120,43 +194,77 @@ function runAlienCode(code: string, expectedTrace?: ExpectedStep[]): { output: s
120194 continue ;
121195 }
122196
123- // --- unrecognised line ---
124197 return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
125198 }
126199
127- if ( outputLines . length === 0 ) {
200+ if ( outputLines . length === 0 )
128201 return { output : "No output" , error : true } ;
129- }
130202
131- // ---- line-by-line translation check ----
132- if ( expectedTrace && expectedTrace . length > 0 ) {
133- if ( alienTrace . length !== expectedTrace . length ) {
134- return { output : "Alien Error: Translation incorrect" , error : true } ;
135- }
203+ const traceErr = checkTrace ( alienTrace , expectedTrace ) ;
204+ if ( traceErr ) return { output : traceErr , error : true } ;
136205
137- for ( let i = 0 ; i < alienTrace . length ; i ++ ) {
138- const a = alienTrace [ i ] ;
139- const e = expectedTrace [ i ] ;
206+ return { output : outputLines . join ( "\n" ) , error : false } ;
207+ }
140208
141- if ( a . kind !== e . kind ) {
142- return { output : `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` , error : true } ;
143- }
209+ // ---- Level 3: array init + copy ops + output ----
210+ // "[N,...] into nums" — initialise
211+ // "nums[A] into nums[B]" — copy (1-indexed)
212+ // "output nums" — print
213+ function runLevel3 (
214+ code : string ,
215+ expectedOutputStr ?: string
216+ ) : { output : string ; error : boolean } {
217+ const lines = code
218+ . split ( "\n" )
219+ . map ( ( text , i ) => ( { text : text . trim ( ) , lineNum : i + 1 } ) )
220+ . filter ( ( l ) => l . text . length > 0 ) ;
144221
145- if ( a . kind === "assign" && e . kind === "assign" ) {
146- if ( a . var !== e . var || ! approxEqual ( a . val , e . val ) ) {
147- return { output : `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` , error : true } ;
148- }
149- }
222+ if ( lines . length === 0 )
223+ return { output : "Alien Error [Line 1]: Syntax Error" , error : true } ;
150224
151- if ( a . kind === "print" && e . kind === "print" ) {
152- if ( ! approxEqual ( a . val , e . val ) ) {
153- return { output : `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` , error : true } ;
154- }
155- }
225+ const INIT_RE = / ^ \[ \s * ( \d + (?: \s * , \s * \d + ) * ) \s * \] \s + i n t o \s + n u m s $ / ;
226+ const COPY_RE = / ^ n u m s \s * \[ \s * ( \d + ) \s * \] \s + i n t o \s + n u m s \s * \[ \s * ( \d + ) \s * \] $ / ;
227+ const OUTPUT_RE = / ^ o u t p u t \s + n u m s $ / ;
228+
229+ const firstLine = lines [ 0 ] ;
230+ const initMatch = firstLine . text . match ( INIT_RE ) ;
231+ if ( ! initMatch )
232+ return { output : "Alien Error [Line 1]: Syntax Error" , error : true } ;
233+
234+ const nums = initMatch [ 1 ] . split ( "," ) . map ( ( s ) => Number ( s . trim ( ) ) ) ;
235+ const len = nums . length ;
236+ const outputLines : string [ ] = [ ] ;
237+
238+ for ( let i = 1 ; i < lines . length ; i ++ ) {
239+ const { text : line , lineNum } = lines [ i ] ;
240+
241+ const copyMatch = line . match ( COPY_RE ) ;
242+ if ( copyMatch ) {
243+ const from = Number ( copyMatch [ 1 ] ) ;
244+ const to = Number ( copyMatch [ 2 ] ) ;
245+ if ( from < 1 || from > len || to < 1 || to > len )
246+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
247+ nums [ to - 1 ] = nums [ from - 1 ] ;
248+ continue ;
156249 }
250+
251+ if ( OUTPUT_RE . test ( line ) ) {
252+ outputLines . push ( "[" + nums . join ( ", " ) + "]" ) ;
253+ continue ;
254+ }
255+
256+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
157257 }
158258
159- return { output : outputLines . join ( "\n" ) , error : false } ;
259+ if ( outputLines . length === 0 )
260+ return { output : "No output" , error : true } ;
261+
262+ const outputStr = outputLines . join ( "\n" ) ;
263+
264+ if ( expectedOutputStr && outputStr !== expectedOutputStr )
265+ return { output : "Alien Error: Translation incorrect" , error : true } ;
266+
267+ return { output : outputStr , error : false } ;
160268}
161269
162270// ---- component ----
@@ -167,16 +275,19 @@ export default function Codebox(props: CodeboxProps) {
167275 const handleRun = ( ) => {
168276 setRunning ( true ) ;
169277 setTimeout ( ( ) => {
170- const { output, error } = runAlienCode ( code , props . expectedTrace ) ;
171- props . onOutput ( output , error ) ;
278+ let result ;
279+ if ( props . level === 1 ) result = runLevel1 ( code , props . expectedTrace ) ;
280+ else if ( props . level === 2 ) result = runLevel2 ( code , props . expectedTrace ) ;
281+ else result = runLevel3 ( code , props . expectedOutputStr ) ;
282+ props . onOutput ( result . output , result . error ) ;
172283 setRunning ( false ) ;
173284 } , 300 ) ;
174285 } ;
175286
176287 return (
177288 < div
178289 style = { { backgroundColor : props . lightcol , boxShadow : `0 12px 0 ${ props . darkcol } ` } }
179- className = "rounded-2xl overflow-hidden flex flex-col mt-25"
290+ className = "rounded-2xl overflow-hidden flex flex-col mt-25 flex-1 min-h-0 "
180291 >
181292 < div className = "flex items-center justify-center p-4" >
182293 < img src = { "/" + props . headerimage } alt = { props . headerimage } className = "relative h-12 w-auto object-contain" />
@@ -187,7 +298,7 @@ export default function Codebox(props: CodeboxProps) {
187298 value = { code }
188299 onChange = { ( e ) => setCode ( e . target . value ) }
189300 placeholder = "Write alien code here!"
190- className = "p-6 text-2xl mx-4 rounded-lg resize-none min-h-[300px] "
301+ className = "p-6 text-2xl mx-4 rounded-lg resize-none flex-1 min-h-0 "
191302 />
192303
193304 < div className = "m-4 mx-4 h-14" >
0 commit comments