第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

コードを見る: docs/os-on-arduino/code/11_tiny_python

ソースコード#

この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。

src/main.cpp — 262 行

GitHub で開く

  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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

  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 行

GitHub で開く

  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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 になります。

../_images/ch11_repl.gif

ボード上の 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
>>> 

実機ではこう光ります。

../_images/ch11_led_matrix.gif

led.on() / led.off() で L LED が点いて消え、matrix.fill(1) でマトリクスの 96 個が全部点き、matrix.clear() で消えます。(UNO R4 WiFi を Web カメラで撮影)#

シミュレータで見る#

実機が無くても、同じファームウェアを シミュレータ で動かすとこう見えます。

../_images/sim_ch11_repl.gif

TinyPython の REPL で led.on() を打つと L が点き、matrix.fill(1) でマトリクスが全点灯、led.off() で L が消えます。(シミュレータで撮影)#

つまずきやすいポイント#

(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)