feat: using ブレース均等解明完了&環境変数簡略化戦略策定

🎯 using ブレース均等の正体完全解明:
- SeamInspector.report()のprelude_brace_delta計算を解析
- "static box Main {" より前のブレース{/}バランス検証
- usingシステムでファイル結合時の整合性チェック機能と判明

📝 環境変数地獄(8変数)の簡略化戦略策定:
- Phase 1: NYASH_ENABLE_USING, NYASH_RESOLVE_FIX_BRACESデフォルト化
- Phase 2: PyVM/ny_plugins安定化後の段階的デフォルト化
- Phase 3: デバッグ変数のCLIフラグ化(--debug-pyvm等)
- 理想形: 8変数→0変数(./target/release/nyash program.nyash)

🔧 skip_newlines()削除革命継続:
- TokenCursor v2パーサー実装(nyash_parser_v2.rs新規)
- 既存パーサー拡張版(parser_enhanced.rs)
- Smart advance()とTokenCursorの協調実装

📚 次の課題:
- 環境変数デフォルト化の段階的実装
- using systemの完全安定化
- codex協働でのデバッグ効率化

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-23 11:58:31 +09:00
parent 9b801de98d
commit bbc581a07f
10 changed files with 469 additions and 20 deletions

View File

@ -205,6 +205,31 @@ NYASH_DISABLE_PLUGINS=1 NYASH_ENABLE_USING=1 NYASH_VM_USE_PY=1 ./target/release/
**💡 覚え方**:迷ったら`NYASH_DISABLE_PLUGINS=1`から試す!
### ⚠️ **using system環境変数地獄要整理**
**現状**: using関連テストに**8個**の環境変数が必要で複雑すぎる状況
```bash
# using混在スモークテスト用の環境変数地獄
NYASH_ENABLE_USING=1 # using構文有効化
NYASH_VM_USE_PY=1 # PyVM使用
NYASH_LOAD_NY_PLUGINS=1 # Nyプラグイン読み込み
NYASH_RESOLVE_FIX_BRACES=1 # ブレース修正
NYASH_PARSER_STATIC_INIT_STRICT=1 # パーサー厳格モード
NYASH_PYVM_DUMP_CODE=1 # PyVMコードダンプ
NYASH_RESOLVE_SEAM_DEBUG=1 # seam結合デバッグ
NYASH_RESOLVE_DEDUP_BOX=1 # 重複Box削除
```
**問題**:
- 🔥 認知負荷高すぎ8個は覚えられない
- 🔥 相互依存性不明(どれが必須?)
- 🔥 組み合わせ爆発2^8 = 256通り
- 🔥 デバッグ困難(どれが原因?)
**将来の簡略化案**:
- `NYASH_USING_PROFILE=dev|smoke|debug` でプロファイル化
- または `--using-mode=dev` CLIフラグで統合
## 🚀 よく使う実行コマンド(忘れやすい)
### 🎯 基本実行方法

View File

@ -34,7 +34,6 @@ pub(crate) fn parse_header(
p.advance();
if p.match_token(&TokenType::COMMA) {
p.advance();
p.skip_newlines();
}
} else {
return Err(ParseError::UnexpectedToken {
@ -66,7 +65,6 @@ pub(crate) fn parse_header(
}
while p.match_token(&TokenType::COMMA) {
p.advance(); // consume ','
p.skip_newlines();
if let TokenType::IDENTIFIER(parent) = &p.current_token().token_type {
parents.push(parent.clone());
p.advance();

View File

@ -25,12 +25,10 @@ pub(crate) fn parse_interface_box(p: &mut NyashParser) -> Result<ASTNode, ParseE
};
p.consume(TokenType::LBRACE)?;
p.skip_newlines(); // ブレース後の改行をスキップ
let mut methods = HashMap::new();
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
p.skip_newlines(); // ループ開始時に改行をスキップ
if let TokenType::IDENTIFIER(method_name) = &p.current_token().token_type {
let method_name = method_name.clone();
p.advance();
@ -64,9 +62,6 @@ pub(crate) fn parse_interface_box(p: &mut NyashParser) -> Result<ASTNode, ParseE
};
methods.insert(method_name, method_decl);
// メソッド宣言後の改行をスキップ
p.skip_newlines();
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {

View File

@ -36,7 +36,6 @@ pub(crate) fn try_parse_constructor(
}
p.consume(TokenType::RPAREN)?;
let mut body = p.parse_block_statements()?;
p.skip_newlines();
// Optional postfix catch/cleanup (method-level gate)
if p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP) {
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
@ -52,7 +51,6 @@ pub(crate) fn try_parse_constructor(
body: catch_body,
span: Span::unknown(),
});
p.skip_newlines();
if p.match_token(&TokenType::CATCH) {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {

View File

@ -30,7 +30,6 @@ pub(crate) fn wrap_with_optional_postfix(
body: catch_body,
span: Span::unknown(),
});
p.skip_newlines();
if p.match_token(&TokenType::CATCH) {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
@ -78,7 +77,6 @@ pub(crate) fn try_parse_method_postfix_after_last_method(
body: catch_body,
span: Span::unknown(),
});
p.skip_newlines();
if p.match_token(&TokenType::CATCH) {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {

View File

@ -129,7 +129,6 @@ pub(crate) fn try_parse_block_first_property(
}
// 1) Parse block body first
let mut final_body = p.parse_block_statements()?;
p.skip_newlines();
// 2) Expect 'as'
if let TokenType::IDENTIFIER(kw) = &p.current_token().token_type {

View File

@ -297,7 +297,6 @@ impl NyashParser {
self.consume(TokenType::LBRACE)?;
let mut body: Vec<ASTNode> = Vec::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if !self.match_token(&TokenType::RBRACE) {
body.push(self.parse_statement()?);
}

View File

@ -0,0 +1,244 @@
/*!
* NyashParser v2 - TokenCursorベースの新パーサー
*
* 改行処理を完全自動化した次世代パーサー
* skip_newlines()の手動呼び出しを完全排除
*/
use crate::ast::{ASTNode, Span};
use crate::parser::cursor::{TokenCursor, NewlineMode};
use crate::parser::ParseError;
use crate::tokenizer::{Token, TokenType};
use std::collections::{HashMap, HashSet};
/// TokenCursorベースの新パーサー
pub struct NyashParserV2<'a> {
cursor: TokenCursor<'a>,
static_box_dependencies: HashMap<String, HashSet<String>>,
debug_fuel: Option<usize>,
}
impl<'a> NyashParserV2<'a> {
/// 新しいパーサーを作成
pub fn new(tokens: &'a [Token]) -> Self {
Self {
cursor: TokenCursor::new(tokens),
static_box_dependencies: HashMap::new(),
debug_fuel: Some(100_000),
}
}
/// プログラムをパース(エントリーポイント)
pub fn parse_program(&mut self) -> Result<ASTNode, ParseError> {
let mut statements = Vec::new();
// 文モードでパース(改行が文の区切り)
while !self.cursor.is_at_end() {
statements.push(self.parse_statement()?);
// 文の区切り(改行やセミコロン)は自動処理
while self.cursor.match_token(&TokenType::NEWLINE)
|| self.cursor.match_token(&TokenType::SEMICOLON) {
self.cursor.advance();
}
}
Ok(ASTNode::Program {
statements,
span: Span::unknown(),
})
}
/// 文をパース
pub fn parse_statement(&mut self) -> Result<ASTNode, ParseError> {
// 文モードで実行(改行を文の区切りとして扱う)
match &self.cursor.current().token_type {
TokenType::LOCAL => self.parse_local_declaration(),
TokenType::IF => self.parse_if_statement(),
TokenType::LOOP => self.parse_loop_statement(),
TokenType::RETURN => self.parse_return_statement(),
TokenType::BREAK => self.parse_break_statement(),
TokenType::CONTINUE => self.parse_continue_statement(),
_ => {
// 式文(代入や関数呼び出しなど)
self.parse_expression_statement()
}
}
}
/// 式をパース
pub fn parse_expression(&mut self) -> Result<ASTNode, ParseError> {
// 式モードで実行(改行を自動的にスキップ)
self.cursor.with_expr_mode(|c| {
Self::parse_or_expression_internal(c)
})
}
/// OR式をパース内部実装
fn parse_or_expression_internal(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_and_expression_internal(cursor)?;
while cursor.match_token(&TokenType::OR) {
cursor.advance();
let right = Self::parse_and_expression_internal(cursor)?;
left = ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::Or,
left: Box::new(left),
right: Box::new(right),
span: Span::unknown(),
};
}
Ok(left)
}
/// AND式をパース内部実装
fn parse_and_expression_internal(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_primary_expression_internal(cursor)?;
while cursor.match_token(&TokenType::AND) {
cursor.advance();
let right = Self::parse_primary_expression_internal(cursor)?;
left = ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::And,
left: Box::new(left),
right: Box::new(right),
span: Span::unknown(),
};
}
Ok(left)
}
/// プライマリ式をパース(内部実装)
fn parse_primary_expression_internal(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
match &cursor.current().token_type.clone() {
TokenType::NUMBER(n) => {
let value = *n;
cursor.advance();
Ok(ASTNode::Literal {
value: crate::ast::LiteralValue::Integer(value),
span: Span::unknown(),
})
}
TokenType::STRING(s) => {
let value = s.clone();
cursor.advance();
Ok(ASTNode::Literal {
value: crate::ast::LiteralValue::String(value),
span: Span::unknown(),
})
}
TokenType::TRUE => {
cursor.advance();
Ok(ASTNode::Literal {
value: crate::ast::LiteralValue::Bool(true),
span: Span::unknown(),
})
}
TokenType::FALSE => {
cursor.advance();
Ok(ASTNode::Literal {
value: crate::ast::LiteralValue::Bool(false),
span: Span::unknown(),
})
}
TokenType::IDENTIFIER(name) => {
let name = name.clone();
cursor.advance();
Ok(ASTNode::Variable {
name,
span: Span::unknown(),
})
}
TokenType::LBRACE => {
// オブジェクトリテラル(改行は自動処理)
Self::parse_object_literal_internal(cursor)
}
TokenType::LPAREN => {
cursor.advance();
let expr = Self::parse_or_expression_internal(cursor)?;
cursor.consume(TokenType::RPAREN)?;
Ok(expr)
}
_ => {
let line = cursor.current().line;
Err(ParseError::InvalidExpression { line })
}
}
}
/// オブジェクトリテラルをパース(改行完全自動化)
fn parse_object_literal_internal(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
cursor.consume(TokenType::LBRACE)?;
let mut entries = Vec::new();
// ブレース内は改行が自動的にスキップされる!
while !cursor.match_token(&TokenType::RBRACE) && !cursor.is_at_end() {
// キーをパース
let key = match &cursor.current().token_type {
TokenType::STRING(s) => {
let k = s.clone();
cursor.advance();
k
}
TokenType::IDENTIFIER(id) => {
let k = id.clone();
cursor.advance();
k
}
_ => {
let line = cursor.current().line;
return Err(ParseError::UnexpectedToken {
found: cursor.current().token_type.clone(),
expected: "string or identifier key".to_string(),
line,
});
}
};
cursor.consume(TokenType::COLON)?;
let value = Self::parse_or_expression_internal(cursor)?;
entries.push((key, value));
if cursor.match_token(&TokenType::COMMA) {
cursor.advance();
}
}
cursor.consume(TokenType::RBRACE)?;
Ok(ASTNode::MapLiteral {
entries,
span: Span::unknown(),
})
}
// 以下、各種文のパースメソッド(スタブ)
fn parse_local_declaration(&mut self) -> Result<ASTNode, ParseError> {
todo!("local宣言のパース実装")
}
fn parse_if_statement(&mut self) -> Result<ASTNode, ParseError> {
todo!("if文のパース実装")
}
fn parse_loop_statement(&mut self) -> Result<ASTNode, ParseError> {
todo!("loop文のパース実装")
}
fn parse_return_statement(&mut self) -> Result<ASTNode, ParseError> {
todo!("return文のパース実装")
}
fn parse_break_statement(&mut self) -> Result<ASTNode, ParseError> {
todo!("break文のパース実装")
}
fn parse_continue_statement(&mut self) -> Result<ASTNode, ParseError> {
todo!("continue文のパース実装")
}
fn parse_expression_statement(&mut self) -> Result<ASTNode, ParseError> {
self.parse_expression()
}
}

View File

@ -0,0 +1,200 @@
/*!
* Parser Enhanced - 既存パーサーの改行処理自動化
*
* 既存のNyashParserを拡張し、advance()で自動的に改行をスキップ
* skip_newlines()の明示的呼び出しを不要にする
*/
use crate::tokenizer::{Token, TokenType};
use std::cell::Cell;
/// パーサーコンテキスト(改行処理のモード管理)
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ParserContext {
/// 文コンテキスト(改行は文の区切り)
Statement,
/// 式コンテキスト(改行を自動スキップ)
Expression,
/// ブロック内(改行を自動スキップ)
Block,
}
thread_local! {
/// 現在のパーサーコンテキスト
static PARSER_CONTEXT: Cell<ParserContext> = Cell::new(ParserContext::Statement);
/// 括弧の深度(自動改行スキップの判定用)
static PAREN_DEPTH: Cell<usize> = Cell::new(0);
static BRACE_DEPTH: Cell<usize> = Cell::new(0);
static BRACKET_DEPTH: Cell<usize> = Cell::new(0);
}
/// コンテキストガードRAIIパターンで自動復元
pub struct ContextGuard {
prev_context: ParserContext,
}
impl Drop for ContextGuard {
fn drop(&mut self) {
PARSER_CONTEXT.with(|c| c.set(self.prev_context));
}
}
/// 式コンテキストで実行
pub fn with_expr_context<F, T>(f: F) -> T
where
F: FnOnce() -> T,
{
let prev = PARSER_CONTEXT.with(|c| c.get());
PARSER_CONTEXT.with(|c| c.set(ParserContext::Expression));
let result = f();
PARSER_CONTEXT.with(|c| c.set(prev));
result
}
/// ブロックコンテキストで実行
pub fn with_block_context<F, T>(f: F) -> T
where
F: FnOnce() -> T,
{
let prev = PARSER_CONTEXT.with(|c| c.get());
PARSER_CONTEXT.with(|c| c.set(ParserContext::Block));
let result = f();
PARSER_CONTEXT.with(|c| c.set(prev));
result
}
/// 改行をスキップすべきか判定
pub fn should_skip_newlines() -> bool {
// 括弧内では常にスキップ
if PAREN_DEPTH.with(|d| d.get()) > 0
|| BRACE_DEPTH.with(|d| d.get()) > 0
|| BRACKET_DEPTH.with(|d| d.get()) > 0
{
return true;
}
// コンテキストによる判定
match PARSER_CONTEXT.with(|c| c.get()) {
ParserContext::Expression | ParserContext::Block => true,
ParserContext::Statement => false,
}
}
/// トークンタイプによる深度更新
pub fn update_depth(token_type: &TokenType, advancing: bool) {
match token_type {
TokenType::LPAREN => {
if advancing {
PAREN_DEPTH.with(|d| d.set(d.get() + 1));
}
}
TokenType::RPAREN => {
if !advancing {
PAREN_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
}
}
TokenType::LBRACE => {
if advancing {
BRACE_DEPTH.with(|d| d.set(d.get() + 1));
}
}
TokenType::RBRACE => {
if !advancing {
BRACE_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
}
}
TokenType::LBRACK => {
if advancing {
BRACKET_DEPTH.with(|d| d.set(d.get() + 1));
}
}
TokenType::RBRACK => {
if !advancing {
BRACKET_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
}
}
_ => {}
}
}
/// 改良されたadvance実装自動改行スキップ付き
pub fn smart_advance(
tokens: &[Token],
current: &mut usize,
prev_token: Option<&TokenType>,
) {
if *current >= tokens.len() {
return;
}
// 現在のトークンで深度を更新
let current_token = &tokens[*current].token_type;
update_depth(current_token, true);
// 位置を進める
*current += 1;
// 改行を自動的にスキップ
while *current < tokens.len() {
let token_type = &tokens[*current].token_type;
// 改行判定
if matches!(token_type, TokenType::NEWLINE) {
// スキップすべきか判定
if should_skip_newlines() || is_line_continuation(prev_token) {
*current += 1;
continue;
}
}
// セミコロンも同様に処理
if matches!(token_type, TokenType::SEMICOLON) {
if std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().as_deref() == Some("1") {
if should_skip_newlines() {
*current += 1;
continue;
}
}
}
break;
}
}
/// 行継続判定(直前のトークンから判断)
fn is_line_continuation(prev_token: Option<&TokenType>) -> bool {
match prev_token {
Some(token) => matches!(
token,
TokenType::PLUS
| TokenType::MINUS
| TokenType::MULTIPLY
| TokenType::DIVIDE
| TokenType::MODULO
| TokenType::AND
| TokenType::OR
| TokenType::DOT
| TokenType::DoubleColon
| TokenType::COMMA
| TokenType::FatArrow
),
None => false,
}
}
/// 既存のParserUtilsトレイトを拡張
pub trait EnhancedParserUtils {
/// 改良版advance改行自動処理
fn advance_smart(&mut self);
/// 式コンテキストでパース
fn parse_in_expr_context<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Self) -> T;
/// ブロックコンテキストでパース
fn parse_in_block_context<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Self) -> T;
}

View File

@ -17,9 +17,6 @@ impl NyashParser {
// Parse the block body first
let try_body = self.parse_block_statements()?;
// Allow whitespace/newlines between block and postfix keywords
self.skip_newlines();
if crate::config::env::block_postfix_catch()
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
{
@ -39,7 +36,6 @@ impl NyashParser {
});
// Singlecatch policy (MVP): disallow multiple catch in postfix form
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
@ -88,7 +84,6 @@ impl NyashParser {
self.consume(TokenType::LBRACE)?;
let mut body = Vec::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if !self.match_token(&TokenType::RBRACE) {
body.push(self.parse_statement()?);
}
@ -473,8 +468,6 @@ impl NyashParser {
/// return文をパース
pub(super) fn parse_return(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'return'
// 許容: 改行をスキップしてから式有無を判定
self.skip_newlines();
// returnの後に式があるかチェックRBRACE/EOFなら値なし
let value = if self.is_at_end() || self.match_token(&TokenType::RBRACE) {
None