Last active
December 20, 2022 09:12
-
-
Save adacola/fbce2eb9dcee7da92e19de42793fe698 to your computer and use it in GitHub Desktop.
FParsecでJSONパーサ書いてみた
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
{ | |
"cells": [ | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
} | |
}, | |
"source": [ | |
"# FParsec で JSON パーサ書いてみた\n", | |
"\n", | |
"- FParsec のドキュメント : https://www.quanttec.com/fparsec/\n", | |
"- JSON の定義(RFC8259) : https://www.rfc-editor.org/rfc/rfc8259" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>FParsec, 1.1.1</span></li><li><span>FSharpPlus, 1.3.2</span></li></ul></div></div>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"#r \"nuget: FSharpPlus\"\n", | |
"#r \"nuget: FParsec\"" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"open System\n", | |
"open FSharpPlus\n", | |
"open FParsec.Primitives\n", | |
"open FParsec.CharParsers" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"/// JSONの型定義\n", | |
"type JsonValue =\n", | |
" | JNull\n", | |
" | JBool of bool\n", | |
" | JString of string\n", | |
" | JNumber of decimal\n", | |
" | JObject of (string * JsonValue) list\n", | |
" | JArray of JsonValue list\n", | |
"\n", | |
"let jBoolTrue = JBool true\n", | |
"let jBoolFalse = JBool false\n", | |
"\n", | |
"/// JSON文字列を JsonValue にパースする\n", | |
"let parseJson input =\n", | |
" // JsonValueへのパーサを書いている途中に再帰的にJsonValueへのパーサを使う必要があるが、それを実現するための関数\n", | |
" // 戻り値の1つ目(jValue)を使ってパーサを書き、最終的にJsonValueへのパーサが完成したら戻り値の2つ目(jValueRef)に代入する\n", | |
" let jValue, jValueRef = createParserForwardedToRef<JsonValue, unit>()\n", | |
"\n", | |
" // anyOf は文字列内のいずれかの文字にマッチするパーサで、skipMany は引数のパーサに複数回(0回含む)マッチして結果を全部捨てるパーサ\n", | |
" // つまり ws は空白とみなされる文字たちに複数マッチしてそれを全部捨てるパーサになる\n", | |
" let ws = anyOf \" \\t\\r\\n\" |> skipMany\n", | |
"\n", | |
" // pchar は指定した文字にマッチするパーサで、 >>. は前後のパーサにマッチして.のある方(つまり後ろ)のパース結果だけを採用する演算子\n", | |
" // つまり valueSeparator は , の後に複数の空白文字がある文字列にマッチし、ws の方の結果を採用するが、ws は結果を全部捨てるパーサなので結局全部捨てられることになる\n", | |
" let valueSeparator = pchar ',' >>. ws\n", | |
"\n", | |
" // リテラルのパーサ\n", | |
" // .>> は前後のパーサにマッチして.のある方(つまり前)のパース結果だけを採用する演算子\n", | |
" // >>% は前のパーサにマッチしたら後ろの値を結果として採用する演算子\n", | |
" // つまり jNull は null という文字列にマッチしたら JNull の値を結果として採用するパーサ\n", | |
" let jNull = pstring \"null\" .>> ws >>% JNull\n", | |
" let jTrue = pstring \"true\" .>> ws >>% jBoolTrue\n", | |
" let jFalse = pstring \"false\" .>> ws >>% jBoolFalse\n", | |
"\n", | |
" // 文字列のパーサ\n", | |
" let str =\n", | |
" let escape = pchar '\\\\'\n", | |
" let hexToInt (c: char) = let c = int c in (c &&& 15) + (c >>> 6) * 9 // 16進数文字を数値に変換する関数\n", | |
"\n", | |
" // hex は16進数数字、つまり 0~9、a~f、A~F のいずれか1文字にマッチするパーサ\n", | |
" // pipe4 は4つのパーサを順番に適用して、それぞれのパース結果を最後の4引数関数の引数に順番に渡し、関数の結果を最終結果として採用するパーサ\n", | |
" // つまり unicodeHexCh は4つの連続した16進数数字にマッチして、4桁の16進数数値として解釈するパーサになる\n", | |
" let unicodeHexCh = pipe4 hex hex hex hex (fun h1 h2 h3 h4 -> hexToInt h1 * 4096 + hexToInt h2 * 256 + hexToInt h3 * 16 + hexToInt h4 |> char)\n", | |
"\n", | |
" // |>> は前のパーサの結果を引数として後ろの関数に渡し、関数の結果をパース結果として採用する演算子\n", | |
" // つまり escapedWithoutUnicodeCh は \\/\"bfnrt のいずれかにマッチして、例えば n だったら \\n(LF)にするみたいに |>> 以降のパターンマッチの結果がパーサの結果となる\n", | |
" let escapedWithoutUnicodeCh =\n", | |
" anyOf \"\"\"\\/\"bfnrt\"\"\" |>> function\n", | |
" | 'b' -> '\\b'\n", | |
" | 'f' -> '\\u000C'\n", | |
" | 'n' -> '\\n'\n", | |
" | 'r' -> '\\r'\n", | |
" | 't' -> '\\t'\n", | |
" | otherwise -> otherwise\n", | |
"\n", | |
" // <|> は前のパーサが成功したらその結果を採用し、前のパーサが失敗したら後ろのパーサを適用する演算子\n", | |
" // つまり escapedCh は \\ の後に、 \\/\"bfnrt のいずれかが来るかもしくは u の後にUnicode文字を表す4つの16進数文字が来るパーサ\n", | |
" // 注意点として、 <|> の前のパーサで途中までは成功したけど結局失敗した場合、元の状態に復帰できないのでエラーとなってしまう\n", | |
" // これを回避するには attempt という関数を使って途中で失敗しても最初まで巻き戻るようにする\n", | |
" // ここでは <|> の前の方の escapedWithoutUnicodeCh は1文字のパースを試みるだけで途中まで成功という状態が発生しないので attempt を使わなくても大丈夫\n", | |
" let escapedCh = escape >>. (escapedWithoutUnicodeCh <|> (pchar 'u' >>. unicodeHexCh))\n", | |
"\n", | |
" // satisfy は読み込んだ文字が与えられた関数の条件を満たした場合だけ成功するパーサ\n", | |
" // つまり unescapedCh は読み込んだ文字が 0x20~0x21、0x23~0x5b、0x5d以上の場合だけパースに成功する\n", | |
" let unescapedCh = satisfy (int >> fun c -> (0x23 <= c && c <= 0x5b) || 0x5d <= c || c = 0x20 || c = 0x21)\n", | |
"\n", | |
" let ch = escapedCh <|> unescapedCh\n", | |
" let qq = pchar '\"'\n", | |
"\n", | |
" // between は1つ目のパーサから始まり2つ目のパーサで終わり、その中は3つ目のパーサとマッチする場合、3つ目のパーサの結果を採用するパーサ\n", | |
" // manyChars は引数の文字パーサに複数(0個含む)マッチして文字を結合した文字列が結果となるパーサ\n", | |
" // つまり str は \" で始まり \" で終わり、その中は文字列となっていて、\"\"の中の文字列を結果として採用するパーサとなる\n", | |
" // 注意点として、manyChars ch のパースが終わった時、つまり ch のパースに失敗した時に終わりの \" が来たかのチェックが行われるため、\" が ch にマッチしないように定義しておく必要がある\n", | |
" let str = between qq qq (manyChars ch)\n", | |
" str\n", | |
" let jString = str .>> ws |>> JString\n", | |
"\n", | |
" // 数値のパーサ\n", | |
" let jNumber =\n", | |
" // pstring は指定した文字列にマッチするパーサ\n", | |
" let minus = pstring \"-\"\n", | |
" let plus = pstring \"+\"\n", | |
"\n", | |
" // pipe2 は前述した pipe4 のパーサ2つ版\n", | |
" // digit は 0~9 のいずれか1文字にマッチするパーサ\n", | |
" let integer = (pstring \"0\") <|> (pipe2 (anyOf ['1' .. '9'] |>> string) (manyChars digit) (+))\n", | |
"\n", | |
" // <|>% は前のパーサに失敗したら後ろの値をパース結果として採用するパーサ\n", | |
" // つまり minus <|> plus <|>% \"+\" は - または + にマッチし、両方ともない場合でも結果を + ということにしてパース成功とみなす\n", | |
" // many1Chars は manyChars とほぼ同じだが1個以上はないといけないパーサ\n", | |
" let exp = anyOf \"eE\" >>. pipe2 (minus <|> plus <|>% \"+\") (many1Chars digit) (fun sign digits -> \"e\" + sign + digits)\n", | |
"\n", | |
" let frac = pchar '.' >>. many1Chars digit |>> (+) \".\"\n", | |
"\n", | |
" pipe4 (minus <|>% \"\") integer (frac <|>% \"\") (exp <|>% \"\") (fun s i f e -> s + i + f + e |> decimal |> JNumber) .>> ws\n", | |
"\n", | |
" // オブジェクトのパーサ\n", | |
" let jObject =\n", | |
" let beginObject = pchar '{' >>. ws\n", | |
" let endObject = pchar '}' >>. ws\n", | |
" let nameSeparator = ws >>. pchar ':' >>. ws\n", | |
"\n", | |
" // .>>. は前後のパーサにマッチして.のある方(つまり両方)の結果とも採用する演算子(結果はタプルとなる)\n", | |
" // つまり objectMember はキーとなる文字列の後に : が来てその後にJsonValueが来る場合にマッチし、結果はキー文字列とJsonValueのタプルとなる\n", | |
" let objectMember = str .>> nameSeparator .>>. jValue\n", | |
"\n", | |
" // sepBy は1つ目のパーサが2つ目のパーサで区切られて連続しているパーサ(0個でもOKで最後に区切りが来るのは許容しない)\n", | |
" // つまり objectMembers は objectMember が , で区切られて連続している場合にマッチするパーサ\n", | |
" // 1個以上あることを要求する場合は sepBy1 を使い、最後に区切りが来てもOKにする場合は sepEndBy を使う(sepEndBy1もある)\n", | |
" let objectMembers = sepBy objectMember valueSeparator\n", | |
"\n", | |
" beginObject >>. objectMembers .>> endObject |>> JObject\n", | |
"\n", | |
" // 配列のパーサ\n", | |
" let jArray =\n", | |
" let beginArray = pchar '[' >>. ws\n", | |
" let endArray = pchar ']' >>. ws\n", | |
" let arrayMembers = sepBy jValue valueSeparator\n", | |
" beginArray >>. arrayMembers .>> endArray |>> JArray\n", | |
"\n", | |
" // JsonValue のパーサ\n", | |
" // choice はリストで与えられたいずれかのパーサにマッチした場合に成功となるパーサで、<|> のリスト版みたいな感じ\n", | |
" // これを jValueRef に代入、つまり jValue の定義としている\n", | |
" jValueRef.Value <- ws >>. choice [jFalse; jNull; jTrue; jObject; jArray; jNumber; jString]\n", | |
"\n", | |
" // JSON のパーサ\n", | |
" // eofは入力の終わりとマッチするパーサ\n", | |
" // つまり json とは入力に JsonValue だけが書かれている場合にマッチするパーサ\n", | |
" let json = jValue .>> eof\n", | |
" \n", | |
" // json パーサを入力文字列に対して実行\n", | |
" match run json input with\n", | |
" | ParserResult.Success(result, _, _) -> Result.Ok result\n", | |
" | ParserResult.Failure(reason, _, _) -> Result.Error reason" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"// JSONクイズの内容をJSONパーサに食わせてみる\n", | |
"let jsonQuiz = [\n", | |
" \"{}\"\n", | |
" \"[]\"\n", | |
" \"[{}]\"\n", | |
" \"{{}}\"\n", | |
" \"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\"\n", | |
" \"undefined\"\n", | |
" \"Null\"\n", | |
" \"\\\"\\\"\"\n", | |
" \"0.00\"\n", | |
" \"0xA0\"\n", | |
" \"047\"\n", | |
" \"\\\"aaa\n", | |
"bbb\\\"\"\n", | |
" \"\\\"\\\\n\\\"\"\n", | |
" \"100+200\"\n", | |
" \"{key: 100}\"\n", | |
" \"\"\"{\"key1\": 100, \"key2\": 200,}\"\"\"\n", | |
" \"\"\"{\"key1\": [], \"key2\": {}}\"\"\"\n", | |
" \"\"\"{ \"key1\"\n", | |
":\n", | |
"100\n", | |
",\n", | |
" \"key2\": 200 }\"\"\"\n", | |
" \"1 23\"\n", | |
" \"'1 23'\"\n", | |
" \" \"\n", | |
" \"{}\\n\"\n", | |
" \"true\"\n", | |
" \"!false\"\n", | |
" \"\"\"{\"\": 100}\"\"\"\n", | |
" \"\"\"{\"\"}\"\"\"\n", | |
" \"{null: 100}\"\n", | |
" \"\"\"{\"\"::100}\"\"\"\n", | |
" \"[,]\"\n", | |
" \"\"\"{,\"key\": 100}\"\"\"\n", | |
" \"\\\"\\\"\\\"\\\"\"\n", | |
" \"\\\"2022-12-07T07:10:48.672Z\\\"\"\n", | |
" \"\"\"{\n", | |
" \"key\":\n", | |
" - 100\n", | |
" - 200\n", | |
"}\"\"\"\n", | |
" \"\"\"{\n", | |
" \"key\": \"100,200\"\n", | |
"}\"\"\"\n", | |
" \"\"\"{\n", | |
" \"key\": [100, 200]\n", | |
"}\"\"\"\n", | |
" \"100\n", | |
"100\"\n", | |
"]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"問題1: {}\n", | |
"Ok (JObject [])\n", | |
"--------------------\n", | |
"問題2: []\n", | |
"Ok (JArray [])\n", | |
"--------------------\n", | |
"問題3: [{}]\n", | |
"Ok (JArray [JObject []])\n", | |
"--------------------\n", | |
"問題4: {{}}\n", | |
"Error \"Error in Ln: 1 Col: 2\n", | |
"{{}}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, '\"' or '}'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題5: [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\n", | |
"Ok\n", | |
" (JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" [JArray\n", | |
" []]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]])\n", | |
"--------------------\n", | |
"問題6: undefined\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 1\n", | |
"undefined\n", | |
"^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題7: Null\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 1\n", | |
"Null\n", | |
"^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題8: \"\"\n", | |
"Ok (JString \"\")\n", | |
"--------------------\n", | |
"問題9: 0.00\n", | |
"Ok (JNumber 0.00M)\n", | |
"--------------------\n", | |
"問題10: 0xA0\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
"0xA0\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, any char in ‘eE’, end of input or '.'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題11: 047\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
"047\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, any char in ‘eE’, end of input or '.'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題12: \"aaa\n", | |
"bbb\"\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 5\n", | |
"\"aaa\n", | |
" ^\n", | |
"Note: The error occurred at the end of the line.\n", | |
"Expecting: '\"' or '\\\\'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題13: \"\\n\"\n", | |
"Ok (JString \"\n", | |
"\")\n", | |
"--------------------\n", | |
"問題14: 100+200\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 4\n", | |
"100+200\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, any char in ‘eE’, decimal digit, end of input or\n", | |
"'.'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題15: {key: 100}\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
"{key: 100}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, '\"' or '}'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題16: {\"key1\": 100, \"key2\": 200,}\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 27\n", | |
"{\"key1\": 100, \"key2\": 200,}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’ or '\"'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題17: {\"key1\": [], \"key2\": {}}\n", | |
"Ok (JObject [(\"key1\", JArray []); (\"key2\", JObject [])])\n", | |
"--------------------\n", | |
"問題18: { \"key1\"\n", | |
":\n", | |
"100\n", | |
",\n", | |
" \"key2\": 200 }\n", | |
"Ok (JObject [(\"key1\", JNumber 100M); (\"key2\", JNumber 200M)])\n", | |
"--------------------\n", | |
"問題19: 1 23\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 3\n", | |
"1 23\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’ or end of input\n", | |
"\"\n", | |
"--------------------\n", | |
"問題20: '1 23'\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 1\n", | |
"'1 23'\n", | |
"^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題21: \n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
" \n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題22: {}\n", | |
"\n", | |
"Ok (JObject [])\n", | |
"--------------------\n", | |
"問題23: true\n", | |
"Ok (JBool true)\n", | |
"--------------------\n", | |
"問題24: !false\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 1\n", | |
"!false\n", | |
"^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題25: {\"\": 100}\n", | |
"Ok (JObject [(\"\", JNumber 100M)])\n", | |
"--------------------\n", | |
"問題26: {\"\"}\n", | |
"Error \"Error in Ln: 1 Col: 4\n", | |
"{\"\"}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’ or ':'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題27: {null: 100}\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
"{null: 100}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, '\"' or '}'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題28: {\"\"::100}\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 5\n", | |
"{\"\"::100}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題29: [,]\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
"[,]\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[', ']',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題30: {,\"key\": 100}\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 2\n", | |
"{,\"key\": 100}\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’, '\"' or '}'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題31: \"\"\"\"\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 3\n", | |
"\"\"\"\"\n", | |
" ^\n", | |
"Expecting: any char in ‘ ’ or end of input\n", | |
"\"\n", | |
"--------------------\n", | |
"問題32: \"2022-12-07T07:10:48.672Z\"\n", | |
"Ok (JString \"2022-12-07T07:10:48.672Z\")\n", | |
"--------------------\n", | |
"問題33: {\n", | |
" \"key\":\n", | |
" - 100\n", | |
" - 200\n", | |
"}\n", | |
"Error\n", | |
" \"Error in Ln: 3 Col: 3\n", | |
" - 100\n", | |
" ^\n", | |
"Expecting: any char in ‘123456789’ or '0'\n", | |
"\"\n", | |
"--------------------\n", | |
"問題34: {\n", | |
" \"key\": \"100,200\"\n", | |
"}\n", | |
"Ok (JObject [(\"key\", JString \"100,200\")])\n", | |
"--------------------\n", | |
"問題35: {\n", | |
" \"key\": [100, 200]\n", | |
"}\n", | |
"Ok (JObject [(\"key\", JArray [JNumber 100M; JNumber 200M])])\n", | |
"--------------------\n", | |
"問題36: 100\n", | |
"100\n", | |
"Error \"Error in Ln: 2 Col: 1\n", | |
"100\n", | |
"^\n", | |
"Expecting: any char in ‘ ’ or end of input\n", | |
"\"\n", | |
"--------------------\n" | |
] | |
} | |
], | |
"source": [ | |
"jsonQuiz |> List.iteri (fun i q -> let answer = parseJson q in printfn $\"問題{i + 1}: {q}\\n%A{answer}\\n--------------------\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"// JSONクイズにないが試しておきたいテストパターン\n", | |
"// 成功パターン\n", | |
"let anotherQuizSuccess = [\n", | |
" // まだテストしてないリテラル\n", | |
" \"null\"\n", | |
" \"false\"\n", | |
" // 数値\n", | |
" \"1\"\n", | |
" \"0\"\n", | |
" \"1.2\"\n", | |
" \"1.23\"\n", | |
" \"1.0\"\n", | |
" \"-1\"\n", | |
" \"-0\"\n", | |
" \"1e2\"\n", | |
" \"1e-2\"\n", | |
" \"1e+2\"\n", | |
" \"1E2\"\n", | |
" \"1E-2\"\n", | |
" \"1E+2\"\n", | |
" \"1e0\"\n", | |
" \"1e-0\"\n", | |
" \"1e+0\"\n", | |
" \"1.23e2\"\n", | |
" \"1.23e-2\"\n", | |
" \"1.23e+2\"\n", | |
" \"1.0e2\"\n", | |
" \"1.0e-2\"\n", | |
" \"1.0e+2\"\n", | |
" \"-1.23e-10\"\n", | |
" \"-2e+11\"\n", | |
" \"-3e12\"\n", | |
" // 制御文字\n", | |
" \"\\\"\\\\r\\\\n\\\"\"\n", | |
" \"\\\"\\\\t\\\"\"\n", | |
" \"\\\"1文字削除!\\\\b\\\"\"\n", | |
" \"\\\"\\\\f\\\"\"\n", | |
" // ユニコード\n", | |
" \"\\\"\\\\u3041\\\"\"\n", | |
" \"\\\"\\\\u304A\\\"\"\n", | |
" \"\\\"\\\\u304a\\\"\"\n", | |
" \"\\\"\\\\u30411\\\"\"\n", | |
" \"\\\"\\\\uD867\\\\uDE3D\\\"\" // サロゲートペアなので1文字の漢字になる\n", | |
" // 複合\n", | |
" \"\"\"{\"key1\":{\"key1-1\":\"hoge\",\"key1-2\":1,\"key1-3\":true,\"key1-4\":false,\"key1-5\":null,\"key1-6\":[\"foo\",\"bar\"]},\"key2\":[1.1,\"2\",true,false,null,{\"key2-1\":\"fuga\"},[\"sumi\",\"nazu\"]]}\"\"\"\n", | |
"]\n", | |
"// 失敗パターン\n", | |
"let anotherQuizFailure = [\n", | |
" \"1.\"\n", | |
" \"-1.\"\n", | |
" \"+1\"\n", | |
" \"+0\"\n", | |
" \"0e\"\n", | |
" \"1E\"\n", | |
" \"0e-\"\n", | |
" \"0e+\"\n", | |
" \"\\\"\\\\u123\\\"\"\n", | |
" \"\\\"\\\\u12\\\"\"\n", | |
" \"\\\"\\\\u1\\\"\"\n", | |
" \"\\\"\\\\u\\\"\"\n", | |
"]\n", | |
"let anotherQuiz = anotherQuizSuccess @ anotherQuizFailure" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": { | |
"dotnet_interactive": { | |
"language": "fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelName": "fsharp" | |
}, | |
"vscode": { | |
"languageId": "polyglot-notebook" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"問題1: null\n", | |
"Ok JNull\n", | |
"------------------------\n", | |
"問題2: false\n", | |
"Ok (JBool false)\n", | |
"------------------------\n", | |
"問題3: 1\n", | |
"Ok (JNumber 1M)\n", | |
"------------------------\n", | |
"問題4: 0\n", | |
"Ok (JNumber 0M)\n", | |
"------------------------\n", | |
"問題5: 1.2\n", | |
"Ok (JNumber 1.2M)\n", | |
"------------------------\n", | |
"問題6: 1.23\n", | |
"Ok (JNumber 1.23M)\n", | |
"------------------------\n", | |
"問題7: 1.0\n", | |
"Ok (JNumber 1.0M)\n", | |
"------------------------\n", | |
"問題8: -1\n", | |
"Ok (JNumber -1M)\n", | |
"------------------------\n", | |
"問題9: -0\n", | |
"Ok (JNumber 0M)\n", | |
"------------------------\n", | |
"問題10: 1e2\n", | |
"Ok (JNumber 100M)\n", | |
"------------------------\n", | |
"問題11: 1e-2\n", | |
"Ok (JNumber 0.01M)\n", | |
"------------------------\n", | |
"問題12: 1e+2\n", | |
"Ok (JNumber 100M)\n", | |
"------------------------\n", | |
"問題13: 1E2\n", | |
"Ok (JNumber 100M)\n", | |
"------------------------\n", | |
"問題14: 1E-2\n", | |
"Ok (JNumber 0.01M)\n", | |
"------------------------\n", | |
"問題15: 1E+2\n", | |
"Ok (JNumber 100M)\n", | |
"------------------------\n", | |
"問題16: 1e0\n", | |
"Ok (JNumber 1M)\n", | |
"------------------------\n", | |
"問題17: 1e-0\n", | |
"Ok (JNumber 1M)\n", | |
"------------------------\n", | |
"問題18: 1e+0\n", | |
"Ok (JNumber 1M)\n", | |
"------------------------\n", | |
"問題19: 1.23e2\n", | |
"Ok (JNumber 123M)\n", | |
"------------------------\n", | |
"問題20: 1.23e-2\n", | |
"Ok (JNumber 0.0123M)\n", | |
"------------------------\n", | |
"問題21: 1.23e+2\n", | |
"Ok (JNumber 123M)\n", | |
"------------------------\n", | |
"問題22: 1.0e2\n", | |
"Ok (JNumber 100M)\n", | |
"------------------------\n", | |
"問題23: 1.0e-2\n", | |
"Ok (JNumber 0.010M)\n", | |
"------------------------\n", | |
"問題24: 1.0e+2\n", | |
"Ok (JNumber 100M)\n", | |
"------------------------\n", | |
"問題25: -1.23e-10\n", | |
"Ok (JNumber -0.000000000123M)\n", | |
"------------------------\n", | |
"問題26: -2e+11\n", | |
"Ok (JNumber -200000000000M)\n", | |
"------------------------\n", | |
"問題27: -3e12\n", | |
"Ok (JNumber -3000000000000M)\n", | |
"------------------------\n", | |
"問題28: \"\\r\\n\"\n", | |
"Ok (JString \"\n", | |
"\")\n", | |
"------------------------\n", | |
"問題29: \"\\t\"\n", | |
"Ok (JString \"\t\")\n", | |
"------------------------\n", | |
"問題30: \"1文字削除!\\b\"\n", | |
"Ok (JString \"1文字削除\")\n", | |
"------------------------\n", | |
"問題31: \"\\f\"\n", | |
"Ok (JString \"\f\")\n", | |
"------------------------\n", | |
"問題32: \"\\u3041\"\n", | |
"Ok (JString \"ぁ\")\n", | |
"------------------------\n", | |
"問題33: \"\\u304A\"\n", | |
"Ok (JString \"お\")\n", | |
"------------------------\n", | |
"問題34: \"\\u304a\"\n", | |
"Ok (JString \"お\")\n", | |
"------------------------\n", | |
"問題35: \"\\u30411\"\n", | |
"Ok (JString \"ぁ1\")\n", | |
"------------------------\n", | |
"問題36: \"\\uD867\\uDE3D\"\n", | |
"Ok (JString \"𩸽\")\n", | |
"------------------------\n", | |
"問題37: {\"key1\":{\"key1-1\":\"hoge\",\"key1-2\":1,\"key1-3\":true,\"key1-4\":false,\"key1-5\":null,\"key1-6\":[\"foo\",\"bar\"]},\"key2\":[1.1,\"2\",true,false,null,{\"key2-1\":\"fuga\"},[\"sumi\",\"nazu\"]]}\n", | |
"Ok\n", | |
" (JObject\n", | |
" [(\"key1\",\n", | |
" JObject\n", | |
" [(\"key1-1\", JString \"hoge\"); (\"key1-2\", JNumber 1M);\n", | |
" (\"key1-3\", JBool true); (\"key1-4\", JBool false); (\"key1-5\", JNull);\n", | |
" (\"key1-6\", JArray [JString \"foo\"; JString \"bar\"])]);\n", | |
" (\"key2\",\n", | |
" JArray\n", | |
" [JNumber 1.1M; JString \"2\"; JBool true; JBool false; JNull;\n", | |
" JObject [(\"key2-1\", JString \"fuga\")];\n", | |
" JArray [JString \"sumi\"; JString \"nazu\"]])])\n", | |
"------------------------\n", | |
"問題38: 1.\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 3\n", | |
"1.\n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: decimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題39: -1.\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 4\n", | |
"-1.\n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: decimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題40: +1\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 1\n", | |
"+1\n", | |
"^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"------------------------\n", | |
"問題41: +0\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 1\n", | |
"+0\n", | |
"^\n", | |
"Expecting: any char in ‘ ’, any char in ‘123456789’, '\"', '-', '0', '[',\n", | |
"'false', 'null', 'true' or '{'\n", | |
"\"\n", | |
"------------------------\n", | |
"問題42: 0e\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 3\n", | |
"0e\n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: decimal digit, '+' or '-'\n", | |
"\"\n", | |
"------------------------\n", | |
"問題43: 1E\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 3\n", | |
"1E\n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: decimal digit, '+' or '-'\n", | |
"\"\n", | |
"------------------------\n", | |
"問題44: 0e-\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 4\n", | |
"0e-\n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: decimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題45: 0e+\n", | |
"Error\n", | |
" \"Error in Ln: 1 Col: 4\n", | |
"0e+\n", | |
" ^\n", | |
"Note: The error occurred at the end of the input stream.\n", | |
"Expecting: decimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題46: \"\\u123\"\n", | |
"Error \"Error in Ln: 1 Col: 7\n", | |
"\"\\u123\"\n", | |
" ^\n", | |
"Expecting: hexadecimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題47: \"\\u12\"\n", | |
"Error \"Error in Ln: 1 Col: 6\n", | |
"\"\\u12\"\n", | |
" ^\n", | |
"Expecting: hexadecimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題48: \"\\u1\"\n", | |
"Error \"Error in Ln: 1 Col: 5\n", | |
"\"\\u1\"\n", | |
" ^\n", | |
"Expecting: hexadecimal digit\n", | |
"\"\n", | |
"------------------------\n", | |
"問題49: \"\\u\"\n", | |
"Error \"Error in Ln: 1 Col: 4\n", | |
"\"\\u\"\n", | |
" ^\n", | |
"Expecting: hexadecimal digit\n", | |
"\"\n", | |
"------------------------\n" | |
] | |
} | |
], | |
"source": [ | |
"anotherQuiz |> List.iteri (fun i q -> let answer = parseJson q in printfn $\"問題{i + 1}: {q}\\n%A{answer}\\n------------------------\")" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": ".NET (F#)", | |
"language": "F#", | |
"name": ".net-fsharp" | |
}, | |
"polyglot_notebook": { | |
"kernelInfo": { | |
"defaultKernelName": "fsharp", | |
"items": [ | |
{ | |
"aliases": [ | |
"f#", | |
"F#" | |
], | |
"languageName": "F#", | |
"name": "fsharp" | |
}, | |
{ | |
"aliases": [], | |
"name": ".NET" | |
}, | |
{ | |
"aliases": [ | |
"frontend" | |
], | |
"name": "vscode" | |
}, | |
{ | |
"aliases": [ | |
"c#", | |
"C#" | |
], | |
"languageName": "C#", | |
"name": "csharp" | |
}, | |
{ | |
"aliases": [], | |
"languageName": "HTML", | |
"name": "html" | |
}, | |
{ | |
"aliases": [], | |
"languageName": "http", | |
"name": "httpRequest" | |
}, | |
{ | |
"aliases": [ | |
"js" | |
], | |
"languageName": "JavaScript", | |
"name": "javascript" | |
}, | |
{ | |
"aliases": [], | |
"languageName": "KQL", | |
"name": "kql" | |
}, | |
{ | |
"aliases": [], | |
"languageName": "Mermaid", | |
"name": "mermaid" | |
}, | |
{ | |
"aliases": [ | |
"powershell" | |
], | |
"languageName": "PowerShell", | |
"name": "pwsh" | |
}, | |
{ | |
"aliases": [], | |
"languageName": "SQL", | |
"name": "sql" | |
}, | |
{ | |
"aliases": [], | |
"name": "value" | |
}, | |
{ | |
"aliases": [], | |
"name": "webview" | |
} | |
] | |
} | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment