feat(phase12.7): 糖衣構文Phase 12.7-B完了 + 自律型AI開発システム制御機能
🚀 Phase 12.7-B: ChatGPT5糖衣構文(基本実装完了) - パイプライン演算子(|>)実装 - セーフアクセス(?.)とデフォルト値(??)実装 - sugar gateによる段階的有効化機能 - 糖衣構文テストスイート追加 🤖 自律型AI開発システム改善 - codex-async-notify.sh: タスク制御指示追加 - "下の箱を積み過ぎないように先に進んでください" - "フェーズが終わったと判断したら止まってください" - プロセス数表示機能の改善(count_running_codex_display) - 自動停止機能が正常動作(Phase 12.7-C前で停止確認) 📚 ドキュメント更新 - Paper 13: 自律型AI協調開発システムの革新性を文書化 - ANCP可逆マッピング仕様追加 - nyfmt PoC(フォーマッター)計画追加 🧱 箱理論の体現 - 74k行のコードベース(Phase 15で20k行を目指す) - ANCP適用で最終的に6k行相当を狙う - 世界最小の実用コンパイラへの道 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -13,10 +13,98 @@ use super::common::ParserUtils;
|
||||
// Debug macros are now imported from the parent module via #[macro_export]
|
||||
use crate::must_advance;
|
||||
|
||||
#[inline]
|
||||
fn is_sugar_enabled() -> bool { crate::parser::sugar_gate::is_enabled() }
|
||||
|
||||
impl NyashParser {
|
||||
/// 式をパース (演算子優先順位あり)
|
||||
pub(super) fn parse_expression(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.parse_or()
|
||||
self.parse_pipeline()
|
||||
}
|
||||
|
||||
/// パイプライン演算子: lhs |> f(a,b) / lhs |> obj.m(a)
|
||||
/// 基本方針: 右辺が関数呼び出しなら先頭に lhs を挿入。メソッド呼び出しなら引数の先頭に lhs を挿入。
|
||||
fn parse_pipeline(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_coalesce()?;
|
||||
|
||||
while self.match_token(&TokenType::PIPE_FORWARD) {
|
||||
if !is_sugar_enabled() {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full for '|>'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
// consume '|>'
|
||||
self.advance();
|
||||
|
||||
// 右辺は「呼び出し系」の式を期待
|
||||
let rhs = self.parse_call()?;
|
||||
|
||||
// 変換: rhs の形に応じて lhs を先頭引数として追加
|
||||
expr = match rhs {
|
||||
ASTNode::FunctionCall { name, mut arguments, span } => {
|
||||
let mut new_args = Vec::with_capacity(arguments.len() + 1);
|
||||
new_args.push(expr);
|
||||
new_args.append(&mut arguments);
|
||||
ASTNode::FunctionCall { name, arguments: new_args, span }
|
||||
}
|
||||
ASTNode::MethodCall { object, method, mut arguments, span } => {
|
||||
let mut new_args = Vec::with_capacity(arguments.len() + 1);
|
||||
new_args.push(expr);
|
||||
new_args.append(&mut arguments);
|
||||
ASTNode::MethodCall { object, method, arguments: new_args, span }
|
||||
}
|
||||
ASTNode::Variable { name, .. } => {
|
||||
ASTNode::FunctionCall { name, arguments: vec![expr], span: Span::unknown() }
|
||||
}
|
||||
ASTNode::FieldAccess { object, field, .. } => {
|
||||
ASTNode::MethodCall { object, method: field, arguments: vec![expr], span: Span::unknown() }
|
||||
}
|
||||
ASTNode::Call { callee, mut arguments, span } => {
|
||||
let mut new_args = Vec::with_capacity(arguments.len() + 1);
|
||||
new_args.push(expr);
|
||||
new_args.append(&mut arguments);
|
||||
ASTNode::Call { callee, arguments: new_args, span }
|
||||
}
|
||||
other => {
|
||||
// 許容外: 関数/メソッド/変数/フィールド以外には適用不可
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: format!("callable after '|>' (got {})", other.info()),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// デフォルト値(??): x ?? y => peek x { null => y, else => x }
|
||||
fn parse_coalesce(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_or()?;
|
||||
while self.match_token(&TokenType::QMARK_QMARK) {
|
||||
if !is_sugar_enabled() {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full for '??'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
self.advance(); // consume '??'
|
||||
let rhs = self.parse_or()?;
|
||||
let scr = expr;
|
||||
expr = ASTNode::PeekExpr {
|
||||
scrutinee: Box::new(scr.clone()),
|
||||
arms: vec![(crate::ast::LiteralValue::Null, rhs)],
|
||||
else_expr: Box::new(scr),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// OR演算子をパース: ||
|
||||
@ -96,7 +184,7 @@ impl NyashParser {
|
||||
|
||||
/// 比較演算子をパース: < <= > >=
|
||||
fn parse_comparison(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_term()?;
|
||||
let mut expr = self.parse_range()?;
|
||||
|
||||
while self.match_token(&TokenType::LESS) ||
|
||||
self.match_token(&TokenType::LessEquals) ||
|
||||
@ -110,7 +198,7 @@ impl NyashParser {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_term()?;
|
||||
let right = self.parse_range()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
@ -121,6 +209,25 @@ impl NyashParser {
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 範囲演算子: a .. b => Range(a,b)
|
||||
fn parse_range(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_term()?;
|
||||
while self.match_token(&TokenType::RANGE) {
|
||||
if !is_sugar_enabled() {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full for '..'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
self.advance(); // consume '..'
|
||||
let rhs = self.parse_term()?;
|
||||
expr = ASTNode::FunctionCall { name: "Range".to_string(), arguments: vec![expr, rhs], span: Span::unknown() };
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 項をパース: + - >>
|
||||
fn parse_term(&mut self) -> Result<ASTNode, ParseError> {
|
||||
@ -379,6 +486,48 @@ impl NyashParser {
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else if self.match_token(&TokenType::QMARK_DOT) {
|
||||
if !is_sugar_enabled() {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full for '?.'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
self.advance(); // consume '?.'
|
||||
// ident then optional call
|
||||
let name = match &self.current_token().token_type {
|
||||
TokenType::IDENTIFIER(s) => { let v = s.clone(); self.advance(); v }
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "identifier after '?.'".to_string(), line });
|
||||
}
|
||||
};
|
||||
let access = if self.match_token(&TokenType::LPAREN) {
|
||||
// method call
|
||||
self.advance();
|
||||
let mut arguments = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "safe method call arg parsing");
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||
}
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
ASTNode::MethodCall { object: Box::new(expr.clone()), method: name, arguments, span: Span::unknown() }
|
||||
} else {
|
||||
// field access
|
||||
ASTNode::FieldAccess { object: Box::new(expr.clone()), field: name, span: Span::unknown() }
|
||||
};
|
||||
|
||||
// Wrap with peek: peek expr { null => null, else => access(expr) }
|
||||
expr = ASTNode::PeekExpr {
|
||||
scrutinee: Box::new(expr.clone()),
|
||||
arms: vec![(crate::ast::LiteralValue::Null, ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() })],
|
||||
else_expr: Box::new(access),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
} else if self.match_token(&TokenType::LPAREN) {
|
||||
// 関数呼び出し: function(args) または 一般式呼び出し: (callee)(args)
|
||||
self.advance(); // consume '('
|
||||
|
||||
Reference in New Issue
Block a user