第11章: TinyPython — マイコンで動くPythonを作る#
マイコンの上で動く小さな Python 処理系を作ります。
サンプルコード#
ディレクトリ:
docs/os-on-arduino/code/11_tiny_pythonビルドと書き込み:
uv run pio run -d 11_tiny_python -t upload
uv run pio device monitor -b 115200
ソースコード#
この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。
src/main.cpp — 262 行
1/**
2 * Chapter 11: TinyPython - A Minimal Python Interpreter
3 *
4 * A Python subset interpreter running on Arduino Uno R4 WiFi.
5 * Supports: variables, expressions, if/elif/else, while, for,
6 * def functions, print(), and Arduino GPIO control.
7 *
8 * This demonstrates how MicroPython works at a fundamental level.
9 */
10
11#include <Arduino.h>
12#include "Arduino_LED_Matrix.h"
13#include "tp_config.h"
14#include "tp_value.h"
15#include "tp_env.h"
16#include "tp_eval.h"
17#include "tp_module.h"
18#include <string.h>
19
20// LED Matrix globals
21ArduinoLEDMatrix g_matrix;
22uint8_t g_matrix_frame[8][12] = {0};
23
24// Wrapper for renderBitmap (calling via extern doesn't work reliably for C++ objects)
25extern "C" void matrix_render(void)
26{
27 g_matrix.renderBitmap(g_matrix_frame, 8, 12);
28}
29
30// ---- REPL State ----
31static char g_line_buf[TP_LINE_LEN];
32static int g_line_pos = 0;
33static char g_last_char = 0; // for \r\n handling
34
35// Block accumulation mode (for if/while/for/def)
36static int g_in_block = 0;
37static int g_block_start = -1;
38static int g_block_line_count = 0;
39static int g_block_indent = 0;
40static char g_block_type[8]; // "if", "while", "for", "def"
41
42// For def: store function metadata
43static char g_def_name[16];
44static char g_def_params[TP_MAX_PARAMS][16];
45static int g_def_param_count = 0;
46
47static void show_banner(void)
48{
49 Serial.println();
50 Serial.println(" _____ _ ____ _ _");
51 Serial.println(" |_ _|(_)_ __ _ _| _ \\ _ _| |_| |__ ___ _ __");
52 Serial.println(" | | | | '_ \\| | | | |_) | | | | __| '_ \\ / _ \\| '_ \\");
53 Serial.println(" | | | | | | | |_| | __/| |_| | |_| | | | (_) | | | |");
54 Serial.println(" |_| |_|_| |_|\\__, |_| \\__, |\\__|_| |_|\\___/|_| |_|");
55 Serial.println(" |___/ |___/");
56 Serial.println();
57 Serial.println(" TinyPython 0.1 on Arduino UNO R4 WiFi");
58 Serial.print(" SRAM: 32KB, Flash: 256KB");
59 Serial.println();
60 Serial.println(" Type Python code. Try: print(1 + 2)");
61 Serial.println();
62 Serial.println(" Built-ins:");
63 Serial.println(" Python: print, type, len, abs, min, max, int, str, range");
64 Serial.println(" GPIO: pin_mode, digital_write, digital_read, analog_read");
65 Serial.println(" Timing: delay, millis");
66 Serial.println(" LED: led.blink, led.on, led.off");
67 Serial.println(" Matrix: matrix.pixel, matrix.show, matrix.clear,");
68 Serial.println(" matrix.fill, matrix.row");
69 Serial.println();
70}
71
72static void prompt(void)
73{
74 if (g_in_block) {
75 Serial.print("... ");
76 } else {
77 Serial.print(">>> ");
78 }
79}
80
81// Parse def header: "def name(a, b, c):"
82static void parse_def_header(const char *line)
83{
84 // Skip "def "
85 line += 4;
86 while (*line == ' ') line++;
87
88 // Read function name
89 int i = 0;
90 while (*line && *line != '(' && *line != ' ' && i < 15) {
91 g_def_name[i++] = *line++;
92 }
93 g_def_name[i] = '\0';
94
95 // Read parameters
96 g_def_param_count = 0;
97 if (*line == '(') {
98 line++;
99 while (*line && *line != ')') {
100 while (*line == ' ' || *line == ',') line++;
101 if (*line == ')') break;
102 int j = 0;
103 while (*line && *line != ',' && *line != ')' && *line != ' ' && j < 15) {
104 g_def_params[g_def_param_count][j++] = *line++;
105 }
106 g_def_params[g_def_param_count][j] = '\0';
107 g_def_param_count++;
108 if (g_def_param_count >= TP_MAX_PARAMS) break;
109 }
110 }
111}
112
113static void process_line(const char *line)
114{
115 // Count indentation
116 int indent = 0;
117 while (line[indent] == ' ') indent++;
118 const char *trimmed = line + indent;
119
120 // If we're in block accumulation mode
121 if (g_in_block) {
122 // Empty line or line with less/equal indent than block start = end of block
123 if (trimmed[0] == '\0') {
124 // End of block - execute it
125 if (strcmp(g_block_type, "def") == 0) {
126 // Register function
127 TpFunc *fn = tp_func_new(g_def_name);
128 if (fn) {
129 fn->body_start = g_block_start;
130 fn->body_count = g_block_line_count;
131 fn->param_count = g_def_param_count;
132 for (int i = 0; i < g_def_param_count; i++) {
133 strncpy(fn->params[i], g_def_params[i], 15);
134 fn->params[i][15] = '\0';
135 }
136 }
137 } else {
138 // Execute block (if/while/for)
139 tp_clear_error();
140 tp_eval_lines(g_block_start, g_block_line_count, 0);
141 }
142 g_in_block = 0;
143 return;
144 }
145 // Accumulate line into block buffer
146 tp_block_store_line(line);
147 g_block_line_count++;
148 return;
149 }
150
151 // Skip empty lines
152 if (trimmed[0] == '\0') return;
153
154 // Check for block-starting keywords
155 // if ...: while ...: for ...: def ...:
156 int is_block = 0;
157 if (strncmp(trimmed, "if ", 3) == 0 && strchr(trimmed, ':'))
158 { is_block = 1; strncpy(g_block_type, "if", 3); }
159 else if (strncmp(trimmed, "while ", 6) == 0 && strchr(trimmed, ':'))
160 { is_block = 1; strncpy(g_block_type, "while", 6); }
161 else if (strncmp(trimmed, "for ", 4) == 0 && strchr(trimmed, ':'))
162 { is_block = 1; strncpy(g_block_type, "for", 4); }
163 else if (strncmp(trimmed, "def ", 4) == 0 && strchr(trimmed, ':'))
164 { is_block = 1; strncpy(g_block_type, "def", 4); }
165
166 if (is_block) {
167 if (strcmp(g_block_type, "def") == 0) {
168 parse_def_header(trimmed);
169 }
170 g_in_block = 1;
171 g_block_start = tp_block_store_line(line);
172 g_block_line_count = 1;
173 g_block_indent = indent;
174 return;
175 }
176
177 // Single line statement - execute immediately
178 tp_clear_error();
179 TpValue result = tp_eval_line(trimmed);
180
181 // Print result if it's not None and no error (REPL behavior)
182 if (!tp_had_error() && result.type != TP_NONE) {
183 switch (result.type) {
184 case TP_INT: Serial.println(result.ival); break;
185 case TP_BOOL: Serial.println(result.ival ? "True" : "False"); break;
186 case TP_STR:
187 Serial.print("'");
188 Serial.print(result.sval);
189 Serial.println("'");
190 break;
191 default: break;
192 }
193 }
194}
195
196void setup()
197{
198 Serial.begin(115200);
199 while (!Serial) { }
200
201 // Initialize hardware
202 g_matrix.begin();
203
204 // Initialize TinyPython
205 tp_env_init();
206 tp_eval_init();
207 tp_module_init();
208
209 // Register modules (like MicroPython's built-in modules)
210 tp_mod_led_register();
211 tp_mod_matrix_register();
212 tp_mod_gpio_register();
213
214 show_banner();
215 prompt();
216}
217
218void loop()
219{
220 while (Serial.available()) {
221 char c = Serial.read();
222
223 if (c == '\r' || c == '\n') {
224 // Skip \n immediately after \r (handle \r\n as single newline)
225 if (c == '\n' && g_last_char == '\r') {
226 g_last_char = c;
227 continue;
228 }
229 g_last_char = c;
230
231 Serial.println();
232 g_line_buf[g_line_pos] = '\0';
233
234 if (g_line_pos > 0 || g_in_block) {
235 process_line(g_line_buf);
236 }
237
238 g_line_pos = 0;
239 prompt();
240 } else if (c == '\b' || c == 127) {
241 g_last_char = c;
242 // Backspace
243 if (g_line_pos > 0) {
244 g_line_pos--;
245 Serial.print("\b \b");
246 }
247 } else if (c == 0x03) {
248 // Ctrl+C: cancel current block
249 if (g_in_block) {
250 g_in_block = 0;
251 Serial.println();
252 Serial.println("KeyboardInterrupt");
253 g_line_pos = 0;
254 prompt();
255 }
256 } else if (g_line_pos < TP_LINE_LEN - 1) {
257 g_last_char = c;
258 g_line_buf[g_line_pos++] = c;
259 Serial.print(c); // Echo
260 }
261 }
262}
include/tp_config.h — 43 行
1/**
2 * tp_config.h - TinyPython Configuration
3 *
4 * Memory budget for 32KB SRAM (Cortex-M / 32bit):
5 * Scope stack: 8 * 48 * 28 = 10752 bytes <- 最大
6 * Block buffer: 48 * 80 = 3840 bytes
7 * String pool: = 2048 bytes
8 * Function table: 16 * 124 = 1984 bytes
9 * Line buffer: = 128 bytes
10 * Total: ~18.2KB
11 *
12 * 本書 11.14 節の表と対応している。値を変えたら向こうも直すこと。
13 */
14
15#ifndef TP_CONFIG_H
16#define TP_CONFIG_H
17
18// Maximum values in the pool (int, string, bool, none, function)
19#define TP_MAX_VALUES 128
20
21// Maximum variables per scope
22#define TP_MAX_VARS 48
23
24// Maximum scope depth (function calls)
25#define TP_MAX_SCOPES 8
26
27// String pool size
28#define TP_STRING_POOL 2048
29
30// Maximum length of a single input line
31#define TP_LINE_LEN 128
32
33// Maximum lines in a block (if/while/def body)
34#define TP_BLOCK_LINES 48
35#define TP_BLOCK_LINE_LEN 80
36
37// Maximum function parameters
38#define TP_MAX_PARAMS 6
39
40// Maximum nesting depth (for/while/if)
41#define TP_MAX_NEST 8
42
43#endif // TP_CONFIG_H
include/tp_env.h — 60 行
1/**
2 * tp_env.h - TinyPython Environment (Variable Storage)
3 *
4 * Variables are stored in a hash-map-like structure.
5 * Each scope (global, function) has its own variable table.
6 * This is similar to Python's namespace/dict system.
7 */
8
9#ifndef TP_ENV_H
10#define TP_ENV_H
11
12#include "tp_value.h"
13#include "tp_config.h"
14
15#ifdef __cplusplus
16extern "C" {
17#endif
18
19// A variable entry: name -> value
20typedef struct {
21 char name[16];
22 TpValue value;
23 uint8_t used;
24} TpVar;
25
26// A scope (variable namespace)
27typedef struct {
28 TpVar vars[TP_MAX_VARS];
29} TpScope;
30
31// Initialize the environment system
32void tp_env_init(void);
33
34// Push/pop scope (for function calls)
35int tp_env_push_scope(void);
36void tp_env_pop_scope(void);
37
38// Get/set variable in current scope (falls back to global)
39TpValue tp_env_get(const char *name);
40void tp_env_set(const char *name, TpValue value);
41
42// Check if variable exists
43int tp_env_exists(const char *name);
44
45// String pool: intern a string and return a pointer to the pool
46const char *tp_str_intern(const char *s);
47
48// Function storage
49TpFunc *tp_func_new(const char *name);
50TpFunc *tp_func_find(const char *name);
51
52// Block storage (shared buffer for function/loop bodies)
53int tp_block_store_line(const char *line);
54const char *tp_block_get_line(int index);
55
56#ifdef __cplusplus
57}
58#endif
59
60#endif // TP_ENV_H
include/tp_eval.h — 58 行
1/**
2 * tp_eval.h - TinyPython Evaluator
3 *
4 * Recursive descent parser that evaluates as it parses.
5 * This is the simplest interpreter architecture:
6 * 1. Tokenize the input string
7 * 2. Parse according to grammar rules
8 * 3. Evaluate immediately during parsing
9 *
10 * Grammar (simplified):
11 * statement = assignment | if_stmt | while_stmt | for_stmt
12 * | def_stmt | return_stmt | expr_stmt
13 * assignment = NAME '=' expr
14 * expr = or_expr
15 * or_expr = and_expr ('or' and_expr)*
16 * and_expr = not_expr ('and' not_expr)*
17 * not_expr = 'not' not_expr | comparison
18 * comparison = add_expr (('==' | '!=' | '<' | '>' | '<=' | '>=') add_expr)*
19 * add_expr = mul_expr (('+' | '-') mul_expr)*
20 * mul_expr = unary (('+' | '-' | '%') unary)*
21 * unary = '-' unary | atom
22 * atom = INT | STRING | 'True' | 'False' | 'None'
23 * | NAME '(' args ')' | NAME | '(' expr ')'
24 */
25
26#ifndef TP_EVAL_H
27#define TP_EVAL_H
28
29#include "tp_value.h"
30
31#ifdef __cplusplus
32extern "C" {
33#endif
34
35// Initialize the evaluator
36void tp_eval_init(void);
37
38// Execute a single line of Python code
39// Returns the result (TP_NONE for statements)
40TpValue tp_eval_line(const char *line);
41
42// Execute multiple lines (for blocks)
43// Returns result of last statement, or return value
44TpValue tp_eval_lines(int block_start, int block_count, int indent);
45
46// Flag set by 'return' statement
47extern int tp_return_flag;
48extern TpValue tp_return_value;
49
50// Error handling
51int tp_had_error(void);
52void tp_clear_error(void);
53
54#ifdef __cplusplus
55}
56#endif
57
58#endif // TP_EVAL_H
include/tp_module.h — 69 行
1/**
2 * tp_module.h - TinyPython Module System
3 *
4 * Modules are how MicroPython organizes built-in functions.
5 * Each module is a separate file with a registration function.
6 *
7 * Usage from TinyPython REPL:
8 * >>> import led
9 * >>> led.blink(3)
10 * >>> import matrix
11 * >>> matrix.clear()
12 */
13
14#ifndef TP_MODULE_H
15#define TP_MODULE_H
16
17#include "tp_value.h"
18
19#ifdef __cplusplus
20extern "C" {
21#endif
22
23// C function signature for built-in functions
24typedef TpValue (*TpCFunc)(TpValue *args, int argc);
25
26// A single function entry in a module
27typedef struct {
28 const char *name;
29 TpCFunc func;
30} TpCFuncEntry;
31
32// A module: name + array of functions
33typedef struct {
34 const char *name;
35 const TpCFuncEntry *funcs;
36 int func_count;
37} TpModule;
38
39// Maximum modules
40#define TP_MAX_MODULES 8
41
42// Register a module (called at startup)
43void tp_module_register(const TpModule *mod);
44
45// Find a module by name (returns NULL if not found)
46const TpModule *tp_module_find(const char *name);
47
48// Find a function within a module (returns NULL if not found)
49TpCFunc tp_module_find_func(const TpModule *mod, const char *func_name);
50
51// Find a global builtin function (not in any module)
52TpCFunc tp_builtin_find(const char *name);
53
54// Register a global builtin function
55void tp_builtin_register(const char *name, TpCFunc func);
56
57// Initialize module system
58void tp_module_init(void);
59
60// Module registration functions (called from main.cpp)
61void tp_mod_led_register(void);
62void tp_mod_matrix_register(void);
63void tp_mod_gpio_register(void);
64
65#ifdef __cplusplus
66}
67#endif
68
69#endif // TP_MODULE_H
include/tp_value.h — 72 行
1/**
2 * tp_value.h - TinyPython Value System
3 *
4 * All Python values are represented as tagged unions.
5 * This is how MicroPython (and CPython) work internally:
6 * every value carries its type information at runtime.
7 */
8
9#ifndef TP_VALUE_H
10#define TP_VALUE_H
11
12#include <stdint.h>
13#include "tp_config.h"
14
15// Value types
16typedef enum {
17 TP_NONE,
18 TP_INT,
19 TP_BOOL,
20 TP_STR,
21 TP_FUNC,
22} TpType;
23
24// Forward declaration
25struct TpFunc;
26
27// A TinyPython value (tagged union)
28typedef struct {
29 TpType type;
30 union {
31 int32_t ival; // TP_INT, TP_BOOL
32 const char *sval; // TP_STR (points into string pool)
33 struct TpFunc *fval; // TP_FUNC
34 };
35} TpValue;
36
37// Function definition
38typedef struct TpFunc {
39 char name[16];
40 char params[TP_MAX_PARAMS][16];
41 int param_count;
42 // Body stored as line indices into block buffer
43 int body_start; // index into global block storage
44 int body_count; // number of lines
45} TpFunc;
46
47// Convenience constructors
48static inline TpValue tp_none(void) {
49 TpValue v; v.type = TP_NONE; v.ival = 0; return v;
50}
51static inline TpValue tp_int(int32_t n) {
52 TpValue v; v.type = TP_INT; v.ival = n; return v;
53}
54static inline TpValue tp_bool(int b) {
55 TpValue v; v.type = TP_BOOL; v.ival = b ? 1 : 0; return v;
56}
57static inline TpValue tp_str(const char *s) {
58 TpValue v; v.type = TP_STR; v.sval = s; return v;
59}
60
61// Check truthiness (like Python's bool())
62static inline int tp_is_truthy(TpValue v) {
63 switch (v.type) {
64 case TP_NONE: return 0;
65 case TP_INT: return v.ival != 0;
66 case TP_BOOL: return v.ival != 0;
67 case TP_STR: return v.sval && v.sval[0] != '\0';
68 default: return 1;
69 }
70}
71
72#endif // TP_VALUE_H
src/tp_env.cpp — 161 行
1/**
2 * tp_env.cpp - Environment Implementation
3 *
4 * Variable storage, string interning, function registry, block storage.
5 */
6
7#include "tp_env.h"
8#include <string.h>
9
10// ---- Scope stack ----
11static TpScope g_scopes[TP_MAX_SCOPES];
12static int g_scope_depth = 0;
13
14void tp_env_init(void)
15{
16 memset(g_scopes, 0, sizeof(g_scopes));
17 g_scope_depth = 0;
18}
19
20int tp_env_push_scope(void)
21{
22 if (g_scope_depth >= TP_MAX_SCOPES - 1) return -1;
23 g_scope_depth++;
24 memset(&g_scopes[g_scope_depth], 0, sizeof(TpScope));
25 return 0;
26}
27
28void tp_env_pop_scope(void)
29{
30 if (g_scope_depth > 0) g_scope_depth--;
31}
32
33// Look up variable: check current scope first, then global (scope 0)
34TpValue tp_env_get(const char *name)
35{
36 // Search current scope
37 for (int i = 0; i < TP_MAX_VARS; i++) {
38 if (g_scopes[g_scope_depth].vars[i].used &&
39 strcmp(g_scopes[g_scope_depth].vars[i].name, name) == 0) {
40 return g_scopes[g_scope_depth].vars[i].value;
41 }
42 }
43 // Search global scope if not at global
44 if (g_scope_depth > 0) {
45 for (int i = 0; i < TP_MAX_VARS; i++) {
46 if (g_scopes[0].vars[i].used &&
47 strcmp(g_scopes[0].vars[i].name, name) == 0) {
48 return g_scopes[0].vars[i].value;
49 }
50 }
51 }
52 return tp_none();
53}
54
55void tp_env_set(const char *name, TpValue value)
56{
57 TpScope *scope = &g_scopes[g_scope_depth];
58 // Update existing
59 for (int i = 0; i < TP_MAX_VARS; i++) {
60 if (scope->vars[i].used && strcmp(scope->vars[i].name, name) == 0) {
61 scope->vars[i].value = value;
62 return;
63 }
64 }
65 // Insert new
66 for (int i = 0; i < TP_MAX_VARS; i++) {
67 if (!scope->vars[i].used) {
68 strncpy(scope->vars[i].name, name, 15);
69 scope->vars[i].name[15] = '\0';
70 scope->vars[i].value = value;
71 scope->vars[i].used = 1;
72 return;
73 }
74 }
75}
76
77int tp_env_exists(const char *name)
78{
79 for (int i = 0; i < TP_MAX_VARS; i++) {
80 if (g_scopes[g_scope_depth].vars[i].used &&
81 strcmp(g_scopes[g_scope_depth].vars[i].name, name) == 0) {
82 return 1;
83 }
84 }
85 if (g_scope_depth > 0) {
86 for (int i = 0; i < TP_MAX_VARS; i++) {
87 if (g_scopes[0].vars[i].used &&
88 strcmp(g_scopes[0].vars[i].name, name) == 0) {
89 return 1;
90 }
91 }
92 }
93 return 0;
94}
95
96// ---- String pool ----
97static char g_str_pool[TP_STRING_POOL];
98static int g_str_pool_pos = 0;
99
100const char *tp_str_intern(const char *s)
101{
102 // Check if already interned
103 int i = 0;
104 while (i < g_str_pool_pos) {
105 if (strcmp(&g_str_pool[i], s) == 0) {
106 return &g_str_pool[i];
107 }
108 i += strlen(&g_str_pool[i]) + 1;
109 }
110 // Add to pool
111 int len = strlen(s);
112 if (g_str_pool_pos + len + 1 > TP_STRING_POOL) {
113 return "ERR:pool_full";
114 }
115 char *dst = &g_str_pool[g_str_pool_pos];
116 strcpy(dst, s);
117 g_str_pool_pos += len + 1;
118 return dst;
119}
120
121// ---- Function storage ----
122#define TP_MAX_FUNCS 16
123static TpFunc g_funcs[TP_MAX_FUNCS];
124static int g_func_count = 0;
125
126TpFunc *tp_func_new(const char *name)
127{
128 if (g_func_count >= TP_MAX_FUNCS) return NULL;
129 TpFunc *f = &g_funcs[g_func_count++];
130 memset(f, 0, sizeof(TpFunc));
131 strncpy(f->name, name, 15);
132 return f;
133}
134
135TpFunc *tp_func_find(const char *name)
136{
137 for (int i = 0; i < g_func_count; i++) {
138 if (strcmp(g_funcs[i].name, name) == 0) {
139 return &g_funcs[i];
140 }
141 }
142 return NULL;
143}
144
145// ---- Block storage (shared for function bodies, loops, etc.) ----
146static char g_block_buf[TP_BLOCK_LINES][TP_BLOCK_LINE_LEN];
147static int g_block_count = 0;
148
149int tp_block_store_line(const char *line)
150{
151 if (g_block_count >= TP_BLOCK_LINES) return -1;
152 strncpy(g_block_buf[g_block_count], line, TP_BLOCK_LINE_LEN - 1);
153 g_block_buf[g_block_count][TP_BLOCK_LINE_LEN - 1] = '\0';
154 return g_block_count++;
155}
156
157const char *tp_block_get_line(int index)
158{
159 if (index < 0 || index >= g_block_count) return "";
160 return g_block_buf[index];
161}
src/tp_eval.cpp — 774 行
1/**
2 * tp_eval.cpp - TinyPython Evaluator
3 *
4 * A recursive descent parser/evaluator.
5 * Parses Python-like syntax and evaluates immediately.
6 *
7 * Key concept: each parse function corresponds to a grammar rule
8 * and returns the evaluated result.
9 */
10
11#include "tp_eval.h"
12#include "tp_env.h"
13#include "tp_module.h"
14#include "tp_config.h"
15#include <Arduino.h>
16#include <string.h>
17#include <stdlib.h>
18#include <ctype.h>
19
20// ---- Tokenizer ----
21// The tokenizer breaks input into tokens (words, numbers, operators)
22
23typedef enum {
24 TOK_EOF, TOK_INT, TOK_STR, TOK_NAME,
25 TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT,
26 TOK_EQ, TOK_EQEQ, TOK_NEQ, TOK_LT, TOK_GT, TOK_LE, TOK_GE,
27 TOK_LPAREN, TOK_RPAREN, TOK_COMMA, TOK_COLON,
28 TOK_AND, TOK_OR, TOK_NOT,
29 TOK_IF, TOK_ELIF, TOK_ELSE, TOK_WHILE, TOK_FOR, TOK_IN,
30 TOK_DEF, TOK_RETURN, TOK_IMPORT, TOK_TRUE, TOK_FALSE, TOK_NONE,
31 TOK_LBRACKET, TOK_RBRACKET, TOK_DOT,
32 TOK_PLUSEQ, TOK_MINUSEQ,
33} TokenType;
34
35static const char *g_src; // current source pointer
36static TokenType g_tok; // current token type
37static int32_t g_tok_ival; // integer value of current token
38static char g_tok_sval[64]; // string/name value of current token
39
40int tp_return_flag = 0;
41TpValue tp_return_value;
42
43// ---- Error handling ----
44static int g_error_flag = 0;
45
46static void tp_error(const char *type, const char *msg)
47{
48 g_error_flag = 1;
49 Serial.print(type);
50 Serial.print(": ");
51 Serial.println(msg);
52}
53
54// 再帰が深すぎてスコープを積めないとき。掲載コードの行数を増やさないよう関数にしてある。
55static TpValue tp_recursion_error(void)
56{
57 tp_error("RecursionError", "maximum recursion depth exceeded");
58 return tp_none();
59}
60
61static void tp_error_name(const char *name)
62{
63 g_error_flag = 1;
64 Serial.print("NameError: name '");
65 Serial.print(name);
66 Serial.println("' is not defined");
67}
68
69int tp_had_error(void) { return g_error_flag; }
70void tp_clear_error(void) { g_error_flag = 0; }
71
72// Skip whitespace (but not newlines - we handle one line at a time)
73static void skip_space(void)
74{
75 while (*g_src == ' ' || *g_src == '\t') g_src++;
76}
77
78// Read next token from g_src
79static void next_token(void)
80{
81 skip_space();
82
83 if (*g_src == '\0' || *g_src == '#') {
84 g_tok = TOK_EOF;
85 return;
86 }
87
88 // Numbers
89 if (isdigit(*g_src) || (*g_src == '-' && isdigit(g_src[1]) &&
90 (g_tok == TOK_EOF || g_tok == TOK_LPAREN || g_tok == TOK_COMMA ||
91 g_tok == TOK_EQ || g_tok == TOK_PLUS || g_tok == TOK_MINUS ||
92 g_tok == TOK_STAR || g_tok == TOK_SLASH || g_tok == TOK_COLON ||
93 g_tok == TOK_EQEQ || g_tok == TOK_NEQ || g_tok == TOK_LT ||
94 g_tok == TOK_GT || g_tok == TOK_LE || g_tok == TOK_GE ||
95 g_tok == TOK_RETURN))) {
96 char *end;
97 g_tok_ival = strtol(g_src, &end, 0);
98 g_src = end;
99 g_tok = TOK_INT;
100 return;
101 }
102
103 // Strings
104 if (*g_src == '"' || *g_src == '\'') {
105 char quote = *g_src++;
106 int i = 0;
107 while (*g_src && *g_src != quote && i < 63) {
108 if (*g_src == '\\' && g_src[1]) {
109 g_src++;
110 switch (*g_src) {
111 case 'n': g_tok_sval[i++] = '\n'; break;
112 case 't': g_tok_sval[i++] = '\t'; break;
113 case '\\': g_tok_sval[i++] = '\\'; break;
114 default: g_tok_sval[i++] = *g_src; break;
115 }
116 } else {
117 g_tok_sval[i++] = *g_src;
118 }
119 g_src++;
120 }
121 if (*g_src == quote) g_src++;
122 g_tok_sval[i] = '\0';
123 g_tok = TOK_STR;
124 return;
125 }
126
127 // Names and keywords
128 if (isalpha(*g_src) || *g_src == '_') {
129 int i = 0;
130 while ((isalnum(*g_src) || *g_src == '_') && i < 63) {
131 g_tok_sval[i++] = *g_src++;
132 }
133 g_tok_sval[i] = '\0';
134
135 // Check keywords
136 if (strcmp(g_tok_sval, "if") == 0) { g_tok = TOK_IF; return; }
137 if (strcmp(g_tok_sval, "elif") == 0) { g_tok = TOK_ELIF; return; }
138 if (strcmp(g_tok_sval, "else") == 0) { g_tok = TOK_ELSE; return; }
139 if (strcmp(g_tok_sval, "while") == 0) { g_tok = TOK_WHILE; return; }
140 if (strcmp(g_tok_sval, "for") == 0) { g_tok = TOK_FOR; return; }
141 if (strcmp(g_tok_sval, "in") == 0) { g_tok = TOK_IN; return; }
142 if (strcmp(g_tok_sval, "def") == 0) { g_tok = TOK_DEF; return; }
143 if (strcmp(g_tok_sval, "return") == 0) { g_tok = TOK_RETURN; return; }
144 if (strcmp(g_tok_sval, "import") == 0) { g_tok = TOK_IMPORT; return; }
145 if (strcmp(g_tok_sval, "and") == 0) { g_tok = TOK_AND; return; }
146 if (strcmp(g_tok_sval, "or") == 0) { g_tok = TOK_OR; return; }
147 if (strcmp(g_tok_sval, "not") == 0) { g_tok = TOK_NOT; return; }
148 if (strcmp(g_tok_sval, "True") == 0) { g_tok = TOK_TRUE; return; }
149 if (strcmp(g_tok_sval, "False") == 0) { g_tok = TOK_FALSE; return; }
150 if (strcmp(g_tok_sval, "None") == 0) { g_tok = TOK_NONE; return; }
151 g_tok = TOK_NAME;
152 return;
153 }
154
155 // Two-character operators
156 if (g_src[0] == '=' && g_src[1] == '=') { g_src += 2; g_tok = TOK_EQEQ; return; }
157 if (g_src[0] == '!' && g_src[1] == '=') { g_src += 2; g_tok = TOK_NEQ; return; }
158 if (g_src[0] == '<' && g_src[1] == '=') { g_src += 2; g_tok = TOK_LE; return; }
159 if (g_src[0] == '>' && g_src[1] == '=') { g_src += 2; g_tok = TOK_GE; return; }
160 if (g_src[0] == '+' && g_src[1] == '=') { g_src += 2; g_tok = TOK_PLUSEQ; return; }
161 if (g_src[0] == '-' && g_src[1] == '=') { g_src += 2; g_tok = TOK_MINUSEQ; return; }
162
163 // Single-character operators
164 char c = *g_src++;
165 switch (c) {
166 case '+': g_tok = TOK_PLUS; return;
167 case '-': g_tok = TOK_MINUS; return;
168 case '*': g_tok = TOK_STAR; return;
169 case '/': g_tok = TOK_SLASH; return;
170 case '%': g_tok = TOK_PERCENT; return;
171 case '=': g_tok = TOK_EQ; return;
172 case '<': g_tok = TOK_LT; return;
173 case '>': g_tok = TOK_GT; return;
174 case '(': g_tok = TOK_LPAREN; return;
175 case ')': g_tok = TOK_RPAREN; return;
176 case '[': g_tok = TOK_LBRACKET; return;
177 case ']': g_tok = TOK_RBRACKET; return;
178 case ',': g_tok = TOK_COMMA; return;
179 case ':': g_tok = TOK_COLON; return;
180 case '.': g_tok = TOK_DOT; return;
181 default: g_tok = TOK_EOF; return;
182 }
183}
184
185// ---- Parser/Evaluator ----
186// Each function parses one grammar rule and returns evaluated result
187
188static TpValue parse_expr(void);
189
190// atom = INT | STR | True | False | None | NAME '(' args ')' | NAME | '(' expr ')'
191static TpValue parse_atom(void)
192{
193 if (g_tok == TOK_INT) {
194 TpValue v = tp_int(g_tok_ival);
195 next_token();
196 return v;
197 }
198 if (g_tok == TOK_STR) {
199 TpValue v = tp_str(tp_str_intern(g_tok_sval));
200 next_token();
201 return v;
202 }
203 if (g_tok == TOK_TRUE) { next_token(); return tp_bool(1); }
204 if (g_tok == TOK_FALSE) { next_token(); return tp_bool(0); }
205 if (g_tok == TOK_NONE) { next_token(); return tp_none(); }
206
207 if (g_tok == TOK_NAME) {
208 char name[64];
209 strncpy(name, g_tok_sval, 63);
210 name[63] = '\0';
211 next_token();
212
213 // Dot notation: module.func(args)
214 if (g_tok == TOK_DOT) {
215 next_token();
216 if (g_tok != TOK_NAME) { return tp_none(); }
217 char func_name[64];
218 strncpy(func_name, g_tok_sval, 63);
219 func_name[63] = '\0';
220 next_token();
221
222 // Parse arguments
223 TpValue args[TP_MAX_PARAMS];
224 int argc = 0;
225 if (g_tok == TOK_LPAREN) {
226 next_token();
227 while (g_tok != TOK_RPAREN && g_tok != TOK_EOF) {
228 if (argc >= TP_MAX_PARAMS) break;
229 args[argc++] = parse_expr();
230 if (g_tok == TOK_COMMA) next_token();
231 }
232 if (g_tok == TOK_RPAREN) next_token();
233 }
234
235 // Look up module and function
236 const TpModule *mod = tp_module_find(name);
237 if (mod) {
238 TpCFunc fn = tp_module_find_func(mod, func_name);
239 if (fn) return fn(args, argc);
240 tp_error("AttributeError", "module has no such function");
241 return tp_none();
242 }
243 tp_error_name(name);
244 return tp_none();
245 }
246
247 // Function call: name(args)
248 if (g_tok == TOK_LPAREN) {
249 next_token();
250 TpValue args[TP_MAX_PARAMS];
251 int argc = 0;
252 while (g_tok != TOK_RPAREN && g_tok != TOK_EOF) {
253 if (argc >= TP_MAX_PARAMS) break;
254 args[argc++] = parse_expr();
255 if (g_tok == TOK_COMMA) next_token();
256 }
257 if (g_tok == TOK_RPAREN) next_token();
258
259 // ---- Built-in functions (always available) ----
260 if (strcmp(name, "print") == 0) {
261 for (int i = 0; i < argc; i++) {
262 if (i > 0) Serial.print(' ');
263 switch (args[i].type) {
264 case TP_INT: Serial.print(args[i].ival); break;
265 case TP_BOOL: Serial.print(args[i].ival ? "True" : "False"); break;
266 case TP_STR: Serial.print(args[i].sval); break;
267 case TP_NONE: Serial.print("None"); break;
268 default: break;
269 }
270 }
271 Serial.println();
272 return tp_none();
273 }
274 if (strcmp(name, "type") == 0 && argc == 1) {
275 switch (args[0].type) {
276 case TP_INT: return tp_str(tp_str_intern("<int>"));
277 case TP_BOOL: return tp_str(tp_str_intern("<bool>"));
278 case TP_STR: return tp_str(tp_str_intern("<str>"));
279 case TP_NONE: return tp_str(tp_str_intern("<NoneType>"));
280 case TP_FUNC: return tp_str(tp_str_intern("<function>"));
281 }
282 }
283 if (strcmp(name, "int") == 0 && argc == 1) {
284 if (args[0].type == TP_STR) return tp_int(atoi(args[0].sval));
285 if (args[0].type == TP_BOOL) return tp_int(args[0].ival);
286 return args[0];
287 }
288 if (strcmp(name, "str") == 0 && argc == 1) {
289 char buf[32];
290 if (args[0].type == TP_INT) {
291 snprintf(buf, sizeof(buf), "%ld", (long)args[0].ival);
292 return tp_str(tp_str_intern(buf));
293 }
294 return args[0];
295 }
296 if (strcmp(name, "len") == 0 && argc == 1 && args[0].type == TP_STR) {
297 return tp_int(strlen(args[0].sval));
298 }
299 if (strcmp(name, "abs") == 0 && argc == 1) {
300 return tp_int(args[0].ival < 0 ? -args[0].ival : args[0].ival);
301 }
302 if (strcmp(name, "range") == 0) {
303 return tp_int(argc > 0 ? args[0].ival : 0);
304 }
305 if (strcmp(name, "min") == 0 && argc == 2) {
306 return tp_int(args[0].ival < args[1].ival ? args[0].ival : args[1].ival);
307 }
308 if (strcmp(name, "max") == 0 && argc == 2) {
309 return tp_int(args[0].ival > args[1].ival ? args[0].ival : args[1].ival);
310 }
311
312 // Registered global builtins (from modules)
313 TpCFunc builtin = tp_builtin_find(name);
314 if (builtin) return builtin(args, argc);
315
316 // User-defined function
317 TpFunc *fn = tp_func_find(name);
318 if (fn) {
319 if (tp_env_push_scope() < 0) return tp_recursion_error();
320 for (int i = 0; i < fn->param_count && i < argc; i++) {
321 tp_env_set(fn->params[i], args[i]);
322 }
323 tp_return_flag = 0;
324 TpValue result = tp_eval_lines(fn->body_start, fn->body_count, 0);
325 if (tp_return_flag) {
326 result = tp_return_value;
327 tp_return_flag = 0;
328 }
329 tp_env_pop_scope();
330 return result;
331 }
332
333 tp_error_name(name);
334 return tp_none();
335 }
336
337 // Variable access
338 if (!tp_env_exists(name)) {
339 tp_error_name(name);
340 return tp_none();
341 }
342 return tp_env_get(name);
343 }
344
345 if (g_tok == TOK_LPAREN) {
346 next_token();
347 TpValue v = parse_expr();
348 if (g_tok == TOK_RPAREN) next_token();
349 return v;
350 }
351
352 next_token(); // skip unknown
353 return tp_none();
354}
355
356// unary = '-' unary | 'not' not_expr | atom
357static TpValue parse_unary(void)
358{
359 if (g_tok == TOK_MINUS) {
360 next_token();
361 TpValue v = parse_unary();
362 return tp_int(-v.ival);
363 }
364 if (g_tok == TOK_NOT) {
365 next_token();
366 TpValue v = parse_unary();
367 return tp_bool(!tp_is_truthy(v));
368 }
369 return parse_atom();
370}
371
372// mul_expr = unary (('*' | '/' | '%') unary)*
373static TpValue parse_mul(void)
374{
375 TpValue left = parse_unary();
376 while (g_tok == TOK_STAR || g_tok == TOK_SLASH || g_tok == TOK_PERCENT) {
377 TokenType op = g_tok;
378 next_token();
379 TpValue right = parse_unary();
380 if (op == TOK_STAR) left = tp_int(left.ival * right.ival);
381 if (op == TOK_SLASH) {
382 if (right.ival == 0) { tp_error("ZeroDivisionError", "division by zero"); return tp_none(); }
383 left = tp_int(left.ival / right.ival);
384 }
385 if (op == TOK_PERCENT) {
386 if (right.ival == 0) { tp_error("ZeroDivisionError", "integer modulo by zero"); return tp_none(); }
387 left = tp_int(left.ival % right.ival);
388 }
389 }
390 return left;
391}
392
393// add_expr = mul_expr (('+' | '-') mul_expr)*
394static TpValue parse_add(void)
395{
396 TpValue left = parse_mul();
397 while (g_tok == TOK_PLUS || g_tok == TOK_MINUS) {
398 TokenType op = g_tok;
399 next_token();
400 TpValue right = parse_mul();
401 if (op == TOK_PLUS) {
402 if (left.type == TP_STR && right.type == TP_STR) {
403 char buf[64];
404 snprintf(buf, sizeof(buf), "%s%s", left.sval, right.sval);
405 left = tp_str(tp_str_intern(buf));
406 } else if (left.type == TP_STR || right.type == TP_STR) {
407 tp_error("TypeError", "can only concatenate str to str");
408 return tp_none();
409 } else {
410 left = tp_int(left.ival + right.ival);
411 }
412 }
413 if (op == TOK_MINUS) {
414 if (left.type == TP_STR || right.type == TP_STR) {
415 tp_error("TypeError", "unsupported operand type(s) for -");
416 return tp_none();
417 }
418 left = tp_int(left.ival - right.ival);
419 }
420 }
421 return left;
422}
423
424// comparison = add_expr (('==' | '!=' | '<' | '>' | '<=' | '>=') add_expr)*
425static TpValue parse_comparison(void)
426{
427 TpValue left = parse_add();
428 while (g_tok == TOK_EQEQ || g_tok == TOK_NEQ ||
429 g_tok == TOK_LT || g_tok == TOK_GT ||
430 g_tok == TOK_LE || g_tok == TOK_GE) {
431 TokenType op = g_tok;
432 next_token();
433 TpValue right = parse_add();
434 int result = 0;
435 if (left.type == TP_STR && right.type == TP_STR) {
436 int cmp = strcmp(left.sval, right.sval);
437 switch (op) {
438 case TOK_EQEQ: result = (cmp == 0); break;
439 case TOK_NEQ: result = (cmp != 0); break;
440 case TOK_LT: result = (cmp < 0); break;
441 case TOK_GT: result = (cmp > 0); break;
442 case TOK_LE: result = (cmp <= 0); break;
443 case TOK_GE: result = (cmp >= 0); break;
444 default: break;
445 }
446 } else {
447 switch (op) {
448 case TOK_EQEQ: result = (left.ival == right.ival); break;
449 case TOK_NEQ: result = (left.ival != right.ival); break;
450 case TOK_LT: result = (left.ival < right.ival); break;
451 case TOK_GT: result = (left.ival > right.ival); break;
452 case TOK_LE: result = (left.ival <= right.ival); break;
453 case TOK_GE: result = (left.ival >= right.ival); break;
454 default: break;
455 }
456 }
457 left = tp_bool(result);
458 }
459 return left;
460}
461
462// and_expr = comparison ('and' comparison)*
463static TpValue parse_and(void)
464{
465 TpValue left = parse_comparison();
466 while (g_tok == TOK_AND) {
467 next_token();
468 TpValue right = parse_comparison();
469 left = tp_bool(tp_is_truthy(left) && tp_is_truthy(right));
470 }
471 return left;
472}
473
474// or_expr = and_expr ('or' and_expr)*
475static TpValue parse_expr(void)
476{
477 TpValue left = parse_and();
478 while (g_tok == TOK_OR) {
479 next_token();
480 TpValue right = parse_and();
481 left = tp_bool(tp_is_truthy(left) || tp_is_truthy(right));
482 }
483 return left;
484}
485
486// ---- Statement evaluation ----
487
488void tp_eval_init(void)
489{
490 tp_return_flag = 0;
491}
492
493// Count leading spaces (for indentation)
494static int count_indent(const char *line)
495{
496 int n = 0;
497 while (line[n] == ' ') n++;
498 return n;
499}
500
501// Execute a block of stored lines
502TpValue tp_eval_lines(int block_start, int block_count, int base_indent)
503{
504 TpValue result = tp_none();
505 int i = 0;
506
507 while (i < block_count) {
508 if (tp_return_flag || g_error_flag) return result;
509
510 const char *line = tp_block_get_line(block_start + i);
511 int indent = count_indent(line);
512 const char *trimmed = line + indent;
513
514 if (*trimmed == '\0' || *trimmed == '#') { i++; continue; }
515
516 // Check for if/while/for/def that have sub-blocks
517 g_src = trimmed;
518 g_tok = TOK_EOF;
519 next_token();
520
521 if (g_tok == TOK_IF) {
522 next_token();
523 TpValue cond = parse_expr();
524 // skip colon
525 // Find body lines (indented more than current)
526 int body_start = block_start + i + 1;
527 int body_count = 0;
528 while (i + 1 + body_count < block_count) {
529 const char *bl = tp_block_get_line(body_start + body_count);
530 if (count_indent(bl) <= indent && bl[count_indent(bl)] != '\0') break;
531 body_count++;
532 }
533
534 if (tp_is_truthy(cond)) {
535 result = tp_eval_lines(body_start, body_count, indent + 2);
536 } else {
537 // Check for elif/else
538 i += 1 + body_count;
539 while (i < block_count) {
540 const char *nextl = tp_block_get_line(block_start + i);
541 int nindent = count_indent(nextl);
542 if (nindent != indent) break;
543 const char *nt = nextl + nindent;
544
545 // Check elif
546 if (strncmp(nt, "elif ", 5) == 0) {
547 g_src = nt + 5;
548 g_tok = TOK_EOF;
549 next_token();
550 cond = parse_expr();
551
552 int elif_body_start = block_start + i + 1;
553 int elif_body_count = 0;
554 while (i + 1 + elif_body_count < block_count) {
555 const char *bl = tp_block_get_line(elif_body_start + elif_body_count);
556 if (count_indent(bl) <= indent && bl[count_indent(bl)] != '\0') break;
557 elif_body_count++;
558 }
559 if (tp_is_truthy(cond)) {
560 result = tp_eval_lines(elif_body_start, elif_body_count, indent + 2);
561 i += 1 + elif_body_count;
562 break;
563 }
564 i += 1 + elif_body_count;
565 }
566 // Check else
567 else if (strncmp(nt, "else:", 5) == 0 || strncmp(nt, "else :", 6) == 0) {
568 int else_body_start = block_start + i + 1;
569 int else_body_count = 0;
570 while (i + 1 + else_body_count < block_count) {
571 const char *bl = tp_block_get_line(else_body_start + else_body_count);
572 if (count_indent(bl) <= indent && bl[count_indent(bl)] != '\0') break;
573 else_body_count++;
574 }
575 result = tp_eval_lines(else_body_start, else_body_count, indent + 2);
576 i += 1 + else_body_count;
577 break;
578 } else {
579 break;
580 }
581 }
582 continue;
583 }
584 i += 1 + body_count;
585 continue;
586 }
587
588 if (g_tok == TOK_WHILE) {
589 next_token();
590 const char *cond_src = g_src - strlen(g_tok_sval); // reparse each iteration
591 // We need to save the condition text
592 char cond_text[TP_LINE_LEN];
593 strncpy(cond_text, trimmed + 6, sizeof(cond_text) - 1); // skip "while "
594 // Remove trailing ':'
595 char *colon = strrchr(cond_text, ':');
596 if (colon) *colon = '\0';
597
598 int body_start_idx = block_start + i + 1;
599 int body_count = 0;
600 while (i + 1 + body_count < block_count) {
601 const char *bl = tp_block_get_line(body_start_idx + body_count);
602 if (count_indent(bl) <= indent && bl[count_indent(bl)] != '\0') break;
603 body_count++;
604 }
605
606 int iterations = 0;
607 while (iterations < 10000) {
608 g_src = cond_text;
609 g_tok = TOK_EOF;
610 next_token();
611 TpValue cond = parse_expr();
612 if (!tp_is_truthy(cond)) break;
613 tp_eval_lines(body_start_idx, body_count, indent + 2);
614 if (tp_return_flag || g_error_flag) return result;
615 iterations++;
616 }
617 i += 1 + body_count;
618 continue;
619 }
620
621 if (g_tok == TOK_FOR) {
622 next_token();
623 if (g_tok != TOK_NAME) { i++; continue; }
624 char var_name[16];
625 strncpy(var_name, g_tok_sval, 15);
626 var_name[15] = '\0';
627 next_token(); // expect 'in'
628 if (g_tok != TOK_IN) {
629 tp_error("SyntaxError", "expected 'in' after variable in for");
630 i++;
631 continue;
632 }
633 next_token();
634
635 // Parse range(...)
636 int range_start = 0, range_end = 0, range_step = 1;
637 if (g_tok == TOK_NAME && strcmp(g_tok_sval, "range") == 0) {
638 next_token(); // skip 'range'
639 if (g_tok == TOK_LPAREN) next_token();
640 TpValue a = parse_expr();
641 if (g_tok == TOK_COMMA) {
642 next_token();
643 TpValue b = parse_expr();
644 range_start = a.ival;
645 range_end = b.ival;
646 if (g_tok == TOK_COMMA) {
647 next_token();
648 TpValue c = parse_expr();
649 range_step = c.ival;
650 }
651 } else {
652 range_end = a.ival;
653 }
654 if (g_tok == TOK_RPAREN) next_token();
655 } else {
656 tp_error("TypeError", "for loop only supports range() iterable");
657 }
658
659 int body_start_idx = block_start + i + 1;
660 int body_count = 0;
661 while (i + 1 + body_count < block_count) {
662 const char *bl = tp_block_get_line(body_start_idx + body_count);
663 if (count_indent(bl) <= indent && bl[count_indent(bl)] != '\0') break;
664 body_count++;
665 }
666
667 if (range_step > 0) {
668 for (int32_t j = range_start; j < range_end; j += range_step) {
669 tp_env_set(var_name, tp_int(j));
670 tp_eval_lines(body_start_idx, body_count, indent + 2);
671 if (tp_return_flag || g_error_flag) return result;
672 }
673 } else if (range_step < 0) {
674 for (int32_t j = range_start; j > range_end; j += range_step) {
675 tp_env_set(var_name, tp_int(j));
676 tp_eval_lines(body_start_idx, body_count, indent + 2);
677 if (tp_return_flag || g_error_flag) return result;
678 }
679 }
680 i += 1 + body_count;
681 continue;
682 }
683
684 // Regular single-line statement
685 result = tp_eval_line(trimmed);
686 i++;
687 }
688 return result;
689}
690
691// Execute a single line
692TpValue tp_eval_line(const char *line)
693{
694 // Skip empty / comment
695 while (*line == ' ' || *line == '\t') line++;
696 if (*line == '\0' || *line == '#') return tp_none();
697
698 g_src = line;
699 g_tok = TOK_EOF;
700 next_token();
701
702 // import statement: import module_name
703 if (g_tok == TOK_IMPORT) {
704 next_token();
705 if (g_tok == TOK_NAME) {
706 const TpModule *mod = tp_module_find(g_tok_sval);
707 if (mod) {
708 Serial.print("import ");
709 Serial.println(mod->name);
710 } else {
711 tp_error("ModuleNotFoundError", g_tok_sval);
712 }
713 }
714 return tp_none();
715 }
716
717 // return statement
718 if (g_tok == TOK_RETURN) {
719 next_token();
720 if (g_tok != TOK_EOF) {
721 tp_return_value = parse_expr();
722 } else {
723 tp_return_value = tp_none();
724 }
725 tp_return_flag = 1;
726 return tp_return_value;
727 }
728
729 // Assignment: NAME = expr / NAME += expr / NAME -= expr
730 if (g_tok == TOK_NAME) {
731 char name[64];
732 strncpy(name, g_tok_sval, 63);
733 name[63] = '\0';
734 const char *save = g_src;
735 TokenType save_tok = g_tok;
736 next_token();
737
738 if (g_tok == TOK_EQ) {
739 next_token();
740 TpValue val = parse_expr();
741 tp_env_set(name, val);
742 return val;
743 }
744 if (g_tok == TOK_PLUSEQ) {
745 next_token();
746 TpValue right = parse_expr();
747 TpValue left = tp_env_get(name);
748 TpValue val = tp_int(left.ival + right.ival);
749 tp_env_set(name, val);
750 return val;
751 }
752 if (g_tok == TOK_MINUSEQ) {
753 next_token();
754 TpValue right = parse_expr();
755 TpValue left = tp_env_get(name);
756 TpValue val = tp_int(left.ival - right.ival);
757 tp_env_set(name, val);
758 return val;
759 }
760
761 // Not an assignment - rewind and parse as expression
762 g_src = save;
763 g_tok = save_tok;
764 // Re-parse from beginning
765 g_src = line;
766 while (*g_src == ' ' || *g_src == '\t') g_src++;
767 g_tok = TOK_EOF;
768 next_token();
769 }
770
771 // Expression statement
772 TpValue result = parse_expr();
773 return result;
774}
src/tp_mod_gpio.cpp — 77 行
1/**
2 * tp_mod_gpio.cpp - GPIO Module
3 *
4 * Usage:
5 * >>> import gpio
6 * >>> gpio.pin_mode(13, 1)
7 * >>> gpio.write(13, 1)
8 * >>> gpio.read(7)
9 * >>> gpio.analog(0)
10 *
11 * Also registers global aliases: pin_mode, digital_write, etc.
12 */
13
14#include "tp_module.h"
15#include <Arduino.h>
16
17static TpValue fn_pin_mode(TpValue *args, int argc)
18{
19 if (argc >= 2) pinMode(args[0].ival, args[1].ival);
20 return tp_none();
21}
22
23static TpValue fn_write(TpValue *args, int argc)
24{
25 if (argc >= 2) digitalWrite(args[0].ival, args[1].ival);
26 return tp_none();
27}
28
29static TpValue fn_read(TpValue *args, int argc)
30{
31 if (argc >= 1) return tp_int(digitalRead(args[0].ival));
32 return tp_int(0);
33}
34
35static TpValue fn_analog(TpValue *args, int argc)
36{
37 if (argc >= 1) return tp_int(analogRead(args[0].ival));
38 return tp_int(0);
39}
40
41static TpValue fn_delay(TpValue *args, int argc)
42{
43 if (argc >= 1) delay(args[0].ival);
44 return tp_none();
45}
46
47static TpValue fn_millis(TpValue *args, int argc)
48{
49 (void)args; (void)argc;
50 return tp_int((int32_t)millis());
51}
52
53static const TpCFuncEntry gpio_funcs[] = {
54 {"pin_mode", fn_pin_mode},
55 {"write", fn_write},
56 {"read", fn_read},
57 {"analog", fn_analog},
58};
59
60static const TpModule gpio_module = {
61 "gpio",
62 gpio_funcs,
63 sizeof(gpio_funcs) / sizeof(gpio_funcs[0]),
64};
65
66void tp_mod_gpio_register(void)
67{
68 tp_module_register(&gpio_module);
69
70 // Also register global aliases (available without import)
71 tp_builtin_register("pin_mode", fn_pin_mode);
72 tp_builtin_register("digital_write", fn_write);
73 tp_builtin_register("digital_read", fn_read);
74 tp_builtin_register("analog_read", fn_analog);
75 tp_builtin_register("delay", fn_delay);
76 tp_builtin_register("millis", fn_millis);
77}
src/tp_mod_led.cpp — 59 行
1/**
2 * tp_mod_led.cpp - LED Module
3 *
4 * Usage:
5 * >>> import led
6 * >>> led.blink(3)
7 * >>> led.on()
8 * >>> led.off()
9 */
10
11#include "tp_module.h"
12#include <Arduino.h>
13
14static TpValue fn_blink(TpValue *args, int argc)
15{
16 int n = (argc >= 1) ? args[0].ival : 1;
17 int ms = (argc >= 2) ? args[1].ival : 200;
18 pinMode(LED_BUILTIN, OUTPUT);
19 for (int i = 0; i < n; i++) {
20 digitalWrite(LED_BUILTIN, HIGH);
21 delay(ms);
22 digitalWrite(LED_BUILTIN, LOW);
23 delay(ms);
24 }
25 return tp_none();
26}
27
28static TpValue fn_on(TpValue *args, int argc)
29{
30 (void)args; (void)argc;
31 pinMode(LED_BUILTIN, OUTPUT);
32 digitalWrite(LED_BUILTIN, HIGH);
33 return tp_none();
34}
35
36static TpValue fn_off(TpValue *args, int argc)
37{
38 (void)args; (void)argc;
39 pinMode(LED_BUILTIN, OUTPUT);
40 digitalWrite(LED_BUILTIN, LOW);
41 return tp_none();
42}
43
44static const TpCFuncEntry led_funcs[] = {
45 {"blink", fn_blink},
46 {"on", fn_on},
47 {"off", fn_off},
48};
49
50static const TpModule led_module = {
51 "led",
52 led_funcs,
53 sizeof(led_funcs) / sizeof(led_funcs[0]),
54};
55
56void tp_mod_led_register(void)
57{
58 tp_module_register(&led_module);
59}
src/tp_mod_matrix.cpp — 88 行
1/**
2 * tp_mod_matrix.cpp - LED Matrix Module
3 *
4 * Controls the built-in 12x8 LED matrix on Arduino Uno R4 WiFi.
5 *
6 * Usage:
7 * >>> import matrix
8 * >>> matrix.clear()
9 * >>> matrix.pixel(3, 5)
10 * >>> matrix.show()
11 * >>> matrix.fill(1)
12 */
13
14#include "tp_module.h"
15#include <Arduino.h>
16#include <string.h>
17
18// Defined in main.cpp
19extern uint8_t g_matrix_frame[8][12];
20extern "C" void matrix_render(void);
21
22static TpValue fn_clear(TpValue *args, int argc)
23{
24 (void)args; (void)argc;
25 memset(g_matrix_frame, 0, sizeof(g_matrix_frame));
26 matrix_render();
27 return tp_none();
28}
29
30static TpValue fn_pixel(TpValue *args, int argc)
31{
32 if (argc < 2) return tp_none();
33 int row = args[0].ival;
34 int col = args[1].ival;
35 int val = (argc >= 3) ? args[2].ival : 1;
36 if (row >= 0 && row < 8 && col >= 0 && col < 12) {
37 g_matrix_frame[row][col] = val ? 1 : 0;
38 }
39 return tp_none();
40}
41
42static TpValue fn_show(TpValue *args, int argc)
43{
44 (void)args; (void)argc;
45 matrix_render();
46 return tp_none();
47}
48
49static TpValue fn_fill(TpValue *args, int argc)
50{
51 uint8_t val = (argc >= 1 && args[0].ival) ? 1 : 0;
52 memset(g_matrix_frame, val, sizeof(g_matrix_frame));
53 matrix_render();
54 return tp_none();
55}
56
57static TpValue fn_row(TpValue *args, int argc)
58{
59 if (argc < 2) return tp_none();
60 int row = args[0].ival;
61 int pattern = args[1].ival;
62 if (row >= 0 && row < 8) {
63 for (int c = 0; c < 12; c++) {
64 g_matrix_frame[row][11 - c] = (pattern >> c) & 1;
65 }
66 matrix_render();
67 }
68 return tp_none();
69}
70
71static const TpCFuncEntry matrix_funcs[] = {
72 {"clear", fn_clear},
73 {"pixel", fn_pixel},
74 {"show", fn_show},
75 {"fill", fn_fill},
76 {"row", fn_row},
77};
78
79static const TpModule matrix_module = {
80 "matrix",
81 matrix_funcs,
82 sizeof(matrix_funcs) / sizeof(matrix_funcs[0]),
83};
84
85void tp_mod_matrix_register(void)
86{
87 tp_module_register(&matrix_module);
88}
src/tp_module.cpp — 72 行
1/**
2 * tp_module.cpp - Module Registry
3 *
4 * Manages the registry of built-in modules and global builtins.
5 */
6
7#include "tp_module.h"
8#include <string.h>
9
10// Module registry
11static const TpModule *g_modules[TP_MAX_MODULES];
12static int g_module_count = 0;
13
14// Global builtins (print, type, len, etc. - no import needed)
15#define TP_MAX_BUILTINS 24
16static struct {
17 const char *name;
18 TpCFunc func;
19} g_builtins[TP_MAX_BUILTINS];
20static int g_builtin_count = 0;
21
22void tp_module_init(void)
23{
24 g_module_count = 0;
25 g_builtin_count = 0;
26}
27
28void tp_module_register(const TpModule *mod)
29{
30 if (g_module_count < TP_MAX_MODULES) {
31 g_modules[g_module_count++] = mod;
32 }
33}
34
35const TpModule *tp_module_find(const char *name)
36{
37 for (int i = 0; i < g_module_count; i++) {
38 if (strcmp(g_modules[i]->name, name) == 0) {
39 return g_modules[i];
40 }
41 }
42 return NULL;
43}
44
45TpCFunc tp_module_find_func(const TpModule *mod, const char *func_name)
46{
47 for (int i = 0; i < mod->func_count; i++) {
48 if (strcmp(mod->funcs[i].name, func_name) == 0) {
49 return mod->funcs[i].func;
50 }
51 }
52 return NULL;
53}
54
55void tp_builtin_register(const char *name, TpCFunc func)
56{
57 if (g_builtin_count < TP_MAX_BUILTINS) {
58 g_builtins[g_builtin_count].name = name;
59 g_builtins[g_builtin_count].func = func;
60 g_builtin_count++;
61 }
62}
63
64TpCFunc tp_builtin_find(const char *name)
65{
66 for (int i = 0; i < g_builtin_count; i++) {
67 if (strcmp(g_builtins[i].name, name) == 0) {
68 return g_builtins[i].func;
69 }
70 }
71 return NULL;
72}
実機での出力#
Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。
REPL に print(1 + 2) と再帰関数を打ち込んだところです。TP_MAX_SCOPES(8)を超える深さの再帰は RecursionError になります。
ボード上の REPL に関数を定義して呼ぶところ。fact(7) は 5040 を返し、
fact(30) は RecursionError で止まります#
print(1 + 2)
3
>>> def fact(n):
... if n < 2:
... return 1
... return n * fact(n - 1)
...
>>> print(fact(7))
5040
>>> print(fact(30))
RecursionError: maximum recursion depth exceeded
0
>>> print(42)
42
>>>
実機ではこう光ります。
led.on() / led.off() で L LED が点いて消え、matrix.fill(1) でマトリクスの 96 個が全部点き、matrix.clear() で消えます。(UNO R4 WiFi を Web カメラで撮影)#
シミュレータで見る#
実機が無くても、同じファームウェアを シミュレータ で動かすとこう見えます。
TinyPython の REPL で led.on() を打つと L が点き、matrix.fill(1) でマトリクスが全点灯、led.off() で L が消えます。(シミュレータで撮影)#
つまずきやすいポイント#
(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)