[プログラミング言語の作り方] グループ化の()対応を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算
– 符号
– グループ化の()対応

### source.3

a = (5-2) * 3;
print("a = (5-2) * 3 = ", a);

b = 2 * (3 - 5);
print("b = 2 * (3 - 5) = ", b);

### parser.js

function paren(){

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();

        console.log();
        op += expect(tokens,")");
        return right;
    }
    return value();
}

printなどの関数がleftに来る場合は、opが()になるが、計算上の()の場合は、opはreturnされない。
その場合の()で囲む場合は、tokens.shift()の直前で処理をする
L ASTのleftとrightの処理順序が重要

[プログラミング言語の作り方] 符号処理を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算
– 符号

### source.3

a = -2 * 3;
print("a = -2 * 3 = ", a);

b = -2 * -3;
print("b = -2 * -3 = ", b);

### parser.js
単項の符号に対応するため、flagを追加。右結合なので、リセットでcommaであること、単項であるため、左辺読まずに最後のreturnでfunccallを呼ぶ

function flag(){

    var op;
    while(op = accept(tokens,"+","-")){

        var right = comma();

        if(op == "+") return right;

        return {left:{left:"0", op:"-",right:"1"}, op:"*",right};
    }
    return funccall();
}

$ node interpretor.js
token =[
‘a’, ‘=’,
‘-‘, ‘2’,
‘*’, ‘3’,
‘;’, ‘print’,
‘(‘, ‘”a = -2 * 3 = “‘,
‘,’, ‘a’,
‘)’, ‘;’,
‘b’, ‘=’,
‘-‘, ‘2’,
‘*’, ‘-‘,
‘3’, ‘;’,
‘print’, ‘(‘,
‘”b = -2 * -3 = “‘, ‘,’,
‘b’, ‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘a’,
op: ‘=’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: { left: ‘2’, op: ‘*’, right: ‘3’ }
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”a = -2 * 3 = “‘, op: ‘,’, right: ‘a’ }
}
},
op: ‘;’,
right: {
left: ‘b’,
op: ‘=’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: {
left: ‘2’,
op: ‘*’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: ‘3’
}
}
}
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”b = -2 * -3 = “‘, op: ‘,’, right: ‘b’ }
}
},
op: ‘;’,
right: undefined
}
a = -2 * 3 = -6
b = -2 * -3 = 6

余白の有無関係なしに”-“をopとして解釈してしまうから、加算、引き算の後に、符号の処理をするのね
return {left:{left:”0″, op:”-“,right:”1″}, op:”*”,right};とすることで、-3がASTでは以下のように -1 * 3 と表される

right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: ‘3’
}

[プログラミング言語の作り方] 掛け算、割り算を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算

### source.3

a = 1.24 / 2 - 1;
print("a = 1.24 / 2 - 1 = ", a);

b = 5 - 2 * 3;
print("b = 5 - 2 * 3 = ", b);

### parse.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function mul(){

    var left = funccall();

    var op;
    while(op = accept(tokens,"*","/")){

        var right = funccall();

        left = {left, op, right};
    }
    return left;
}

function plus(){

    var left = mul();

    var op;
    while(op = accept(tokens,"+","-")){

        var right = mul();

        left = {left, op, right};
    }
    return left;
}

function assign(){
    var left = plus();

    var op;
    while(op = accept(tokens, "=")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = assign();

    var op;
    while(op = accept(tokens,",")){
        var right = assign();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

### run.js

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if (a.op=="+"){
        return run(a.left) + run(a.right);
    } else if (a.op=="-"){
        return run(a.left) - run(a.right);
    } else if (a.op=="*"){
        return run(a.left) * run(a.right);
    } else if (a.op=="/"){
        return run(a.left) / run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

ASTのparse.jsで優先したいopをより深くvalueに近い位置でcallすれば、mulはplusよりも優先して処理される。

[プログラミング言語の作り方] 足し算、引き算対応を作る

足し算、引き算を追加する

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算

### source.3

a = 1 + 2 - 4;
print("a = 1 + 2 - 4 =", a);

b = 3.5 - 1.2 + 0.1;
print("b = 3.5 - 1.2 + 0.1 =", b);

### parse.js

function plus(){

    var left = funccall();

    var op;
    while(op = accept(tokens,"+","-")){

        var right = funccall();

        left = {left, op, right};
    }
    return left;
}

### run.js

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if (a.op=="+"){
        return run(a.left) + run(a.right);
    } else if (a.op=="-"){
        return run(a.left) - run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

$ node interpretor.js
token =[
‘a’,
‘=’,
‘1’,
‘+’,
‘2’,
‘-‘,
‘4’,
‘;’,
‘print’,
‘(‘,
‘”a = 1 + 2 – 4 =”‘,
‘,’,
‘a’,
‘)’,
‘;’,
‘b’,
‘=’,
‘3.5’,
‘-‘,
‘1.2’,
‘+’,
‘0.1’,
‘;’,
‘print’,
‘(‘,
‘”b = 3.5 – 1.2 + 0.1 =”‘,
‘,’,
‘b’,
‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘a’,
op: ‘=’,
right: {
left: { left: ‘1’, op: ‘+’, right: ‘2’ },
op: ‘-‘,
right: ‘4’
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”a = 1 + 2 – 4 =”‘, op: ‘,’, right: ‘a’ }
}
},
op: ‘;’,
right: {
left: ‘b’,
op: ‘=’,
right: {
left: { left: ‘3.5’, op: ‘-‘, right: ‘1.2’ },
op: ‘+’,
right: ‘0.1’
}
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”b = 3.5 – 1.2 + 0.1 =”‘, op: ‘,’, right: ‘b’ }
}
},
op: ‘;’,
right: undefined
}
a = 1 + 2 – 4 =-1
b = 3.5 – 1.2 + 0.1 =2.4

流れがわかってきましたね。

[プログラミング言語の作り方] グローバル変数、代入、変数参照(=)に対応を作る

グローバル変数、代入、変数参照に対応する。変数に型はないので、整数、実数、文字列が代入できる。
変数を宣言しないと、変数定義される。代入演算子としてイコール(=)を導入する。
変数を使ったら、変数に格納されている値を参照できるようにする。

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント

### source.3

a = b = 3;

msg = "a=";

print(msg, a, ", b=", b);

### parser.js
代入演算子(=)を扱うため、assign関数を導入
代入演算子(=)は右結合のため、right側がcommaになっている。a = (b = 1)

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function assign(){
    var left = funccall();

    var op;
    while(op = accept(tokens, "=")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = assign();

    var op;
    while(op = accept(tokens,",")){
        var right = assign();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}


a = b = 3 は、[a, '=', [b, '=', 3]] となる

### run.js
globalを作り、代入演算子の場合、globalオブジェクトに変数名をキーに値を格納する

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

変数の代入の場合は、"="で宣言したタイミングで、グローバル変数としてkey & valueで持っていて、keyが表示されたタイミングで valueを代入している。

ということはjavaのpublic, privateなども変数の持ち方を制限しているのかな。
"=" だけであれば、leftとrightに分けられるから比較的簡単か。これに"=>" "=<"などが増えた場合にどうなるかは興味深いね。

[プログラミング言語の作り方] コメント、数値、複数引数(コンマ)対応の作り方

言語仕様を拡張する
– 文字列
– 整数123, 実数123.4
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント

### source.3

print("引数1個目","引数1個目","引数1個目");
print("整数=",1234);
print("実数=",1234.5);

### lexer.js
\/\/.*$ =>コメント削除、mオプションが必要
\d+\.\d+ =>実数
\d+ => 整数
“.*?” => 文字列
\w+ => print
\s => 空白、改行
. => セミコロン、カッコ、その他

module.exports = function(source){
    var tokens = source.split(/\/\/.*$|(\d+\.\d+|\d+|".*?"|\w+)|\s|(.)/m);

    tokens = tokens.filter(a=>a);

    return tokens;
}

### parser.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = funccall();

    var op;
    while(op = accept(tokens,",")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

### run.js
1234, 1234.5は文字列になっているので、1を掛けて数値化
tree形式から配列化している
flatに[left, right]に戻す

const { error } = require("./utils");

module.exports = run;

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

opに追加する場合は、字句解析の正規表現と実行時の処理を追加する
カンマがあった場合は、left, op, rightで入れ子にするが、なかった場合は、そのまま処理を続ける

$ node interpretor.js
token =[
‘print’, ‘(‘, ‘”引数1個目”‘,
‘,’, ‘”引数2個目”‘, ‘,’,
‘”引数3個目”‘, ‘)’, ‘;’,
‘print’, ‘(‘, ‘”引数4″‘,
‘)’, ‘;’, ‘print’,
‘(‘, ‘”整数=”‘, ‘,’,
‘1234’, ‘)’, ‘;’,
‘print’, ‘(‘, ‘”実数=”‘,
‘,’, ‘1234.5’, ‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘print’,
op: ‘()’,
right: {
left: { left: ‘”引数1個目”‘, op: ‘,’, right: ‘”引数2個目”‘ },
op: ‘,’,
right: ‘”引数3個目”‘
}
},
op: ‘;’,
right: { left: ‘print’, op: ‘()’, right: ‘”引数4″‘ }
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”整数=”‘, op: ‘,’, right: ‘1234’ }
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”実数=”‘, op: ‘,’, right: ‘1234.5’ }
}
},
op: ‘;’,
right: undefined
}
引数1個目引数2個目引数3個目
引数4
整数=1234
実数=1234.5

[プログラミング言語の作り方] 関数を読み込む作り方

print文専用の構文解析ではなく、汎用的な関数呼び出しの構文解析にする
関数呼び出しのfunccallを作り、どれにも該当しない場合、そのままの値を返す

### parser.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = semi();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = funccall();

    var op;
    while(op = accept(tokens, ";")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

関数valueで、tokens.shift();で値を取ってleftに入れて、()をop、中をrightにして、セミコロンがあった場合は全体をleft、opをセミコロン、セミコロン後をrightにしている。

これは、この言語が必ず 関数名、()、; の順番で書かれていることを前提に立っている。

言語設計者側に立つと、命名規則ってのはちゃんと意味があるんだなぁ
function valueのtokens.shiftのところで、case文で関数名による処理を分岐するのかな?

[プログラミング言語の作り方] 複数文(セミコロン)対応の作り方

複数文はセミコロンで区切るとする。
### source.3

print("hello world1");
print("hello world2");
print("hello world3");

### lexer.js
正規表現にセミコロンを追加

module.exports = function(source){
    var tokens = source.split(/(".*"|print|;)|\n/);

    tokens = tokens.filter(a=>a);

    return tokens;
}

### parser.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    return semi();
}

function semi(){
    var left = callprint();

    var op;
    while(op = accept(tokens, ";")){
        var right = callprint();
        left = {left, op, right};
    }
    return left;
}

function callprint(){
    if(tokens.length==0) return;

    var left = expect(tokens, "print");

    var op = expect(tokens, "(");

    var msg = tokens.shift();
    num = calc(msg.length);

    var right = msg.substr(1, num);

    op += expect(tokens,")");

    return {left, op, right};
}

function calc(len) {
    var total = len - 2;
    return Number.isInteger(total) > 0 ? total : 0;
}

上記を実行すると、leftが入れ子になる
[[print, (), hell1] [;] [print, (), hell1]] [;] [print, (), hell1]

### run.js

const { error } = require("./utils");

module.exports = run;

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = run(a.right)
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

run(a.left); run(a.right); と左から実行することで、上から入れ子になっている式を上から順番に実行する。

$ node interpretor.js
token =[
‘print’, ‘(‘,
‘”hello world1″‘, ‘)’,
‘;’, ‘print’,
‘(‘, ‘”hello world2″‘,
‘)’, ‘;’,
‘print’, ‘(‘,
‘”hello world3″‘, ‘)’,
‘;’
]
ast={
left: {
left: {
left: { left: ‘print’, op: ‘()’, right: ‘hello world1’ },
op: ‘;’,
right: { left: ‘print’, op: ‘()’, right: ‘hello world2’ }
},
op: ‘;’,
right: { left: ‘print’, op: ‘()’, right: ‘hello world3’ }
},
op: ‘;’,
right: undefined
}
hello world1
hello world2
hello world3

parserでセミコロンが来たら、セミコロンをopにしてleftとrightに入れるというのは面白い
C, C++はセミコロンがあるが、pythonは文末にセミコロンがないけどどうやってるんだろうか?

[プログラミング言語の作り方] 抽象木構文(AST)

構文解析の結果として抽象木構文(AST)で出力する

### 抽象木構文(AST)とは
木構造はオブジェクトで表す

{
	left: "print",
	op: "()",
	right: "hello world"
}

以下のように表す

var left = "print";
var op = "()";
var right = "hello world";

var obj1 = {left:left, op:op, right:right};

var obj2 = {left, op, right};

run.jsを追加する
$ tree
.
├── interpretor.js
├── lexer.js
├── main.js
├── parser.js
├── run.js
├── source.3
└── utils.js

### interpretor.js

var {read, show} = require("./utils.js");
var lexer = require("./lexer.js");
var parser = require("./parser.js");
var run = require("./run.js");

var source = read("source.3");

var tokens = lexer(source);

var token = lexer(source);
show("token =", tokens);

var ast = parser(tokens);
show("ast=", ast);

run(ast);

### parser.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    return callprint();
}

function callprint(){
    if(tokens.length==0) return;

    var left = expect(tokens, "print");

    var op = expect(tokens, "(");

    var msg = tokens.shift();
    num = calc(msg.length);

    var right = msg.substr(1, num);

    op += expect(tokens,")");

    return {left, op, right};
}

function calc(len) {
    var total = len - 2;
    return Number.isInteger(total) > 0 ? total : 0;
}

### run.js

const { error } = require("./utils");

module.exports = run;

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        return a;
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = run(a.right)
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

$ node interpretor.js
token =[ ‘print’, ‘(‘, ‘”hello world”‘, ‘)’ ]
ast={ left: ‘print’, op: ‘()’, right: ‘hello world’ }
hello world

最初にopを見て、それからprintかどうかを判定している。
astでは、ソースコードをleft, right, opに分けている。

[プログラミング言語の作り方] Javascriptでinterpretorを作る

hello worldを出力する言語を開発する。
言語仕様としては文字列とprint関数だ。
source.3というファイルを作成する。

print("hello world")

### インタプリタのファイル構成
– interpretor.js
– lexer.js
– parser.js
– utils.js

### interpretor.js

var {read, show} = require("./utils.js");
var lexer = require("./lexer.js");
var parser = require("./parser.js");

var source = read("source.3");

var tokens = lexer(source);

var token = lexer(source);
show("token =", tokens);

parser(tokens);

### lexer.js
split(/(“.*”|print)|\n/)で、文字列、print、改行で分割
filter(a=>a);で不要な物を捨てる

module.exports = function(source){
    var tokens = source.split(/(".*"|print)|\n/);

    tokens = tokens.filter(a=>a);

    return tokens;
}

### parser.js
callprintでは文法規則に沿って関数名(文字列)の順番通りになっているかを確認し、問題なければその場で文字列の部分を表示(実行)
substr(開始位置、文字数)なので、msg.substr(1, msg.legnth-2);として文字列を切り出す

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    return callprint();
}

function callprint(){
    if(tokens.length==0) return;

    expect(tokens, "print");

    expect(tokens, "(");

    var msg = tokens.shift();
    num = calc(msg.length);

    var msg2 = msg.substr(1, num);

    
    console.log(msg2);

    expect(tokens,")");
}

function calc(len) {
    var total = len - 2;
    return Number.isInteger(total) > 0 ? total : 0;
}

### utils.js
shift()は配列の最初の要素を取り除く

module.exports = {read, show, error, accept, expect}

function read(filename) {
    return require('fs').readFileSync(filename,"utf-8");
}

function show(msg, obj){
    obj = require('util').inspect(obj, {
        showHidden: false, depth: null, maxArrayLength: null, colors: true
    });
    console.log(msg + obj);
}

function error(...msgs){
    console.log(msgs.join(""));
    process.exit();
}

function accept(tokens, ...cs){
    if(tokens.length==0) return;

    if(cs.includes(tokens[0])) return tokens.shift();
    return;
}

function expect(tokens, ...cs){
    var t = accept(tokens, ...cs);

    if(t) return t;

    error("tokens[0]=",tokens[0],"が",cs,"に含まれていたため終了");
}

lexer.jsはsplit以外は殆ど何もしていないのね。parseの方で、msgの切り抜きをやっている。