1
- import React from 'react' ;
2
1
import ReactDOMServer from 'react-dom/server' ;
3
2
import AppLayout from './AppLayout.jsx' ;
4
3
import TuringMachineStateTable from './TuringMachineStateTable.jsx' ;
5
4
import { GITHUB_LINK } from './contants.js' ;
5
+ import { select , id , checked , unchecked , attr , attrContains } from './selector.js' ;
6
6
import indexCSS from './index.css?inline' ;
7
7
import appLayoutCSS from './AppLayout.css?inline' ;
8
8
import turingMachineStateTableCSS from './TuringMachineStateTable.css?inline' ;
@@ -21,7 +21,8 @@ function CompiledMachinePageBody({ config, numTapeCells }) {
21
21
/>
22
22
}
23
23
24
- const BUFFER_SUFFIX = 'b' ;
24
+ const BUFFER_PREFIX0 = 'a' ;
25
+ const BUFFER_PREFIX1 = 'b' ;
25
26
const STATE_PREFIX = 's' ;
26
27
const HEAD_POS_PREFIX = 'h' ;
27
28
const TAPE_VALUE_PREFIX = 't' ;
@@ -32,12 +33,10 @@ function CompiledTuringMachine({ config, numTapeCells }) {
32
33
// TURING MACHINE FORMAT:
33
34
// "Started" radio input
34
35
// "Buffer 0 is destination" checkbox input
36
+ // State radio inputs (interleaved between buffers)
35
37
// Switch buffer label
36
- // Buffer 0 state radio inputs
37
- // Buffer 1 state radio inputs
38
38
// TAPE CELLS
39
- // Buffer 0 state labels
40
- // Buffer 1 state labels
39
+ // State labels (interleaved between buffers)
41
40
// Start label
42
41
43
42
// TAPE CELL FORMAT:
@@ -52,20 +51,25 @@ function CompiledTuringMachine({ config, numTapeCells }) {
52
51
// Buffer 0 tape cell X value display (cannot make checkboxes visible or else user can toggle them via mouse/keyboard)
53
52
// Buffer 1 tape cell X value display
54
53
55
- const dynElems = [ ] ;
54
+ const elems = [ ] ;
56
55
let counter = 0 ;
57
56
57
+ elems . push ( < input id = { STARTED_ID } type = "radio" key = { counter ++ } /> ) ;
58
+ elems . push ( < input id = { BUFFER_SWITCH_ID } type = "checkbox" key = { counter ++ } /> ) ;
59
+
58
60
const numStates = config . length + 1 ; // User configuration does not include halting state.
59
61
// State radio inputs:
60
- for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
61
- for ( let stateIdx = 0 ; stateIdx < numStates ; stateIdx ++ ) {
62
+ for ( let stateIdx = 0 ; stateIdx < numStates ; stateIdx ++ ) {
63
+ for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
62
64
const inputId = getInputId ( buffer , STATE_PREFIX , stateIdx ) ;
63
65
const inputGroup = getInputGroup ( buffer , STATE_PREFIX ) ;
64
66
// Initialize with the Turing machine in the first state.
65
- dynElems . push ( < input id = { inputId } type = "radio" name = { inputGroup } defaultChecked = { stateIdx === 0 } key = { counter ++ } /> ) ;
67
+ elems . push ( < input id = { inputId } type = "radio" name = { inputGroup } defaultChecked = { stateIdx === 0 } key = { counter ++ } /> ) ;
66
68
}
67
69
}
68
70
71
+ elems . push ( < label htmlFor = { BUFFER_SWITCH_ID } key = { counter ++ } /> ) ;
72
+
69
73
// Tape cells:
70
74
const halfwayVisibleTape = Math . min ( Math . ceil ( numTapeCells / 2 ) - 1 , 12 ) ;
71
75
// There is an extra dummy tape cell to fit the last tape head label.
@@ -77,71 +81,82 @@ function CompiledTuringMachine({ config, numTapeCells }) {
77
81
const tapeHeadInputId = getInputId ( buffer , HEAD_POS_PREFIX , tapeCellIdx ) ;
78
82
const tapeHeadInputGroup = getInputGroup ( buffer , HEAD_POS_PREFIX ) ;
79
83
// Initialize the head position halfway along the visible tape.
80
- dynElems . push ( < input id = { tapeHeadInputId } type = "radio" name = { tapeHeadInputGroup } defaultChecked = { tapeCellIdx === halfwayVisibleTape } key = { counter ++ } /> ) ;
84
+ elems . push ( < input id = { tapeHeadInputId } type = "radio" name = { tapeHeadInputGroup } defaultChecked = { tapeCellIdx === halfwayVisibleTape } key = { counter ++ } /> ) ;
81
85
} else {
82
86
// Maintain the relative positioning of elements for the last tape head label.
83
- dynElems . push ( < i key = { counter ++ } /> ) ;
87
+ elems . push ( < i key = { counter ++ } /> ) ;
84
88
}
85
89
}
86
90
// Tape cell value inputs:
87
91
for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
88
92
if ( ! isDummyIdx ) {
89
93
const tapeValInputId = getInputId ( buffer , TAPE_VALUE_PREFIX , tapeCellIdx ) ;
90
- dynElems . push ( < input id = { tapeValInputId } type = "checkbox" key = { counter ++ } /> ) ;
94
+ elems . push ( < input id = { tapeValInputId } type = "checkbox" key = { counter ++ } /> ) ;
91
95
} else {
92
96
// Maintain the relative positioning of elements for the last tape head label.
93
- dynElems . push ( < i key = { counter ++ } /> ) ;
97
+ elems . push ( < i key = { counter ++ } /> ) ;
94
98
}
95
99
}
96
100
// Tape head labels:
97
101
for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
98
102
// Tape head labels are offset by 1, the first label is just a dummy element.
99
103
if ( tapeCellIdx > 0 ) {
100
104
const forTapeHeadInputId = getInputId ( buffer , HEAD_POS_PREFIX , tapeCellIdx - 1 ) ;
101
- dynElems . push ( < label htmlFor = { forTapeHeadInputId } key = { counter ++ } /> ) ;
105
+ elems . push ( < label htmlFor = { forTapeHeadInputId } key = { counter ++ } /> ) ;
102
106
} else {
103
- dynElems . push ( < i key = { counter ++ } /> ) ;
107
+ elems . push ( < i key = { counter ++ } /> ) ;
104
108
}
105
109
}
106
110
if ( ! isDummyIdx ) {
107
111
// Tape cell value labels:
108
112
for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
109
113
const forTapeValInputId = getInputId ( buffer , TAPE_VALUE_PREFIX , tapeCellIdx ) ;
110
- dynElems . push ( < label htmlFor = { forTapeValInputId } key = { counter ++ } /> ) ;
114
+ elems . push ( < label htmlFor = { forTapeValInputId } key = { counter ++ } /> ) ;
111
115
}
112
116
// Tape cell value display:
113
117
for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
114
- dynElems . push ( < p key = { counter ++ } /> ) ;
118
+ elems . push ( < p key = { counter ++ } /> ) ;
115
119
}
116
120
}
117
121
}
118
122
119
123
// State labels:
120
- for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
121
- for ( let stateIdx = 0 ; stateIdx < numStates ; stateIdx ++ ) {
124
+ for ( let stateIdx = 0 ; stateIdx < numStates ; stateIdx ++ ) {
125
+ for ( let buffer = 0 ; buffer < 2 ; buffer ++ ) {
122
126
const forInputId = getInputId ( buffer , STATE_PREFIX , stateIdx ) ;
123
- dynElems . push ( < label htmlFor = { forInputId } key = { counter ++ } /> )
127
+ elems . push ( < label htmlFor = { forInputId } key = { counter ++ } /> )
124
128
}
125
129
}
130
+
131
+ elems . push ( < label htmlFor = { STARTED_ID } key = { counter ++ } /> )
132
+
126
133
return < div className = "machine" >
127
134
< div className = "scroll-x" >
128
135
< div className = "machine-grid" >
129
- < input id = { STARTED_ID } type = "radio" />
130
- < input id = { BUFFER_SWITCH_ID } type = "checkbox" />
131
- < label htmlFor = { BUFFER_SWITCH_ID } />
132
- { dynElems }
133
- < label htmlFor = { STARTED_ID } />
136
+ { elems }
134
137
</ div >
135
138
</ div >
136
139
</ div > ;
137
140
}
138
141
139
142
function getInputId ( buffer , prefix , idx ) {
140
- return `${ buffer } ${ BUFFER_SUFFIX } ${ prefix } ${ idx } ` ;
143
+ return `${ getBufferPrefix ( buffer ) } ${ prefix } ${ idx } ` ;
141
144
}
142
145
143
146
function getInputGroup ( buffer , prefix ) {
144
- return `${ buffer } ${ BUFFER_SUFFIX } ${ prefix } ` ;
147
+ return `${ getBufferPrefix ( buffer ) } ${ prefix } ` ;
148
+ }
149
+
150
+ function getBufferPrefix ( buffer ) {
151
+ return buffer === 0 ? BUFFER_PREFIX0 : BUFFER_PREFIX1 ;
152
+ }
153
+
154
+ export default function toHTML ( config , numTapeCells ) {
155
+ return '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8"/>\n<style>\n' +
156
+ getCompiledMachinePageStyles ( config ) +
157
+ '\n</style>\n<title>CSS Turing Machine</title>\n</head>\n<body>\n' +
158
+ getCompiledMachinePageBody ( config , numTapeCells ) +
159
+ '\n</body>\n</html>' ;
145
160
}
146
161
147
162
function getCompiledMachinePageBody ( config , numTapeCells ) {
@@ -155,22 +170,90 @@ function getCompiledMachinePageStyles(config) {
155
170
compiledMachinePageBodyCSS
156
171
. replaceAll ( 'STARTED_ID' , STARTED_ID )
157
172
. replaceAll ( 'TAPE_VALUE_PREFIX' , TAPE_VALUE_PREFIX )
158
- . replaceAll ( 'BUFFER_SUFFIX' , BUFFER_SUFFIX )
173
+ . replaceAll ( 'BUFFER_PREFIX0' , BUFFER_PREFIX0 )
174
+ . replaceAll ( 'BUFFER_PREFIX1' , BUFFER_PREFIX1 )
159
175
. replaceAll ( 'HEAD_POS_PREFIX' , HEAD_POS_PREFIX )
160
176
. replaceAll ( 'BUFFER_SWITCH_ID' , BUFFER_SWITCH_ID ) ;
161
- return staticStyles ;
177
+
162
178
// Refer to the project's README for a detailed description of how this is supposed to work.
163
- // addMachineDisplayStyling(sb, config);
179
+ const dynStyles = [ ] ;
180
+ addBufferSwitchLabelStyling ( dynStyles , config ) ;
181
+ addStateDisplayStyling ( dynStyles , config ) ;
182
+ addStateLabelStyling ( dynStyles , config ) ;
164
183
// addToggleLabelStyling(sb, config);
165
- // addStateLabelStyling(sb, config);
166
184
// addTapeCellStyling(sb, config);
167
185
// addHeadPosStyling(sb, config);
186
+ return staticStyles + '\n' + dynStyles . join ( '\n' ) ;
168
187
}
169
188
170
- export default function toHTML ( config , numTapeCells ) {
171
- return '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8"/>\n<style>\n' +
172
- getCompiledMachinePageStyles ( config ) +
173
- '\n</style>\n<title>CSS Turing Machine</title>\n</head>\n<body>\n' +
174
- getCompiledMachinePageBody ( config , numTapeCells ) +
175
- '\n</body>\n</html>' ;
189
+ function addBufferSwitchLabelStyling ( sb , config ) {
190
+ // Show the buffer switch label if we're not in the halting state.
191
+ const haltingState = config . length ;
192
+ const b0HaltingStateInputId = getInputId ( 0 , STATE_PREFIX , haltingState ) ;
193
+
194
+ const showBufferSwitchLabel = select ( id ( b0HaltingStateInputId ) . unchecked ( ) , '+' , unchecked ( ) , '~' , attr ( 'for' , BUFFER_SWITCH_ID ) )
195
+ . displayBlock ( ) ;
196
+ sb . push ( showBufferSwitchLabel ) ;
197
+ }
198
+
199
+ function addStateDisplayStyling ( sb , config ) {
200
+ const stateNames = getOrderedStateNames ( config ) ;
201
+ stateNames . forEach ( ( stateName , idx ) => {
202
+ const b0StateInputId = getInputId ( 0 , STATE_PREFIX , idx ) ;
203
+ const b1StateInputId = getInputId ( 1 , STATE_PREFIX , idx ) ;
204
+
205
+ const displayStateNameTop = select ( id ( b0StateInputId ) . checked ( ) , '~' , 'p' , '+' , 'i::after' )
206
+ . content ( stateName ) ;
207
+ const displayStateNameBottom = select ( id ( STARTED_ID ) . checked ( ) , '~' , id ( b1StateInputId ) . checked ( ) , '~' , 'p' , '+' , 'i' , '+' , 'i::after' )
208
+ . content ( stateName ) ;
209
+
210
+ sb . push ( displayStateNameTop ) ;
211
+ sb . push ( displayStateNameBottom ) ;
212
+ } ) ;
213
+ }
214
+
215
+ function addStateLabelStyling ( sb , config ) {
216
+ const stateNames = getOrderedStateNames ( config ) ;
217
+ for ( let source = 0 ; source < 2 ; source ++ ) {
218
+ const dest = 1 - source ;
219
+ const matchesSourceSelector = source === 0 ? id ( BUFFER_SWITCH_ID ) . unchecked ( ) : id ( BUFFER_SWITCH_ID ) . checked ( ) ;
220
+
221
+ // Outer loop represents the destination state for which we are computing the visibility of its label.
222
+ stateNames . forEach ( ( stateName , idx ) => {
223
+ const destStateInputId = getInputId ( dest , STATE_PREFIX , idx ) ;
224
+ const destStateLabelSelector = attr ( 'for' , destStateInputId ) ;
225
+ const destStateUncheckedSelector = id ( destStateInputId ) . unchecked ( ) ;
226
+
227
+ const currentHeadSelector = attrContains ( 'id' , getBufferPrefix ( source ) + HEAD_POS_PREFIX ) . checked ( ) ;
228
+
229
+ // Inner loop represents each possible value of the current state.
230
+ // No rules necessary for case that current state is halting state (cannot transition to any other state).
231
+ config . forEach ( ( currentState , currentStateIdx ) => {
232
+ const currentStateInputId = getInputId ( source , STATE_PREFIX , currentStateIdx ) ;
233
+ const currentStateCheckedSelector = id ( currentStateInputId ) . checked ( ) ;
234
+ // currentStateCheckedSelector/destStateUncheckedSelector need to be ordered when creating the selector based on DOM layout.
235
+ const [ first , second ] = [ currentStateIdx , source ] < [ idx , dest ] ?
236
+ [ currentStateCheckedSelector , destStateUncheckedSelector ] :
237
+ [ destStateUncheckedSelector , currentStateCheckedSelector ] ;
238
+
239
+ if ( currentState [ 1 ] . next === stateName ) {
240
+ const displayNextStateFor1 = select ( matchesSourceSelector , '~' , first , '~' , second , '~' , currentHeadSelector , '+' , '*' , '+' , checked ( ) , '~' , destStateLabelSelector )
241
+ . displayBlock ( ) ;
242
+ sb . push ( displayNextStateFor1 ) ;
243
+ }
244
+
245
+ if ( currentState [ 0 ] . next === stateName ) {
246
+ const displayNextStateFor0 = select ( matchesSourceSelector , '~' , first , '~' , second , '~' , currentHeadSelector , '+' , '*' , '+' , unchecked ( ) , '~' , destStateLabelSelector )
247
+ . displayBlock ( ) ;
248
+ sb . push ( displayNextStateFor0 ) ;
249
+ }
250
+ } ) ;
251
+ } ) ;
252
+ }
253
+ }
254
+
255
+ function getOrderedStateNames ( config ) {
256
+ const stateNames = config . map ( s => s . name ) ;
257
+ stateNames . push ( 'HALT' ) ;
258
+ return stateNames ;
176
259
}
0 commit comments