なぜ

今までの会話でこんなことがありました

A. 使ったことあるプログラミング言語は?
B. うーん… HTMLCSS かな?

経験者なら違和感を感じると思います

どっちもプログラミング言語じゃなくね?

厳密に言うと HTML は マークアップ言語で CSS は スタイルシート言語 です。
プログラミング言語だ、と言う人もいれば違うと言う人もいます。

そこで私は思いつきました。
「プログラミング言語にすればいい!」

ということで作りました
目標は

CSSをプログラミング言語にする

はじめに

プログラミング言語を作るには、他のプログラミング言語を使用します

  • Ruby は C言語
  • Python は C言語, Java…

やはりC言語で実装するのが基本的ぽい、が、めんどくさい!
そこで私は JavaScript で実装することにしました。

決め手は以下の通りです

  • 使い慣れている
  • ブラウザで実行できる
  • 公開しやすい
  • パクれる 参考になるリポジトリがあった

自作言語について

言語仕様は この (GitHub) ようにしました。
サンプルコードは こちら (GitHub) です。

言語名は

CSS(カスケーディング・スタイル・シート) ではない

ということで

NCSS (not CSS)

としました。SCSSを意識してますね

工夫点はcssらしくしたことです。(目標だから当然)
さらに scss らしいあの うるさい ネストを再現できるように、 グループ というものを追加しました。

/* scssらしいうるささの再現 */
#main {
  width: 100vw;

  .container {
    margin: 10px;

    .content {
      font-size: 1.2rem;
      background-color: aqua;
    }
  }
}

実装には JavaScriptを使った と書きましたが、制作は TypeScript に切り替えました。 型が無いとあまりにも不便だったからです。型を定義すれば文字列でも選択肢を絞れるから楽でした。
公開する前に JavaScript にコンパイルしています。

使い方

node が入っている場合は↓でインストールできます

npm i -g @satooru65536/ncss

↓ でncssを実行できます。

ncss ./main.ncss

ncss を使うためにVScodeの拡張機能も作りました。

難しかった点

こぴぺしたので あまり苦労しませんでしたが、returnberak の扱いが厄介でした。
関数の中で同じ関数をなん度も呼び出しているので、どこまで遡って中断すればいいか分からなかったからです。

何言ってるか分からない場合は ↓ を見て察してください。(簡単に説明する方法わからない)

/* mian.ncss */
#main {
  sub: "";
  content: "main!!";
  return: "";
}

#sub {
  --i: 0;
  .while [--i < 3] {
    .if [--i == 0] {
      return: "sub return";
    }
    transform: --i var(--i) + 1;
  }
  content: "error";
}
// ブロック内の処理を順番に実行する関数
function eval_statement_list(/* ... */) {
  for (/* ブロック内の処理を一つずつ */) {
    eal_statement(/* ... */);
  }
}

// 処理を実行する関数
function eval_statement(/* ... */) {
  ...
  else if (/* 関数を呼び出す場合 */) eval_statement_list(/* ... */);
  else if (/* 代入の場合 */) eval_assign(/* ... */);
  else if (/* 変数宣言の場合 */) eval_assign(/* ... */);
  ...
}
[処理手順はイメージこんな感じ]

#main 内のブロックを処理 eval_statement_list()
    #sub 内のブロックを処理 eval_statement_list()
    eal_statement(--i: 0;)
    eal_statement(.while)
        .while 内のブロックを処理 eval_statement_list()
        eal_statement(.if)
            .if 内のブロックを処理 eval_statement_list()
            eal_statement(return) // eval_statement_list を何回遡れば...?
            // if ブロック > 中断
            // eal_statement(transform)

        // while ブロック > 中断
        // eal_statement(.if)
        // eal_statement(.if)

    // #sub ブロック > 中断
    // eal_statement(content)

// #main ブロック > 続行
    eal_statement(content)
    eal_statement(return)
// #main ブロック > 中断
// プログラム終了

/* ------------------------------------ */

// 中断と続行の判断基準は?
// => https://github.com/ncss-project/ncss/blob/main/src/evaluator.ts#L70
// called_global_func フラッグがポイント
// 関数のブロックから直接呼び出されているかを判断(if/else/group...のブロックはfalse)
// false なら中断する
// => https://github.com/ncss-project/ncss/blob/main/src/evaluator.ts#L78

おわりに

ちゃんとしたプログラミング言語は難しくても、この程度ならとても簡単でした。
ぜひ ソースコード を解読してみてください