Created
March 3, 2022 14:14
-
-
Save ya-s-u/cdc9f40c6232acd9038ab4a2bc0ed97c to your computer and use it in GitHub Desktop.
パーサーコンビネータ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface Done<T> { | |
type: 'done'; | |
result: T; | |
tail: string; | |
}; | |
interface Failed { | |
type: 'failed'; | |
}; | |
type Reply<T> = Done<T> | Failed | |
interface Parser<T> { | |
parse: (input: string) => Reply<T> | |
} | |
// 文字列 | |
const token = (token: string): Parser<string> => ({ | |
parse: (input: string) => { | |
if (input.startsWith(token)) { | |
return { | |
type: 'done', | |
result: token, | |
tail: input.substring(token.length) | |
} | |
} else { | |
return { | |
type: 'failed' | |
} | |
} | |
} | |
}) | |
const hogeParser = token('hoge'); | |
// hogeParser.parse('hogehoge') | |
// { | |
// "type": "done", | |
// "result": "hoge", | |
// "tail": "hoge" | |
// } | |
// hogeParser.parse('fugafuga') | |
// { | |
// "type": "failed" | |
// } | |
// 繰り返し | |
const many = (parser: Parser<string>): Parser<string[]> => ({ | |
parse: (input: string) => { | |
const results: string[] = []; | |
var tail = input; | |
loop: { | |
while (true) { | |
const reply = parser.parse(tail) | |
switch (reply.type) { | |
case 'done': | |
results.push(reply.result) | |
tail = reply.tail | |
break | |
case 'failed': | |
break loop | |
} | |
} | |
} | |
return { | |
type: 'done', | |
result: results, | |
tail | |
} | |
} | |
}) | |
// many(hogeParser).parse('hogehoge') | |
// { | |
// "type": "done", | |
// "result": [ | |
// "hoge", | |
// "hoge" | |
// ], | |
// "tail": "" | |
// } | |
// many(hogeParser).parse('hogehogefuga') | |
// { | |
// "type": "done", | |
// "result": [ | |
// "hoge", | |
// "hoge" | |
// ], | |
// "tail": "fuga" | |
// } | |
// 選択 | |
const choice = (parser1: Parser<string>, parser2: Parser<string>): Parser<string> => ({ | |
parse: (input: string) => { | |
const result1 = parser1.parse(input) | |
const result2 = parser2.parse(input) | |
if (result1.type === 'done') { | |
return result1 | |
} else if (result2.type === 'done') { | |
return result2 | |
} else { | |
return { | |
type: 'failed' | |
} | |
} | |
} | |
}) | |
const fugaParser = token('fuga') | |
// choice(hogeParser, fugaParser).parse('hoge') | |
// { | |
// "type": "done", | |
// "result": "hoge", | |
// "tail": "" | |
// } | |
// choice(hogeParser, fugaParser).parse('fuga') | |
// { | |
// "type": "done", | |
// "result": "fuga", | |
// "tail": "" | |
// } | |
// choice(hogeParser, fugaParser).parse('piyo') | |
// { | |
// "type": "failed" | |
// } | |
// 加工 | |
const map = (parser: Parser<string>, transform: (from: string) => string): Parser<string> => ({ | |
parse: (input: string) => { | |
const result = parser.parse(input) | |
switch (result.type) { | |
case 'done': | |
return { | |
type: 'done', | |
result: transform(result.result), | |
tail: result.tail | |
} | |
case 'failed': | |
return result | |
} | |
} | |
}) | |
const describeParser = map(hogeParser, (from: string) => `${from}パースした`) | |
// describeParser.parse('hoge') | |
// { | |
// "type": "done", | |
// "result": "hogeパースした", | |
// "tail": "" | |
// } | |
// 連結 | |
const seq = (parsers: Parser<string>[]): Parser<string[]> => ({ | |
parse: (input: string) => { | |
const results: string[] = []; | |
var tail = input; | |
parsers.forEach(parser => { | |
const result = parser.parse(tail) | |
switch (result.type) { | |
case 'done': | |
results.push(result.result); | |
tail = result.tail; | |
break | |
case 'failed': | |
return result; | |
} | |
}) | |
return { | |
type: 'done', | |
result: results, | |
tail | |
} | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment