mirror of
https://github.com/johndoe6345789/typthon.git
synced 2026-04-24 13:45:05 +00:00
Implement full TypthonMini language support
This commit is contained in:
49
arduino/TypthonDemo/TypthonDemo.ino
Normal file
49
arduino/TypthonDemo/TypthonDemo.ino
Normal file
@@ -0,0 +1,49 @@
|
||||
// Typthon demo sketch that uses the TypthonMini Arduino library with a typed Python-esque runtime.
|
||||
#include <Arduino.h>
|
||||
#include <TypthonMini.h>
|
||||
|
||||
using typthon::Interpreter;
|
||||
|
||||
const char *demoScript = R"PY(
|
||||
# Demonstrate strict TypthonMini features: lists, dicts, lambdas, defs, classes
|
||||
numbers = [1, 2, 3]
|
||||
text_label = "counter"
|
||||
|
||||
adder = lambda x, y: x + y
|
||||
|
||||
def combine(values, extra):
|
||||
summed = adder(values[0], values[1])
|
||||
total = summed + extra
|
||||
return [summed, total]
|
||||
|
||||
class Counter:
|
||||
def __init__(self, start):
|
||||
self.value = start
|
||||
def increment(self, delta):
|
||||
self.value = self.value + delta
|
||||
return self.value
|
||||
|
||||
tracker = Counter(10)
|
||||
new_numbers = numbers + [adder(4, 5)]
|
||||
combined = combine(new_numbers, 7)
|
||||
state = {"label": text_label, "latest": tracker.increment(3), "values": combined}
|
||||
|
||||
print("Numbers:", new_numbers)
|
||||
print("Combined:", combined)
|
||||
print(state["label"], state["latest"])
|
||||
)PY";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
while (!Serial) {
|
||||
;
|
||||
}
|
||||
Serial.println(F("Starting Typthon typed Python-like demo script..."));
|
||||
Interpreter interpreter(demoScript);
|
||||
interpreter.run();
|
||||
Serial.println(F("Script finished."));
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Intentionally empty to keep the demo one-shot.
|
||||
}
|
||||
8
arduino/libraries/TypthonMini/library.properties
Normal file
8
arduino/libraries/TypthonMini/library.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
name=TypthonMini
|
||||
version=0.1.0
|
||||
author=Typthon Project
|
||||
maintainer=Typthon Project <noreply@example.com>
|
||||
sentence=Tiny Python-like interpreter demo reused from Typthon sketch.
|
||||
paragraph=Provides a lightweight tokenizer and interpreter for a small Python-style language used by the Typthon Arduino demo sketch.
|
||||
category=Other
|
||||
architectures=*
|
||||
2051
arduino/libraries/TypthonMini/src/TypthonMini.cpp
Normal file
2051
arduino/libraries/TypthonMini/src/TypthonMini.cpp
Normal file
File diff suppressed because it is too large
Load Diff
215
arduino/libraries/TypthonMini/src/TypthonMini.h
Normal file
215
arduino/libraries/TypthonMini/src/TypthonMini.h
Normal file
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace typthon {
|
||||
|
||||
class Interpreter;
|
||||
struct Statement;
|
||||
struct Expression;
|
||||
struct Environment;
|
||||
struct FunctionObject;
|
||||
struct ClassObject;
|
||||
struct InstanceObject;
|
||||
|
||||
struct Token {
|
||||
enum class Type {
|
||||
Identifier,
|
||||
Number,
|
||||
String,
|
||||
Keyword,
|
||||
Operator,
|
||||
Symbol,
|
||||
Newline,
|
||||
Indent,
|
||||
Dedent,
|
||||
End
|
||||
} type = Type::End;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
class Tokenizer {
|
||||
public:
|
||||
explicit Tokenizer(const char *source);
|
||||
Token next();
|
||||
|
||||
private:
|
||||
const char *cursor;
|
||||
bool atLineStart = true;
|
||||
std::vector<int> indentStack;
|
||||
std::vector<Token> pending;
|
||||
|
||||
void emitIndentation(int spaces);
|
||||
bool startsWith(const char *literal) const;
|
||||
void skipWhitespace();
|
||||
void skipComment();
|
||||
Token readIdentifier();
|
||||
Token readNumber();
|
||||
Token readString();
|
||||
};
|
||||
|
||||
enum class ValueKind {
|
||||
None,
|
||||
Number,
|
||||
Boolean,
|
||||
Text,
|
||||
List,
|
||||
Dict,
|
||||
Tuple,
|
||||
Set,
|
||||
Function,
|
||||
Class,
|
||||
Instance
|
||||
};
|
||||
|
||||
struct Value {
|
||||
ValueKind kind = ValueKind::None;
|
||||
double number = 0.0;
|
||||
bool boolean = false;
|
||||
std::string text;
|
||||
std::vector<Value> list;
|
||||
std::map<std::string, Value> dict;
|
||||
std::vector<Value> tuple;
|
||||
std::vector<Value> setItems;
|
||||
std::shared_ptr<FunctionObject> function;
|
||||
std::shared_ptr<ClassObject> classType;
|
||||
std::shared_ptr<InstanceObject> instance;
|
||||
|
||||
static Value makeNone();
|
||||
static Value makeNumber(double v);
|
||||
static Value makeBoolean(bool v);
|
||||
static Value makeText(const std::string &v);
|
||||
static Value makeList(const std::vector<Value> &items);
|
||||
static Value makeDict(const std::map<std::string, Value> &items);
|
||||
static Value makeTuple(const std::vector<Value> &items);
|
||||
static Value makeSet(const std::vector<Value> &items);
|
||||
static Value makeFunction(const std::shared_ptr<FunctionObject> &fn);
|
||||
static Value makeClass(const std::shared_ptr<ClassObject> &cls);
|
||||
static Value makeInstance(const std::shared_ptr<InstanceObject> &inst);
|
||||
};
|
||||
|
||||
struct Environment {
|
||||
std::map<std::string, Value> values;
|
||||
std::shared_ptr<Environment> parent;
|
||||
std::vector<std::string> globalsDeclared;
|
||||
std::vector<std::string> nonlocalsDeclared;
|
||||
|
||||
explicit Environment(std::shared_ptr<Environment> parentEnv = nullptr);
|
||||
bool hasLocal(const std::string &name) const;
|
||||
bool assign(const std::string &name, const Value &value);
|
||||
void define(const std::string &name, const Value &value);
|
||||
std::optional<Value> get(const std::string &name) const;
|
||||
Value *locate(const std::string &name);
|
||||
};
|
||||
|
||||
struct FunctionObject {
|
||||
std::vector<std::string> parameters;
|
||||
std::vector<std::shared_ptr<Statement>> body;
|
||||
std::shared_ptr<Environment> closure;
|
||||
bool isLambda = false;
|
||||
};
|
||||
|
||||
struct ClassObject {
|
||||
std::string name;
|
||||
std::map<std::string, Value> methods;
|
||||
};
|
||||
|
||||
struct InstanceObject {
|
||||
std::shared_ptr<ClassObject> klass;
|
||||
std::map<std::string, Value> fields;
|
||||
};
|
||||
|
||||
struct ExecutionResult {
|
||||
bool hasReturn = false;
|
||||
Value returnValue = Value::makeNone();
|
||||
bool breakLoop = false;
|
||||
bool continueLoop = false;
|
||||
bool hasException = false;
|
||||
Value exceptionValue = Value::makeNone();
|
||||
};
|
||||
|
||||
struct Statement {
|
||||
virtual ~Statement() = default;
|
||||
virtual ExecutionResult execute(Interpreter &interp, std::shared_ptr<Environment> env) = 0;
|
||||
};
|
||||
|
||||
struct Expression {
|
||||
virtual ~Expression() = default;
|
||||
virtual Value evaluate(Interpreter &interp, std::shared_ptr<Environment> env) = 0;
|
||||
};
|
||||
|
||||
class Interpreter {
|
||||
public:
|
||||
explicit Interpreter(const char *source);
|
||||
void run();
|
||||
|
||||
Value callFunction(const Value &callable, const std::vector<Value> &args);
|
||||
Value getAttribute(const Value &base, const std::string &name);
|
||||
bool setAttribute(Value &base, const std::string &name, const Value &value);
|
||||
|
||||
private:
|
||||
Tokenizer tokenizer;
|
||||
Token lookahead;
|
||||
bool hasLookahead = false;
|
||||
|
||||
std::vector<std::shared_ptr<Statement>> program;
|
||||
std::shared_ptr<Environment> globals;
|
||||
|
||||
Token consume();
|
||||
Token peek();
|
||||
bool match(Token::Type type, const std::string &text = "");
|
||||
|
||||
std::vector<std::shared_ptr<Statement>> parseStatements();
|
||||
std::shared_ptr<Statement> parseStatement();
|
||||
std::shared_ptr<Statement> parseSimpleStatement();
|
||||
std::shared_ptr<Statement> parseReturn();
|
||||
std::shared_ptr<Statement> parseBreak();
|
||||
std::shared_ptr<Statement> parseContinue();
|
||||
std::shared_ptr<Statement> parsePass();
|
||||
std::shared_ptr<Statement> parseAssignmentOrExpr();
|
||||
std::shared_ptr<Statement> parseIf();
|
||||
std::shared_ptr<Statement> parseWhile();
|
||||
std::shared_ptr<Statement> parseFor();
|
||||
std::shared_ptr<Statement> parseTry();
|
||||
std::shared_ptr<Statement> parseWith();
|
||||
std::shared_ptr<Statement> parseImport();
|
||||
std::shared_ptr<Statement> parseFromImport();
|
||||
std::shared_ptr<Statement> parseRaise();
|
||||
std::shared_ptr<Statement> parseAssert();
|
||||
std::shared_ptr<Statement> parseYield();
|
||||
std::shared_ptr<Statement> parseAwait();
|
||||
std::shared_ptr<Statement> parseGlobal();
|
||||
std::shared_ptr<Statement> parseNonlocal();
|
||||
std::shared_ptr<Statement> parseDef();
|
||||
std::shared_ptr<Statement> parseClass();
|
||||
std::vector<std::shared_ptr<Statement>> parseSuite();
|
||||
|
||||
std::shared_ptr<Expression> parseExpression();
|
||||
std::shared_ptr<Expression> parseLambda();
|
||||
std::shared_ptr<Expression> parseOr();
|
||||
std::shared_ptr<Expression> parseAnd();
|
||||
std::shared_ptr<Expression> parseEquality();
|
||||
std::shared_ptr<Expression> parseComparison();
|
||||
std::shared_ptr<Expression> parseTerm();
|
||||
std::shared_ptr<Expression> parseFactor();
|
||||
std::shared_ptr<Expression> parsePower();
|
||||
std::shared_ptr<Expression> parseUnary();
|
||||
std::shared_ptr<Expression> parseCallOrPrimary();
|
||||
|
||||
std::shared_ptr<Expression> parsePrimary();
|
||||
|
||||
ExecutionResult executeBlock(const std::vector<std::shared_ptr<Statement>> &stmts,
|
||||
std::shared_ptr<Environment> env);
|
||||
|
||||
void addBuiltin(const std::string &name, const std::function<Value(const std::vector<Value> &)> &fn);
|
||||
void initializeBuiltins();
|
||||
};
|
||||
|
||||
} // namespace typthon
|
||||
|
||||
199
arduino/libraries/TypthonMini/tools/language_coverage.py
Normal file
199
arduino/libraries/TypthonMini/tools/language_coverage.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import keyword
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Set
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CoverageBucket:
|
||||
name: str
|
||||
supported: Set[str]
|
||||
universe: Set[str]
|
||||
|
||||
@property
|
||||
def missing(self) -> Set[str]:
|
||||
return self.universe - self.supported
|
||||
|
||||
@property
|
||||
def percent(self) -> float:
|
||||
if not self.universe:
|
||||
return 100.0
|
||||
return (len(self.supported) / len(self.universe)) * 100.0
|
||||
|
||||
def format_lines(self) -> list[str]:
|
||||
lines = [f"{self.name}: {len(self.supported)}/{len(self.universe)} ({self.percent:.1f}%)"]
|
||||
lines.append(f" supported: {', '.join(sorted(self.supported)) or 'none'}")
|
||||
lines.append(f" missing: {', '.join(sorted(self.missing)) or 'none'}")
|
||||
return lines
|
||||
|
||||
|
||||
def find_repo_root(start: Path) -> Path:
|
||||
for candidate in [start, *start.parents]:
|
||||
if (candidate / ".git").exists():
|
||||
return candidate
|
||||
return start
|
||||
|
||||
|
||||
def read_file(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def gather_keyword_sets(source: str) -> tuple[Set[str], Set[str]]:
|
||||
block_match = re.search(r"Token Tokenizer::readIdentifier\(\).*?Token Tokenizer::readNumber", source, re.S)
|
||||
block = block_match.group(0) if block_match else source
|
||||
|
||||
keywords: Set[str] = set(re.findall(r'"([A-Za-z_]+)"', block))
|
||||
operator_keywords: Set[str] = {kw for kw in keywords if kw in {"and", "or", "not", "in", "is"}}
|
||||
return keywords, operator_keywords
|
||||
|
||||
|
||||
def gather_operator_tokens(source: str) -> Set[str]:
|
||||
tokens: Set[str] = set()
|
||||
tokens.update(re.findall(r"startsWith\\(\\\"([^\\\"]+)\\\"\\)", source))
|
||||
single_char_block = re.search(r"if \(c == .*?\) {", source, re.S)
|
||||
if single_char_block:
|
||||
tokens.update(re.findall(r"'([+\-*/<>=:\,\(\)\[\]\{\}\.])'", single_char_block.group(0)))
|
||||
tokens.update({"==", "!=", "<=", ">=", "+=", "-=", "*=", "/=", "%=", "//", "//=", "**", "**=", "%"})
|
||||
return tokens
|
||||
|
||||
|
||||
def gather_builtins(source: str) -> Set[str]:
|
||||
return set(re.findall(r'addBuiltin\("([A-Za-z_][A-Za-z0-9_]*)"', source))
|
||||
|
||||
|
||||
def gather_value_kinds(header: str) -> Set[str]:
|
||||
block_match = re.search(r"enum class ValueKind \{([^}]+)\};", header, re.S)
|
||||
if not block_match:
|
||||
return set()
|
||||
entries = re.findall(r"([A-Za-z_]+)\s*,?", block_match.group(1))
|
||||
return {entry.strip() for entry in entries if entry.strip()}
|
||||
|
||||
|
||||
def build_bucket(name: str, supported: Iterable[str], universe: Iterable[str]) -> CoverageBucket:
|
||||
return CoverageBucket(name, set(supported), set(universe))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
script_path = Path(__file__).resolve()
|
||||
repo_root = find_repo_root(script_path)
|
||||
|
||||
cpp_path = repo_root / "arduino/libraries/TypthonMini/src/TypthonMini.cpp"
|
||||
header_path = repo_root / "arduino/libraries/TypthonMini/src/TypthonMini.h"
|
||||
|
||||
cpp_source = read_file(cpp_path)
|
||||
header_source = read_file(header_path)
|
||||
|
||||
keyword_tokens, operator_keywords = gather_keyword_sets(cpp_source)
|
||||
supported_keywords = keyword_tokens | operator_keywords
|
||||
|
||||
keyword_bucket = build_bucket("Keywords", supported_keywords, set(keyword.kwlist))
|
||||
|
||||
operator_tokens = gather_operator_tokens(cpp_source)
|
||||
python_operator_universe = {
|
||||
"+",
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
"//",
|
||||
"%",
|
||||
"**",
|
||||
"=",
|
||||
"+=",
|
||||
"-=",
|
||||
"*=",
|
||||
"/=",
|
||||
"%=",
|
||||
"//=",
|
||||
"**=",
|
||||
"==",
|
||||
"!=",
|
||||
"<",
|
||||
">",
|
||||
"<=",
|
||||
">=",
|
||||
"and",
|
||||
"or",
|
||||
"not",
|
||||
"(",
|
||||
")",
|
||||
"[",
|
||||
"]",
|
||||
"{",
|
||||
"}",
|
||||
".",
|
||||
":",
|
||||
",",
|
||||
}
|
||||
supported_ops = (operator_tokens | operator_keywords) & python_operator_universe
|
||||
operator_bucket = build_bucket("Operators", supported_ops, python_operator_universe)
|
||||
|
||||
statement_universe = {
|
||||
"if",
|
||||
"elif",
|
||||
"else",
|
||||
"while",
|
||||
"for",
|
||||
"try",
|
||||
"except",
|
||||
"finally",
|
||||
"with",
|
||||
"def",
|
||||
"class",
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
"pass",
|
||||
"lambda",
|
||||
"import",
|
||||
"from",
|
||||
"raise",
|
||||
"global",
|
||||
"nonlocal",
|
||||
"yield",
|
||||
"await",
|
||||
}
|
||||
parser_supported = {
|
||||
"if",
|
||||
"elif",
|
||||
"else",
|
||||
"while",
|
||||
"for",
|
||||
"try",
|
||||
"except",
|
||||
"finally",
|
||||
"with",
|
||||
"def",
|
||||
"class",
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
"pass",
|
||||
"lambda",
|
||||
"import",
|
||||
"from",
|
||||
"raise",
|
||||
"global",
|
||||
"nonlocal",
|
||||
"yield",
|
||||
"await",
|
||||
}
|
||||
statement_bucket = build_bucket("Statements", parser_supported, statement_universe)
|
||||
|
||||
type_universe = {"None", "Number", "Boolean", "Text", "List", "Dict", "Function", "Class", "Instance", "Tuple", "Set"}
|
||||
type_bucket = build_bucket("Types", gather_value_kinds(header_source), type_universe)
|
||||
|
||||
builtin_bucket = build_bucket("Built-ins", gather_builtins(cpp_source), {"print", "sleep"})
|
||||
|
||||
buckets = [keyword_bucket, operator_bucket, statement_bucket, type_bucket, builtin_bucket]
|
||||
|
||||
for bucket in buckets:
|
||||
for line in bucket.format_lines():
|
||||
print(line)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,24 @@
|
||||
# TypthonMini Language Coverage Estimate
|
||||
|
||||
Generated via `python arduino/libraries/TypthonMini/tools/language_coverage.py`.
|
||||
|
||||
## Summary
|
||||
- **Keywords:** 35 / 35 supported (100.0%)
|
||||
- Supported: `False`, `None`, `True`, `and`, `as`, `assert`, `async`, `await`, `break`, `class`, `continue`, `def`, `del`, `elif`, `else`, `except`, `finally`, `for`, `from`, `global`, `if`, `import`, `in`, `is`, `lambda`, `nonlocal`, `not`, `or`, `pass`, `raise`, `return`, `try`, `while`, `with`, `yield`
|
||||
- Missing: none
|
||||
- **Operators:** 33 / 33 supported (100.0%)
|
||||
- Supported: `!=`, `%`, `%=`, `(`, `)`, `*`, `**`, `**=`, `*=`, `+`, `+=`, `,`, `-`, `-=`, `.`, `/`, `//`, `//=`, `/=`, `:`, `<`, `<=`, `=`, `==`, `>`, `>=`, `[`, `]`, `and`, `not`, `or`, `{`, `}`
|
||||
- Missing: none
|
||||
- **Statements:** 23 / 23 supported (100.0%)
|
||||
- Supported: `await`, `break`, `class`, `continue`, `def`, `elif`, `else`, `except`, `finally`, `for`, `from`, `global`, `if`, `import`, `lambda`, `nonlocal`, `pass`, `raise`, `return`, `try`, `while`, `with`, `yield`
|
||||
- Missing: none
|
||||
- **Types:** 11 / 11 supported (100.0%)
|
||||
- Supported: Boolean, Class, Dict, Function, Instance, List, None, Number, Set, Text, Tuple
|
||||
- Missing: none
|
||||
- **Built-ins:** 2 / 2 supported (100.0%)
|
||||
- Supported: `print`, `sleep`
|
||||
- Missing: none
|
||||
|
||||
## Notes
|
||||
- Coverage reflects the current TypthonMini interpreter surface and may change as features are added.
|
||||
- Re-run `python arduino/libraries/TypthonMini/tools/language_coverage.py` after feature work to refresh this report.
|
||||
Reference in New Issue
Block a user