どうも、ポテト君です
バターを背負って来ました
この記事はすしすきーアドベントカレンダーの19日目(ほんとは誕生日の20日がよかった)ということで
やるのはいいけど何しようかな~って
何しよ~~~
そうだな...
コンパイラでも作るか~
はい、ということでコンパイラを作って遊びます
コンパイラって言ってたけど作ってたの実はインタプリタらしい、まああんま変わらんだろ
せっかくだしAiScriptで動かしてPlayとかでも使ってみたい
完成する言語の仕様はこんな感じ
https://sushi.ski/@potatokun/pages/potescript
まあなんか特に捻りのない言語、ちょっと機能が足りない感もあるけどそれは遊びで作っただけだから仕方ないとして
言語名はPoteScriptにしておいた、AiScriptにちなんで(?)
ちなむって動詞面白いな
とりあえず設計図を、ステートマシンみたいな感じでコードの先頭から読み込んでいく感じ
矢印上にあるのは上から遷移条件、次の文字を読み込むか、内容をバッファとして保存するか(基本、たまに違う書き方もする)
完璧だね~
いや嘘、まだ全然ある
上はコメントと変数定義の例、こんな感じで作ってくよ~
最終的に完成したもの
GitHubに上げたほうを見てもいいかも
あんまり分けにくいっていうのはある、まあでもAiScriptで実装するから仕方ないかな~(AiScriptでオブジェクト指向言語的な使い方はできない)
ここで設計したのはコメントと変数定義(+イニシャライズ)、if文(+else if,else)、while文(+continue,break)、変数の更新、ユーザ関数定義(+return)
この中に一致しない文は式単体で書いたものとして評価するよ
この設計図で書いたものはif文とか全体的な構文を読み取るもので式の評価はしてくれないからその部分は別で設計するよ(値を単体で置いても式として評価することにする)
評価の部分はこれ、文字列と括弧( (),[],{} )の部分はよく使うから独立させておいた(再利用性+コードの短縮)
MisskeyPlayには文字数制限があるからコードの短縮は意外と重要、まあめったに制限来ないけどある程度の大きなになると考慮しないといけなくなる
評価するものは値(数値、文字列、リスト)、関数の実行、変数の読み込み、()、値の演算(+,-,*,/,%,==,<,>,<=,>=,!,&&,||,++,--)
では実装
まずコンパイラと式評価以外の関数
コード(長い)
// 言語上で扱えるデータは基本的に{type: 型名,value: 値}の形で整理する、コメント中で「データ」と言ったときは基本これを指す
// エラーの場合は{type: "error",reason: "エラーの理由"}とし、すぐに解析を終了させる
let DEBUG=true // デバッグ情報のプリントをするか
let PRINTSTATE=false // 遷移状況のプリントをするか
var runEnd=false // 強制終了するか
@az(chr){ // a-z,A-Z,_にマッチするか
if chr==null {return false}
var pt=chr.codepoint_at(0)
if 97<=pt&&pt<=122 {return true}
if 65<=pt&&pt<=90 {return true}
if pt==95 {return true}
return false
}
@in09(chr){ // 0-9にマッチするか
if chr==null {return false}
var pt=chr.codepoint_at(0)
if 48<=pt&&pt<=57 {return true}
return false
}
@Print(text){ // プリントに使う関数
print(text)
}
let BS=Str:from_codepoint(92) // バックスペース(環境によって直で書くとバグるから定数としておく)
var vars={} // 変数名と中身の辞書、中身はリストとしインデックスが小さいほどスコープが小さい
var funcs={} // 関数名とコードの辞書
@regiVar(name,data){ // 変数の定義
if DEBUG {
Print(`debug info - register {name} : {Core:to_str(data)}`)
}
if vars[name]==null {
vars[name]=[data]
}
else {
vars[name].unshift(data)
}
}
@runFunc(name,args){ // 関数の実行、結果をデータで返す
if DEBUG {
Print(`debug info - run {name} <- {Core:to_str(args)}`)
}
// 定義済み関数
if name=="print" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
Print(runFunc("toStr",args).value)
return {type: "undefined"}
}
else if name=="toStr" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
else if args[0].type=="undefined" {
return {type: "str",value: "undefined"}
}
else if args[0].type=="list" {
var value=""
value=[value,"[ "].join()
var first=true
each let e , args[0].value {
if !first {
value=[value,", "].join()
}
else {
first=false
}
if e.type=="str" {
value=[value,'"',runFunc(name,[e]).value,'"'].join()
}
else {
value=[value,runFunc(name,[e]).value].join()
}
}
value=[value," ]"].join()
return {type: "str",value: value}
}
else {
return {type: "str",value: Core:to_str(args[0].value)}
}
}
else if name=="toNum" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
else if args[0].type=="str" {
let value=args[0].value.to_num()
if value==null {
return {type: "error",reason: `関数{name} 数値にできない値`}
}
return {type: "num",value: value}
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="type" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
return {type: "str",value: args[0].type}
}
else if name=="sleep" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
else if args[0].type=="num" {
Core:sleep(args[0].value)
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="len" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
else if ["str","list"].incl(args[0].type) {
return {type: "num",value: args[0].value.len}
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="floor" {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数がない`}
}
else if args[0].type=="num" {
return {type: "num",value: Math:floor(args[0].value)}
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="at" {
if args.len<2 {
return {type: "error",reason: `関数{name}の引数が足りない`}
}
else if args[0].type=="list"&&args[1].type=="num" {
if args[0].value.len<=args[1].value||args[1].value<0 {
return {type: "error",reason: `関数{name} インデックスエラー`}
}
return Obj:copy(args[0].value[args[1].value])
}
else if args[0].type=="str"&&args[1].type=="num" {
if args[0].value.len<=args[1].value||args[1].value<0 {
return {type: "error",reason: `関数{name} インデックスエラー`}
}
return {type: "str",value: args[0].value.pick(args[1].value)}
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if ["push","unshift"].incl(name) {
if args.len<2 {
return {type: "error",reason: `関数{name}の引数が足りない`}
}
else if args[0].type=="list"&&args[1].type!="undefined" {
if name=="push" {
args[0].value.push(Obj:copy(args[1]))
}
else {
args[0].value.unshift(Obj:copy(args[1]))
}
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if ["pop","shift"].incl(name) {
if args.len==0 {
return {type: "error",reason: `関数{name}の引数が足りない`}
}
else if args[0].type=="list" {
if name=="pop" {
return args[0].value.pop()
}
else {
return args[0].value.shift()
}
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="remove" {
if args.len<2 {
return {type: "error",reason: `関数{name}の引数が足りない`}
}
else if args[0].type=="list"&&args[1].type=="num" {
if args[0].value.len<=args[1].value||args[1].value<0 {
return {type: "error",reason: `関数{name} インデックスエラー`}
}
return args[0].value.remove(args[1].value)
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="insert" {
if args.len<3 {
return {type: "error",reason: `関数{name}の引数が足りない`}
}
else if args[0].type=="list"&&args[1].type=="num"&&args[2].type!="undefined" {
if args[0].value.len<args[1].value||args[1].value<0 {
return {type: "error",reason: `関数{name} インデックスエラー`}
}
args[0].value.insert(args[1].value,Obj:copy(args[2]))
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
else if name=="set" {
if args.len<3 {
return {type: "error",reason: `関数{name}の引数が足りない`}
}
else if args[0].type=="list"&&args[1].type=="num"&&args[2].type!="undefined" {
if args[0].value.len<=args[1].value||args[1].value<0 {
return {type: "error",reason: `関数{name} インデックスエラー`}
}
args[0].value[args[1].value]=Obj:copy(args[2])
}
else {
return {type: "error",reason: `関数{name} 定義されない値`}
}
}
// 以下はユーザ関数
else if funcs[name]==null {
return {type: "error",reason: "定義されていない関数"}
}
else {
if args.len!=funcs[name]["args"].len {
return {type: "error",reason: `関数{name}の引数の数が合わない`}
}
for let i, args.len {
regiVar(funcs[name]["args"][i],args[i])
}
let result=run(funcs[name]["code"])
each let name , funcs[name]["args"] {
vars[name].shift()
}
if result["state"]=="return" {
return result["value"]
}
}
return {type: "undefined"}
}
@getVar(name){ // 変数の取得(変数を表すデータそのものを返す)
let dataArr=vars[name]
if dataArr==null||dataArr.len==0 {
return {type: "error",reason: "宣言されていない変数"}
}
let data=dataArr[0]
if data.type=="undefined" {
return {type: "error",reason: "値が定義されていない変数"}
}
return data
}
@getValue(name){ // 変数の参照(変数のデータのコピーを返す、シャローコピーなのでデータの値がリストやオブジェクトの場合中身の変更は適用される)
return Obj:copy(getVar(name))
}
馬鹿長いコード書いた時って全部説明する気にはならないんだよね(長文になるから)
まあ...コメント見て~(放棄)
次は全体構造の解析部分
コード(長い)
@run(text){ // コードの実行、変数のスコープと対応し、再帰的に実行されることもある、返り値は{state: 終了状態(error,success,return,break,continue),value: 返り値(状態がreturn以外ではnull)}
var st="common0" // 状態(status)
var ind=0 // textのインデックス
var branchBuffer=""
var expressionBuffer=""
var nameBuffer=""
var conditionBuffer=""
var reason="" // エラー理由
var bracCount=0
var ifRun=false // if文のかたまりで既に実行されたか(trueのときにelif文やelse文が来たら無視する)
var doRun=false
var registered=[] // 定義した変数の名前
var argBuffer=""
var argList=[]
var returnValue=null
loop {
if runEnd {
st="error"
reason="強制終了"
}
let chr=text.pick(ind)
if PRINTSTATE {
Print(`state:{st},index:{ind},char:{chr}`)
}
// 状態を追っていく、追い方は図で説明する
if st=="common0" {
if chr==" "||chr==Str:lf {ind+=1}
else if chr=="/" {
st="comment0"
ind+=1
}
else if chr==null {
st="success"
break
}
else if az(chr) {
st="common1"
branchBuffer=""
conditionBuffer=""
}
else {
st="error"
reason="定義されない実行文"
}
}
else if st=="common1" {
if az(chr)||in09(chr) {
ind+=1
branchBuffer=[branchBuffer,chr].join()
}
else if chr==" " {
ind+=1
if branchBuffer=="var" {st="var0"}
else if ["if","elif","while"].incl(branchBuffer) {st="if0"}
else if branchBuffer=="else" {
st="ifRun0"
doRun=!ifRun
}
else if text.pick(ind)=="=" {
ind+=1
st="upd0"
}
else if text.pick(ind)==";"&&branchBuffer=="continue" {
st="continue"
break
}
else if text.pick(ind)==";"&&branchBuffer=="break" {
st="break"
break
}
else if branchBuffer=="while" {
st="loop0"
}
else if branchBuffer=="def" {
st="def0"
}
else if branchBuffer=="return" {
st="ret0"
}
else{st="expr0"}
}
else if chr=="{"&&branchBuffer=="else" {
st="ifRun0"
doRun=!ifRun
}
else if chr=="("&&["if","elif","while"].incl(branchBuffer) {
st="if0"
}
else if chr=="{"&&branchBuffer=="while" {
st="loop0"
}
else if chr=="=" {
st="upd0"
ind+=1
}
else if chr==";"&&branchBuffer=="continue" {
st="continue"
break
}
else if chr==";"&&branchBuffer=="break" {
st="break"
break
}
else if chr==";"&&branchBuffer=="return" {
st="success"
}
else {
st="expr0"
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
else if st=="comment0" {
if chr=="/" {
st="comment1"
ind+=1
}
else {
st="error"
reason="もしかして-コメント文"
}
}
else if st=="comment1" {
if chr==Str:lf {st="common0"}
else if chr==null {st="success"}
else {ind+=1}
}
else if st=="error" {
Print(`error : {reason}`)
break
}
else if st=="success" {
break
}
else if st=="var0" {
if chr==" "||chr==Str:lf {ind+=1}
else if az(chr) {st="var1"}
else {
st="error"
reason="不正な変数名"
}
}
else if st=="var1" {
if chr==" " {
st="var2"
ind+=1
}
else if az(chr)||in09(chr) {
nameBuffer=[nameBuffer,chr].join()
ind+=1
}
else if chr==";" {
ind+=1
regiVar(nameBuffer,{type: "undefined"})
registered.push(nameBuffer)
nameBuffer=""
st="common0"
}
else if chr=="=" {
ind+=1
st="var3"
}
else {
st="error"
reason="不正な変数名"
}
}
else if st=="var2" {
if chr==" "||chr==Str:lf {ind+=1}
else if chr==";" {
ind+=1
regiVar(nameBuffer,{type: "undefined"})
registered.push(nameBuffer)
nameBuffer=""
st="common0"
}
else if chr=="=" {
ind+=1
st="var3"
}
else {
st="error"
reason="不正な変数定義"
}
}
else if st=="var3" {
if chr==" "||chr==Str:lf {ind+=1}
else if chr==null {
st="error"
reason="不正な変数定義"
}
else {st="var4"}
}
else if st=="var4" {
if chr==";" {
let value=evalExp(expressionBuffer)
if value.type=="error" {
st="error"
reason=value.reason
continue
}
regiVar(nameBuffer,value)
registered.push(nameBuffer)
nameBuffer=""
expressionBuffer=""
ind+=1
st="common0"
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="不正な変数定義"
}
else {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
else if st=="expr0" {
if chr==";" {
ind+=1
let value=evalExp([branchBuffer,expressionBuffer].join())
if value.type=="error" {
st="error"
reason=value.reason
continue
}
expressionBuffer=""
st="common0"
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="もしかして - ;がない"
}
else {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
else if st=="if0" {
if chr==" "||chr==Str:lf {ind+=1}
else if chr=="(" {
st="if1"
ind+=1
bracCount=1
}
else {
st="error"
reason="if文の構造が合わない"
}
}
else if st=="if1" {
if chr=="(" {
bracCount+=1
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
else if chr==")" {
bracCount=bracCount-1
ind+=1
if (bracCount>0) {expressionBuffer=[expressionBuffer,chr].join()}
}
else if bracCount<=0 {
doRun=evalExp(expressionBuffer)
conditionBuffer=expressionBuffer
expressionBuffer=""
if doRun.type=="error" {
st="error"
reason=doRun.reason
continue
}
if doRun.type!="bool" {
st="error"
reason=`条件に使えない型 : {doRun.type}`
}
if branchBuffer=="elif"&&ifRun {
doRun=false
ifRun=true
}
else {
ifRun=doRun.value
doRun=doRun.value
}
st="ifRun0"
}
else if chr==null {
st="error"
reason="閉じていない条件文"
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else {
expressionBuffer=[expressionBuffer,chr].join()
ind+=1
}
}
else if st=="ifRun0" {
if chr==" "||chr==Str:lf {ind+=1}
else if chr=="{" {
st="ifRun1"
ind+=1
bracCount=1
}
else {
st="error"
reason="if文の構造が合わない"
}
}
else if st=="ifRun1" {
if chr=="{" {
bracCount+=1
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
else if chr=="}" {
bracCount=bracCount-1
ind+=1
if (bracCount>0) {expressionBuffer=[expressionBuffer,chr].join()}
}
else if bracCount<=0 {
if (doRun) {
let result=run(expressionBuffer)["state"]
if branchBuffer!="while" {
st="common0"
expressionBuffer=""
}
else {
doRun=evalExp(conditionBuffer)
if doRun.type=="error" {
st="error"
reason=doRun.reason
continue
}
doRun=doRun.value
}
if result=="error" {
st="error"
break
}
else if result=="continue"&&branchBuffer!="while" {
st="continue"
break
}
else if result=="break"&&branchBuffer!="while" {
st="break"
break
}
else if result=="break"&&branchBuffer=="while" {
doRun=false
}
} else {
st="common0"
expressionBuffer=""
}
}
else if chr==null {
st="error"
reason="閉じていないif文"
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else {
expressionBuffer=[expressionBuffer,chr].join()
ind+=1
}
}
else if st=="upd0" {
if chr==";" {
let dataArr=vars[branchBuffer]
if dataArr==null||dataArr.len==0 {
st="error"
reason="宣言されていない変数"
}
let data=dataArr[0]
let value=evalExp(expressionBuffer)
if value.type=="error" {
st="error"
reason=value.reason
}
data.type=value.type
data.value=value.value
st="common0"
ind+=1
expressionBuffer=""
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="終了していない代入文"
}
else {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
else if st=="def0" {
if chr==" "||chr==Str:lf {
ind+=1
}
else if az(chr) {
ind+=1
nameBuffer=[nameBuffer,chr].join()
st="def1"
}
else {
st="error"
reason="不正な関数定義"
}
}
else if st=="def1" {
if az(chr)||in09(chr) {
ind+=1
nameBuffer=[nameBuffer,chr].join()
}
else if chr=="(" {
ind+=1
st="def2"
}
else {
st="error"
reason="不正な関数定義"
}
}
else if st=="def2" {
if chr==" "||chr==Str:lf {
ind+=1
}
else if az(chr) {
ind+=1
argBuffer=[argBuffer,chr].join()
st="def3"
}
else {
st="error"
reason="不正な関数定義"
}
}
else if st=="def3" {
if chr==" "||chr==Str:lf {
ind+=1
}
else if az(chr)||in09(chr) {
ind+=1
argBuffer=[argBuffer,chr].join()
}
else if chr==")" {
ind+=1
argList.push(argBuffer)
argBuffer=""
st="def4"
}
else if chr=="," {
ind+=1
argList.push(argBuffer)
argBuffer=""
st="def2"
}
else {
st="error"
reason="不正な関数定義"
}
}
else if st=="def4" {
if chr==" "||chr==Str:lf {
ind+=1
}
else if chr=="{" {
ind+=1
bracCount=1
st="def5"
}
else {
st="error"
reason="不正な関数定義"
}
}
else if st=="def5" {
if bracCount<=0 {
funcs[nameBuffer]={args: argList,code: expressionBuffer}
if DEBUG {
Print(`debug info - register function {nameBuffer} : {Core:to_str(funcs[nameBuffer])}`)
}
nameBuffer=""
argList=[]
expressionBuffer=""
st="common0"
}
else if chr=="{" {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
bracCount+=1
}
else if chr=="}" {
ind+=1
bracCount=bracCount-1
if bracCount>0 {expressionBuffer=[expressionBuffer,chr].join()}
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="不正な関数定義"
}
else {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
else if st=="ret0" {
if chr==" "||chr==Str:lf {
ind+=1
}
else {
st="ret1"
}
}
else if st=="ret1" {
if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==";" {
if expressionBuffer.len<=0 {
st="success"
}
else {
st="return"
returnValue=evalExp(expressionBuffer)
break
}
}
else {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
}
each let name , registered {
vars[name].shift()
}
return {state: st,value: returnValue}
}
まああの長いのは仕方ないのとして...やってることはほとんど設計図通りに遷移と処理を実行している作業
st
(状態)を見て、chr
(今の位置の文字)が何かで分岐して、必要ならind
(次に読む文字の位置)を移動したり状態を遷移させたりバッファとして文字列に文字を保存したりをループしてる
これは変数のスコープと対応してるから最後に宣言した変数を全部消してる
while文やif文、関数定義で定義した関数の実行とかは、中身を一旦まとめてから再帰的にrun関数を実行させてるからこれによってスコープの変化が実現できる
次に式の評価(+独立部分)、大体同じような作り方をしてる
コード(長い)
@strEval(text,ind){ // 文字列の取得、indの位置を含んで次の"までを得る、結果は{st: 結果(errorかsuccess),reason: エラー内容(error時),ind: 終了位置(success時、"の次),str: 文字列(success時、"は含まない)}
var st="str0"
var buffer=""
var chr=""
loop {
let chr=text.pick(ind)
if st=="error" {
return {st: st,reason: "不正な文字列"}
}
else if st=="str0" {
if chr==Str:lf||chr==null {
st="error"
}
else if chr==BS {
st="str1"
ind+=1
buffer=[buffer,chr].join()
}
else if chr=='"' {
ind+=1
return {st: "success",ind: ind,str: buffer}
}
else {
ind+=1
buffer=[buffer,chr].join()
}
}
else if st=="str1" {
if chr==Str:lf||chr==null {
st="error"
}
else {
st="str0"
ind+=1
buffer=[buffer,chr].join()
}
}
}
}
@bracEval(text,ind,startChar){ // 括弧で囲まれた部分の取得、始まりの記号はstartCharに指定
let bracs=["(","{","["]
let endBracs=[")","}","]"]
var starter={} // 括弧の終わりを鍵にして括弧の始まりに対応する辞書
starter["}"]="{"
starter[")"]="("
starter["]"]="["
var startWith=[]
var buffer=""
var chr=""
var st="brac0"
startWith.push(startChar)
loop {
let chr=text.pick(ind)
if st=="error" {
return {st: st,reason: "不正な括弧"}
}
else if st=="brac0" {
if bracs.incl(chr) {
startWith.push(startChar)
ind+=1
buffer=[buffer,chr].join()
}
else if endBracs.incl(chr) {
ind+=1
let startChar=startWith.pop()
if startChar!=starter[chr] {
st="error"
continue
}
if startWith.len==0 {
return {st: "success",ind: ind,str: buffer}
}
buffer=[buffer,chr].join()
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
continue
}
buffer=[buffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
}
else {
ind+=1
buffer=[buffer,chr].join()
}
}
}
}
@evalExp(text){ // 式の評価
if DEBUG {
Print(`debug info - evaluate : {text}`)
}
var st="common0" // 状態(state)
var ind=0 // textのインデックス
var reason="" // エラー理由
var nameBuffer=""
var dataBuffer=""
var expressionBuffer=""
let oprs=["+","-","*","/","%","&","|","!","<",">","="]
let doubleOprs=["+","-","&","|","="] // 2つ記号が連続するタイプの演算子で2つ目に該当するもの
let bracs=["(","{","["]
var oprChar=""
var expressionList=[] // 分解したデータと演算子のリスト、{type: タイプ(valueかvarかopr),value: 値(oprなら演算子、varなら変数名、valueならデータ)}が入る
var dataList=[]
loop { // 構文解析が終わるまで無限ループ、終わらずはまる可能性あり
if runEnd {
return {type: "error",reason: "強制終了"}
}
let chr=text.pick(ind)
if PRINTSTATE {
Print(`expression - state:{st},index:{ind},char:{chr}`)
}
// 状態を追っていく、追い方は図で説明する
if st=="common0" {
if chr==" "||chr==Str:lf {ind+=1}
else if az(chr) {st="varFunc0"}
else if oprs.incl(chr) {
st="opr0"
ind+=1
oprChar=chr
}
else if chr=="(" {
st="brac0"
ind+=1
}
else if chr=="[" {
st="list0"
ind+=1
}
else if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionList.push({type: "value",value: {type: "str",value: result.str}})
ind=result.ind
}
else if in09(chr) {
st="num0"
ind+=1
dataBuffer=[dataBuffer,chr].join()
}
else if chr==null { // 式の分解が終わって演算を行う部分
// 値が1つならそれを返す
if expressionList.len==1 {
if expressionList[0].type=="value" {
return expressionList[0].value
}
else if expressionList[0].type=="var" {
return getValue(expressionList[0].value)
}
else {
return {type: "error",reason: "式の実行に失敗"}
}
}
// それぞれの演算を優先度順に行う、基本はデータの取り出し→演算→格納のループ
var i=0
loop { // ++,--
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&(expressionList[i].value=="++"||expressionList[i].value=="--") {
let opr=expressionList[i].value
var data=null
var before=true
if i-1>=0&&expressionList[i-1].type=="var" {
data=expressionList[i-1]
before=false
}
else if i+1<expressionList.len&&expressionList[i+1].type=="var" {
data=expressionList[i+1]
}
else {
return {type: "error",reason: "演算できる値がない"}
}
data=getVar(data.value)
if data.type=="error" {
return data
}
if data.type=="num" {
expressionList.remove(i)
if before {expressionList.remove(i)}
else {expressionList.remove(i-1)}
if opr=="++" {
if before {data.value=data.value+1}
expressionList.insert(i,{type: "value",value: {type: "num",value: data.value}})
if !before {
data.value=data.value+1
i=i-1
}
}
else {
if before {data.value=data.value-1}
expressionList.insert(i,{type: "value",value: {type: "num",value: data.value}})
if !before {
data.value=data.value-1
i=i-1
}
}
}
else {
return {type: "error",reason: `定義されない式 : {opr} to {Core:to_str(data.value)}({data.type})`}
}
}
i+=1
}
i=0
loop { // !
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&expressionList[i].value=="!" {
if i+1>=expressionList.len {
return {type: "error",reason: "演算できる値がない"}
}
var data=expressionList[i+1]
if data.type=="var" {
data=getValue(data.value)
if data.type=="error" {
return data
}
}
else if data.type=="value" {
data=data.value
}
if data.type=="bool" {
expressionList.remove(i)
expressionList.remove(i)
expressionList.insert(i,{type: "value",value: {type: "bool",value: !data.value}})
}
else {
return {type: "error",reason: `定義されない式 : ! {Core:to_str(data.value)}({data.type})`}
}
}
i+=1
}
i=0
loop { // *,/,%
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&(expressionList[i].value=="*"||expressionList[i].value=="/"||expressionList[i].value=="%") {
let opr=expressionList[i].value
if i+1>=expressionList.len||i==0 {
return {type: "error",reason: "演算できる値がない"}
}
var data1=expressionList[i-1]
var data2=expressionList[i+1]
if data1.type=="var" {
data1=getValue(data1.value)
if data1.type=="error" {
return data1
}
}
else if data1.type=="value" {
data1=data1.value
}
if data2.type=="var" {
data2=getValue(data2.value)
if data2.type=="error" {
return data2
}
}
else if data2.type=="value" {
data2=data2.value
}
if data1.type=="num"&&data2.type=="num" {
expressionList.remove(i-1)
expressionList.remove(i-1)
expressionList.remove(i-1)
if opr=="*" {
expressionList.insert(i-1,{type: "value",value: {type: "num",value: data1.value*data2.value}})
}
else if opr=="/" {
expressionList.insert(i-1,{type: "value",value: {type: "num",value: data1.value/data2.value}})
}
else {
expressionList.insert(i-1,{type: "value",value: {type: "num",value: data1.value%data2.value}})
}
i=i-1
}
else {
return {type: "error",reason: `定義されない式 : {Core:to_str(data1.value)}({data1.type}){opr}{Core:to_str(data2.value)}({data2.type})`}
}
}
i+=1
}
i=0
loop { // +,-
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&(expressionList[i].value=="+"||expressionList[i].value=="-") {
let opr=expressionList[i].value
if i+1>=expressionList.len {
return {type: "error",reason: "演算できる値がない"}
}
if i==0 {
var data=expressionList[i+1]
if data.type=="var" {
data=getValue(data.value)
if data.type=="error" {
return data
}
}
else if data.type=="value" {
data=data.value
}
if data.type=="num" {
expressionList.remove(i)
expressionList.remove(i)
if opr=="-" {
expressionList.insert(i,{type: "value",value: {type: "num",value: 0-data.value}})
}
i+=1
continue
}
else{
return {type: "error",reason: `定義されない式 : {opr}{Core:to_str(data.value)}({data.type})`}
}
}
var data1=expressionList[i-1]
var data2=expressionList[i+1]
if data1.type=="var" {
data1=getValue(data1.value)
if data1.type=="error" {
return data1
}
}
else if data1.type=="value" {
data1=data1.value
}
if data2.type=="var" {
data2=getValue(data2.value)
if data2.type=="error" {
return data2
}
}
else if data2.type=="value" {
data2=data2.value
}
expressionList.remove(i-1)
expressionList.remove(i-1)
expressionList.remove(i-1)
if data1.type=="num"&&data2.type=="num" {
if opr=="+" {
expressionList.insert(i-1,{type: "value",value: {type: "num",value: data1.value+data2.value}})
}
else {
expressionList.insert(i-1,{type: "value",value: {type: "num",value: data1.value-data2.value}})
}
i=i-1
}
else if data1.type=="str"&&data2.type=="str"&&opr=="+" {
expressionList.insert(i-1,{type: "value",value: {type: "str",value: [data1.value,data2.value].join()}})
i=i-1
}
else if data1.type=="list"&&data2.type=="list"&&opr=="+" {
expressionList.insert(i-1,{type: "value",value: {type: "list",value: data1.value.concat(data2.value)}})
i=i-1
}
else {
return {type: "error",reason: `定義されない式 : {Core:to_str(data1.value)}({data1.type}){opr}{Core:to_str(data2.value)}({data2.type})`}
}
}
i+=1
}
i=0
loop { // <,>,<=,>=,==
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&(["<",">","<=",">=","=="].incl(expressionList[i].value)) {
let opr=expressionList[i].value
if i+1>=expressionList.len||i==0 {
return {type: "error",reason: "演算できる値がない"}
}
var data1=expressionList[i-1]
var data2=expressionList[i+1]
if data1.type=="var" {
data1=getValue(data1.value)
if data1.type=="error" {
return data1
}
}
else if data1.type=="value" {
data1=data1.value
}
else {
return {type: "error",reason: "式の実行に失敗"}
}
if data2.type=="var" {
data2=getValue(data2.value)
if data2.type=="error" {
return data2
}
}
else if data2.type=="value" {
data2=data2.value
}
else {
return {type: "error",reason: "式の実行に失敗"}
}
expressionList.remove(i-1)
expressionList.remove(i-1)
expressionList.remove(i-1)
if opr=="==" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value==data2.value}})
i=i-1
}
else if opr=="<"&&data1.type=="num"&&data2.type=="num" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value<data2.value}})
i=i-1
}
else if opr==">="&&data1.type=="num"&&data2.type=="num" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value>=data2.value}})
i=i-1
}
else if opr==">"&&data1.type=="num"&&data2.type=="num" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value>data2.value}})
i=i-1
}
else if opr=="<"&&data1.type=="num"&&data2.type=="num" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value>data2.value}})
i=i-1
}
else if opr=="<="&&data1.type=="num"&&data2.type=="num" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value<=data2.value}})
i=i-1
}
else {
return {type: "error",reason: `定義されない式 : {Core:to_str(data1.value)}({data1.type}){opr}{Core:to_str(data2.value)}({data2.type})`}
}
}
i+=1
}
i=0
loop { // &&
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&expressionList[i].value=="&&" {
let opr=expressionList[i].value
if i+1>=expressionList.len||i==0 {
return {type: "error",reason: "演算できる値がない"}
}
var data1=expressionList[i-1]
var data2=expressionList[i+1]
if data1.type=="var" {
data1=getValue(data1.value)
if data1.type=="error" {
return data1
}
}
else if data1.type=="value" {
data1=data1.value
}
if data2.type=="var" {
data2=getValue(data2.value)
if data2.type=="error" {
return data2
}
}
else if data2.type=="value" {
data2=data2.value
}
expressionList.remove(i-1)
expressionList.remove(i-1)
expressionList.remove(i-1)
if data1.type=="bool"&&data2.type=="bool" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value&&data2.value}})
i=i-1
}
else {
return {type: "error",reason: `定義されない式 : {Core:to_str(data1.value)}({data1.type}){opr}{Core:to_str(data2.value)}({data2.type})`}
}
}
i+=1
}
i=0
loop { // ||
if i>=expressionList.len {
break
}
if expressionList[i].type=="opr"&&expressionList[i].value=="||" {
let opr=expressionList[i].value
if i+1>=expressionList.len||i==0 {
return {type: "error",reason: "演算できる値がない"}
}
var data1=expressionList[i-1]
var data2=expressionList[i+1]
if data1.type=="var" {
data1=getValue(data1.value)
if data1.type=="error" {
return data1
}
}
else if data1.type=="value" {
data1=data1.value
}
if data2.type=="var" {
data2=getValue(data2.value)
if data2.type=="error" {
return data2
}
}
else if data2.type=="value" {
data2=data2.value
}
expressionList.remove(i-1)
expressionList.remove(i-1)
expressionList.remove(i-1)
if data1.type=="bool"&&data2.type=="bool" {
expressionList.insert(i-1,{type: "value",value: {type: "bool",value: data1.value||data2.value}})
i=i-1
}
else {
return {type: "error",reason: `定義されない式 : {Core:to_str(data1.value)}({data1.type}){opr}{Core:to_str(data2.value)}({data2.type})`}
}
}
i+=1
}
// 演算終了、結果が1つのデータにならなければエラー
if expressionList.len==1 {
if expressionList[0].type=="value" {
return expressionList[0].value
}
else if expressionList[0].type=="var" {
return getValue(expressionList[0].value)
}
else {
return {type: "error",reason: "式の実行に失敗"}
}
}
else {
return {type: "error",reason: "式の実行に失敗"}
}
}
else {
st="error"
reason="定義されない式"
}
}
else if st=="varFunc0" {
if az(chr)||in09(chr) {
ind+=1
nameBuffer=[nameBuffer,chr].join()
}
else if chr=="(" {
st="func0"
ind+=1
}
else {
st="common0"
if nameBuffer=="true" {
expressionList.push({type: "value",value: {type: "bool",value: true}})
}
else if nameBuffer=="false" {
expressionList.push({type: "value",value: {type: "bool",value: false}})
}
else {
expressionList.push({type: "var",value: nameBuffer})
}
nameBuffer=""
}
}
else if st=="func0" {
if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
dataBuffer=[dataBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="閉じていない式"
}
else if bracs.incl(chr) {
ind+=1
let result=bracEval(text,ind,chr)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
ind=result.ind
dataBuffer=[dataBuffer,chr,result.str,text.pick(ind-1)].join()
}
else if chr=="," {
ind+=1
dataList.push(dataBuffer)
dataBuffer=""
}
else if chr==")" {
ind+=1
if dataBuffer.len!=0 {
dataList.push(dataBuffer)
}
dataBuffer=""
var args=[]
each let d , dataList {
let value=evalExp(d)
if value.type=="error" {
return value
}
args.push(value)
}
expressionList.push({type: "value",value: runFunc(nameBuffer,args)})
nameBuffer=""
dataList=[]
st="common0"
}
else {
ind+=1
dataBuffer=[dataBuffer,chr].join()
}
}
else if st=="error" {
return {type: "error",reason: reason}
}
else if st=="opr0" {
if doubleOprs.incl(chr) {
st="opr1"
}
else {
expressionList.push({type: "opr",value: oprChar})
st="common0"
}
}
else if st=="opr1" {
expressionList.push({type: "opr",value: [oprChar,chr].join()})
ind+=1
st="common0"
}
else if st=="brac0" {
if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
expressionBuffer=[expressionBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="閉じていない括弧"
}
else if bracs.incl(chr) {
ind+=1
let result=bracEval(text,ind,chr)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
ind=result.ind
expressionBuffer=[expressionBuffer,chr,result.str,text.pick(ind-1)].join()
}
else if chr==")" {
ind+=1
let value=evalExp(expressionBuffer)
if value.type=="error" {
return value
}
expressionList.push(value)
expressionBuffer=""
st="common0"
}
else {
ind+=1
expressionBuffer=[expressionBuffer,chr].join()
}
}
else if st=="num0" {
if chr=="." {
ind+=1
dataBuffer=[dataBuffer,chr].join()
st="float0"
}
else if in09(chr) {
dataBuffer=[dataBuffer,chr].join()
ind+=1
}
else {
expressionList.push({type: "value",value: {type: "num",value: dataBuffer.to_num()}})
dataBuffer=""
st="common0"
}
}
else if st=="float0" {
if in09(chr) {
dataBuffer=[dataBuffer,chr].join()
ind+=1
}
else {
expressionList.push({type: "value",value: {type: "num",value: dataBuffer.to_num()}})
dataBuffer=""
st="common0"
}
}
else if st=="list0" {
if chr=='"' {
ind+=1
let result=strEval(text,ind)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
dataBuffer=[dataBuffer,'"',result.str,'"'].join()
ind=result.ind
}
else if chr==null {
st="error"
reason="閉じていない式"
}
else if bracs.incl(chr) {
ind+=1
let result=bracEval(text,ind,chr)
if result.type=="error" {
st="error"
reason=result.reason
continue
}
ind=result.ind
dataBuffer=[dataBuffer,chr,result.str,text.pick(ind-1)].join()
}
else if chr=="," {
ind+=1
dataList.push(dataBuffer)
dataBuffer=""
}
else if chr=="]" {
ind+=1
if dataBuffer.len!=0 {
dataList.push(dataBuffer)
}
dataBuffer=""
var evalList=[]
each let d , dataList {
let value=evalExp(d)
if value.type=="error" {
return value
}
evalList.push(value)
}
expressionList.push({type: "value",value: {type: "list",value: evalList}})
nameBuffer=""
dataList=[]
st="common0"
}
else {
ind+=1
dataBuffer=[dataBuffer,chr].join()
}
}
}
}
こっちの方が長い、特に演算を実行する部分がほんとに長い
分解してリストにまとめて最後に演算をするのだけれど、リストの中身は{要素の種類: 値か変数か演算子か,中身: 中身}
みたいにしてて、値の場合は更に{型: 型名,値: 値}
みたいな形で整理してるから、演算子に対してこの型同士で演算できるか(+どんな演算をするか)、それが変数の場合は中身を取り出すとかを毎回しないといけない
いや、もしかしたら最初に変数の中身を全部取り出したほうがよかったのかも(ただ、そうするにしてもインクリメントとデクリメントは元の変数の値を更新するために先に処理する必要がある)
まあそんな感じで完成して、実行例
var x = 3;
var y = 4;
var r = 5;
if(x*x+y*y<r*r){
print("("+toStr(x)+","+toStr(y)+")は半径"+toStr(r)+"の円の中にある");
}
elif(x*x+y*y==r*r){
print("("+toStr(x)+","+toStr(y)+")は半径"+toStr(r)+"の円の円周上にある");
}
else{
print("("+toStr(x)+","+toStr(y)+")は半径"+toStr(r)+"の円の中にない");
}
このコードを文字列としてrun関数に渡して実行させると、出力は
(3,4)は半径5の円の円周上にある
とちゃんと演算できる
これを元にプログラミングをするゲームをPlayで作ってみるのもあり、コードは公開しておくけどPlayで使う場合は圧縮版の方を使うといい、圧縮版はデバッグ機能の削除とインデントの削除、コメントの削除、変数名の短縮をしてできるだけ文字数が減るようにしてる、読みづらいからインタプリタ自体を改造する場合はノーマル版を書き換えてから自分で圧縮するほうがいいかも
勝手に使ったり改変したりしていいけどオリジナルが僕であることは言ってね、すしすきーの垢のリンク(https://sushi.ski/@potatokun
)やユーザー名(@potatokun@sushi.ski
)、それかGitHubのリンク(https://github.com/PotatoTimeKun
)とかどれかを貼ってもらえたらいいかな
使う上での注意は、print
関数をPoteScriptで実行したときやエラーが起きたときはAiScriptのprint
が呼び出されるようになってるからPlayにする場合はこの部分を変えないといけない、コードの上の方でPrint
としてprint関数の仕様を定義してるからその部分を変えることで対応させることができるよ、それと、無限ループを止めたい場合とか時間制限を設けたい場合は、終了したいときにグローバル変数runEnd
をtrueにすることで強制的にエラーを吐いて停止するようになってる、あとは、run関数は実行に成功すれば{state: "success"}
、エラーなら理由のプリントと同時に{state: "error"}
が返ってくる
PoteScriptの実行自体は僕が公開したPlayの方でもできるよ
それじゃ、またね~