@mathscan/math-exercise-engine 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # Math Exercise Engine
2
+
3
+ A TypeScript DSL parser for mathematical exercises. Parse exercise definitions into structured AST and JSON for use in educational apps.
4
+
5
+ ## ✨ Features
6
+
7
+ - **Text-first DSL** — Write exercises in readable text format
8
+ - **Chevrotain parser** — Robust tokenization and parsing
9
+ - **AST-based** — Clean separation between parsing and rendering
10
+ - **Dual validation** — Simple (predetermined answers) and expression-based
11
+ - **TypeScript-first** — Full type definitions included
12
+ - **React renderer** — Production-ready components included
13
+ - **Ref-based API** — Full programmatic control (submit, reset, focus, get values)
14
+
15
+ ## 📦 Installation
16
+
17
+ ```bash
18
+ npm install math-exercise-engine
19
+ # or
20
+ pnpm add math-exercise-engine
21
+ # or
22
+ yarn add math-exercise-engine
23
+ ```
24
+
25
+ ## 🚀 Quick Start
26
+
27
+ ```typescript
28
+ import { compile, validate, ExerciseRenderer } from 'math-exercise-engine';
29
+
30
+ // Compile a DSL exercise to JSON
31
+ const json = compile(`
32
+ COMPARISON[350158 ? 2334443, answer="<"]
33
+ WIDGET_ANSWER[type=simple]
34
+ `);
35
+
36
+ // Render in React
37
+ import 'math-exercise-engine/style.css'; // Opt-in: import minimal default styles
38
+
39
+ // Option 1: Use built-in submit button
40
+ <ExerciseRenderer
41
+ json={json}
42
+ onSubmit={(values) => {
43
+ const result = validate(json, values);
44
+ console.log(result.isCorrect);
45
+ }}
46
+ showSubmitButton={true}
47
+ />
48
+
49
+ // Option 2: Control submit with ref (recommended for custom styling)
50
+ const exerciseRef = useRef<ExerciseRendererRef>(null);
51
+
52
+ <ExerciseRenderer
53
+ ref={exerciseRef}
54
+ json={json}
55
+ onSubmit={(values) => {
56
+ const result = validate(json, values);
57
+ console.log(result.isCorrect);
58
+ }}
59
+ />
60
+
61
+ // Custom buttons (fully controlled by your app)
62
+ <div>
63
+ <button
64
+ className="my-custom-submit-btn"
65
+ onClick={() => {
66
+ const result = exerciseRef.current?.submit();
67
+ console.log('Validation result:', result);
68
+ if (result?.isCorrect) {
69
+ alert('Correct! 🎉');
70
+ } else {
71
+ alert('Try again! ❌');
72
+ }
73
+ }}
74
+ >
75
+ Check My Answer
76
+ </button>
77
+ <button onClick={() => exerciseRef.current?.reset()}>
78
+ Reset Exercise
79
+ </button>
80
+ <button onClick={() => exerciseRef.current?.focusFirstInput()}>
81
+ Focus First Input
82
+ </button>
83
+ </div>
84
+ ```
85
+
86
+ ## 🎛️ Ref API - Programmatic Control
87
+
88
+ When using `ref` for custom button styling, you get full programmatic control:
89
+
90
+ ```typescript
91
+ import type { ExerciseRendererRef } from 'math-exercise-engine';
92
+
93
+ const exerciseRef = useRef<ExerciseRendererRef>(null);
94
+
95
+ // Available methods:
96
+ const result = exerciseRef.current?.submit(); // Returns ValidationResult
97
+ exerciseRef.current?.getValues(); // Get current input values
98
+ exerciseRef.current?.reset(); // Clear all inputs
99
+ exerciseRef.current?.focusFirstInput(); // Focus first input field
100
+ ```
101
+
102
+ ### Method Details
103
+
104
+ - **`submit()`**: Performs validation and returns `ValidationResult` (also triggers `onSubmit` callback)
105
+ - **`getValues()`**: Returns `Record<string, string>` of all current input values
106
+ - **`reset()`**: Clears all input fields to empty state
107
+ - **`focusFirstInput()`**: Automatically focuses the first input element
108
+
109
+ ## 📝 DSL Syntax
110
+
111
+ ### Numeric Input Widget
112
+
113
+ ```
114
+ W_N_I[name=a, digits=2]
115
+ ```
116
+
117
+ Creates an input field for numbers with up to 2 digits.
118
+
119
+ ### Comparison Widget
120
+
121
+ ```
122
+ COMPARISON[350158 ? 2334443, answer="<"]
123
+ ```
124
+
125
+ Student must choose `<`, `>`, or `=` to compare two numbers.
126
+
127
+ ### Validators
128
+
129
+ **Simple mode** — each widget has a predetermined answer:
130
+
131
+ ```
132
+ WIDGET_ANSWER[type=simple]
133
+ ```
134
+
135
+ **Expression mode** — validate using a math expression:
136
+
137
+ ```
138
+ WIDGET_ANSWER[type=expression, expr="(a * b) + (c * d) == 1200"]
139
+ ```
140
+
141
+ ## 📚 Examples
142
+
143
+ ### Example 1: Simple Comparison
144
+
145
+ ```
146
+ COMPARISON[350158 ? 2334443, answer="<"]
147
+ WIDGET_ANSWER[type=simple]
148
+ ```
149
+
150
+ ### Example 2: Math Expression
151
+
152
+ ```
153
+ (W_N_I[name=a, digits=2] × W_N_I[name=b, digits=2]) + (W_N_I[name=c, digits=2] × W_N_I[name=d, digits=2]) = 1200
154
+ WIDGET_ANSWER[type=expression, expr="(a * b) + (c * d) == 1200"]
155
+ ```
156
+
157
+ ### Example 3: Simple Addition
158
+
159
+ ```
160
+ W_N_I[name=x, digits=2] + W_N_I[name=y, digits=2] = 100
161
+ WIDGET_ANSWER[type=expression, expr="x + y == 100"]
162
+ ```
163
+
164
+ ## 🔧 API Reference
165
+
166
+ ### Parsing
167
+
168
+ ```typescript
169
+ import { compile, tokenize, parse, toAST, toJSON } from 'math-exercise-engine';
170
+
171
+ // Full pipeline (recommended)
172
+ const json = compile(dslText);
173
+
174
+ // Step by step
175
+ const tokens = tokenize(dslText);
176
+ const cst = parse(tokens);
177
+ const ast = toAST(cst);
178
+ const json = toJSON(ast);
179
+
180
+ // With both AST and JSON
181
+ const { ast, json } = compileWithAST(dslText);
182
+ ```
183
+
184
+ ### Validation
185
+
186
+ ```typescript
187
+ import { validate, validateSimple, validateExpression } from 'math-exercise-engine';
188
+
189
+ // Auto-detect validation type
190
+ const result = validate(exerciseJSON, userInputs);
191
+
192
+ // Manual validation
193
+ const simpleResult = validateSimple(exerciseJSON, userInputs);
194
+ const exprResult = validateExpression(exerciseJSON, userInputs);
195
+ ```
196
+
197
+ ### Types
198
+
199
+ ```typescript
200
+ import type {
201
+ ExerciseJSON,
202
+ ExerciseNode,
203
+ ValidationResult,
204
+ WidgetJSON,
205
+ ExerciseRendererRef
206
+ } from 'math-exercise-engine';
207
+
208
+ // ExerciseRendererRef interface:
209
+ interface ExerciseRendererRef {
210
+ submit: () => ValidationResult; // Perform validation & return result
211
+ getValues: () => Record<string, string>; // Get current values
212
+ reset: () => void; // Clear all inputs
213
+ focusFirstInput: () => void; // Focus first input
214
+ }
215
+ ```
216
+
217
+ ## 📊 JSON Output Format
218
+
219
+ All layout items include unique `id` fields for React/JSX rendering:
220
+
221
+ ```json
222
+ {
223
+ "version": "2.0",
224
+ "layout": [
225
+ { "id": "text_1", "type": "text", "value": "350158" },
226
+ { "id": "widget-ref_2", "type": "widget-ref", "widgetId": "comparison_1" },
227
+ { "id": "text_3", "type": "text", "value": "2334443" }
228
+ ],
229
+ "widgets": {
230
+ "comparison_1": {
231
+ "type": "comparison",
232
+ "id": "comparison_1",
233
+ "config": {
234
+ "left": 350158,
235
+ "right": 2334443
236
+ }
237
+ }
238
+ },
239
+ "validation": {
240
+ "mode": "simple",
241
+ "answers": {
242
+ "comparison_1": "<"
243
+ }
244
+ }
245
+ }
246
+ ```
247
+
248
+ > 💡 **New:** Layout items now include unique IDs perfect for React keys. [Learn more](docs/LAYOUT_IDS.md)
249
+
250
+ ## ✅ Testing
251
+
252
+ ```bash
253
+ pnpm test
254
+ ```
255
+
256
+ All 83 tests passing ✅
257
+
258
+ ## 📄 License
259
+
260
+ [Your License]
261
+
262
+ ## 👥 Authors
263
+
264
+ [Your Name]
265
+
266
+ ---
267
+
268
+ **Status**: Production ready. All tests passing.