Skip to content

NeoCombi DSL Grammar Specification

NeoCombi の制約 DSL の文法仕様。PICT 制約言語の最小サブセットを EBNF として独立定義し、PICT BNF からの差分(捨てた要素)を明示する。

Status: draft (2026-05-04). MVP 対応範囲を定義する一次資料。 Grammar version: 1.0 — the EBNF in §4 is versioned independently of this document's status so tools and the user manual can pin a known grammar. Related: ADR-001, ADR-005.

1. 目的とスコープ — Purpose & Scope

NeoCombi の DSL は以下の 2 つの役割を担う:

  1. PICT 入力の生成元 — DSL を PICT 入力形式に変換し、外部 CLI として呼び出してテストケースを生成する(ADR-002)
  2. 内製 DSL 評価器の入力 — DSL を NeoCombi 自身が評価し、N 因子禁則組合せをローカルで列挙する(ADR-005)

両用途で同じ DSL を使うため、文法は PICT BNF と意味的に一致しなければならない。本仕様は PICT BNF を 1:1 で mirror しつつ、MVP の対応範囲を最小サブセットに限定する(ADR-001)。

The NeoCombi DSL is a strict subset of the PICT constraint language. It serves both as the input that gets passed to the external PICT CLI and as the input to NeoCombi's built-in evaluator that derives forbidden combination matrices without invoking PICT.

2. MVP 対応範囲 — MVP Coverage

2.1 採用する構文

  • パラメータ宣言(因子・水準のリスト)
  • 制約節IF ... THEN ... [ELSE ...] 形式、および無条件制約(Predicate;
  • 比較演算子=, <>, >, >=, <, <=
  • 論理演算子AND, OR, NOT
  • 集合包含[Param] IN { v1, v2, ... }
  • パラメータ参照[ParameterName](角括弧で囲む)
  • 括弧によるグループ化( ... )
  • コメント# で始まる行

2.2 MVP では採用しない構文

構文 PICT BNF での役割 落とした理由
[Param] LIKE "pattern" ワイルドカードパターンマッチ 実装コスト高、MVP の評価器では重い
サブモデル { p1, p2 } @ N 部分グループ生成方略 制約意味論ではなく PICT 生成方略チューニング。内製評価器の責務外
Weight Value (N) 水準への重み付け 同上
Negative value ~Value 異常系テスト用マーカー 同上
Default alias separator \| 水準のエイリアス分離記号 エイリアス機能自体が MVP 範囲外
制約 Value 位置の bare Identifier 制約 RHS の値表現の 1 形態 PICT 自身が Incorrect numeric value で拒否する形なので、subset として受け入れる意味がない(受け入れると DSL は通るが PICT が落ちる罠になる)。Value はクォート文字列か数値のみ

採用外構文を含む DSL は、パーサが unsupported in MVP 診断を返す(SR-002)。位置情報付きで本ファイル §7 の差分表へリンクする UI を提供する。

3. Lexical Conventions — Lexical 規則

3.1 文字集合

ソースは UTF-8 でエンコードされる。識別子・コメント・文字列リテラル内に Unicode 文字を含めることができる(パラメータ名・水準名に日本語等を使用可)。

3.2 空白・改行

  • スペース(U+0020)とタブ(U+0009)は token 間で無視される
  • 改行(\n\r\n\r)は ParameterDecl の終端、および Constraint 末尾の ; 後の区切りとして意味を持つ

3.3 コメント

# 任意のコメントテキスト(行末まで)

# から行末までがコメント。行頭以外でも # 以降は行コメント として扱う(PICT 互換)。コメントは構文的には空白扱い。

3.4 識別子(Identifier)

  • パラメータ名、水準名、参照名で使用
  • 先頭文字:英字(A-Z / a-z)、_、または非 ASCII 文字
  • 後続文字:上記+数字+内部空白
  • 末尾の空白は trim される

3.5 文字列リテラル(StringLiteral)

"quoted with double quotes"

エスケープシーケンス:\\, \", \n, \t

3.6 数値リテラル(NumberLiteral)

-?[0-9]+(\.[0-9]+)?

整数・小数を許可。指数表記は MVP では採用しない。

3.7 キーワード(大文字小文字)

IF, THEN, ELSE, AND, OR, NOT, INcase-insensitive(PICT 互換)。本仕様では大文字で記述するが、if, And 等も等価。識別子としては使用できない(予約語)。

4. EBNF Grammar — Version 1.0

W3C-style EBNF 表記。? はオプション、* はゼロ回以上、+ は 1 回以上、| は選択、( ... ) はグループ化、'literal' または "literal" は終端記号。

Grammar version 1.0. This is the first stable grammar version. Any change that alters what source text parses (new construct, relaxed/tightened rule) bumps the version; an editorial change that does not affect parsing does not.

(* ====================================================================
   NeoCombi DSL Grammar — Version 1.0
   ==================================================================== *)
(* ====================================================================
   File structure
   ==================================================================== *)
Model            ::= ParameterSection ConstraintSection?

ParameterSection ::= ( BlankOrComment | ParameterDecl )+
ConstraintSection
                 ::= ( BlankOrComment | Constraint )+

BlankOrComment   ::= EmptyLine | Comment
EmptyLine        ::= NewLine
Comment          ::= '#' CommentChar* NewLine
CommentChar      ::= [^\r\n]

(* ====================================================================
   Parameter section
   ==================================================================== *)
ParameterDecl    ::= ParameterName ':' LevelList NewLine
ParameterName    ::= Identifier
LevelList        ::= Level ( ',' Level )*
Level            ::= Identifier
                   | StringLiteral
                   | NumberLiteral

(* ====================================================================
   Constraint section
   ==================================================================== *)
Constraint       ::= ( IfStatement | UnconditionalConstraint ) ';'

IfStatement      ::= 'IF' Predicate 'THEN' Predicate ( 'ELSE' Predicate )?
UnconditionalConstraint
                 ::= Predicate

Predicate        ::= OrExpr
OrExpr           ::= AndExpr ( 'OR' AndExpr )*
AndExpr          ::= NotExpr ( 'AND' NotExpr )*
NotExpr          ::= 'NOT' NotExpr
                   | AtomicPred
AtomicPred       ::= '(' Predicate ')'
                   | Comparison
                   | InClause

Comparison       ::= ParameterRef Relation ComparisonRhs
ComparisonRhs    ::= Value
                   | ParameterRef
ParameterRef     ::= '[' ParameterName ']'
Relation         ::= '=' | '<>' | '>' | '>=' | '<' | '<='

InClause         ::= ParameterRef 'IN' '{' ValueList '}'
ValueList        ::= Value ( ',' Value )*

(* ====================================================================
   Values

   Constraint Value positions deliberately disallow bare Identifier
   tokens. PICT itself rejects them with `Incorrect numeric value` —
   PICT requires a quoted string or a number on the right-hand side
   of any comparison or inside an IN clause. Mirroring that rule
   keeps NeoCombi's DSL a true subset of PICT (per ADR-001), so the
   source can be passed verbatim with no translation layer.

   Note: a Level (in a parameter declaration) is a separate rule that
   still accepts bare Identifier — declarations are PICT-compatible
   either bare or as quoted strings, and bare is the conventional form.
   ==================================================================== *)
Value            ::= StringLiteral
                   | NumberLiteral

(* ====================================================================
   Lexical primitives
   ==================================================================== *)
Identifier       ::= IdHead ( IdMid* IdHead )?
IdHead           ::= Letter | Digit | '_' | NonAsciiLetter
IdMid            ::= IdHead | ' '
StringLiteral    ::= '"' StringChar* '"'
StringChar       ::= [^"\\] | EscapeSeq
EscapeSeq        ::= '\\' ( '\\' | '"' | 'n' | 't' )
NumberLiteral    ::= '-'? Digit+ ( '.' Digit+ )?
Letter           ::= [A-Za-z]
Digit            ::= [0-9]
NonAsciiLetter   ::= [^\x00-\x7F]    (* Unicode letter, simplified *)
NewLine          ::= '\r'? '\n' | '\r'

(* === Authoring notes (incl. AI) ======================================
   When generating DSL, output ONLY text conforming to this grammar
   (no prose, no code fences). It is also valid Microsoft PICT input.

   Modeling conventions:
   - Express impossible / infeasible combinations as constraints;
     anything not ruled out is a valid combination.
   - A comparison right-hand side and IN { } members must be a "quoted
     string" or a number — a bare word there is an error (even though a
     level in a DECLARATION may be written bare).
   - A factor is numeric only if every level is a number (then > < >= <=
     compare numerically; otherwise as text).
   - _MASK_ : NeoCombi reads a level whose value is exactly _MASK_ as that
     factor's "not applicable" state — use it when a factor is meaningless
     under some condition (declared and pinned as in the example below).

   Example model (idiomatic — note the masked CardStatus):

     Payment:    Cash, CreditCard, BankTransfer
     CardStatus: Valid, Expired, _MASK_
     Shipping:   Standard, Express
     Region:     Domestic, Overseas

     # CardStatus applies only when paying by credit card
     IF [Payment] <> "CreditCard" THEN [CardStatus] =  "_MASK_";
     IF [Payment]  = "CreditCard" THEN [CardStatus] <> "_MASK_";
     # Cash and Express are domestic-only
     IF [Payment]  = "Cash"    THEN [Region] = "Domestic";
     IF [Shipping] = "Express" THEN [Region] = "Domestic";
   ===================================================================== *)

4.1 Operator Precedence

論理演算子の優先順位(高 → 低):

  1. NOT(単項、右結合)
  2. AND(左結合)
  3. OR(左結合)

( ) で明示的にグループ化可能。比較・IN 句は同レベル(atomic predicate)。

5. Type Inference — 型推論

PICT に倣い、パラメータの型は宣言された水準セットから推論する:

  • 数値型:すべての水準値が NumberLiteral として解析可能
  • 文字列型:それ以外(混在を含む)

比較・包含演算は型整合性を要する:

  • 数値パラメータ vs 数値値:OK
  • 文字列パラメータ vs 文字列値:OK
  • 数値パラメータ vs 文字列値:パースエラー(type mismatch
  • 文字列パラメータ間の >, <, >=, <=:辞書順比較

6. 例 — Examples

6.1 最小例

# OS と Browser の組合せ
OS:           Windows, Linux, macOS
Browser:      Chrome, Firefox, Safari

IF [OS] = "Linux" THEN [Browser] <> "Safari";

6.2 IN 句

OS:    Windows, Linux, macOS, FreeBSD
Cloud: AWS, GCP, Azure

IF [OS] IN { "Linux", "FreeBSD" } THEN [Cloud] <> "Azure";

6.3 数値パラメータ

Disk:   100, 500, 1000
Memory: 4, 8, 16

IF [Memory] < 8 THEN [Disk] < 1000;

6.4 ELSE 句

Auth:     OAuth, Basic, None
HTTPS:    Yes, No

IF [Auth] = "OAuth" THEN [HTTPS] = "Yes" ELSE [HTTPS] = "No";

6.5 NOT と括弧によるグループ化

Region:   APAC, EMEA, AMER
Tier:     Free, Pro, Enterprise

IF NOT ( [Region] = "APAC" AND [Tier] = "Free" ) THEN [Tier] <> "Free";

6.6 パラメータ間比較

Min:  1, 2, 3
Max:  1, 2, 3, 4, 5

IF [Min] > [Max] THEN [Min] = [Max];

6.7 無条件制約(Unconditional Constraint)

Status: Active, Inactive, Pending
Role:   Admin, User, Guest

[Status] = "Active" OR [Role] <> "Guest";

6.8 採用外構文(MVP ではエラー)

以下は MVP では unsupported in MVP 診断となる:

# LIKE
IF [Filename] LIKE "*.tmp" THEN [Skip] = "Yes";

# サブモデル
{ A, B, C } @ 2

# Weight
OS: Windows (5), Linux (1)

# Negative value
Counter: ~-1, 0, 1, 2

7. PICT BNF からの差分 — Differences from PICT BNF

7.1 PICT 公式 BNF(Constraints セクション)

PICT 公式仕様より引用(microsoft/pict pict.md):

Constraint    ::=
  IF Predicate THEN Predicate ELSE Predicate;
| Predicate;

Predicate     ::=
  Clause
| Clause LogicalOperator Predicate

Clause        ::=
  Term
| ( Predicate )
| NOT Predicate

Term          ::=
  ParameterName Relation Value
| ParameterName LIKE PatternString
| ParameterName IN { ValueSet }
| ParameterName Relation ParameterName

LogicalOperator ::= AND | OR
Relation      ::= = | <> | > | >= | < | <=

7.2 NeoCombi MVP との差分

PICT BNF NeoCombi MVP 状態
ParameterName Relation Value 採用
ParameterName Relation ParameterName 採用
ParameterName IN { ValueSet } 採用
IF Predicate THEN Predicate ELSE Predicate 採用
Predicate;(無条件制約) 採用
LogicalOperator ::= AND \| OR 採用
NOT Predicate 採用
Relation 全 6 種 採用
( Predicate ) 採用
Comments # 採用
ParameterName LIKE PatternString 不採用 ❌ MVP 範囲外
サブモデル { p1, p2 } @ N 不採用 ❌ MVP 範囲外
Level weight Value (N) 不採用 ❌ MVP 範囲外
Negative value ~Value 不採用 ❌ MVP 範囲外
Default value separator override(/d 不採用 カンマ固定
Default alias separator override(/a 不採用 エイリアス機能自体が範囲外

7.3 NeoCombi 独自の追加

なし。本 MVP では PICT BNF のサブセットのみで、独自構文の追加はしない。これにより:

  • ユーザは PICT 公式ドキュメントをそのまま参照可能
  • DSL → PICT 入力の変換は薄く(passthrough あるいは AST 経由の再シリアライズ)
  • AI 生成(UR-007)時のターゲット文法が単純で、生成成功率が上がりやすい

8. PICT への変換 — Translation to PICT Input

NeoCombi MVP DSL は PICT BNF のサブセットなので、PICT 入力ファイルへの変換は基本的に identity transform

NeoCombi DSL PICT 入力
パラメータ宣言 そのまま渡す
制約節(IF/THEN/ELSE / 無条件) そのまま渡す
[ParameterName] 参照 そのまま渡す
IN { ... } そのまま渡す
比較・論理演算子 そのまま渡す
コメント # そのまま渡す

実装方針としては、AST 経由で再シリアライズする方が安全(ホワイトスペース正規化、エラー位置の精度向上)。文字列レベルの passthrough は避ける。

9. Validation — DSL 検証の振る舞い

SR-002 で定義する診断分類:

診断種別 条件
Syntax error 文法エラー IF [A] THEN ;IF [F] = barewordValue 位置で bare Identifier、PICT が拒否するので源泉で止める
Unknown parameter 未宣言パラメータ参照 IF [Undefined] = "x" THEN ...
Unknown level 宣言にない水準値の参照 IF [OS] = "iOS"(OS に iOS が無い)
Type mismatch 型不一致 IF [NumericParam] = "string"
Unsupported in MVP MVP 範囲外構文 LIKE~{...} @ N(N) weight

すべて位置情報付きで報告し、エディタでアンダーライン表示する。

10. References

  • Microsoft PICT — pict.md
  • ADR-001: PICT BNF を最小サブセットとして mirror する
  • ADR-005: DSL 評価器を内製し、禁則ビューを PICT 非依存で計算する
  • SR-001..003: DSL Authoring — 編集・検証・エラー表示
  • PROJECT_KICKOFF.md §4 — 「mirror する範囲(MVP は最小サブセット)」の決定経緯