├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Examples ├── Diagram │ ├── 第一章.ykm │ ├── 第三章.ykm │ └── 第二章.ykm └── Type Checker Example.ykm ├── LICENSE ├── README.md ├── YukimiScript.CodeGen.Bytecode ├── Bytecode.fs └── YukimiScript.CodeGen.Bytecode.fsproj ├── YukimiScript.CodeGen ├── Json.fs ├── Lua.fs ├── PyMO.fs └── YukimiScript.CodeGen.fsproj ├── YukimiScript.CommandLineTool ├── Program.fs └── YukimiScript.CommandLineTool.fsproj ├── YukimiScript.Parser.Test ├── Main.fs ├── TestConstants.fs ├── TestScript.fs ├── TestStatments.fs ├── TestText.fs ├── TestTopLevels.fs ├── Utils.fs └── YukimiScript.Parser.Test.fsproj ├── YukimiScript.Parser ├── Basics.fs ├── CompilePipe.fs ├── Constants.fs ├── Diagram.fs ├── Dom.fs ├── EditorHelper.fs ├── Elements.fs ├── ErrorStringing.fs ├── Intermediate.fs ├── Macro.fs ├── Parser.fs ├── ParserMonad.fs ├── Statment.fs ├── Text.fs ├── TopLevels.fs ├── TypeChecker.fs ├── Utils.fs └── YukimiScript.Parser.fsproj ├── YukimiScript.Runtime.Bytecode.Parser ├── YukimiScript.Runtime.Bytecode.Parser.csproj └── YukimiScriptBinary.cs ├── YukimiScript.sln └── yukimiscript-syntax-highlight-vscode ├── .vscode └── launch.json ├── .vscodeignore ├── language-configuration.json ├── package.json └── syntaxes └── yukimiscript.tmLanguage.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-compiler-nuget-pack: 11 | name: Build NuGet Version 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | name: Checkout 16 | 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 8.0.x 21 | 22 | - name: Test 23 | run: dotnet test -c Release 24 | 25 | - name: Build 26 | run: dotnet pack YukimiScript.CommandLineTool/YukimiScript.CommandLineTool.fsproj -c Release 27 | 28 | - name: Upload 29 | uses: actions/upload-artifact@v3.1.1 30 | with: 31 | name: YukimiScript Compiler for NuGet 32 | path: ./YukimiScript.CommandLineTool/bin/Release/*.nupkg 33 | if-no-files-found: error 34 | 35 | build-vscode-extension: 36 | name: Build Visual Studio Code Extension 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | name: Checkout 41 | 42 | - name: Build 43 | run: | 44 | npm i vsce -g 45 | cd ./yukimiscript-syntax-highlight-vscode 46 | vsce package 47 | 48 | - name: Upload 49 | uses: actions/upload-artifact@v3.1.1 50 | with: 51 | name: YukimiScript Visual Studio Code Extension 52 | path: ./yukimiscript-syntax-highlight-vscode/*.vsix 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | .vscode 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Mono auto generated files 19 | mono_crash.* 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | [Ww][Ii][Nn]32/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # Tye 68 | .tye/ 69 | 70 | # ASP.NET Scaffolding 71 | ScaffoldingReadMe.txt 72 | 73 | # StyleCop 74 | StyleCopReport.xml 75 | 76 | # Files built by Visual Studio 77 | *_i.c 78 | *_p.c 79 | *_h.h 80 | *.ilk 81 | *.meta 82 | *.obj 83 | *.iobj 84 | *.pch 85 | *.pdb 86 | *.ipdb 87 | *.pgc 88 | *.pgd 89 | *.rsp 90 | *.sbr 91 | *.tlb 92 | *.tli 93 | *.tlh 94 | *.tmp 95 | *.tmp_proj 96 | *_wpftmp.csproj 97 | *.log 98 | *.vspscc 99 | *.vssscc 100 | .builds 101 | *.pidb 102 | *.svclog 103 | *.scc 104 | 105 | # Chutzpah Test files 106 | _Chutzpah* 107 | 108 | # Visual C++ cache files 109 | ipch/ 110 | *.aps 111 | *.ncb 112 | *.opendb 113 | *.opensdf 114 | *.sdf 115 | *.cachefile 116 | *.VC.db 117 | *.VC.VC.opendb 118 | 119 | # Visual Studio profiler 120 | *.psess 121 | *.vsp 122 | *.vspx 123 | *.sap 124 | 125 | # Visual Studio Trace Files 126 | *.e2e 127 | 128 | # TFS 2012 Local Workspace 129 | $tf/ 130 | 131 | # Guidance Automation Toolkit 132 | *.gpState 133 | 134 | # ReSharper is a .NET coding add-in 135 | _ReSharper*/ 136 | *.[Rr]e[Ss]harper 137 | *.DotSettings.user 138 | 139 | # TeamCity is a build add-in 140 | _TeamCity* 141 | 142 | # DotCover is a Code Coverage Tool 143 | *.dotCover 144 | 145 | # AxoCover is a Code Coverage Tool 146 | .axoCover/* 147 | !.axoCover/settings.json 148 | 149 | # Coverlet is a free, cross platform Code Coverage Tool 150 | coverage*.json 151 | coverage*.xml 152 | coverage*.info 153 | 154 | # Visual Studio code coverage results 155 | *.coverage 156 | *.coveragexml 157 | 158 | # NCrunch 159 | _NCrunch_* 160 | .*crunch*.local.xml 161 | nCrunchTemp_* 162 | 163 | # MightyMoose 164 | *.mm.* 165 | AutoTest.Net/ 166 | 167 | # Web workbench (sass) 168 | .sass-cache/ 169 | 170 | # Installshield output folder 171 | [Ee]xpress/ 172 | 173 | # DocProject is a documentation generator add-in 174 | DocProject/buildhelp/ 175 | DocProject/Help/*.HxT 176 | DocProject/Help/*.HxC 177 | DocProject/Help/*.hhc 178 | DocProject/Help/*.hhk 179 | DocProject/Help/*.hhp 180 | DocProject/Help/Html2 181 | DocProject/Help/html 182 | 183 | # Click-Once directory 184 | publish/ 185 | 186 | # Publish Web Output 187 | *.[Pp]ublish.xml 188 | *.azurePubxml 189 | # Note: Comment the next line if you want to checkin your web deploy settings, 190 | # but database connection strings (with potential passwords) will be unencrypted 191 | *.pubxml 192 | *.publishproj 193 | 194 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 195 | # checkin your Azure Web App publish settings, but sensitive information contained 196 | # in these scripts will be unencrypted 197 | PublishScripts/ 198 | 199 | # NuGet Packages 200 | *.nupkg 201 | # NuGet Symbol Packages 202 | *.snupkg 203 | # The packages folder can be ignored because of Package Restore 204 | **/[Pp]ackages/* 205 | # except build/, which is used as an MSBuild target. 206 | !**/[Pp]ackages/build/ 207 | # Uncomment if necessary however generally it will be regenerated when needed 208 | #!**/[Pp]ackages/repositories.config 209 | # NuGet v3's project.json files produces more ignorable files 210 | *.nuget.props 211 | *.nuget.targets 212 | 213 | # Microsoft Azure Build Output 214 | csx/ 215 | *.build.csdef 216 | 217 | # Microsoft Azure Emulator 218 | ecf/ 219 | rcf/ 220 | 221 | # Windows Store app package directories and files 222 | AppPackages/ 223 | BundleArtifacts/ 224 | Package.StoreAssociation.xml 225 | _pkginfo.txt 226 | *.appx 227 | *.appxbundle 228 | *.appxupload 229 | 230 | # Visual Studio cache files 231 | # files ending in .cache can be ignored 232 | *.[Cc]ache 233 | # but keep track of directories ending in .cache 234 | !?*.[Cc]ache/ 235 | 236 | # Others 237 | ClientBin/ 238 | ~$* 239 | *~ 240 | *.dbmdl 241 | *.dbproj.schemaview 242 | *.jfm 243 | *.pfx 244 | *.publishsettings 245 | orleans.codegen.cs 246 | 247 | # Including strong name files can present a security risk 248 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 249 | #*.snk 250 | 251 | # Since there are multiple workflows, uncomment next line to ignore bower_components 252 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 253 | #bower_components/ 254 | 255 | # RIA/Silverlight projects 256 | Generated_Code/ 257 | 258 | # Backup & report files from converting an old project file 259 | # to a newer Visual Studio version. Backup files are not needed, 260 | # because we have git ;-) 261 | _UpgradeReport_Files/ 262 | Backup*/ 263 | UpgradeLog*.XML 264 | UpgradeLog*.htm 265 | ServiceFabricBackup/ 266 | *.rptproj.bak 267 | 268 | # SQL Server files 269 | *.mdf 270 | *.ldf 271 | *.ndf 272 | 273 | # Business Intelligence projects 274 | *.rdl.data 275 | *.bim.layout 276 | *.bim_*.settings 277 | *.rptproj.rsuser 278 | *- [Bb]ackup.rdl 279 | *- [Bb]ackup ([0-9]).rdl 280 | *- [Bb]ackup ([0-9][0-9]).rdl 281 | 282 | # Microsoft Fakes 283 | FakesAssemblies/ 284 | 285 | # GhostDoc plugin setting file 286 | *.GhostDoc.xml 287 | 288 | # Node.js Tools for Visual Studio 289 | .ntvs_analysis.dat 290 | node_modules/ 291 | 292 | # Visual Studio 6 build log 293 | *.plg 294 | 295 | # Visual Studio 6 workspace options file 296 | *.opt 297 | 298 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 299 | *.vbw 300 | 301 | # Visual Studio LightSwitch build output 302 | **/*.HTMLClient/GeneratedArtifacts 303 | **/*.DesktopClient/GeneratedArtifacts 304 | **/*.DesktopClient/ModelManifest.xml 305 | **/*.Server/GeneratedArtifacts 306 | **/*.Server/ModelManifest.xml 307 | _Pvt_Extensions 308 | 309 | # Paket dependency manager 310 | .paket/paket.exe 311 | paket-files/ 312 | 313 | # FAKE - F# Make 314 | .fake/ 315 | 316 | # CodeRush personal settings 317 | .cr/personal 318 | 319 | # Python Tools for Visual Studio (PTVS) 320 | __pycache__/ 321 | *.pyc 322 | 323 | # Cake - Uncomment if you are using it 324 | # tools/** 325 | # !tools/packages.config 326 | 327 | # Tabs Studio 328 | *.tss 329 | 330 | # Telerik's JustMock configuration file 331 | *.jmconfig 332 | 333 | # BizTalk build output 334 | *.btp.cs 335 | *.btm.cs 336 | *.odx.cs 337 | *.xsd.cs 338 | 339 | # OpenCover UI analysis results 340 | OpenCover/ 341 | 342 | # Azure Stream Analytics local run output 343 | ASALocalRun/ 344 | 345 | # MSBuild Binary and Structured Log 346 | *.binlog 347 | 348 | # NVidia Nsight GPU debugger configuration file 349 | *.nvuser 350 | 351 | # MFractors (Xamarin productivity tool) working folder 352 | .mfractor/ 353 | 354 | # Local History for Visual Studio 355 | .localhistory/ 356 | 357 | # BeatPulse healthcheck temp database 358 | healthchecksdb 359 | 360 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 361 | MigrationBackup/ 362 | 363 | # Ionide (cross platform F# VS Code tools) working folder 364 | .ionide/ 365 | 366 | # Fody - auto-generated XML schema 367 | FodyWeavers.xsd 368 | 369 | ## 370 | ## Visual studio for Mac 371 | ## 372 | 373 | 374 | # globs 375 | Makefile.in 376 | *.userprefs 377 | *.usertasks 378 | config.make 379 | config.status 380 | aclocal.m4 381 | install-sh 382 | autom4te.cache/ 383 | *.tar.gz 384 | tarballs/ 385 | test-results/ 386 | 387 | # Mac bundle stuff 388 | *.dmg 389 | *.app 390 | 391 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 392 | # General 393 | .DS_Store 394 | .AppleDouble 395 | .LSOverride 396 | 397 | # Icon must end with two \r 398 | Icon 399 | 400 | 401 | # Thumbnails 402 | ._* 403 | 404 | # Files that might appear in the root of a volume 405 | .DocumentRevisions-V100 406 | .fseventsd 407 | .Spotlight-V100 408 | .TemporaryItems 409 | .Trashes 410 | .VolumeIcon.icns 411 | .com.apple.timemachine.donotpresent 412 | 413 | # Directories potentially created on remote AFP share 414 | .AppleDB 415 | .AppleDesktop 416 | Network Trash Folder 417 | Temporary Items 418 | .apdisk 419 | 420 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 421 | # Windows thumbnail cache files 422 | Thumbs.db 423 | ehthumbs.db 424 | ehthumbs_vista.db 425 | 426 | # Dump file 427 | *.stackdump 428 | 429 | # Folder config file 430 | [Dd]esktop.ini 431 | 432 | # Recycle Bin used on file shares 433 | $RECYCLE.BIN/ 434 | 435 | # Windows Installer files 436 | *.cab 437 | *.msi 438 | *.msix 439 | *.msm 440 | *.msp 441 | 442 | # Windows shortcuts 443 | *.lnk 444 | 445 | # JetBrains Rider 446 | .idea/ 447 | *.sln.iml 448 | 449 | ## 450 | ## Visual Studio Code 451 | ## 452 | .vscode/* 453 | !.vscode/settings.json 454 | !.vscode/tasks.json 455 | !.vscode/launch.json 456 | !.vscode/extensions.json 457 | yukimiscript-syntax-highlight-vscode/yukimiscript-syntax-highlight-*.vsix 458 | -------------------------------------------------------------------------------- /Examples/Diagram/第一章.ykm: -------------------------------------------------------------------------------- 1 | - scene "第一章" 2 | @__diagram_link_to "第二章 开始" 3 | 4 | 5 | -------------------------------------------------------------------------------- /Examples/Diagram/第三章.ykm: -------------------------------------------------------------------------------- 1 | - scene "第三章" 2 | @__diagram_link_to "第三章 结尾" 3 | 4 | - scene "第三章 结尾" 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Diagram/第二章.ykm: -------------------------------------------------------------------------------- 1 | - scene "第二章 开始" 2 | @__diagram_link_to "第二章 选择支A" 3 | @__diagram_link_to "第二章 选择支B" 4 | 5 | - scene "第二章 选择支A" 6 | @__diagram_link_to "第二章 结尾" 7 | 8 | - scene "第二章 选择支B" 9 | @__diagram_link_to "第二章 结尾" 10 | 11 | - scene "第二章 结尾" 12 | @__diagram_link_to "第三章" 13 | 14 | -------------------------------------------------------------------------------- /Examples/Type Checker Example.ykm: -------------------------------------------------------------------------------- 1 | - macro checkTypeCharacterName name 2 | @__type_symbol name a 3 | @__type_symbol name b 4 | @__type_symbol name c 5 | @__type_symbol name d 6 | 7 | - macro sayFromCharacter name text 8 | @checkTypeCharacterName name 9 | @__type text string 10 | 11 | - scene "test" 12 | @sayFromCharacter a "Hello" 13 | @sayFromCharacter b "Hello" 14 | @sayFromCharacter c "Hello" 15 | @sayFromCharacter d "Hello" 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Strrationalism 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yukimi Script 2 | 3 | 为描述视觉小说而设计的领域专用语言。 4 | 5 | 参见[Github Wiki页面](https://github.com/Strrationalism/YukimiScript/wiki)。 6 | 7 | ## 适用于 8 | * 视觉小说 9 | * 文字类游戏 10 | * 需要对话演出的游戏 11 | 12 | ## 特点 13 | * 类似krkr的键值对传参和flags传参语法。 14 | * 按行Parse,便于分析。 15 | * 以可用性为优先。 16 | * 可选的静态强类型系统。 17 | * 可编译到多个目标。 18 | 19 | ## 谁在使用? 20 | 21 | [![弦语蝶梦游戏工作室 - 《空梦》](https://cdn.cloudflare.steamstatic.com/steam/apps/1059850/header_schinese.jpg?t=1629427718)](https://store.steampowered.com/app/1059850/) 22 | 23 | ## 设计原则 24 | * 按行Parse 25 | * 易于实现实时可视化编辑器 26 | * 可以实时检查任意点状态 27 | * 引入的特性需要切实解决实际开发中遇到的问题 28 | 29 | ## 安装 30 | 31 | 你可以在[这里](https://marketplace.visualstudio.com/items?itemName=seng-jik.yukimiscript-syntax-highlight)安装用于Visual Studio Code的YukimiScript代码高亮工具。 32 | 33 | ### 在.NET中通过NuGet安装YukimiScript命令行工具 34 | 35 | ```shell 36 | dotnet tool install -g YukimiScript.CommandLineTool 37 | ``` 38 | 39 | ### 手动安装不依赖.NET运行时的YukimiScript命令行工具 40 | 41 | 在[Release页面](https://github.com/Strrationalism/YukimiScript/releases)下载对应平台的可执行文件并将其拷贝到可被命令行环境访问的目录中。 42 | 43 | ### 在.NET项目中引用YukimiScript Parser 44 | 45 | ```shell 46 | dotnet add package YukimiScript.Parser 47 | ``` 48 | 49 | 50 | ## 概览 51 | 52 | ``` 53 | - extern systemAPI_sleep_begin force # 在这里定义宿主命令 54 | - extern systemAPI_sleep_end 55 | - extern systemAPI_sleep time=1 56 | - extern systemAPI_jumpToSection target 57 | - extern name 58 | 59 | - macro jumpToSection target 60 | @__diagram_link_to target 61 | @systemAPI_jumpToSection target 62 | 63 | - scene "entrypoint" 64 | @jumpToSection "场景 第一个场景" 65 | 66 | - macro wait time=1 force=false 67 | @systemAPI_sleep_begin force # 这里的内容将会被展开 68 | @systemAPI_sleep time 69 | @systemAPI_sleep_end 70 | 71 | - scene "场景 第一个场景" 72 | y:你好~我叫[name],[wait --time 1 --force] \ 73 | 欢迎你来我家里玩~ 74 | @wait 3 75 | y:感谢您使用由纪美脚本语言! 76 | @wait 77 | 78 | # 以上文字内容编译为 79 | # @__text_begin --character "y" 80 | # @__text_type --text "你好~我叫" 81 | # @name 82 | # @__text_type --text "," 83 | # @wait --time 1 --force true 84 | # @__text_pushMark --mark ani 85 | # @__text_type --text "很高兴认识你!" 86 | # @__text_popMark --mark ani 87 | # @__text_end --hasMore true 88 | # @__text_begin 89 | # @__text_type "欢迎你来我家里玩~" 90 | # @__text_end --hasMore false 91 | 92 | # @__text_begin --character "y" 93 | # @__text_type --text "感谢您使用由纪美脚本语言!" 94 | # @__text_end 95 | 96 | 97 | - scene "场景 第一个场景 的子场景" inherit "场景 第一个场景" 98 | # 这个场景的状态机将会继承于"场景 第一个场景". 99 | 100 | ``` 101 | -------------------------------------------------------------------------------- /YukimiScript.CodeGen.Bytecode/Bytecode.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.CodeGen.Bytecode 2 | 3 | open YukimiScript.Parser 4 | open YukimiScript.Parser.Elements 5 | open System.IO 6 | open type System.Text.Encoding 7 | 8 | 9 | let inline private getBytes'''<'t, 'x when (^t or ^x): (static member GetBytes: ^x -> byte[])> (x: 'x) = 10 | ((^t or ^x): (static member GetBytes: ^x -> byte[]) x) 11 | 12 | 13 | let inline getBytesLE (x: 'x) = 14 | let bytes = getBytes''' x 15 | if System.BitConverter.IsLittleEndian 16 | then bytes 17 | else Array.rev bytes 18 | 19 | 20 | let private writeBytes (stream: Stream) byte = 21 | stream.Write (byte, 0, Array.length byte) 22 | 23 | 24 | let generateBytecode genDebug (Intermediate scenes) (target: FileStream) = 25 | use cstrBlock = new MemoryStream () 26 | use extrBlock = new MemoryStream () 27 | 28 | let cstrIndex = ref Map.empty 29 | let extrIndex = ref Map.empty 30 | 31 | let getString (str: string) = 32 | match Map.tryFind str cstrIndex.Value with 33 | | Some offset -> offset 34 | | None -> 35 | cstrIndex.Value 36 | |> Map.tryPick (fun s offset -> 37 | if not <| s.EndsWith str then None else 38 | UTF8.GetByteCount(s.[..s.Length - str.Length - 1]) 39 | |> uint32 40 | |> (+) offset 41 | |> Some) 42 | |> Option.defaultWith (fun () -> 43 | let pos = uint32 cstrBlock.Position 44 | let strBytes = UTF8.GetBytes(str: string) 45 | writeBytes cstrBlock strBytes 46 | cstrBlock.WriteByte(0uy) 47 | cstrIndex.Value <- Map.add str pos cstrIndex.Value 48 | pos) 49 | 50 | let getExtern name = 51 | match Map.tryFind name extrIndex.Value with 52 | | Some extrId -> extrId 53 | | None -> 54 | let extrId = Map.count extrIndex.Value |> uint16 55 | 56 | getString name 57 | |> uint32 58 | |> getBytesLE 59 | |> writeBytes extrBlock 60 | 61 | extrIndex.Value <- Map.add name extrId extrIndex.Value 62 | extrId 63 | 64 | let generateScene (scene: IntermediateScene) = 65 | let code = new MemoryStream () 66 | let debugInfo = if genDebug then Some <| new MemoryStream () else None 67 | 68 | let pSceneName = 69 | getString (scene.Name: string) 70 | |> getBytesLE 71 | 72 | writeBytes code pSceneName 73 | debugInfo |> Option.iter (fun d -> 74 | writeBytes d pSceneName 75 | scene.DebugInfo.File |> getString |> getBytesLE |> writeBytes d 76 | uint32 scene.DebugInfo.LineNumber |> getBytesLE |> writeBytes d) 77 | 78 | let writeValue block = 79 | function 80 | | Integer i -> 81 | writeBytes block <| getBytesLE 0 82 | writeBytes block <| getBytesLE i 83 | | Real f -> 84 | writeBytes block <| getBytesLE 1 85 | writeBytes block <| getBytesLE (float32 f) 86 | | String s -> 87 | getString s |> int |> (+) 2 |> getBytesLE |> writeBytes block 88 | | Symbol s -> 89 | - int (getString s) - 1 |> getBytesLE |> writeBytes block 90 | 91 | for call in scene.Block do 92 | debugInfo |> Option.iter (fun d -> 93 | let rec countStackFrames = 94 | function 95 | | { Outter = None } -> 1 96 | | { Outter = Some x } -> 1 + countStackFrames x 97 | 98 | let stackFrameDepth = countStackFrames call.DebugInfo 99 | uint32 code.Position |> getBytesLE |> writeBytes d 100 | uint32 stackFrameDepth |> getBytesLE |> writeBytes d 101 | 102 | let mutable dbg = call.DebugInfo 103 | for _ in 1 .. stackFrameDepth do 104 | getString dbg.File |> getBytesLE |> writeBytes d 105 | uint32 dbg.LineNumber |> getBytesLE |> writeBytes d 106 | 107 | let isSceneFlag, scopeName = 108 | match dbg.Scope with 109 | | Some (Choice1Of2 a) -> 0u, a.Name 110 | | Some (Choice2Of2 a) -> 0x80000000u, a.Name 111 | | None -> 0x80000000u, "" 112 | 113 | getString scopeName |> getBytesLE |> writeBytes d 114 | (uint32 dbg.MacroVars.Length) ||| isSceneFlag 115 | |> getBytesLE |> writeBytes d 116 | 117 | for (name, var) in dbg.MacroVars do 118 | getString name |> getBytesLE |> writeBytes d 119 | writeValue d var 120 | 121 | if dbg.Outter.IsSome then 122 | dbg <- dbg.Outter.Value 123 | ) 124 | 125 | call.Callee 126 | |> getExtern 127 | |> getBytesLE 128 | |> writeBytes code 129 | 130 | call.Arguments.Length 131 | |> uint16 132 | |> getBytesLE 133 | |> writeBytes code 134 | 135 | for arg in call.Arguments do 136 | writeValue code arg 137 | 138 | code, debugInfo 139 | 140 | let writeFourCC fourCC (target: Stream) = 141 | assert (String.length fourCC = 4) 142 | fourCC |> ASCII.GetBytes |> writeBytes target 143 | 144 | let scenes, debugInfos = List.map generateScene scenes |> List.unzip 145 | let debugInfos = List.choose id debugInfos 146 | while (int cstrBlock.Length) % 4 <> 0 do 147 | cstrBlock.WriteByte 0uy 148 | 149 | writeFourCC "RIFF" target 150 | 151 | let riffDataSize = 152 | 4L + // FourCC 'YUKI' 153 | 8L + cstrBlock.Length + // Block 'CSTR' 154 | 8L + extrBlock.Length + // Block 'EXTR' 155 | 8L * int64 scenes.Length + List.sumBy (fun (x: MemoryStream) -> x.Length) scenes + 156 | 8L * int64 debugInfos.Length + List.sumBy (fun (x: MemoryStream) -> x.Length) debugInfos 157 | |> uint32 158 | 159 | riffDataSize |> getBytesLE |> writeBytes target 160 | writeFourCC "YUKI" target 161 | 162 | let writeRiffBlock fourCC (block: MemoryStream) = 163 | writeFourCC fourCC target 164 | block.Flush () 165 | block.Length |> uint32 |> getBytesLE |> writeBytes target 166 | block.Position <- 0L 167 | block.CopyTo(target) 168 | 169 | writeRiffBlock "CSTR" cstrBlock 170 | writeRiffBlock "EXTR" extrBlock 171 | 172 | for scene in scenes do 173 | use scene = scene 174 | writeRiffBlock "SCEN" scene 175 | 176 | for dbg in debugInfos do 177 | use dbg = dbg 178 | writeRiffBlock "DBGS" dbg 179 | 180 | 181 | -------------------------------------------------------------------------------- /YukimiScript.CodeGen.Bytecode/YukimiScript.CodeGen.Bytecode.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1;net8.0;net9.0 5 | true 6 | 0.8.6 7 | Seng Jik 8 | Strrationalism 9 | https://github.com/Strrationalism/YukimiScript 10 | https://github.com/Strrationalism/YukimiScript 11 | git 12 | MIT 13 | YukimiScript.CodeGen.Bytecode 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /YukimiScript.CodeGen/Json.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.CodeGen.Json 2 | 3 | open YukimiScript.Parser 4 | 5 | let private writeConstant (writer: System.Text.Json.Utf8JsonWriter) = 6 | function 7 | | Elements.Symbol "true" -> writer.WriteBooleanValue(true) 8 | | Elements.Symbol "false" -> writer.WriteBooleanValue(false) 9 | | Elements.Symbol "null" -> writer.WriteNullValue() 10 | | Elements.String x -> writer.WriteStringValue(x) 11 | | Elements.Symbol x -> writer.WriteStringValue(x) 12 | | Elements.Integer x -> writer.WriteNumberValue(x) 13 | | Elements.Real x -> writer.WriteNumberValue(x) 14 | 15 | let genJson debug (Intermediate scenes) targetFile = 16 | use fileStream = 17 | System.IO.File.Open (targetFile, System.IO.FileMode.Create) 18 | 19 | use writer = 20 | new System.Text.Json.Utf8JsonWriter (fileStream) 21 | 22 | writer.WriteStartArray() 23 | 24 | let rec writeDebugInfo (debugInfo: Elements.DebugInfo) = 25 | writer.WriteString("src", debugInfo.File) 26 | writer.WriteNumber("line", debugInfo.LineNumber) 27 | match debugInfo.Scope with 28 | | Some (Choice1Of2 a) -> 29 | writer.WriteString ("scope_type", "macro") 30 | writer.WriteString ("macro", a.Name) 31 | | Some (Choice2Of2 a) -> 32 | writer.WriteString ("scope_type", "scene") 33 | writer.WriteString ("scene", a.Name) 34 | | None -> 35 | writer.WriteNull ("scope_type") 36 | writer.WriteStartObject ("vars") 37 | for (name, var) in debugInfo.MacroVars do 38 | writer.WritePropertyName name 39 | writeConstant writer var 40 | writer.WriteEndObject () 41 | 42 | writer.WriteStartObject ("outter") 43 | debugInfo.Outter |> Option.iter writeDebugInfo 44 | writer.WriteEndObject () 45 | 46 | for scene in scenes do 47 | writer.WriteStartObject() 48 | writer.WriteString("scene", scene.Name) 49 | 50 | if debug then 51 | writer.WriteStartObject("debug") 52 | writeDebugInfo scene.DebugInfo 53 | writer.WriteEndObject() 54 | 55 | begin 56 | writer.WriteStartArray("block") 57 | for call in scene.Block do 58 | writer.WriteStartObject() 59 | writer.WriteString("call", call.Callee) 60 | writer.WriteStartArray("args") 61 | for arg in call.Arguments do 62 | writeConstant writer arg 63 | 64 | writer.WriteEndArray() 65 | if debug then 66 | writer.WriteStartObject("debug") 67 | writeDebugInfo call.DebugInfo 68 | writer.WriteEndObject() 69 | writer.WriteEndObject() 70 | writer.WriteEndArray() 71 | end 72 | 73 | writer.WriteEndObject() 74 | 75 | writer.WriteEndArray() 76 | 77 | -------------------------------------------------------------------------------- /YukimiScript.CodeGen/Lua.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.CodeGen.Lua 2 | 3 | open YukimiScript.Parser 4 | open YukimiScript.Parser.Elements 5 | 6 | 7 | let generateLua genDebug (Intermediate scenes) = 8 | let sb = System.Text.StringBuilder() 9 | let sbDebug = if genDebug then Some (System.Text.StringBuilder ()) else None 10 | 11 | let strPool = ref Map.empty 12 | let getStr str = 13 | match Map.tryFind str strPool.Value with 14 | | Some x -> "s" + string x 15 | | None -> 16 | let curId = Map.count strPool.Value 17 | strPool.Value <- Map.add str curId strPool.Value 18 | "s" + string curId 19 | 20 | sb.AppendLine("return function(api) return {") 21 | |> ignore 22 | 23 | let genConst = 24 | function 25 | | Symbol "true" -> "true" 26 | | Symbol "false" -> "false" 27 | | Symbol "null" 28 | | Symbol "nil" -> "nil" 29 | | Symbol x -> getStr x 30 | | Integer x -> string x 31 | | Real x -> string x 32 | | String x -> getStr x 33 | 34 | scenes 35 | |> List.iter 36 | (fun scene -> 37 | let scenName = Constants.string2literal scene.Name 38 | 39 | sb 40 | .Append(" [") 41 | .Append(getStr scenName) 42 | .Append("] = {") 43 | |> ignore 44 | 45 | sb.AppendLine() |> ignore 46 | 47 | sbDebug |> Option.iter (fun sbd -> 48 | sbd 49 | .Append(" [") 50 | .Append(getStr scenName) 51 | .AppendLine("] = {") 52 | .Append(" F = ") 53 | .Append(getStr scene.DebugInfo.File) 54 | .AppendLine(",") 55 | .Append(" L = ") 56 | .Append(string scene.DebugInfo.LineNumber) 57 | |> ignore) 58 | 59 | scene.Block 60 | |> List.iter 61 | (fun c -> 62 | let rec genDebugInfo (w: System.Text.StringBuilder) dbg = 63 | let vars = 64 | match dbg.MacroVars with 65 | | [] -> "" 66 | | vars -> 67 | vars 68 | |> List.map (fun (name, var) -> 69 | "[" + getStr name + "] = " + genConst var) 70 | |> List.reduce (fun a b -> a + ", " + b) 71 | w 72 | .Append("{ F = ") 73 | .Append(getStr dbg.File) 74 | .Append(", L = ") 75 | .Append(string dbg.LineNumber) 76 | .Append(", V = {") 77 | .Append(vars) 78 | .Append("}") 79 | |> ignore 80 | 81 | match dbg.Scope with 82 | | None -> w.Append(", ScopeType = nil") 83 | | Some (Choice1Of2 a) -> 84 | w 85 | .Append(", ScopeType = ") 86 | .Append(getStr "macro") 87 | .Append(", MacroName = ") 88 | .Append(getStr a.Name) 89 | | Some (Choice2Of2 a) -> 90 | w 91 | .Append(", ScopeType = ") 92 | .Append(getStr "scene") 93 | .Append(", SceneName = ") 94 | .Append(getStr a.Name) 95 | |> ignore 96 | 97 | if Option.isSome dbg.Outter then 98 | w.Append(", O = ") |> ignore 99 | genDebugInfo w dbg.Outter.Value 100 | 101 | w.Append (" }") |> ignore 102 | 103 | sbDebug |> Option.iter (fun sbd -> 104 | sbd 105 | .AppendLine(",") 106 | .Append(" ") 107 | |> ignore 108 | genDebugInfo sbd c.DebugInfo) 109 | sb 110 | .Append(" function() ") 111 | .Append("api[") 112 | .Append(getStr c.Callee) 113 | .Append("]") 114 | .Append("(api") 115 | |> ignore 116 | 117 | if not <| List.isEmpty c.Arguments then 118 | sb.Append(", ") |> ignore 119 | 120 | let args = List.map genConst c.Arguments 121 | if not <| List.isEmpty args then 122 | args 123 | |> List.reduce (fun a b -> a + ", " + b) 124 | |> sb.Append 125 | |> ignore 126 | 127 | sb.Append(") end,") |> ignore 128 | 129 | sb.AppendLine() |> ignore) 130 | 131 | sbDebug |> Option.iter (fun sbd -> 132 | sbd.AppendLine().AppendLine (" },") |> ignore) 133 | 134 | sb.AppendLine().AppendLine(" },") |> ignore) 135 | 136 | sbDebug |> Option.iter (fun sbDbg -> 137 | sb.AppendLine (" [0] = {") |> ignore 138 | sb.Append (sbDbg.ToString ()) |> ignore 139 | sb.AppendLine (" }") |> ignore) 140 | 141 | let strPool = 142 | Map.toSeq strPool.Value 143 | |> Seq.map (fun (str, strId) -> 144 | "local s" + string strId + " = \"" 145 | + Constants.string2literal str + "\"") 146 | |> Seq.fold (fun acc x -> acc + x + "\n") "" 147 | 148 | strPool + sb.AppendLine("} end").ToString() 149 | 150 | -------------------------------------------------------------------------------- /YukimiScript.CodeGen/PyMO.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.CodeGen.PyMO 2 | 3 | open YukimiScript.Parser 4 | open YukimiScript.Parser.ParserMonad 5 | open YukimiScript.Parser.Elements 6 | open System.Text 7 | open System 8 | 9 | 10 | let private genArg = function 11 | | String x -> Some x 12 | | Real x -> Some <| string x 13 | | Integer x -> Some <| string x 14 | | Symbol "true" -> Some "1" 15 | | Symbol "false" -> Some "0" 16 | | Symbol "a" -> Some "a" 17 | | Symbol "BG_VERYFAST" -> Some "BG_VERYFAST" 18 | | Symbol "BG_FAST" -> Some "BG_FAST" 19 | | Symbol "BG_NORMAL" -> Some "BG_NORMAL" 20 | | Symbol "BG_SLOW" -> Some "BG_SLOW" 21 | | Symbol "BG_VERYSLOW" -> Some "BG_VERYSLOW" 22 | | Symbol "cm0" -> Some "0" 23 | | Symbol "cm1" -> Some "1" 24 | | Symbol "cm2" -> Some "2" 25 | | Symbol "cm3" -> Some "3" 26 | | Symbol "cm4" -> Some "4" 27 | | Symbol "cm5" -> Some "5" 28 | | Symbol "cm6" -> Some "6" 29 | | Symbol "BG_ALPHA" -> Some "BG_ALPHA" 30 | | Symbol "BG_FADE" -> Some "BG_FADE" 31 | | Symbol "BG_NOFADE" -> Some "BG_NOFADE" 32 | | Symbol "null" -> None 33 | | Symbol x -> failwith <| "Unsupported PyMO keyword " + x + "." 34 | 35 | 36 | let private genArgUntyped = function 37 | | Symbol x -> x 38 | | String x -> x 39 | | Integer x -> string x 40 | | Real x -> string x 41 | 42 | 43 | let private checkSceneName (name: string) = 44 | match name with 45 | | "$init" -> true 46 | | name -> 47 | if name.Contains '.' 48 | then false 49 | else 50 | let p = seq { yield! ['0'..'9']; yield! ['a' .. 'z']; yield! ['A' .. 'Z']; yield! [ '_'; '-' ] } 51 | match run name <| oneOrMore (inRange p) with 52 | | Ok _ -> true 53 | | _ -> false 54 | 55 | 56 | let private genArgs' genArg = 57 | genArg 58 | >> function 59 | | [] -> "" 60 | | ls -> List.reduce (fun a b -> a + "," + b) ls 61 | 62 | 63 | let private colorArg = function 64 | | Symbol "white" -> Constant.String "#FFFFFF" 65 | | Symbol "black" -> Constant.String "#000000" 66 | | Symbol "red" -> Constant.String "#FF0000" 67 | | Symbol "green" -> Constant.String "#00FF00" 68 | | Symbol "blue" -> Constant.String "#0000FF" 69 | | Integer i -> 70 | let mutable hex = Math.Clamp(i, 0, 0xFFFFFF).ToString("X") 71 | while hex.Length < 6 do hex <- "0" + hex 72 | Constant.String <| "#" + hex 73 | | _ -> failwith "" 74 | 75 | 76 | let private genArgs = genArgs' <| List.choose genArg 77 | let private genArgsUntyped = genArgs' <| List.map genArgUntyped 78 | 79 | 80 | type private ComplexCommand = ComplexCommand of string * string * (Constant list -> Constant list -> string) 81 | 82 | 83 | let private gen'Untyped c = genArgsUntyped >> (+) ("#" + c + " ") 84 | 85 | 86 | let private sayCommand = 87 | ComplexCommand 88 | ("__text_type", "__text_end", fun m _ -> 89 | let character = match m.[0] with | Symbol "null" -> None | x -> Some <| genArgUntyped x 90 | let text = m.[1..] |> List.map genArgUntyped |> List.fold (+) "" 91 | "#say " + (match character with | None -> "" | Some x -> x + ",") + text) 92 | 93 | 94 | let private complexCommands = 95 | let gen command a b = a @ b |> genArgs |> (+) ("#" + command + " ") 96 | let gen' c = genArgs >> (+) ("#" + c + " ") 97 | 98 | let genSel c argGroupSize = fun m d -> 99 | let d = 100 | if List.length d >= 4 101 | then 102 | let arr = List.toArray d 103 | arr.[4] <- colorArg d.[4] 104 | List.ofArray arr 105 | else d 106 | Integer (List.length m / argGroupSize) :: m @ d |> gen'Untyped c 107 | [ "chara_multi", "chara_multi_do", gen "chara" 108 | "chara_quake_multi", "chara_quake_multi_do", gen "chara_quake" 109 | "chara_down_multi", "chara_down_multi_do", gen "chara_down" 110 | "chara_up_multi", "chara_up_multi_do", gen "chara_up" 111 | "chara_y_multi", "chara_y_multi_do", fun multi d -> d.[0] :: multi @ [d.[1]] |> gen' "chara_y" 112 | "chara_anime", "chara_anime_do", fun multi d -> d @ multi |> gen' "chara_anime" 113 | "sel", "sel_do", fun multi d -> 114 | let firstLine = "#sel " + genArgs (Integer (List.length multi) :: d) 115 | let allLines = (firstLine :: List.choose genArg multi) 116 | allLines |> List.reduce (fun a b -> a + "\n" + b) 117 | "select_text", "select_text_do", genSel "select_text" 1 118 | "select_var", "select_var_do", genSel "select_var" 2 119 | "select_img", "select_img_do", fun m d -> genSel "select_img" 3 (d.[0] :: m) d.[1..] 120 | "select_imgs", "select_imgs_do", genSel "select_imgs" 4 121 | (let (ComplexCommand (x, y, z)) = sayCommand in x, y, z) ] 122 | |> Seq.map (fun x -> (let (k, _, _) = x in k), ComplexCommand x) 123 | |> Map.ofSeq 124 | 125 | 126 | let private commandsWithUntypedSymbol = 127 | [ "set" 128 | "add" 129 | "sub" 130 | "rand" ] 131 | |> Set.ofSeq 132 | 133 | 134 | type Scope = 135 | | IfScope of ifs: (string * StringBuilder) list * def: StringBuilder option 136 | 137 | 138 | type private CodeGenContext = 139 | { Characters: Map 140 | CurrentComplexCommand: (ComplexCommand * Constant list) option 141 | ScopeStack: Scope list 142 | Inc: int 143 | GenExitLabel: bool ref } 144 | 145 | 146 | let private (|ComplexCommand'|_|) x = 147 | Map.tryFind x complexCommands 148 | 149 | 150 | let private colorCommands = 151 | [ "text", 5 152 | "flash", 0 153 | "fade_out", 0 154 | "date", 3 ] 155 | |> Map.ofList 156 | 157 | 158 | let private getDebugString (table: ref>) str = 159 | match Map.tryFind str table.Value with 160 | | Some x -> x 161 | | None -> 162 | let curId = Map.count table.Value 163 | table.Value <- Map.add str curId table.Value 164 | curId 165 | 166 | 167 | let private genCommand 168 | dbgStrs 169 | genDbg 170 | (sbRoot: StringBuilder) 171 | context 172 | (call: IntermediateCommandCall) 173 | : Result = 174 | 175 | let getScopeSb = 176 | function 177 | | [] -> sbRoot 178 | | IfScope ((_, sb) :: _, None) :: _ -> sb 179 | | IfScope (_, Some sb) :: _ -> sb 180 | | IfScope _ :: _ -> failwith "Invalid if scope!" 181 | 182 | let sb = getScopeSb context.ScopeStack 183 | 184 | if genDbg && call.Callee <> "__text_begin" && call.Callee <> "__text_end" then 185 | sb.Append (";YKMDBG") |> ignore 186 | 187 | sb.Append (";C") |> ignore 188 | sb.Append (getDebugString dbgStrs call.Callee) |> ignore 189 | 190 | for arg in call.Arguments do 191 | match arg with 192 | | Constant.String x -> ";AS" + string (getDebugString dbgStrs x) 193 | | Symbol x -> ";As" + string (getDebugString dbgStrs x) 194 | | Integer i -> ";Ai" + string i 195 | | Real r -> ";Ar" + string r 196 | |> sb.Append 197 | |> ignore 198 | 199 | sb.Append (";E") |> ignore 200 | 201 | let mutable d = Some call.DebugInfo 202 | while Option.isSome d do 203 | let dbgInfo = d.Value 204 | sb.Append (";L") |> ignore 205 | sb.Append (dbgInfo.LineNumber) |> ignore 206 | sb.Append (";F") |> ignore 207 | sb.Append (getDebugString dbgStrs dbgInfo.File) |> ignore 208 | 209 | for i in dbgInfo.MacroVars do 210 | let var = fst i 211 | match snd i with 212 | | Constant.String x -> 213 | ";VS" + var + "=" + string (getDebugString dbgStrs x) 214 | | Symbol x -> 215 | ";Vs" + var + "=" + string (getDebugString dbgStrs x) 216 | | Integer x -> ";Vi" + var + "=" + string x 217 | | Real x -> ";Vr" + var + "=" + string x 218 | |> sb.Append 219 | |> ignore 220 | 221 | match dbgInfo.Scope with 222 | | None -> () 223 | | Some (Choice2Of2 a) -> 224 | sb.Append(";S").Append(getDebugString dbgStrs a.Name) |> ignore 225 | | Some (Choice1Of2 a) -> 226 | sb.Append(";M").Append(getDebugString dbgStrs a.Name) |> ignore 227 | 228 | sb.Append (";E") |> ignore 229 | 230 | d <- dbgInfo.Outter 231 | sb.AppendLine () |> ignore 232 | 233 | let genComplexCommandError () = 234 | Error ("当你使用PyMO变参命令时,不应该在中间夹杂其他命令。", call.DebugInfo) 235 | 236 | let condStr left op right = 237 | let left, op, right = 238 | genArgUntyped left, 239 | genArgUntyped op, 240 | genArgUntyped right 241 | 242 | let op = 243 | match op with 244 | | "eq" -> "=" 245 | | "ne" -> "!=" 246 | | "gt" -> ">" 247 | | "ge" -> ">=" 248 | | "lt" -> "<" 249 | | "le" -> "<=" 250 | | _ -> failwith "" 251 | 252 | left + op + right 253 | 254 | match context.CurrentComplexCommand with 255 | | Some (ComplexCommand (openCmd, closeCmd, gen) as cc, args) -> 256 | match call.Callee with 257 | | "__text_begin" when openCmd = "__text_type" && call.Arguments.[0] = Symbol "null" -> Ok context 258 | | "__text_end" when openCmd = "__text_type" && call.Arguments.[0] = Symbol "true" -> 259 | Ok { context with CurrentComplexCommand = Some (cc, args @ [Constant.String "\\n"]) } 260 | | c when c = openCmd -> 261 | Ok { context with CurrentComplexCommand = Some (cc, args @ call.Arguments)} 262 | | c when c = closeCmd -> 263 | sb.AppendLine (gen args call.Arguments) |> ignore 264 | Ok { context with CurrentComplexCommand = None } 265 | | _ -> genComplexCommandError () 266 | | None -> 267 | match call.Callee with 268 | | "__define_character" -> 269 | Ok { context with 270 | Characters = 271 | Map.add 272 | (genArgUntyped call.Arguments.[0]) 273 | (genArgUntyped call.Arguments.[1]) 274 | context.Characters } 275 | | "exit" -> 276 | sb.AppendLine ("#goto EXIT") |> ignore 277 | context.GenExitLabel.Value <- true 278 | Ok context 279 | | "if" -> 280 | let condStr = condStr call.Arguments[0] call.Arguments[1] call.Arguments[2] 281 | if context.CurrentComplexCommand.IsSome 282 | then genComplexCommandError () 283 | else 284 | { context with 285 | ScopeStack = 286 | IfScope ([condStr, StringBuilder ()], None)::context.ScopeStack } 287 | |> Ok 288 | | "elif" -> 289 | let condStr = condStr call.Arguments[0] call.Arguments[1] call.Arguments[2] 290 | if context.CurrentComplexCommand.IsSome 291 | then genComplexCommandError () 292 | else 293 | match context.ScopeStack with 294 | | IfScope (x, None)::ls -> 295 | { context with 296 | ScopeStack = IfScope ((condStr, StringBuilder ())::x, None)::ls } 297 | |> Ok 298 | | _ -> Error ("这里不应该使用elif命令。", call.DebugInfo) 299 | | "else" -> 300 | if context.CurrentComplexCommand.IsSome 301 | then genComplexCommandError () 302 | else 303 | match context.ScopeStack with 304 | | IfScope (ifs, None)::ls -> 305 | { context with ScopeStack = IfScope (ifs, Some <| StringBuilder ()) :: ls } 306 | |> Ok 307 | | _ -> Error ("这里不应该使用else命令。", call.DebugInfo) 308 | | "endif" -> 309 | if context.CurrentComplexCommand.IsSome 310 | then genComplexCommandError () 311 | else 312 | match context.ScopeStack with 313 | | (IfScope (ifs, def)) :: outter -> 314 | let sb = getScopeSb outter 315 | let ifs = List.rev ifs 316 | ifs 317 | |> List.iteri (fun i (cond, _) -> 318 | sb 319 | .Append("#if ") 320 | .Append(cond) 321 | .Append(",goto ") 322 | .Append("IF_") 323 | .AppendLine(string <| i + context.Inc) 324 | |> ignore) 325 | 326 | let endOfIfLabel = "IF_END_" + string context.Inc 327 | 328 | if def.IsSome then 329 | sb 330 | .AppendLine(def.Value.ToString ()) 331 | |> ignore 332 | 333 | sb.Append("#goto ").AppendLine(endOfIfLabel) |> ignore 334 | 335 | ifs 336 | |> List.iteri (fun i (_, body) -> 337 | sb 338 | .Append("#label IF_") 339 | .AppendLine(string <| i + context.Inc) 340 | .AppendLine(body.ToString ()) 341 | .Append("#goto ") 342 | .AppendLine(endOfIfLabel) 343 | |> ignore) 344 | 345 | sb.Append("#label ").AppendLine(endOfIfLabel) |> ignore 346 | Ok { context with ScopeStack = outter; Inc = context.Inc + List.length ifs } 347 | 348 | | _ -> Error ("这里不应该使用endif命令。", call.DebugInfo) 349 | 350 | | "if_goto" -> 351 | let condStr, label = 352 | condStr 353 | call.Arguments[0] 354 | call.Arguments[1] 355 | call.Arguments[2], 356 | genArgUntyped call.Arguments.[3] 357 | 358 | 359 | sb.Append("#if ").Append(condStr).Append(",goto SCN_").AppendLine(label) 360 | |> ignore 361 | 362 | Ok context 363 | 364 | | "goto" -> 365 | sb.Append("#goto SCN_").AppendLine(genArgUntyped call.Arguments[0]) |> ignore 366 | Ok context 367 | 368 | | "__text_begin" -> 369 | match call.Arguments.[0] with 370 | | Symbol "null" as n -> Ok { context with CurrentComplexCommand = Some (sayCommand, [n])} 371 | | String x -> 372 | match Map.tryFind x context.Characters with 373 | | None -> Ok { context with CurrentComplexCommand = Some (sayCommand, [Constant.String x])} 374 | | Some x -> Ok { context with CurrentComplexCommand = Some (sayCommand, [Constant.String x])} 375 | | _ -> failwith "" 376 | | "__text_type" -> Error ("错误的__text_type用法。", call.DebugInfo) 377 | | "__text_pushMark" | "__text_popMark" -> Error ("PyMO不支持高级文本语法。", call.DebugInfo) 378 | | "__text_end" -> Error ("错误的__text_end用法。", call.DebugInfo) 379 | | c when Set.contains c commandsWithUntypedSymbol -> 380 | sb.Append('#').Append(c).Append(' ').AppendLine(genArgsUntyped call.Arguments) |> ignore 381 | Ok context 382 | | ComplexCommand' complex -> 383 | Ok { context with CurrentComplexCommand = Some (complex, call.Arguments) } 384 | | simple -> 385 | let simple = 386 | if simple = "chara_scroll_complex" 387 | then "chara_scroll" 388 | else simple 389 | 390 | let args = 391 | match Map.tryFind simple colorCommands with 392 | | None -> call.Arguments 393 | | Some x -> 394 | let arg = Array.ofList call.Arguments 395 | arg.[x] <- colorArg arg.[x] 396 | List.ofArray arg 397 | sb.Append('#').Append(simple).Append(' ').AppendLine(genArgs args) |> ignore 398 | Ok context 399 | 400 | 401 | let private generateScene strings genDbg (scene: IntermediateScene) context (sb: StringBuilder) = 402 | 403 | if genDbg then 404 | sb.AppendLine( 405 | ";YKMDBG" + 406 | ";L" + 407 | string scene.DebugInfo.LineNumber + 408 | ";F" + string (getDebugString strings scene.DebugInfo.File) + 409 | ";S" + string (getDebugString strings scene.Name)) 410 | |> ignore 411 | 412 | 413 | sb 414 | .Append("#label SCN_") 415 | .AppendLine(scene.Name) 416 | |> ignore 417 | 418 | match checkSceneName scene.Name with 419 | | false -> 420 | ErrorStringing.header scene.DebugInfo 421 | + "场景名称 " + scene.Name 422 | + " 非法,在PyMO中只可以使用由字母、数字和下划线组成的场景名。" 423 | |> Console.WriteLine 424 | context, false 425 | | true -> 426 | scene.Block 427 | |> List.fold 428 | (fun (context, success) -> 429 | genCommand strings genDbg sb context 430 | >> function 431 | | Ok context -> context, success 432 | | Error (msg, dbg) -> 433 | ErrorStringing.header dbg + msg 434 | |> Console.WriteLine 435 | context, false) 436 | (context, true) 437 | |> function 438 | | { CurrentComplexCommand = Some (ComplexCommand (o, e, _), _) } as context, _ -> 439 | ErrorStringing.header scene.DebugInfo 440 | + "在此场景的末尾,应当使用" 441 | + e 442 | + "命令来结束" 443 | + o 444 | + "变参命令组。" 445 | |> Console.WriteLine 446 | context, false 447 | | { ScopeStack = _::_ } as context, _ -> 448 | ErrorStringing.header scene.DebugInfo 449 | + "在场景的末尾应当结束当前的if区域。" 450 | |> Console.WriteLine 451 | context, false 452 | | c -> c 453 | 454 | 455 | let generateScript genDbg (Intermediate scenes) scriptName = 456 | let sb = new StringBuilder () 457 | let strings: ref> = ref Map.empty 458 | 459 | let scenes, (context, success) = 460 | let initContext = 461 | { Characters = Map.empty 462 | CurrentComplexCommand = None 463 | ScopeStack = [] 464 | Inc = 0 465 | GenExitLabel = ref false } 466 | match List.tryFind (fun (x: IntermediateScene) -> x.Name = "$init") scenes with 467 | | None -> scenes, (initContext, true) 468 | | Some init -> 469 | List.except [init] scenes, 470 | generateScene strings genDbg init initContext sb 471 | 472 | match success, List.tryFind (fun (x: IntermediateScene) -> x.Name = scriptName) scenes with 473 | | false, _ -> Error () 474 | | true, None -> Console.WriteLine "未能找到入口点场景。"; Error () 475 | | true, Some entryPoint -> 476 | (entryPoint :: List.except [entryPoint] scenes) 477 | |> List.fold (fun success scene -> 478 | let succ' = generateScene strings genDbg scene context sb |> snd 479 | success && succ') true 480 | |> function 481 | | false -> Error () 482 | | true -> 483 | let debugSymbolTable = 484 | seq { 0 .. Map.count strings.Value - 1 } 485 | |> Seq.map (fun x -> 486 | strings.Value 487 | |> Map.pick (fun s x' -> 488 | if x = x' 489 | then Some s 490 | else None)) 491 | |> Seq.fold 492 | (fun acc x -> 493 | acc + ";YKMDBG;P" + Constants.string2literal x + "\n") 494 | "" 495 | 496 | if context.GenExitLabel.Value then 497 | sb .AppendLine() 498 | .AppendLine("#label EXIT") 499 | |> ignore 500 | 501 | Ok <| debugSymbolTable + sb.ToString () 502 | 503 | -------------------------------------------------------------------------------- /YukimiScript.CodeGen/YukimiScript.CodeGen.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net9.0 5 | https://github.com/Strrationalism/YukimiScript 6 | https://github.com/Strrationalism/YukimiScript 7 | git 8 | MIT 9 | 0.8.6 10 | YukimiScript.CodeGen 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /YukimiScript.CommandLineTool/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open System.IO 3 | open YukimiScript.Parser 4 | open YukimiScript.Parser.Utils 5 | open YukimiScript.Parser.Elements 6 | 7 | 8 | let private help () = 9 | [ "YukimiScript Command Line Tool" 10 | "by Strrationalism Studio 2022" 11 | "" 12 | "Usage:" 13 | " Compile YukimiScript to Lua:" 14 | " ykmc [--target- ] [OPTIONS...]" 15 | " Create diagram:" 16 | " ykmc diagram [OPTIONS...]" 17 | " Create charset file:" 18 | " ykmc charset [OPTIONS...]" 19 | "" 20 | "Options:" 21 | " --lib Import external library(ies) from file or dir." 22 | " --debug, -g Enable debugging information." 23 | " -L Add library searching dir for -l," 24 | " you can even pass this argument by env variable " 25 | " \"YKM_LIB_PATH\" and split it by \':\'." 26 | " -l Import library from -L dirs," 27 | " -lpymo means search \"libpymo.ykm\"," 28 | " -l\"libpymo.ykm\" means search \"libpymo.ykm\"." 29 | "" 30 | "Diagram Types:" 31 | " dgml Visual Studio Directed Graph Markup Language." 32 | " mermaid Flowchart in Mermaid." 33 | "" 34 | "Targets:" 35 | " bin YukimiScript bytecode." 36 | " lua Lua 5.1 for Lua Runtime 5.1 or LuaJIT (UTF-8)" 37 | " pymo PyMO 1.2 script, you must compile with libpymo.ykm." 38 | " json Json." 39 | "" 40 | "Example:" 41 | " ykmc ./Example/main.ykm --target-pymo ./main.txt -L../lib -lpymo" 42 | " ykmc diagram dgml ./Example/scenario ./Example.dgml --lib ./Example/lib" 43 | " ykmc charset ./Example/ ./ExampleCharset.txt --lib ./Example/lib" 44 | "" ] 45 | |> List.iter Console.WriteLine 46 | 47 | 48 | exception FailException 49 | 50 | 51 | let private unwrapResultExn (errorStringing: ErrorStringing.ErrorStringing) = 52 | function 53 | | Ok x -> x 54 | | Error err -> 55 | errorStringing err |> stderr.WriteLine 56 | raise FailException 57 | 58 | 59 | type private Options = 60 | { LibExactly: string list 61 | LibsToSearch: string list 62 | LibSearchDir: string list 63 | Debugging: bool } 64 | 65 | 66 | let defaultLibSearchDirs = 67 | let e = System.Environment.GetEnvironmentVariable ("YKM_LIB_PATH") 68 | if String.IsNullOrWhiteSpace e then [] else 69 | e.Split ';' |> List.ofArray 70 | 71 | 72 | let private defaultOptions = 73 | { LibExactly = [] 74 | LibsToSearch = [] 75 | Debugging = false 76 | LibSearchDir = "." :: defaultLibSearchDirs } 77 | 78 | 79 | type private TargetOption = 80 | | Lua of outputFile: string 81 | | PyMO of outputFile: string * scriptName: string 82 | | Bytecode of outputFile: string 83 | | Json of outputFile: string 84 | 85 | 86 | type private DiagramType = 87 | | Dgml 88 | | Mermaid 89 | 90 | 91 | type private CmdArg = 92 | | Diagram of DiagramType * inputDir: string * output: string * Options 93 | | Charset of inputDir: string * outputCharsetFile: string * Options 94 | | Compile of inputFile: string * TargetOption list * Options 95 | 96 | 97 | let rec private parseOptions prev = 98 | function 99 | | [] -> Ok prev 100 | | "--lib" :: libPath :: next -> 101 | parseOptions { prev with LibExactly = libPath :: prev.LibExactly } next 102 | | x :: next when x = "-g" || x = "--debug" -> 103 | parseOptions { prev with Debugging = true } next 104 | | x :: next when x.StartsWith "-L" -> 105 | parseOptions 106 | { prev with LibSearchDir = x.[2..] :: prev.LibSearchDir } 107 | next 108 | | x :: next when x.StartsWith "-l" -> 109 | parseOptions 110 | { prev with LibsToSearch = x.[2..] :: prev.LibsToSearch } 111 | next 112 | | _ -> Error() 113 | 114 | 115 | let rec private parseTargetsAndOptions (inputSrc: string) = 116 | function 117 | | "--target-bin" :: binOut :: next -> 118 | parseTargetsAndOptions inputSrc next 119 | |> Result.map (fun (nextTargets, options) -> 120 | Bytecode binOut :: nextTargets, options) 121 | | "--target-pymo" :: pymoOut :: next -> 122 | parseTargetsAndOptions inputSrc next 123 | |> Result.map (fun (nextTargets, options) -> 124 | let scriptName = Path.GetFileNameWithoutExtension inputSrc 125 | PyMO (pymoOut, scriptName) :: nextTargets, options) 126 | | "--target-lua" :: luaOut :: next -> 127 | parseTargetsAndOptions inputSrc next 128 | |> Result.map (fun (nextTargets, options) -> Lua luaOut :: nextTargets, options) 129 | | "--target-json" :: json :: next -> 130 | parseTargetsAndOptions inputSrc next 131 | |> Result.map (fun (nextTargets, options) -> Json json :: nextTargets, options) 132 | | options -> 133 | parseOptions defaultOptions options 134 | |> Result.map (fun options -> [], options) 135 | 136 | 137 | let private parseDiagramType = 138 | function 139 | | "dgml" -> Ok Dgml 140 | | "mermaid" -> Ok Mermaid 141 | | _ -> Error() 142 | 143 | 144 | let private parseArgs = 145 | function 146 | | "diagram" :: diagramType :: inputDir :: output :: options -> 147 | parseDiagramType diagramType 148 | |> Result.bind 149 | (fun diagramType -> 150 | parseOptions defaultOptions options 151 | |> Result.map (fun options -> Diagram(diagramType, inputDir, output, options))) 152 | 153 | | "charset" :: inputDir :: charsetFile :: options -> 154 | parseOptions defaultOptions options 155 | |> Result.map (fun options -> Charset(inputDir, charsetFile, options)) 156 | 157 | | inputSrc :: targetsAndOptions -> 158 | parseTargetsAndOptions inputSrc targetsAndOptions 159 | |> Result.map (fun (targets, options) -> Compile(inputSrc, targets, options)) 160 | 161 | | _ -> Error() 162 | 163 | 164 | let private doAction errStringing = 165 | 166 | let loadLibs options = 167 | CompilePipe.findLibs options.LibSearchDir options.LibsToSearch 168 | |> Result.map (List.append options.LibExactly) 169 | |> Result.bind CompilePipe.loadLibs 170 | |> unwrapResultExn ErrorStringing.schinese 171 | 172 | function 173 | | Compile (inputFile, targets, options) -> 174 | let libs = loadLibs options 175 | let intermediate = CompilePipe.compile libs inputFile |> unwrapResultExn ErrorStringing.schinese 176 | 177 | targets 178 | |> List.iter 179 | (function 180 | | Bytecode output -> 181 | use file = File.Open (output, FileMode.Create) 182 | YukimiScript.CodeGen.Bytecode.generateBytecode options.Debugging intermediate file 183 | file.Close () 184 | | PyMO (output, scriptName) -> 185 | YukimiScript.CodeGen.PyMO.generateScript options.Debugging intermediate scriptName 186 | |> function 187 | | Ok out -> File.WriteAllText(output, out, Text.Encoding.UTF8) 188 | | Error () -> Console.WriteLine "Code generation failed."; exit (-1) 189 | 190 | | Lua output -> 191 | let lua = 192 | YukimiScript.CodeGen.Lua.generateLua options.Debugging intermediate 193 | 194 | File.WriteAllText(output, lua, Text.Encoding.UTF8) 195 | | Json out -> 196 | YukimiScript.CodeGen.Json.genJson options.Debugging intermediate out) 197 | 198 | | Diagram (diagramType, inputDir, out, options) -> 199 | let diagramExporter = 200 | match diagramType with 201 | | Dgml -> Diagram.exportDgml 202 | | _ -> Diagram.exportMermaid 203 | 204 | let lib = loadLibs options 205 | 206 | CompilePipe.getYkmFiles inputDir 207 | |> Array.map 208 | (fun path -> 209 | //loadSrc errStringing lib path 210 | CompilePipe.loadDom path 211 | |> Result.map (Dom.merge lib) 212 | |> Result.bind CompilePipe.checkRepeat 213 | |> Result.map Dom.expandTextCommands 214 | |> Result.bind Dom.expandUserMacros 215 | |> Result.map (fun x -> 216 | Path.GetRelativePath(inputDir, path), x)) 217 | |> List.ofArray 218 | |> Result.transposeList 219 | |> Result.bind Diagram.analyze 220 | |> unwrapResultExn errStringing 221 | |> diagramExporter 222 | |> fun diagram -> File.WriteAllText(out, diagram, Text.Encoding.UTF8) 223 | 224 | | Charset (inputDir, outCharset, options) -> 225 | let lib = loadLibs options 226 | 227 | CompilePipe.getYkmFiles inputDir 228 | |> Array.map (CompilePipe.compile lib) 229 | |> Array.toList 230 | |> Result.transposeList 231 | |> unwrapResultExn ErrorStringing.schinese 232 | |> Seq.collect (fun (Intermediate s) -> s) 233 | |> Seq.collect (fun x -> x.Block) 234 | |> Seq.collect (fun x -> x.Arguments) 235 | |> Seq.choose (function 236 | | String x -> Some x 237 | | _ -> None) 238 | |> Seq.concat 239 | |> Set.ofSeq 240 | |> Set.remove ' ' 241 | |> Set.remove '\n' 242 | |> Set.remove '\r' 243 | |> Array.ofSeq 244 | |> fun x -> new String (x) 245 | |> fun x -> IO.File.WriteAllText(outCharset, x, Text.Encoding.UTF8) 246 | 247 | 248 | [] 249 | let main argv = 250 | let mutable ret = 0 251 | (* 252 | let threadStart = 253 | Threading.ThreadStart (fun () -> 254 | argv 255 | |> Array.toList 256 | |> parseArgs 257 | |> function 258 | | Error () -> 259 | help () 260 | | Ok x -> 261 | try doAction ErrorStringing.schinese x 262 | with FailException -> ret <- -1) 263 | let thread = Threading.Thread (threadStart, 1024 * 1024 * 16) 264 | thread.Start () 265 | thread.Join () 266 | ret*) 267 | 268 | argv 269 | |> Array.toList 270 | |> parseArgs 271 | |> function 272 | | Error () -> 273 | help () 274 | | Ok x -> 275 | try doAction ErrorStringing.schinese x 276 | with FailException -> ret <- -1 277 | ret 278 | -------------------------------------------------------------------------------- /YukimiScript.CommandLineTool/YukimiScript.CommandLineTool.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0;net9.0 6 | ykmc 7 | 3390;$(WarnOn) 8 | true 9 | ykmc 10 | 0.8.6 11 | Seng Jik 12 | Strrationalism 13 | YukimiScript 14 | YukimiScript command line tool. 15 | https://github.com/Strrationalism/YukimiScript 16 | https://github.com/Strrationalism/YukimiScript 17 | git 18 | MIT 19 | YukimiScript.CommandLineTool 20 | 21 | 22 | 23 | 24 | true 25 | false 26 | link 27 | false 28 | false 29 | true 30 | Size 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/Main.fs: -------------------------------------------------------------------------------- 1 | [] 2 | let main _ = failwith "" 3 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/TestConstants.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Test.TestConstants 2 | 3 | open YukimiScript.Parser 4 | open YukimiScript.Parser.Elements 5 | open NUnit.Framework 6 | 7 | 8 | let testConstant (x: string) case = 9 | ParserMonad.run x Constants.constantParser 10 | |> function 11 | | Ok x -> Assert.AreEqual(case, x) 12 | | Error x -> Assert.Fail(x.ToString()) 13 | 14 | 15 | [] 16 | let testIntegers () = 17 | let rnd = System.Random() 18 | 19 | for _ in 0 .. 16 do 20 | let i = rnd.Next() 21 | testConstant (string i) <| Integer i 22 | let j = - rnd.Next() 23 | testConstant (string j) <| Integer j 24 | 25 | testConstant "- 1674" <| Integer -1674 26 | 27 | 28 | [] 29 | let testNumbers () = 30 | let rnd = System.Random() 31 | 32 | for _ in 0 .. 16 do 33 | let i = float (rnd.Next()) + rnd.NextDouble() 34 | testConstant (string i) <| Real i 35 | let j = -(float (rnd.Next()) + rnd.NextDouble()) 36 | testConstant (string j) <| Real j 37 | 38 | testConstant "- 176.00" <| Real -176.0 39 | 40 | 41 | [] 42 | let testStrings () = 43 | let t (str: string) = 44 | let parserInput = 45 | str 46 | .Replace("\\", @"\\") 47 | .Replace("\n", @"\n") 48 | .Replace("\t", @"\t") 49 | .Replace("\"", "\\\"") 50 | .Replace("\'", @"\'") 51 | .Replace("\r", @"\r") 52 | 53 | testConstant ("\"" + parserInput + "\"") 54 | <| String str 55 | 56 | t "中文测试" 57 | t "english test " 58 | t " This is a long text, \n this is a long text." 59 | t "\n\t\r\"\'\\ " 60 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/TestScript.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Test.TestScript 2 | 3 | open YukimiScript.Parser.Test.Utils 4 | open NUnit.Framework 5 | 6 | 7 | [] 8 | let testExampleScript () = 9 | testParseScript 10 | """ 11 | - extern systemAPI_sleep_begin force # 在这里定义宿主命令 12 | - extern systemAPI_sleep_end 13 | - extern systemAPI_sleep time=1 14 | - extern systemAPI_jumpToSection target 15 | - extern name 16 | 17 | - macro jumpToSection target 18 | __diagram_link_to target 19 | systemAPI_jumpToSection target 20 | 21 | - scene "entrypoint" 22 | @jumpToSection "场景 第一个场景" 23 | 24 | - macro wait time=1 force=false 25 | @systemAPI_sleep_begin force # 这里的内容将会被展开 26 | @systemAPI_sleep time 27 | @systemAPI_sleep_end 28 | 29 | - scene "场景 第一个场景" 30 | y:你好~我叫[name],[wait --time 1 --force] \ 31 | 欢迎你来我家里玩~ 32 | @wait 3 33 | y:感谢您使用由纪美脚本语言! 34 | @wait 35 | 36 | # 以上文字内容编译为 37 | # @__text_begin --character y 38 | # @__text_type --text "你好~我叫" 39 | # @name 40 | # @__text_type --text "," 41 | # @wait --time 1 --force true 42 | # @__text_pushMark --mark ani 43 | # @__text_type --text "很高兴认识你!" 44 | # @__text_popMark --mark ani 45 | # @__text_end --hasMore true 46 | # @__text_begin 47 | # @__text_type "欢迎你来我家里玩~" 48 | # @__text_end --hasMore false 49 | 50 | # @__text_begin --character y 51 | # @__text_type --text "感谢您使用由纪美脚本语言!" 52 | # @__text_end 53 | 54 | 55 | 56 | - scene "场景 第一个场景 的子场景" inherit "场景 第一个场景" 57 | # 这个场景的状态机将会继承于"场景 第一个场景". 58 | 59 | """ 60 | 61 | 62 | [] 63 | let testExternLinker () = 64 | testParseScript 65 | """ 66 | - extern system.hello arg1 arg2=1 67 | - macro hello arg1 arg2 68 | @system.hello arg1 arg2 69 | 70 | - scene "main" 71 | @hello --arg2 2 --arg1 1 72 | @system.hello 1 73 | @system.hello --arg2 2 --arg1 1 74 | """ 75 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/TestStatments.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Test.TestStatments 2 | 3 | open YukimiScript.Parser.Test.Utils 4 | open YukimiScript.Parser.Elements 5 | open NUnit.Framework 6 | 7 | 8 | [] 9 | let testCommandCall () = 10 | testParse " @ bg.play Black \"C1\" -256 --effect a --camera -2.0" 11 | <| Line.CommandCall 12 | { Callee = "bg.play" 13 | UnnamedArgs = 14 | [ Symbol "Black" 15 | String "C1" 16 | Integer -256 ] 17 | |> List.map Constant 18 | NamedArgs = 19 | [ "effect", Constant <| Symbol "a" 20 | "camera", Constant <| Real -2.0 ] } 21 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/TestText.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Test.TestText 2 | 3 | open YukimiScript.Parser.Test.Utils 4 | open YukimiScript.Parser.Elements 5 | open NUnit.Framework 6 | 7 | 8 | [] 9 | let testTextOnly () = 10 | testParse "你好? \\ " 11 | <| Line.Text 12 | { Character = None 13 | Text = [ TextSlice.Text "你好?" ] 14 | HasMore = true } 15 | 16 | testParse " A:你好? " 17 | <| Line.Text 18 | { Character = Some "A" 19 | Text = [ TextSlice.Text "你好?" ] 20 | HasMore = false } 21 | 22 | 23 | [] 24 | let testCommand () = 25 | testParse "[wait --time 5]\\" 26 | <| Line.Text 27 | { Character = None 28 | Text = 29 | [ TextSlice.CommandCall 30 | { Callee = "wait" 31 | UnnamedArgs = [] 32 | NamedArgs = [ "time", Constant <| Integer 5 ] } ] 33 | HasMore = true } 34 | 35 | 36 | 37 | testParse "A:测试 [wait 5]测试 " 38 | <| Line.Text 39 | { Character = Some "A" 40 | Text = 41 | [ TextSlice.Text "测试 " 42 | TextSlice.CommandCall 43 | { Callee = "wait" 44 | UnnamedArgs = [ Constant <| Integer 5 ] 45 | NamedArgs = [] } 46 | 47 | TextSlice.Text "测试" ] 48 | HasMore = false } 49 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/TestTopLevels.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Test.TestTopLevels 2 | 3 | open YukimiScript.Parser.Test.Utils 4 | open YukimiScript.Parser 5 | open YukimiScript.Parser.Elements 6 | open NUnit.Framework 7 | 8 | 9 | [] 10 | let testComment () = 11 | Parser.parseLine " # test " 12 | |> function 13 | | Ok { Line = Line.EmptyLine 14 | Comment = Some "test" } -> () 15 | | x -> Assert.Fail <| sprintf "%A" x 16 | 17 | 18 | [] 19 | let testNoComment () = 20 | Parser.parseLine " " 21 | |> function 22 | | Ok { Line = Line.EmptyLine 23 | Comment = None } -> () 24 | | x -> Assert.Fail <| sprintf "%A" x 25 | 26 | 27 | [] 28 | let testEmptyLine () = testParse " " Line.EmptyLine 29 | 30 | 31 | [] 32 | let testSceneDefination () = 33 | testParse "- scene \"测试场景A\"" 34 | <| SceneDefination { Name = "测试场景A"; Inherit = None } 35 | 36 | testParse "- scene \"测试场景B\" inherit \"A\"" 37 | <| SceneDefination { Name = "测试场景B"; Inherit = Some "A" } 38 | 39 | 40 | [] 41 | let testExternDefination () = 42 | testParse "- extern wait time=1" 43 | <| ExternDefination( 44 | ExternCommand( 45 | "wait", 46 | [ { Parameter = "time" 47 | Default = Some <| Constant (Integer 1) } ] 48 | ) 49 | ) 50 | 51 | 52 | [] 53 | let testMacroDefination () = 54 | testParse " - macro test" 55 | <| MacroDefination { Name = "test"; Param = [] } 56 | 57 | testParse " - macro test param1" 58 | <| MacroDefination 59 | { Name = "test" 60 | Param = [ { Parameter = "param1"; Default = None } ] } 61 | 62 | testParse " - macro test param1 param2" 63 | <| MacroDefination 64 | { Name = "test" 65 | Param = 66 | [ { Parameter = "param1"; Default = None } 67 | { Parameter = "param2"; Default = None } ] } 68 | 69 | testParse " - macro test param1=def param2 param3=1 param4 param5=\"what\"" 70 | <| MacroDefination 71 | { Name = "test" 72 | Param = 73 | [ { Parameter = "param1" 74 | Default = Some <| Constant (Symbol "def") } 75 | { Parameter = "param2"; Default = None } 76 | { Parameter = "param3" 77 | Default = Some <| Constant (Integer 1) } 78 | { Parameter = "param4"; Default = None } 79 | { Parameter = "param5" 80 | Default = Some <| Constant (String "what") } ] } 81 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/Utils.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Test.Utils 2 | 3 | open YukimiScript.Parser.Elements 4 | open YukimiScript.Parser.Parser 5 | open NUnit.Framework 6 | 7 | 8 | let testParse (x: string) (case: Line) = 9 | try 10 | match parseLine x with 11 | | Error e -> Assert.Fail(sprintf "%A" e) 12 | | Ok parsed -> Assert.AreEqual(case, parsed.Line) 13 | with 14 | | ex -> Assert.Fail(sprintf "%A" ex) 15 | 16 | 17 | let testParseScript (x: string) = 18 | x.Replace("\r", "").Split('\n') 19 | |> Array.mapi 20 | (fun lineNumber line -> 21 | let lineNumber = lineNumber + 1 22 | 23 | match parseLine line with 24 | | Error e -> 25 | printfn "" 26 | printfn "" 27 | printfn "Error: Line %d" lineNumber 28 | printfn "%A" e 29 | Assert.Fail() 30 | failwith "" 31 | | Ok parsed -> 32 | printfn "" 33 | printfn "" 34 | printfn "%A" parsed.Line 35 | 36 | if parsed.Comment.IsSome then 37 | printfn "# %s" parsed.Comment.Value 38 | 39 | parsed) 40 | |> YukimiScript.Parser.Dom.analyze "Test.ykm" 41 | |> Result.map YukimiScript.Parser.Dom.expandTextCommands 42 | |> Result.bind YukimiScript.Parser.Dom.expandUserMacros 43 | |> Result.map YukimiScript.Parser.Dom.expandSystemMacros 44 | |> Result.bind YukimiScript.Parser.Dom.linkToExternCommands 45 | |> printfn "%A" 46 | -------------------------------------------------------------------------------- /YukimiScript.Parser.Test/YukimiScript.Parser.Test.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | Library 7 | false 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Basics.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Basics 2 | 3 | open ParserMonad 4 | 5 | 6 | let whitespace0, whitespace1 = 7 | let whitespaceChar = inRange [ ' '; '\t' ] 8 | let post = map ignore >> name "whitespace" 9 | zeroOrMore whitespaceChar |> post, oneOrMore whitespaceChar |> post 10 | 11 | 12 | let toString: _ -> string = Seq.toArray >> fun x -> System.String(x) 13 | 14 | 15 | let toStringTrim x = (toString x).Trim() 16 | 17 | 18 | exception InvalidSymbolException 19 | 20 | 21 | let symbol: Parser = 22 | let firstCharacter = 23 | seq { 24 | yield! seq { 'a' .. 'z' } 25 | yield! seq { 'A' .. 'Z' } 26 | '_' 27 | '.' 28 | } 29 | 30 | let character = 31 | firstCharacter |> Seq.append (seq { '0' .. '9' }) 32 | 33 | parser { 34 | let! first = inRange firstCharacter 35 | let! tail = zeroOrMore (inRange character) 36 | return toStringTrim (first :: tail) 37 | } 38 | |> mapError (fun _ -> InvalidSymbolException) 39 | -------------------------------------------------------------------------------- /YukimiScript.Parser/CompilePipe.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.CompilePipe 2 | 3 | open System.IO 4 | open YukimiScript.Parser.Parser 5 | open YukimiScript.Parser.Utils 6 | 7 | 8 | let loadDom path = 9 | try Ok <| File.ReadAllLines path with e -> Error e 10 | |> Result.bind 11 | (parseLines 12 | >> Result.mapError (fun err -> ParseLinesException (path, err))) 13 | |> Result.bind (Dom.analyze <| Path.GetFileName path) 14 | 15 | 16 | let findLib libDirs (libName: string) = 17 | let libFileName = 18 | if libName.ToLower().EndsWith ".ykm" 19 | then libName 20 | else "lib" + libName + ".ykm" 21 | 22 | libDirs 23 | |> List.tryPick (fun x -> 24 | let libPath = Path.Combine (x, "./" + libFileName) 25 | if File.Exists libPath 26 | then Some libPath else None) 27 | 28 | 29 | let findLibs libDirs libs = 30 | let succs, fails = 31 | List.foldBack 32 | (fun libName (succs, fails) -> 33 | match findLib libDirs libName with 34 | | Some path -> (path :: succs, fails) 35 | | None -> (succs, libName :: fails)) 36 | libs 37 | ([], []) 38 | 39 | if fails <> [] then Error <| CanNotFindLib fails else Ok succs 40 | 41 | 42 | let checkRepeat (dom: Dom) = 43 | let findRepeat (items: (string * Elements.DebugInfo) seq) = 44 | Seq.groupBy fst items 45 | |> Seq.choose 46 | (fun (key, matches) -> 47 | if Seq.length matches <= 1 then 48 | None 49 | else 50 | Some(key, matches |> Seq.map snd)) 51 | 52 | dom.Externs 53 | |> Seq.map (fun (Elements.ExternCommand (cmd, _), _, dbg) -> cmd, dbg) 54 | |> findRepeat 55 | |> Seq.tryHead 56 | |> function 57 | | None -> Ok dom 58 | | Some x -> 59 | Dom.ExternRepeatException x 60 | |> Error 61 | |> Result.bind (fun dom -> 62 | dom.Scenes 63 | |> Seq.map (fun (s, _, dbg) -> s.Name, dbg) 64 | |> findRepeat 65 | |> Seq.tryHead 66 | |> function 67 | | None -> Ok dom 68 | | Some x -> 69 | Dom.SceneRepeatException x 70 | |> Error) 71 | |> Result.bind (fun dom -> 72 | dom.Macros 73 | |> Seq.map (fun (s, _, _, dbg) -> s.Name, dbg) 74 | |> findRepeat 75 | |> Seq.tryHead 76 | |> function 77 | | None -> Ok dom 78 | | Some x -> 79 | Dom.MacroRepeatException x 80 | |> Error) 81 | 82 | 83 | let checkLib path dom = 84 | if List.isEmpty dom.Scenes then Ok dom else 85 | Error <| Dom.CannotDefineSceneInLibException path 86 | 87 | 88 | let loadLib path = 89 | loadDom path |> Result.bind (checkLib path) 90 | 91 | 92 | let loadLibs paths = 93 | List.map loadLib paths 94 | |> Result.transposeList 95 | |> Result.map (List.fold Dom.merge Dom.empty) 96 | |> Result.bind checkRepeat 97 | 98 | 99 | let getYkmFiles (path: string) = 100 | if File.Exists path 101 | then [|path|] 102 | else 103 | Directory.EnumerateFiles(path, "*.ykm", SearchOption.AllDirectories) 104 | |> Array.ofSeq 105 | 106 | 107 | let generateIntermediate dom = 108 | checkRepeat dom 109 | |> Result.bind (Dom.expandTextCommands >> Dom.expandUserMacros) 110 | |> Result.bind (fun externAndSysMacro -> 111 | Dom.expandSystemMacros externAndSysMacro 112 | |> Dom.linkToExternCommands 113 | |> Result.map Intermediate.ofDom) 114 | 115 | 116 | let compile lib srcPath = 117 | loadDom srcPath 118 | |> Result.map (Dom.merge lib) 119 | |> Result.bind generateIntermediate 120 | 121 | 122 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Constants.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Constants 2 | 3 | open Basics 4 | open YukimiScript.Parser.Elements 5 | open ParserMonad 6 | 7 | 8 | let private numberParser, integerParser = 9 | let numberChar = inRange <| seq { '0' .. '9' } 10 | 11 | let unsignedIntegerString = 12 | parser { 13 | let! n = oneOrMore numberChar 14 | return toStringTrim n 15 | } 16 | |> name "digit" 17 | 18 | let sign = 19 | parser { 20 | let! sign = zeroOrOne (literal "-") 21 | 22 | if sign.IsSome then 23 | return "-" 24 | else 25 | return "" 26 | } 27 | 28 | let numberParser = 29 | parser { 30 | let! sign = sign 31 | do! whitespace0 32 | let! a = unsignedIntegerString 33 | do! literal "." 34 | let! b = unsignedIntegerString 35 | 36 | return Real <| float (sign + a + "." + b) 37 | } 38 | |> name "number" 39 | 40 | let integerParserDec = 41 | parser { 42 | let! sign = sign 43 | do! whitespace0 44 | let! i = unsignedIntegerString 45 | return Integer <| int (sign + i) 46 | } 47 | |> name "integer" 48 | 49 | let integerParserHex = 50 | parser { 51 | let! sign = sign 52 | do! whitespace0 53 | do! literal "0x" 54 | let aToF = inRange <| Seq.append (seq { 'a' .. 'f' }) (seq { 'A' .. 'F' }) 55 | let! hexStrLs = (numberChar <|> aToF) |> oneOrMore 56 | let hexStr = sign + toStringTrim hexStrLs 57 | return Integer <| System.Convert.ToInt32(hexStr, 16) 58 | } 59 | 60 | let integerParser = integerParserHex <|> integerParserDec 61 | 62 | numberParser, integerParser 63 | 64 | 65 | 66 | exception InvalidStringCharException of string 67 | 68 | 69 | let string2literal = 70 | String.collect 71 | (function 72 | | '\n' -> "\\n" 73 | | '\t' -> "\\t" 74 | | '\r' -> "\\r" 75 | | '\'' -> "\\'" 76 | | '\"' -> "\\\"" 77 | | '\\' -> "\\\\" 78 | | x -> x.ToString()) 79 | 80 | let stringParser = 81 | let stringChar = 82 | let secondCharParser = 83 | parser { 84 | match! anyChar with 85 | | 'n' -> return '\n' 86 | | 't' -> return '\t' 87 | | '\\' -> return '\\' 88 | | 'r' -> return '\r' 89 | | '\'' -> return '\'' 90 | | '\"' -> return '\"' 91 | | x -> 92 | let ex = 93 | InvalidStringCharException <| "\\" + string x 94 | 95 | raise ex 96 | return! fail ex 97 | } 98 | 99 | parser { 100 | match! predicate ((<>) '\"') anyChar with 101 | | '\n' -> 102 | let ex = InvalidStringCharException "newline" 103 | raise ex 104 | return! fail ex 105 | | '\\' -> return! secondCharParser 106 | | x -> return x 107 | } 108 | 109 | parser { 110 | do! literal "\"" 111 | let! chars = zeroOrMore stringChar 112 | do! literal "\"" 113 | return toString chars 114 | } 115 | |> name "string" 116 | 117 | 118 | let private stringConstant = map String stringParser 119 | 120 | 121 | let constantToString = function 122 | | String x -> x 123 | | Symbol x -> x 124 | | Integer x -> string x 125 | | Real x -> string x 126 | 127 | 128 | let constantParser = 129 | [ numberParser 130 | integerParser 131 | stringConstant 132 | map Symbol symbol ] 133 | |> choices 134 | |> name "constant" 135 | 136 | 137 | let commandArg = 138 | map Constant constantParser 139 | <|> map StringFormat (bind (fun () -> stringParser) <| literal "$") -------------------------------------------------------------------------------- /YukimiScript.Parser/Diagram.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Diagram 2 | 3 | open YukimiScript.Parser.Elements 4 | open YukimiScript.Parser.Macro 5 | open System.IO 6 | 7 | 8 | type SceneNode = { Name: string; FileName: string } 9 | 10 | 11 | type FileNode = 12 | { Name: string 13 | Scenes: SceneNode list } 14 | 15 | 16 | type SceneArrow = { From: SceneNode; Target: SceneNode } 17 | 18 | 19 | type Diagram = 20 | { Files: FileNode list 21 | Arrows: SceneArrow list } 22 | 23 | 24 | exception DiagramMacroErrorException of DebugInfo 25 | 26 | 27 | exception CannotFindSceneException of string 28 | 29 | 30 | let analyze (files: (string * Dom) list) : Result = 31 | try 32 | let fileNodes, arrows = 33 | files 34 | |> List.map 35 | (fun (fileName, dom) -> 36 | let scenes = 37 | dom.Scenes 38 | |> List.map 39 | (fun (scene, block, _) -> 40 | let linkTo = 41 | block 42 | |> List.choose 43 | (function 44 | | CommandCall c, debug when c.Callee = "__diagram_link_to" -> 45 | let p = { Parameter = "target"; Default = None } 46 | 47 | matchArguments debug [ p ] c 48 | |> Result.bind ( 49 | TypeChecker.checkApplyTypeCorrect 50 | debug 51 | [ "target", TypeChecker.Types.string ]) 52 | |> function 53 | | Ok [ "target", Constant (String target) ] -> Some target 54 | | Error e -> raise e 55 | | _ -> raise <| DiagramMacroErrorException debug 56 | | _ -> None) 57 | 58 | { Name = scene.Name 59 | FileName = fileName }, 60 | linkTo) 61 | 62 | { Name = fileName 63 | Scenes = List.map fst scenes }, 64 | scenes) 65 | |> List.unzip 66 | 67 | let arrows = 68 | let scenes = 69 | Seq.collect (fun x -> x.Scenes) fileNodes 70 | 71 | arrows 72 | |> List.concat 73 | |> List.collect 74 | (fun (src, dst) -> 75 | dst 76 | |> List.map 77 | (fun dst -> 78 | scenes 79 | |> Seq.tryFind (fun x -> x.Name = dst) 80 | |> function 81 | | None -> raise <| CannotFindSceneException dst 82 | | Some x -> src, x)) 83 | |> List.map (fun (a, b) -> { From = a; Target = b }) 84 | 85 | Ok { Files = fileNodes; Arrows = arrows } 86 | with 87 | | e -> Error e 88 | 89 | 90 | let exportDgml (diagram: Diagram) : string = 91 | let sb = System.Text.StringBuilder() 92 | 93 | sb 94 | .AppendLine("""""") 95 | .AppendLine("""""") 96 | .AppendLine(""" """) 97 | |> ignore 98 | 99 | let fileNodeOnlyContainsOneScene file = 100 | Seq.tryExactlyOne file.Scenes 101 | |> Option.filter (fun x -> x.Name = Path.GetFileNameWithoutExtension file.Name) 102 | |> Option.isSome 103 | 104 | for file in diagram.Files do 105 | if not <| fileNodeOnlyContainsOneScene file then 106 | sb 107 | .Append(" ") 110 | .AppendLine() 111 | |> ignore 112 | 113 | for scene in file.Scenes do 114 | sb 115 | .Append(" ") 118 | .AppendLine() 119 | |> ignore 120 | 121 | sb 122 | .AppendLine(" ") 123 | .AppendLine(" ") 124 | |> ignore 125 | 126 | for file in diagram.Files do 127 | if not <| fileNodeOnlyContainsOneScene file then 128 | for scene in file.Scenes do 129 | sb 130 | .Append(" ") 135 | .AppendLine() 136 | |> ignore 137 | 138 | for arrows in diagram.Arrows do 139 | sb 140 | .Append(" ") 145 | .AppendLine() 146 | |> ignore 147 | 148 | sb 149 | .AppendLine(" ") 150 | .AppendLine(" ") 151 | .AppendLine(" ") 158 | .AppendLine(" ") 159 | .AppendLine("") 160 | .ToString() 161 | 162 | 163 | let exportMermaid (diagram: Diagram) : string = 164 | let arrowInSameFile, anotherArrows = 165 | diagram.Arrows 166 | |> List.partition (fun { From = from; Target = target } -> from.FileName = target.FileName) 167 | 168 | let arrowInSameFile = 169 | arrowInSameFile 170 | |> List.groupBy (fun x -> x.From.FileName) 171 | 172 | let sb = System.Text.StringBuilder() 173 | sb.AppendLine("flowchart LR") |> ignore 174 | 175 | let fileIds = 176 | diagram.Files 177 | |> List.mapi (fun i x -> x.Name, "f" + string i) 178 | |> Map.ofList 179 | 180 | let sceneIds = 181 | diagram.Files 182 | |> List.collect (fun x -> x.Scenes) 183 | |> List.mapi (fun i x -> x, "n" + string i) 184 | |> Map.ofList 185 | 186 | let processArrows arrows = 187 | for arr in arrows do 188 | sb 189 | .Append(" ") 190 | .Append(sceneIds.[arr.From]) 191 | .Append("(\"") 192 | .Append(arr.From.Name) 193 | .Append("\")") 194 | .Append("-->") 195 | .Append(sceneIds.[arr.Target]) 196 | .Append("(\"") 197 | .Append(arr.Target.Name) 198 | .AppendLine("\")") 199 | |> ignore 200 | 201 | for (file, arrows) in arrowInSameFile do 202 | sb 203 | .Append(" subgraph ") 204 | .Append(fileIds.[file]) 205 | .Append("[\"") 206 | .Append(file) 207 | .AppendLine("\"]") 208 | |> ignore 209 | 210 | processArrows arrows 211 | 212 | sb.AppendLine(" end").AppendLine() |> ignore 213 | 214 | processArrows anotherArrows 215 | 216 | sb.ToString() 217 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Dom.fs: -------------------------------------------------------------------------------- 1 | namespace YukimiScript.Parser 2 | 3 | open YukimiScript.Parser.Parser 4 | open YukimiScript.Parser.Elements 5 | open YukimiScript.Parser.TypeChecker 6 | open YukimiScript.Parser.Utils 7 | 8 | 9 | type Dom = 10 | { HangingEmptyLine: DebugInfo list 11 | Externs: (ExternDefination * BlockParamTypes * DebugInfo) list 12 | Macros: (MacroDefination * BlockParamTypes * Block * DebugInfo) list 13 | Scenes: (SceneDefination * Block * DebugInfo) list } 14 | 15 | 16 | module Dom = 17 | let merge dom1 dom2 = 18 | { HangingEmptyLine = dom1.HangingEmptyLine @ dom2.HangingEmptyLine 19 | Externs = dom1.Externs @ dom2.Externs 20 | Macros = dom1.Macros @ dom2.Macros 21 | Scenes = dom1.Scenes @ dom2.Scenes } 22 | 23 | 24 | let empty = 25 | { HangingEmptyLine = [] 26 | Externs = [] 27 | Macros = [] 28 | Scenes = [] } 29 | 30 | 31 | type private AnalyzeState = 32 | { Result: Dom 33 | CurrentBlock: (Line * DebugInfo * Block) option } 34 | 35 | 36 | exception HangingOperationException of debugInfo: DebugInfo 37 | 38 | 39 | exception UnknownException 40 | 41 | 42 | exception SceneRepeatException of scene: string * debugInfo: DebugInfo seq 43 | 44 | 45 | exception MacroRepeatException of macro: string * debugInfo: DebugInfo seq 46 | 47 | 48 | exception ExternRepeatException of name: string * debugInfo: DebugInfo seq 49 | 50 | 51 | exception ExternCannotHasContentException of name: string * DebugInfo 52 | 53 | 54 | let private saveCurrentBlock state = 55 | match state.CurrentBlock with 56 | | None -> state 57 | | Some (label, debugInfo, block) -> 58 | { CurrentBlock = None 59 | Result = 60 | { state.Result with 61 | Macros = 62 | match label with 63 | | MacroDefination x -> 64 | let block = List.rev block 65 | match Macro.parametersTypeFromBlock x.Param block with 66 | | Ok t -> (x, t, block, debugInfo) :: state.Result.Macros 67 | | Error e -> raise e 68 | | _ -> state.Result.Macros 69 | Scenes = 70 | match label with 71 | | SceneDefination x -> 72 | (x, List.rev block, debugInfo) 73 | :: state.Result.Scenes 74 | | _ -> state.Result.Scenes 75 | Externs = 76 | match label with 77 | | ExternDefination (ExternCommand (n, p)) -> 78 | if 79 | List.forall (fst >> function 80 | | CommandCall c when c.Callee = "__type" || c.Callee = "__type_symbol" -> true 81 | | EmptyLine -> true 82 | | _ -> false) block 83 | then 84 | match Macro.parametersTypeFromBlock p block with 85 | | Ok t -> 86 | (ExternCommand (n, p), t, debugInfo) 87 | :: state.Result.Externs 88 | | Error e -> raise e 89 | else raise <| ExternCannotHasContentException (n, debugInfo) 90 | | _ -> state.Result.Externs} } 91 | 92 | 93 | let private analyzeFold state (line, debugInfo) = 94 | 95 | let pushOperation x = 96 | match state.CurrentBlock with 97 | | None -> Error <| HangingOperationException debugInfo 98 | | Some (line, labelDbgInfo, block) -> 99 | { state with 100 | CurrentBlock = 101 | let debugInfo = 102 | { debugInfo with Scope = labelDbgInfo.Scope } 103 | Some(line, labelDbgInfo, (x, debugInfo) :: block) } 104 | |> Ok 105 | 106 | let setLabel state line = 107 | { saveCurrentBlock state with 108 | CurrentBlock = 109 | let debugInfoScope = 110 | match line with 111 | | SceneDefination scene -> Some (Choice2Of2 scene) 112 | | MacroDefination macro -> Some (Choice1Of2 macro) 113 | | _ -> None 114 | Some (line, { debugInfo with Scope = debugInfoScope }, []) } 115 | 116 | match line with 117 | | Line.EmptyLine -> 118 | match state.CurrentBlock with 119 | | Some _ -> pushOperation EmptyLine 120 | | None -> 121 | { state with 122 | Result = 123 | { state.Result with 124 | HangingEmptyLine = debugInfo :: state.Result.HangingEmptyLine } } 125 | |> Ok 126 | 127 | | Line.CommandCall x -> pushOperation <| CommandCall x 128 | | Line.Text x -> pushOperation <| Text x 129 | | SceneDefination scene -> Ok <| setLabel state (SceneDefination scene) 130 | | MacroDefination macro -> Ok <| setLabel state (MacroDefination macro) 131 | | ExternDefination extern' -> Ok <| setLabel state (ExternDefination extern') 132 | 133 | 134 | exception CannotDefineSceneInLibException of string 135 | 136 | 137 | let analyze (fileName: string) (x: Parsed seq) : Result = 138 | let filePath = System.IO.Path.GetFullPath fileName 139 | try 140 | let finalState = 141 | x 142 | |> Seq.indexed 143 | |> Seq.map 144 | (fun (lineNumber, { Line = line }) -> 145 | line, 146 | { LineNumber = lineNumber + 1 147 | File = filePath 148 | Scope = None 149 | Outter = None 150 | MacroVars = [] }) 151 | |> Seq.fold 152 | (fun state x -> Result.bind (fun state -> analyzeFold state x) state) 153 | (Ok { Result = empty; CurrentBlock = None }) 154 | |> Result.map saveCurrentBlock 155 | 156 | finalState 157 | |> Result.map 158 | (fun x -> 159 | { Scenes = List.rev x.Result.Scenes 160 | Macros = List.rev x.Result.Macros 161 | Externs = List.rev x.Result.Externs 162 | HangingEmptyLine = List.rev x.Result.HangingEmptyLine }) 163 | with e -> Error e 164 | 165 | let expandTextCommands (x: Dom) : Dom = 166 | let mapBlock = 167 | List.collect (function 168 | | Text x, debugInfo -> 169 | Text.expandTextBlock x debugInfo 170 | | x -> [ x ]) 171 | 172 | { x with 173 | Scenes = List.map (fun (def, block, d) -> def, mapBlock block, d) x.Scenes 174 | Macros = List.map (fun (def, t, b, d) -> def, t, mapBlock b, d) x.Macros } 175 | 176 | 177 | let expandUserMacros (x: Dom) = 178 | let macros = 179 | List.map (fun (a, t, b, _) -> a, t, b) x.Macros 180 | 181 | let callSystemCallback name args sceneDebugInfo = 182 | if 183 | x.Macros |> List.exists (fun (a, _, _, _) -> a.Name = name) || 184 | x.Externs |> List.exists (fun ((ExternCommand (x, _)), _, _) -> x = name) 185 | then 186 | Some begin 187 | CommandCall { Callee = name 188 | UnnamedArgs = args 189 | NamedArgs = [] }, 190 | sceneDebugInfo 191 | end 192 | else None 193 | 194 | x.Scenes 195 | |> List.map 196 | (fun (sceneDef, block, debugInfo) -> 197 | let lastDebugInfo = 198 | List.tryLast block 199 | |> Option.map snd 200 | |> Option.defaultValue debugInfo 201 | 202 | let beforeSceneCall, afterSceneCall = 203 | match sceneDef.Inherit with 204 | | None -> 205 | callSystemCallback 206 | "__callback_scene_before" 207 | [ Constant <| String sceneDef.Name ] 208 | debugInfo, 209 | callSystemCallback 210 | "__callback_scene_after" 211 | [ Constant <| String sceneDef.Name ] 212 | lastDebugInfo 213 | | Some inheritScene -> 214 | callSystemCallback 215 | "__callback_scene_inherit_before" 216 | [ Constant <| String sceneDef.Name; 217 | Constant <| String inheritScene ] 218 | debugInfo, 219 | callSystemCallback 220 | "__callback_scene_inherit_after" 221 | [ Constant <| String sceneDef.Name; 222 | Constant <| String inheritScene ] 223 | lastDebugInfo 224 | 225 | let block = 226 | Option.toList beforeSceneCall 227 | @ block @ 228 | Option.toList afterSceneCall 229 | 230 | Macro.expandBlock macros block 231 | |> Result.map (fun x -> sceneDef, x, debugInfo)) 232 | |> Result.transposeList 233 | |> Result.map (fun scenes -> { x with Scenes = scenes }) 234 | 235 | 236 | let expandSystemMacros (x: Dom) = 237 | { x with 238 | Scenes = 239 | x.Scenes 240 | |> List.map (fun (a, b, c) -> a, Macro.expandSystemMacros b, c) } 241 | 242 | 243 | exception MustExpandTextBeforeLinkException 244 | 245 | 246 | exception ExternCommandDefinationNotFoundException of string * DebugInfo 247 | 248 | 249 | let private systemCommands : (ExternDefination * BlockParamTypes) list = 250 | let parse str = 251 | TopLevels.topLevels 252 | |> ParserMonad.run str 253 | |> function 254 | | Ok (ExternDefination x) -> x 255 | | _ -> failwith "Bug here!" 256 | 257 | [ parse "- extern __text_begin character=null", [ 258 | "character", ParameterType ("string | null", set [ String'; ExplicitSymbol' "null" ]) ] 259 | parse "- extern __text_type text", [ "text", Types.string ] 260 | parse "- extern __text_pushMark mark", [ "mark", Types.symbol ] 261 | parse "- extern __text_popMark mark", [ "mark", Types.symbol ] 262 | parse "- extern __text_end hasMore", [ "hasMore", Types.bool ] ] 263 | 264 | 265 | let linkToExternCommands (x: Dom) : Result = 266 | let externs = systemCommands @ List.map (fun (x, t, _) -> x, t) x.Externs 267 | 268 | let linkSingleCommand (op, debugInfo) = 269 | match op with 270 | | Text _ -> Error MustExpandTextBeforeLinkException 271 | | CommandCall c -> 272 | match List.tryFind (fun (ExternCommand (name, _), _) -> name = c.Callee) externs with 273 | | None -> 274 | Error 275 | <| ExternCommandDefinationNotFoundException(c.Callee, debugInfo) 276 | | Some (ExternCommand (_, param), t) -> 277 | Macro.matchArguments debugInfo param c 278 | |> Result.bind (checkApplyTypeCorrect debugInfo t) 279 | |> Result.bind 280 | (fun args -> 281 | let args = Macro.sortArgs param args 282 | let args = 283 | List.map (fun (n, a) -> 284 | Macro.commandArgToConstant 285 | args (Some n) a debugInfo) args 286 | |> Result.transposeList 287 | 288 | args 289 | |> Result.map (fun args -> 290 | CommandCall 291 | { c with 292 | UnnamedArgs = List.map Constant args 293 | NamedArgs = [] } )) 294 | | x -> Ok x 295 | |> Result.map (fun x -> x, debugInfo) 296 | 297 | let linkToExternCommands (sceneDef, block, debugInfo) = 298 | List.map linkSingleCommand block 299 | |> Result.transposeList 300 | |> Result.map (fun block -> sceneDef, (block: Block), debugInfo) 301 | 302 | List.map linkToExternCommands x.Scenes 303 | |> Result.transposeList 304 | |> Result.map (fun scenes -> { x with Scenes = scenes }) 305 | -------------------------------------------------------------------------------- /YukimiScript.Parser/EditorHelper.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.EditorHelper 2 | 3 | open YukimiScript.Parser 4 | open YukimiScript.Parser.Elements 5 | 6 | 7 | type Runner<'State> = 'State -> CommandCall -> Result<'State, exn> 8 | 9 | 10 | let rec run (state: 'State) (runner: Runner<'State>) (ops: Block) : Result<'State, exn> = 11 | match Seq.tryHead ops with 12 | | None -> Ok state 13 | | Some (EmptyLine, _) -> run state runner <| List.tail ops 14 | | Some (CommandCall c, _) -> 15 | runner state c 16 | |> Result.bind (fun state -> run state runner <| List.tail ops) 17 | | Some (Text t, d) -> 18 | run state runner <| Text.expandTextBlock t d 19 | |> Result.bind (fun state -> run state runner <| List.tail ops) 20 | 21 | 22 | let idRunner: Runner<'State> = fun a _ -> Ok a 23 | 24 | 25 | let mapRunner (a2b: 'a -> 'b) (b2a: 'b -> 'a) (a: Runner<'a>) : Runner<'b> = 26 | fun state c -> a (b2a state) c |> Result.map a2b 27 | 28 | 29 | let dispatch (dispatcher: CommandCall -> Runner<'State>) : Runner<'State> = fun state c -> (dispatcher c) state c 30 | 31 | 32 | type RunnerWrapper<'TState>(init: 'TState, mainRunner: Runner<'TState>) = 33 | member _.Run(state: 'TState, ops: Block) : Result<'TState, exn> = run state mainRunner ops 34 | 35 | member x.Run(ops: Block) : Result<'TState, exn> = x.Run(init, ops) 36 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Elements.fs: -------------------------------------------------------------------------------- 1 | namespace YukimiScript.Parser.Elements 2 | 3 | 4 | type Constant = 5 | | String of string 6 | | Real of float 7 | | Integer of int32 8 | | Symbol of string 9 | 10 | 11 | type CommandArg = 12 | | Constant of Constant 13 | | StringFormat of string 14 | 15 | 16 | type CommandCall = 17 | { Callee: string 18 | UnnamedArgs: CommandArg list 19 | NamedArgs: (string * CommandArg) list } 20 | 21 | 22 | type TextSlice = 23 | | Text of string 24 | | CommandCall of CommandCall 25 | | Marked of mark: string * inner: TextSlice list 26 | 27 | 28 | type Parameter = 29 | { Parameter: string 30 | Default: CommandArg option } 31 | 32 | 33 | type MacroDefination = { Name: string; Param: Parameter list } 34 | 35 | 36 | type SceneDefination = 37 | { Name: string 38 | Inherit: string option } 39 | 40 | 41 | type TextBlock = 42 | { Character: string option 43 | Text: TextSlice list 44 | HasMore: bool } 45 | 46 | 47 | type ExternDefination = ExternCommand of string * Parameter list 48 | 49 | 50 | type Line = 51 | | EmptyLine 52 | | SceneDefination of SceneDefination 53 | | MacroDefination of MacroDefination 54 | | ExternDefination of ExternDefination 55 | | CommandCall of CommandCall 56 | | Text of TextBlock 57 | 58 | 59 | type DebugInfo = 60 | { LineNumber: int 61 | File: string 62 | Scope: Choice option 63 | Outter: DebugInfo option 64 | MacroVars: (string * Constant) list } 65 | 66 | 67 | type Operation = 68 | | Text of TextBlock 69 | | CommandCall of CommandCall 70 | | EmptyLine 71 | 72 | 73 | module Operation = 74 | let toLine: Operation -> Line = 75 | function 76 | | Text t -> Line.Text t 77 | | CommandCall c -> Line.CommandCall c 78 | | EmptyLine -> Line.EmptyLine 79 | 80 | 81 | exception CanNotConvertToOperationException of Line 82 | 83 | 84 | let ofLine: Line -> Operation = 85 | function 86 | | Line.Text t -> Text t 87 | | Line.CommandCall c -> CommandCall c 88 | | Line.EmptyLine -> EmptyLine 89 | | x -> raise <| CanNotConvertToOperationException x 90 | 91 | 92 | type Block = (Operation * DebugInfo) list 93 | -------------------------------------------------------------------------------- /YukimiScript.Parser/ErrorStringing.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.ErrorStringing 2 | 3 | open YukimiScript.Parser.Basics 4 | open YukimiScript.Parser.Constants 5 | open YukimiScript.Parser.Dom 6 | open YukimiScript.Parser.Macro 7 | open YukimiScript.Parser.ParserMonad 8 | open YukimiScript.Parser.TopLevels 9 | open YukimiScript.Parser.Diagram 10 | open YukimiScript.Parser.TypeChecker 11 | open YukimiScript.Parser.Utils 12 | open System.IO 13 | open type System.Environment 14 | 15 | 16 | type ErrorStringing = exn -> string 17 | 18 | 19 | let header (debug: Elements.DebugInfo) = 20 | Path.GetFileName(debug.File) 21 | + "(" 22 | + string debug.LineNumber 23 | + "):" 24 | 25 | let rec schinese: ErrorStringing = 26 | function 27 | | Parser.ParseLinesException (filePath, errs) -> 28 | errs 29 | |> Seq.map 30 | (fun (lineNumber, err) -> 31 | Path.GetFileName(filePath: string) 32 | + "(" 33 | + string lineNumber 34 | + "):" 35 | + schinese err) 36 | |> Seq.fold (fun acc x -> acc + NewLine + x) "" 37 | | MultiException exns -> 38 | List.fold (fun acc x -> acc + schinese x + NewLine) "" exns 39 | |> fun x -> x.TrimEnd('\n', '\r') 40 | | CanNotFindLib x -> 41 | "找不到这些库:" + List.fold (fun acc x -> acc + "\t" + x + NewLine) "" x 42 | | TypeCheckFailedException (d, i, ParameterType (name, _), a) -> 43 | header d 44 | + "第" + string (i + 1) + "个参数的类型应当为" + name + ",但传入了" + 45 | match a with 46 | | Int' -> "int" 47 | | Real' -> "real" 48 | | String' -> "string" 49 | | ExplicitSymbol' _ | Symbol' -> "symbol" 50 | + "。" 51 | | IsNotAType (x, d) -> 52 | header d + x + "不是一个类型。" 53 | | CannotGetParameterException ls -> 54 | ls 55 | |> List.map (fun (x, d) -> header d + "不能获得" + x + "的类型。") 56 | |> List.reduce (fun a b -> a + NewLine + b) 57 | | InvalidSymbolException -> "非法符号。" 58 | | InvalidStringCharException x -> "字符串中存在非法字符\"" + x + "\"。" 59 | | HangingOperationException debug -> header debug + "存在悬浮操作。" 60 | | SceneRepeatException (scene, dbgs) -> 61 | "重复定义了场景" 62 | + scene 63 | + ",分别在以下位置:" 64 | + NewLine 65 | + (dbgs 66 | |> Seq.map (fun x -> " " + x.File + "(" + string x.LineNumber + ")") 67 | |> Seq.reduce (fun a b -> a + NewLine + b)) 68 | 69 | | MacroRepeatException (macro, dbgs) -> 70 | "重复定义了宏" 71 | + macro 72 | + ",分别在以下位置:" 73 | + NewLine 74 | + (dbgs 75 | |> Seq.map (fun x -> " " + x.File + "(" + string x.LineNumber + ")") 76 | |> Seq.reduce (fun a b -> a + NewLine + b)) 77 | 78 | | ExternRepeatException (ex, dbgs) -> 79 | "重复定义了外部元素" 80 | + ex 81 | + ",分别在以下位置:" 82 | + NewLine 83 | + (dbgs 84 | |> Seq.map (fun x -> " " + x.File + "(" + string x.LineNumber + ")") 85 | |> Seq.reduce (fun a b -> a + NewLine + b)) 86 | 87 | | MustExpandTextBeforeLinkException -> "必须先展开文本元素再连接外部元素。" 88 | | ExternCannotHasContentException (x, d) -> header d + "外部定义中不能包含内容:" + x + "。" 89 | | ExternCommandDefinationNotFoundException (ex, debug) -> header debug + "发现了对外部元素" + ex + "的引用,但未找到此引用。" 90 | | ParamRepeatedException (parent, param) -> "在" + parent + "中发现重复定义的参数" + param + "。" 91 | | NoMacroMatchedException -> "没有匹配的宏。" 92 | | ArgumentsTooMuchException (debug, c) -> header debug + "对" + c.Callee + "传入了过多参数。" 93 | | ArgumentRepeatException (debug, cmd, param) -> 94 | header debug 95 | + "对" 96 | + cmd.Callee 97 | + "传入了重复的参数" 98 | + param 99 | + "。" 100 | | ArgumentUnmatchedException (debug, cmd, param) -> 101 | header debug 102 | + "不能为" 103 | + cmd.Callee 104 | + "匹配参数" 105 | + param 106 | + "。" 107 | | ParseUnfinishedException str -> "解析未能完成,剩余内容为" + str + "。" 108 | | NotLiteralException str -> str + "不符合Literal。" 109 | | NotInRangeException _ -> "不在范围内。" 110 | | PredicateFailedException -> "不符合条件。" 111 | | EndException -> "遇到结尾。" 112 | | ExpectSymbolException x -> "需要一个" + x + ",但并未传入。" 113 | | InvalidTopLevelException topLevel -> "未知的顶级定义" + topLevel + "。" 114 | | CannotDefineSceneInLibException debug -> debug + ":不能在lib中定义scene。" 115 | | DiagramMacroErrorException d -> header d + "__diagram_link_to宏使用方式错误。" 116 | | CannotFindSceneException x -> "不能找到场景\"" + x + "\"的定义。" 117 | | MacroInnerException (debugInfo, x) -> 118 | header debugInfo + "在展开宏时遇到以下错误:" + NewLine + 119 | begin 120 | (schinese x).Split '\n' 121 | |> Array.map (fun x -> " " + x.Trim('\r')) 122 | |> Array.reduce (fun a b -> a + NewLine + b) 123 | end 124 | | e -> "未知错误:" + NewLine + string e 125 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Intermediate.fs: -------------------------------------------------------------------------------- 1 | namespace YukimiScript.Parser 2 | 3 | open YukimiScript.Parser.Elements 4 | 5 | 6 | type IntermediateCommandCall = 7 | { Callee: string 8 | Arguments: Constant list 9 | DebugInfo: DebugInfo } 10 | 11 | 12 | type IntermediateScene = 13 | { Name: string 14 | Block: IntermediateCommandCall list 15 | DebugInfo: DebugInfo } 16 | 17 | 18 | type Intermediate = Intermediate of IntermediateScene list 19 | 20 | 21 | module Intermediate = 22 | let ofDom (dom: Dom) = 23 | dom.Scenes 24 | |> List.map 25 | (fun ({ Name = sceneName }, block, debugScene) -> 26 | let commands = 27 | block 28 | |> List.choose 29 | (fun (op, debugCommand) -> 30 | match op with 31 | | EmptyLine -> None 32 | | CommandCall c -> 33 | { Callee = c.Callee 34 | Arguments = 35 | c.UnnamedArgs 36 | |> List.map (function 37 | | Constant x -> x 38 | | _ -> failwith "Should not here.") 39 | DebugInfo = debugCommand } 40 | |> Some 41 | | a -> failwith <| "Not support in intermediate: " + string a) 42 | 43 | { Name = sceneName; Block = commands; DebugInfo = debugScene }) 44 | |> Intermediate 45 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Macro.fs: -------------------------------------------------------------------------------- 1 | module internal YukimiScript.Parser.Macro 2 | 3 | open YukimiScript.Parser.Elements 4 | open YukimiScript.Parser.Utils 5 | open TypeChecker 6 | open ParserMonad 7 | open Basics 8 | open Constants 9 | 10 | 11 | exception ParamRepeatedException of parent: string * param: string 12 | 13 | 14 | let private parameter: Parser = 15 | parser { 16 | do! whitespace1 17 | let! paramName = symbol 18 | 19 | let! defArg = 20 | parser { 21 | do! literal "=" 22 | return! commandArg 23 | } 24 | |> zeroOrOne 25 | 26 | return 27 | { Parameter = paramName 28 | Default = defArg } 29 | } 30 | |> name "parameter" 31 | 32 | 33 | let parameterList parentName : Parser = 34 | zeroOrMore parameter 35 | |> map 36 | (fun x -> 37 | match Seq.countBy (fun x -> x.Parameter) x 38 | |> Seq.tryFind (fun (_, x) -> x > 1) with 39 | | Some (p, _) -> raise <| ParamRepeatedException(parentName, p) 40 | | None -> x) 41 | 42 | 43 | let macroDefinationParser = 44 | parser { 45 | let! macroName = explicit symbol 46 | let! param = parameterList macroName 47 | return { Name = macroName; Param = param } 48 | } 49 | 50 | 51 | exception NoMacroMatchedException 52 | 53 | 54 | exception ArgumentsTooMuchException of DebugInfo * CommandCall 55 | 56 | 57 | exception ArgumentRepeatException of DebugInfo * CommandCall * string 58 | 59 | 60 | exception ArgumentUnmatchedException of DebugInfo * CommandCall * parameter: string 61 | 62 | 63 | let matchArguments debugInfo (x: Parameter list) (c: CommandCall) : Result<(string * CommandArg) list, exn> = 64 | let defaultArgs = 65 | x 66 | |> List.choose (fun { Parameter = name; Default = x } -> 67 | Option.map (fun x -> name, x) x) 68 | 69 | let parameters = List.map (fun x -> x.Parameter) x.[..c.UnnamedArgs.Length - 1] 70 | 71 | if parameters.Length < c.UnnamedArgs.Length then 72 | Error <| ArgumentsTooMuchException (debugInfo, c) 73 | else 74 | let inputArgs = 75 | c.UnnamedArgs 76 | |> List.zip parameters 77 | |> List.append c.NamedArgs 78 | 79 | // 检查是否有重复传入的参数 80 | let inputArgRepeat = 81 | Seq.countBy fst inputArgs 82 | |> Seq.tryFind (fun (_, l) -> l > 1) 83 | |> function 84 | | None -> Ok() 85 | | Some (p, _) -> Error <| ArgumentRepeatException(debugInfo, c, p) 86 | 87 | let matchArg paramName : Result = 88 | let find = List.tryFind (fst >> (=) paramName) 89 | 90 | match find inputArgs with 91 | | Some x -> Ok x 92 | | None -> 93 | match find defaultArgs with 94 | | Some x -> Ok x 95 | | None -> 96 | Error 97 | <| ArgumentUnmatchedException(debugInfo, c, paramName) 98 | 99 | inputArgRepeat 100 | |> Result.bind 101 | (fun () -> 102 | List.map (fun x -> matchArg x.Parameter) x 103 | |> Result.transposeList) 104 | 105 | 106 | let private matchMacro debug x macro = 107 | let pred (macro: MacroDefination, _, _) = macro.Name = x.Callee 108 | 109 | match List.tryFind pred macro with 110 | | None -> Error NoMacroMatchedException 111 | | Some (macro, _, _) when macro.Param.Length < x.UnnamedArgs.Length -> 112 | Error 113 | <| ArgumentsTooMuchException(debug, x) 114 | | Some (macro, t, other) -> 115 | matchArguments debug macro.Param x 116 | |> Result.bind (checkApplyTypeCorrect debug t) 117 | |> Result.map (fun args -> macro, other, args) 118 | 119 | 120 | let sortArgs parameters args = 121 | parameters 122 | |> List.map (fun p -> 123 | args |> List.find (fst >> ((=) p.Parameter))) 124 | 125 | 126 | let rec private processStringFormat env name format debug = 127 | let hole = 128 | parser { 129 | do! literal "{" 130 | let! holeName = symbol 131 | do! literal "}" 132 | return holeName 133 | } 134 | |> ParserMonad.name "String format hole" 135 | 136 | let stringFormat = 137 | zeroOrMore (map Some hole <|> map (fun _ -> None) anyChar) 138 | |> map (List.choose id) 139 | 140 | run format stringFormat 141 | |> Result.bind ( 142 | List.fold (fun intermediateString hole -> 143 | if Some hole = name then Error <| CannotGetParameterException [hole, debug] else 144 | intermediateString 145 | |> Result.bind (fun (intermediateString: string) -> 146 | match List.tryFind (fst >> (=) hole) env with 147 | | None -> Error <| CannotGetParameterException [hole, debug] 148 | | Some (_, Constant x) -> Ok <| intermediateString.Replace("{" + hole + "}", constantToString x) 149 | | Some (n, StringFormat format) -> processStringFormat env (Some n) format debug)) <| Ok format) 150 | 151 | 152 | 153 | let commandArgToConstant env name arg debug = 154 | match arg with 155 | | Constant x -> Ok x 156 | | StringFormat x -> Result.map String <| processStringFormat env name x debug 157 | 158 | 159 | let private replaceParamToArgs args macroBody debug = 160 | let replaceArg = 161 | function 162 | | Constant (Symbol x) -> 163 | match List.tryFind (fst >> (=) x) args with 164 | | None -> Ok <| Symbol x 165 | | Some (n, x) -> commandArgToConstant args (Some n) x debug 166 | | x -> commandArgToConstant args None x debug 167 | 168 | let unnamedArgs = 169 | List.map (replaceArg >> Result.map Constant) macroBody.UnnamedArgs 170 | |> Result.transposeList 171 | 172 | let namedArgs = 173 | List.map 174 | (fun (name, arg) -> 175 | Result.map (fun x -> name, Constant x) <| replaceArg arg) 176 | macroBody.NamedArgs 177 | |> Result.transposeList 178 | 179 | unnamedArgs 180 | |> Result.bind (fun unnamedArgs -> 181 | namedArgs 182 | |> Result.map (fun namedArgs -> 183 | { macroBody with 184 | UnnamedArgs = unnamedArgs 185 | NamedArgs = namedArgs })) 186 | 187 | 188 | exception MacroInnerException of DebugInfo * exn 189 | 190 | 191 | let rec private expandSingleOperation macros operation : Result = 192 | match operation with 193 | | CommandCall command, debug -> 194 | match matchMacro debug command macros with 195 | | Error NoMacroMatchedException -> Ok <| [ CommandCall command, debug ] 196 | | Error e -> Error e 197 | | Ok (macro, macroBody: Block, args) -> 198 | let macros = 199 | macros 200 | |> List.filter (fun (x, _, _) -> x.Name <> macro.Name) 201 | 202 | macroBody 203 | |> List.map ( 204 | function 205 | | CommandCall call, debugInfo -> 206 | replaceParamToArgs args call debugInfo 207 | |> Result.map (fun x -> 208 | CommandCall x, 209 | { debugInfo with 210 | Outter = Some debug }) 211 | | x -> Ok x 212 | >> Result.map (fun (op, dbg) -> 213 | let dbg = 214 | { dbg with 215 | MacroVars = 216 | args 217 | |> List.map (function 218 | | (a, StringFormat _) -> a, String "?" 219 | | (a, Constant x) -> a, x) } 220 | expandSingleOperation macros (op, dbg)) 221 | ) 222 | |> Result.transposeList 223 | |> Result.bind Result.transposeList 224 | |> Result.map List.concat 225 | | x -> Ok [ x ] 226 | |> Result.mapError (fun err -> MacroInnerException (snd operation, err)) 227 | 228 | 229 | let expandBlock macros (block: Block) = 230 | List.map (expandSingleOperation macros) block 231 | |> Result.transposeList 232 | |> Result.map List.concat 233 | 234 | 235 | let expandSystemMacros (block: Block) = 236 | let systemMacros = [ "__diagram_link_to"; "__type"; "__type_symbol" ] 237 | 238 | block 239 | |> List.map 240 | (function 241 | | CommandCall cmdCall, dbg when List.exists ((=) cmdCall.Callee) systemMacros -> EmptyLine, dbg 242 | | x -> x) 243 | 244 | 245 | let parametersTypeFromBlock (par: Parameter list) (b: Block) : Result = 246 | let typeMacroParams = 247 | [ { Parameter = "param"; Default = None } 248 | { Parameter = "type"; Default = None } ] 249 | 250 | let typeMacroParamsTypes = 251 | [ "param", Types.symbol 252 | "type", Types.symbol ] 253 | 254 | List.choose (function 255 | | CommandCall c, d when c.Callee = "__type" || c.Callee = "__type_symbol" -> Some (c, d) 256 | | _ -> None) b 257 | |> List.map (fun (c, d) -> 258 | matchArguments d typeMacroParams c 259 | |> Result.bind (checkApplyTypeCorrect d typeMacroParamsTypes) 260 | |> Result.map (fun x -> c.Callee, x, d)) 261 | |> Result.transposeList 262 | |> Result.bind (fun x -> 263 | let paramTypePairs = 264 | List.map (fun (macroName, x, d) -> 265 | x 266 | |> readOnlyDict 267 | |> fun x -> 268 | match x.["param"], x.["type"] with 269 | | Constant (Symbol par), Constant (Symbol t) -> macroName, par, t, d 270 | | _ -> failwith "parametersTypeFromBlock: failed!") x 271 | 272 | let dummy = 273 | paramTypePairs 274 | |> List.filter (not << fun (_, n, _, _) -> 275 | List.exists (fun p -> p.Parameter = n) par) 276 | |> List.map (fun (_, n, _, d) -> n, d) 277 | 278 | if List.length dummy = 0 279 | then 280 | par 281 | |> List.map (fun { Parameter = name; Default = _ } -> 282 | paramTypePairs 283 | |> List.filter ((=) name << fun (_, n, _, _) -> n) 284 | |> List.map (fun (macroName, _, t, d) -> macroName, t, d) 285 | |> function 286 | | [] -> Ok (name, Types.any) 287 | | types -> 288 | types 289 | |> List.map (fun (macroName, typeName, d) -> 290 | match macroName with 291 | | "__type" -> 292 | Types.all 293 | |> List.tryFind (fun (ParameterType (n, _)) -> n = typeName) 294 | |> function 295 | | Some x -> Ok x 296 | | None -> Error <| IsNotAType (typeName, d) 297 | | "__type_symbol" -> 298 | Ok <| ParameterType (typeName, set [ExplicitSymbol' typeName]) 299 | | _ -> failwith "?") 300 | |> Result.transposeList 301 | |> Result.map (fun t -> 302 | name, List.reduce sumParameterType t)) 303 | |> Result.transposeList 304 | else Error <| CannotGetParameterException dummy) 305 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Parser.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.Parser 2 | 3 | open YukimiScript.Parser.Elements 4 | open YukimiScript.Parser.Utils 5 | open ParserMonad 6 | open Basics 7 | 8 | 9 | exception ParseLinesException of path: string * (int * exn) list 10 | 11 | 12 | let private lineComment: Parser = 13 | parser { 14 | do! literal "#" 15 | 16 | let commentChar = 17 | predicate (fun x -> x <> '\r' && x <> '\n') anyChar 18 | 19 | let! comment = zeroOrMore commentChar 20 | 21 | return toStringTrim comment 22 | } 23 | |> name "comment" 24 | 25 | 26 | type Parsed = 27 | { Line: Elements.Line 28 | Comment: string option } 29 | 30 | 31 | let parseLine (line: string) = 32 | parser { 33 | do! whitespace0 34 | 35 | let! parsed = 36 | choices [ TopLevels.topLevels 37 | Statment.statment 38 | Text.text 39 | return' Line.EmptyLine ] 40 | 41 | do! whitespace0 42 | let! comment = zeroOrOne lineComment 43 | return { Line = parsed; Comment = comment } 44 | } 45 | |> run line 46 | 47 | 48 | let parseLines (line: string []) : Result = 49 | let parsed = 50 | line 51 | |> Array.Parallel.map parseLine 52 | |> Array.toList 53 | 54 | let errors = 55 | parsed 56 | |> List.indexed 57 | |> List.choose 58 | (function 59 | | lineNumber, Error e -> Some(lineNumber, e) 60 | | _ -> None) 61 | 62 | if List.isEmpty errors then 63 | match Result.transposeList parsed with 64 | | Ok x -> Ok x 65 | | _ -> failwith "Internal Error" 66 | else 67 | Error errors 68 | -------------------------------------------------------------------------------- /YukimiScript.Parser/ParserMonad.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.ParserMonad 2 | 3 | open YukimiScript.Parser.Utils 4 | 5 | 6 | type Parser<'a> = 7 | { Run: char list -> Result<'a * char list, exn> } 8 | 9 | 10 | let bind (f: 'a -> Parser<'b>) (p: Parser<'a>) : Parser<'b> = 11 | { Run = p.Run >> Result.bind (fun (a, ls) -> (f a).Run ls) } 12 | 13 | 14 | let return' (a: 'a) : Parser<'a> = { Run = fun x -> Ok(a, x) } 15 | 16 | 17 | let map (f: 'a -> 'b) (p: Parser<'a>) : Parser<'b> = bind (f >> return') p 18 | 19 | 20 | let mapError (f: exn -> exn) (p: Parser<'a>) : Parser<'a> = { Run = p.Run >> Result.mapError f } 21 | 22 | 23 | exception ExpectSymbolException of string 24 | 25 | 26 | let name (name: string) (a: Parser<'a>) : Parser<'a> = 27 | mapError (fun _ -> ExpectSymbolException name) a 28 | 29 | 30 | let fail<'a> (e: exn) : Parser<'a> = { Run = fun _ -> Error e } 31 | 32 | 33 | let tryWith (f: exn -> Parser<'a>) (a: Parser<'a>) : Parser<'a> = 34 | { Run = 35 | fun input -> 36 | match a.Run input with 37 | | Error e -> (f e).Run input 38 | | Ok x -> Ok x } 39 | 40 | 41 | let explicit (a: Parser<'a>) : Parser<'a> = mapError raise a 42 | 43 | 44 | type ParserBuilder() = 45 | member _.Bind(x, f) = bind f x 46 | member _.Return(x) = return' x 47 | member _.ReturnFrom(x) = x 48 | member _.Delay(x) = x () 49 | member _.TryWith(x, f) = tryWith f x 50 | 51 | 52 | let parser = ParserBuilder() 53 | 54 | 55 | exception EndException 56 | 57 | 58 | let anyChar = 59 | { Run = 60 | function 61 | | x :: ls -> Ok(x, ls) 62 | | [] -> Error EndException } 63 | 64 | 65 | exception PredicateFailedException 66 | 67 | 68 | let predicate (f: 'a -> bool) (a: Parser<'a>) : Parser<'a> = 69 | parser { 70 | match! a with 71 | | a when f a -> return a 72 | | _ -> return! fail PredicateFailedException 73 | } 74 | 75 | 76 | let (<||>) (a: Parser<'a>) (b: Parser<'b>) : Parser> = 77 | { Run = 78 | fun input -> 79 | match a.Run input with 80 | | Ok (x, r) -> Ok(Choice1Of2 x, r) 81 | | Error e1 -> 82 | match b.Run input with 83 | | Ok (x, r) -> Ok(Choice2Of2 x, r) 84 | | Error e2 -> Error(MultiException [ e1; e2 ]) } 85 | 86 | 87 | let (<|>) (a: Parser<'a>) (b: Parser<'a>) = 88 | (a <||> b) 89 | |> map 90 | (function 91 | | Choice1Of2 x -> x 92 | | Choice2Of2 x -> x) 93 | 94 | 95 | let rec choices: Parser<'a> list -> Parser<'a> = 96 | function 97 | | [] -> invalidArg "_arg0" "Choices must more than 1." 98 | | [ a ] -> a 99 | | a :: more -> a <|> choices more 100 | 101 | 102 | let rec zeroOrMore (a: Parser<'a>) : Parser<'a list> = 103 | parser { 104 | try 105 | let! head = a 106 | let! tail = zeroOrMore a 107 | return head :: tail 108 | with 109 | | _ -> return [] 110 | } 111 | 112 | 113 | let oneOrMore (a: Parser<'a>) : Parser<'a list> = 114 | parser { 115 | let! head = a 116 | let! tail = zeroOrMore a 117 | return head :: tail 118 | } 119 | 120 | 121 | let zeroOrOne (a: Parser<'a>) : Parser<'a option> = 122 | parser { 123 | try 124 | let! a = a 125 | return Some a 126 | with 127 | | _ -> return None 128 | } 129 | 130 | 131 | exception NotInRangeException of char seq 132 | 133 | 134 | let rec inRange (range: char seq) : Parser = 135 | anyChar 136 | |> predicate (fun x -> Seq.exists ((=) x) range) 137 | |> mapError (fun _ -> NotInRangeException range) 138 | 139 | 140 | exception NotLiteralException of string 141 | 142 | 143 | let rec literal (x: string) : Parser = 144 | if x = "" then 145 | return' () 146 | else 147 | parser { 148 | let! _ = predicate ((=) x.[0]) anyChar 149 | let! _ = literal x.[1..] 150 | return () 151 | } 152 | |> mapError (fun _ -> NotLiteralException x) 153 | 154 | 155 | exception ParseUnfinishedException of string 156 | 157 | 158 | let run (line: string) (parser: Parser<'a>) : Result<'a, exn> = 159 | try 160 | parser.Run(Seq.toList line) 161 | |> Result.bind 162 | (fun (result, remainder) -> 163 | if List.isEmpty remainder then 164 | Ok result 165 | else 166 | remainder 167 | |> List.toArray 168 | |> System.String 169 | |> ParseUnfinishedException 170 | |> Error) 171 | with 172 | | e -> Error e 173 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Statment.fs: -------------------------------------------------------------------------------- 1 | module internal YukimiScript.Parser.Statment 2 | 3 | open YukimiScript.Parser.Elements 4 | open ParserMonad 5 | open Basics 6 | open Constants 7 | 8 | 9 | let commandCall = 10 | parser { 11 | do! whitespace0 12 | let! command = symbol 13 | 14 | let! unnamedArgs = 15 | parser { 16 | do! whitespace1 17 | return! commandArg 18 | } 19 | |> zeroOrMore 20 | 21 | let! namedArgs = 22 | parser { 23 | do! whitespace1 24 | do! literal "--" 25 | 26 | let! param = explicit symbol 27 | 28 | let arg = 29 | parser { 30 | do! whitespace1 31 | return! commandArg 32 | } 33 | 34 | let! arg = arg <|> return' (Constant <| Symbol "true") 35 | 36 | return param, arg 37 | } 38 | |> zeroOrMore 39 | 40 | return 41 | { Callee = command 42 | UnnamedArgs = unnamedArgs 43 | NamedArgs = namedArgs } 44 | } 45 | |> name "command call" 46 | 47 | 48 | let statment = 49 | parser { 50 | do! literal "@" 51 | return! commandCall |> map Line.CommandCall 52 | } 53 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Text.fs: -------------------------------------------------------------------------------- 1 | module internal YukimiScript.Parser.Text 2 | 3 | open YukimiScript.Parser.Elements 4 | open ParserMonad 5 | open Basics 6 | 7 | 8 | let private commandCall = 9 | parser { 10 | do! literal "[" 11 | do! whitespace0 12 | 13 | let! commandCall = explicit Statment.commandCall 14 | do! whitespace0 15 | do! explicit (literal "]") 16 | return TextSlice.CommandCall commandCall 17 | } 18 | 19 | 20 | let private bareText, characterNameText = 21 | let charPred isCharacterNameText x = 22 | Seq.exists 23 | ((=) x) 24 | [ '\n' 25 | '[' 26 | '<' 27 | ']' 28 | '>' 29 | '#' 30 | '\\' 31 | '\r' 32 | if isCharacterNameText then ':' ] 33 | |> not 34 | 35 | 36 | let p isCharacterNameText = 37 | let textChar = predicate (charPred isCharacterNameText) anyChar 38 | 39 | oneOrMore textChar 40 | 41 | 42 | p false |> map (toString >> TextSlice.Text) |> name "text", 43 | p true |> map toString 44 | 45 | 46 | let rec private markBlock () = 47 | parser { 48 | do! literal "<" 49 | let! mark = symbol 50 | do! whitespace1 51 | let! innerText = zeroOrMore <| textSlice () 52 | do! literal ">" 53 | return Marked(mark, innerText) 54 | } 55 | |> name "text mark" 56 | 57 | 58 | and private textSlice () = 59 | choices [ commandCall 60 | bareText 61 | markBlock () ] 62 | |> name "text slice" 63 | 64 | 65 | let hasMoreSymbol = 66 | parser { 67 | do! whitespace0 68 | do! literal "\\" 69 | do! whitespace0 70 | } 71 | 72 | let text = 73 | let text = 74 | parser { 75 | let! character = 76 | parser { 77 | do! whitespace0 78 | let! character = zeroOrOne characterNameText 79 | do! literal ":" 80 | return character 81 | } 82 | |> zeroOrOne 83 | |> map Option.flatten 84 | 85 | let! text = oneOrMore <| textSlice () 86 | 87 | let text = 88 | text 89 | |> List.filter 90 | (function 91 | | TextSlice.Text x -> not <| System.String.IsNullOrWhiteSpace x 92 | | _ -> true) 93 | 94 | let text = 95 | text 96 | |> List.tryFindIndexBack 97 | (function 98 | | TextSlice.Text _ -> true 99 | | _ -> false) 100 | |> function 101 | | None -> text 102 | | Some index -> 103 | let lastTextSlice = 104 | match text.[index] with 105 | | TextSlice.Text x -> x.Trim() |> TextSlice.Text 106 | | _ -> failwith "" 107 | 108 | text.[..index - 1] 109 | @ [ lastTextSlice ] @ text.[index + 1..] 110 | 111 | let! hasMore = hasMoreSymbol |> zeroOrOne 112 | 113 | return 114 | Line.Text 115 | { Character = character 116 | Text = text 117 | HasMore = hasMore.IsSome } 118 | } 119 | 120 | let hasMoreSymbol = 121 | hasMoreSymbol 122 | |> map (fun () -> Line.Text { Character = None; Text = []; HasMore = true }) 123 | 124 | (text <|> hasMoreSymbol) |> name "text" 125 | 126 | 127 | let toCommands (text: TextBlock) : CommandCall list = 128 | [ { Callee = "__text_begin" 129 | UnnamedArgs = [] 130 | NamedArgs = 131 | [ if text.Character.IsSome then 132 | "character", Constant <| String text.Character.Value ] } 133 | 134 | let rec textSliceToCommand x = 135 | x 136 | |> List.collect 137 | (function 138 | | TextSlice.CommandCall x -> [ x ] 139 | | TextSlice.Text x -> 140 | [ { Callee = "__text_type" 141 | UnnamedArgs = [] 142 | NamedArgs = [ "text", Constant <| String x ] } ] 143 | | Marked (mark, inner) -> 144 | [ { Callee = "__text_pushMark" 145 | UnnamedArgs = [] 146 | NamedArgs = [ "mark", Constant <| Symbol mark ] } 147 | 148 | yield! textSliceToCommand inner 149 | 150 | { Callee = "__text_popMark" 151 | UnnamedArgs = [] 152 | NamedArgs = [ "mark", Constant <| Symbol mark ] } ]) 153 | 154 | yield! textSliceToCommand text.Text 155 | 156 | { Callee = "__text_end" 157 | UnnamedArgs = [] 158 | NamedArgs = [ "hasMore", Constant <| Symbol (text.HasMore.ToString().ToLower()) ] } ] 159 | 160 | 161 | let expandTextBlock (x: TextBlock) (debugInfo: DebugInfo) : Block = 162 | toCommands x 163 | |> List.map (fun x -> CommandCall x, debugInfo) 164 | -------------------------------------------------------------------------------- /YukimiScript.Parser/TopLevels.fs: -------------------------------------------------------------------------------- 1 | module internal YukimiScript.Parser.TopLevels 2 | 3 | open YukimiScript.Parser.Elements 4 | open ParserMonad 5 | open Basics 6 | open Constants 7 | 8 | 9 | let private sceneDefaintion = 10 | parser { 11 | let! sceneName = explicit <| name "scene name" stringParser 12 | 13 | let! inheritScene = 14 | parser { 15 | do! whitespace0 16 | do! literal "inherit" 17 | do! whitespace0 18 | return! explicit <| name "inherit scene name" stringParser 19 | } 20 | |> zeroOrOne 21 | 22 | return 23 | { Name = sceneName 24 | Inherit = inheritScene } 25 | } 26 | |> name "scene defination" 27 | 28 | 29 | exception InvalidTopLevelException of string 30 | 31 | 32 | let private externDefination = 33 | parser { 34 | let! externName = explicit symbol 35 | let! param = Macro.parameterList externName 36 | return ExternCommand(externName, param) 37 | } 38 | 39 | let topLevels = 40 | parser { 41 | do! literal "-" 42 | do! whitespace0 43 | 44 | let! symbol = explicit symbol 45 | do! whitespace0 46 | 47 | match symbol with 48 | | "macro" -> return! map MacroDefination Macro.macroDefinationParser 49 | | "scene" -> return! map SceneDefination sceneDefaintion 50 | | "extern" -> return! map ExternDefination externDefination 51 | | x -> return! explicit <| fail (InvalidTopLevelException x) 52 | } 53 | |> name "top level" 54 | -------------------------------------------------------------------------------- /YukimiScript.Parser/TypeChecker.fs: -------------------------------------------------------------------------------- 1 | module YukimiScript.Parser.TypeChecker 2 | 3 | open YukimiScript.Parser.Elements 4 | open YukimiScript.Parser.Utils 5 | 6 | 7 | type SimpleType = 8 | | Int' 9 | | Real' 10 | | String' 11 | | Symbol' 12 | | ExplicitSymbol' of string 13 | 14 | 15 | type ParameterType = ParameterType of name: string * Set 16 | 17 | 18 | module Types = 19 | let any = ParameterType ("any", set [ Int'; Real'; String'; Symbol' ]) 20 | let int = ParameterType ("int", set [ Int' ]) 21 | let number = ParameterType ("number", set [ Int'; Real' ]) 22 | let real = ParameterType ("real", set [ Real' ]) 23 | let symbol = ParameterType ("symbol", set [ Symbol' ]) 24 | let string = ParameterType ("string", set [ String' ]) 25 | let bool = ParameterType ("bool", set [ ExplicitSymbol' "true"; ExplicitSymbol' "false"]) 26 | let ``null`` = ParameterType ("null", set [ ExplicitSymbol' "null"]) 27 | let all = [ any; int; number; real; symbol; string; bool; ``null`` ] 28 | 29 | 30 | let sumParameterType (ParameterType (n1, s1)) (ParameterType (n2, s2)) = 31 | ParameterType (n1 + " | " + n2, Set.union s1 s2) 32 | 33 | 34 | let checkType = 35 | function 36 | | Constant (String _) -> String' 37 | | Constant (Integer _) -> Int' 38 | | Constant (Real _) -> Real' 39 | | Constant (Symbol x) -> ExplicitSymbol' x 40 | | StringFormat _ -> String' 41 | 42 | 43 | let unify src dst = 44 | match (src, dst) with 45 | | (a, b) when a = b -> Some a 46 | | (ExplicitSymbol' _, Symbol') -> Some Symbol' 47 | | _ -> None 48 | 49 | 50 | exception TypeCheckFailedException of DebugInfo * int * ParameterType * SimpleType 51 | 52 | 53 | let matchType d i (ParameterType (t, paramTypes)) (argType: SimpleType) : Result = 54 | if Set.exists (fun paramType -> unify argType paramType |> Option.isSome) paramTypes 55 | then Ok () 56 | else Error <| TypeCheckFailedException (d, i, (ParameterType (t, paramTypes)), argType) 57 | 58 | 59 | exception IsNotAType of string * DebugInfo 60 | 61 | 62 | type BlockParamTypes = (string * ParameterType) list 63 | 64 | 65 | let checkApplyTypeCorrect d (paramTypes: BlockParamTypes) (args: (string * CommandArg) list) = 66 | paramTypes 67 | |> List.mapi (fun i (paramName, paramType) -> 68 | args 69 | |> List.find (fst >> ((=) paramName)) 70 | |> snd 71 | |> checkType 72 | |> matchType d i paramType) 73 | |> Result.transposeList 74 | |> Result.map (fun _ -> args) 75 | 76 | 77 | exception CannotGetParameterException of (string * DebugInfo) list 78 | 79 | 80 | -------------------------------------------------------------------------------- /YukimiScript.Parser/Utils.fs: -------------------------------------------------------------------------------- 1 | namespace YukimiScript.Parser.Utils 2 | 3 | 4 | exception MultiException of exn list 5 | exception CanNotFindLib of string list 6 | 7 | 8 | module Result = 9 | 10 | let transposeList listOfResult = 11 | let oks, errs = 12 | List.partition 13 | (function Ok _ -> true | Error _ -> false) 14 | listOfResult 15 | 16 | let oks = List.map (function Ok x -> x | _ -> failwith "") oks 17 | match List.map (function Error e -> e | _ -> failwith "") errs with 18 | | [] -> Ok oks 19 | | errs -> Error <| MultiException errs 20 | 21 | -------------------------------------------------------------------------------- /YukimiScript.Parser/YukimiScript.Parser.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;netstandard2.1;net8.0;net9.0 4 | 3390;$(WarnOn) 5 | 0.8.6 6 | Seng Jik 7 | Strrationalism 8 | YukimiScript parser. 9 | https://github.com/Strrationalism/YukimiScript 10 | https://github.com/Strrationalism/YukimiScript 11 | git 12 | MIT 13 | YukimiScript.Parser 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /YukimiScript.Runtime.Bytecode.Parser/YukimiScript.Runtime.Bytecode.Parser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462;netstandard2.0;netstandard2.1;net8.0;net9.0 5 | disable 6 | enable 7 | 9.0 8 | 0.8.6 9 | Seng Jik 10 | Strrationalism 11 | https://github.com/Strrationalism/YukimiScript 12 | https://github.com/Strrationalism/YukimiScript 13 | git 14 | MIT 15 | YukimiScript.Runtime.Bytecode.Parser 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /YukimiScript.Runtime.Bytecode.Parser/YukimiScriptBinary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace YukimiScript.Runtime.Bytecode.Parser 5 | { 6 | public sealed class YukimiScriptBinary 7 | { 8 | const uint riffBlockID = 0x46464952; 9 | const uint yukiMagicCode = 0x494B5559; 10 | const uint cstrBlockID = 0x52545343; 11 | const uint extrBlockID = 0x52545845; 12 | const uint scenBlockID = 0x4E454353; 13 | const uint dbgsBlockID = 0x53474244; 14 | 15 | readonly byte[] bytes; 16 | 17 | uint ReadUInt32(uint offset) 18 | { 19 | if (offset + 4 >= bytes.Length) 20 | throw new InvalidYukimiScriptBinary(); 21 | 22 | uint r = 0; 23 | r |= bytes[offset]; 24 | r |= ((uint)bytes[offset + 1]) << 8; 25 | r |= ((uint)bytes[offset + 2]) << 16; 26 | r |= ((uint)bytes[offset + 3]) << 24; 27 | return r; 28 | } 29 | 30 | public sealed class InvalidYukimiScriptBinary : Exception { }; 31 | 32 | public readonly struct Block 33 | { 34 | public readonly YukimiScriptBinary Bin; 35 | public readonly uint Type; 36 | public readonly uint DataPointer; 37 | public readonly uint DataEndPointer; 38 | public readonly uint Size; 39 | 40 | public Block(YukimiScriptBinary binary, ref uint offset) 41 | { 42 | Bin = binary; 43 | Type = binary.ReadUInt32(offset); 44 | Size = binary.ReadUInt32(offset + 4); 45 | DataPointer = offset + 8; 46 | DataEndPointer = offset = DataPointer + Size; 47 | 48 | if (offset > binary.bytes.Length) 49 | throw new InvalidYukimiScriptBinary(); 50 | } 51 | } 52 | 53 | public readonly IReadOnlyList SceneBlocks; 54 | readonly Block cstrBlock; 55 | readonly Block extrBlock; 56 | 57 | public sealed class SceneParser 58 | { 59 | public readonly Block Block; 60 | public readonly uint SceneNameCStrPointer; 61 | public uint Offset => offsetInFile - Block.DataPointer; 62 | uint offsetInFile; 63 | 64 | public SceneParser(Block scenBlock) 65 | { 66 | Block = scenBlock; 67 | SceneNameCStrPointer = scenBlock 68 | .Bin 69 | .ReadUInt32(scenBlock.DataPointer); 70 | 71 | offsetInFile = scenBlock.DataPointer + 4; 72 | } 73 | 74 | public bool ReadCommand( 75 | out ushort commandID, 76 | out ushort argumentsCount) 77 | { 78 | commandID = 0; 79 | argumentsCount = 0; 80 | 81 | if (offsetInFile + 4 >= Block.DataEndPointer) 82 | return false; 83 | 84 | commandID |= Block.Bin.bytes[offsetInFile]; 85 | commandID |= (ushort)(Block.Bin.bytes[offsetInFile + 1] << 8); 86 | argumentsCount |= Block.Bin.bytes[offsetInFile + 2]; 87 | argumentsCount |= (ushort)(Block.Bin.bytes[offsetInFile + 3] << 8); 88 | offsetInFile += 4; 89 | return true; 90 | } 91 | 92 | public enum ArgumentType 93 | { 94 | Int = 0, 95 | Float = 1, 96 | String = 2, 97 | Symbol = 3 98 | } 99 | 100 | readonly byte[] temp = new byte[4]; 101 | 102 | public ArgumentType ReadArgument( 103 | out int argInt, 104 | out float argFloat, 105 | out uint argCStrPointerString) 106 | { 107 | argInt = 0; 108 | argFloat = 0; 109 | argCStrPointerString = 0; 110 | 111 | var first = (int)Block.Bin.ReadUInt32(offsetInFile); 112 | offsetInFile += 4; 113 | 114 | if (first == 0) 115 | { 116 | argInt = (int)Block.Bin.ReadUInt32(offsetInFile); 117 | offsetInFile += 4; 118 | return ArgumentType.Int; 119 | } 120 | else if (first == 1) 121 | { 122 | if (BitConverter.IsLittleEndian) 123 | { 124 | temp[0] = Block.Bin.bytes[offsetInFile + 0]; 125 | temp[1] = Block.Bin.bytes[offsetInFile + 1]; 126 | temp[2] = Block.Bin.bytes[offsetInFile + 2]; 127 | temp[3] = Block.Bin.bytes[offsetInFile + 3]; 128 | } 129 | else 130 | { 131 | temp[0] = Block.Bin.bytes[offsetInFile + 3]; 132 | temp[1] = Block.Bin.bytes[offsetInFile + 2]; 133 | temp[2] = Block.Bin.bytes[offsetInFile + 1]; 134 | temp[3] = Block.Bin.bytes[offsetInFile + 0]; 135 | } 136 | 137 | argFloat = BitConverter.ToSingle(temp, 0); 138 | offsetInFile += 4; 139 | return ArgumentType.Float; 140 | } 141 | else if (first >= 2) 142 | { 143 | argCStrPointerString = (uint)first - 2; 144 | return ArgumentType.String; 145 | } 146 | else if (first <= -1) 147 | { 148 | argCStrPointerString = (uint)Math.Abs(first + 1); 149 | return ArgumentType.Symbol; 150 | } 151 | 152 | throw new InvalidYukimiScriptBinary(); 153 | } 154 | } 155 | 156 | public IEnumerable ExternDefinitionCStrPointers 157 | { 158 | get 159 | { 160 | for ( 161 | uint offset = extrBlock.DataPointer; 162 | offset < extrBlock.DataEndPointer; 163 | offset += 4) 164 | { 165 | yield return ReadUInt32(offset); 166 | } 167 | } 168 | } 169 | 170 | public YukimiScriptBinary(byte[] bytes) 171 | { 172 | this.bytes = bytes; 173 | 174 | if (ReadUInt32(0) != riffBlockID) 175 | throw new InvalidYukimiScriptBinary(); 176 | 177 | if (ReadUInt32(4) + 8 != bytes.Length) 178 | throw new InvalidYukimiScriptBinary(); 179 | 180 | if (ReadUInt32(8) != yukiMagicCode) 181 | throw new InvalidYukimiScriptBinary(); 182 | 183 | Block? cstrBlock = null; 184 | Block? extrBlock = null; 185 | List scenBlocks = new(); 186 | 187 | for (uint offset = 12; offset < bytes.Length;) 188 | { 189 | Block b = new(this, ref offset); 190 | 191 | if (b.Type == cstrBlockID && cstrBlock == null) 192 | cstrBlock = b; 193 | else if (b.Type == extrBlockID && extrBlock == null) 194 | extrBlock = b; 195 | else if (b.Type == scenBlockID) 196 | scenBlocks.Add(b); 197 | else if (b.Type != dbgsBlockID) { } 198 | else 199 | throw new InvalidYukimiScriptBinary(); 200 | } 201 | 202 | if (cstrBlock == null) 203 | throw new InvalidYukimiScriptBinary(); 204 | 205 | if (extrBlock == null) 206 | throw new InvalidYukimiScriptBinary(); 207 | 208 | this.cstrBlock = cstrBlock.Value; 209 | this.extrBlock = extrBlock.Value; 210 | this.SceneBlocks = scenBlocks.ToArray(); 211 | 212 | Console.WriteLine("Scen Blocks:" + scenBlocks.Count); 213 | } 214 | 215 | public string GetStringByCStrPointer(uint cstrPointer) 216 | { 217 | int len = 0; 218 | while (bytes[cstrBlock.DataPointer + cstrPointer + len] != '\0') 219 | len++; 220 | 221 | return System.Text.Encoding.UTF8.GetString( 222 | bytes, (int)(cstrBlock.DataPointer + cstrPointer), len); 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /YukimiScript.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32126.317 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "YukimiScript.Parser", "YukimiScript.Parser\YukimiScript.Parser.fsproj", "{A13293A1-9DA4-4552-ACEB-39C98BAC73B0}" 7 | EndProject 8 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "YukimiScript.Parser.Test", "YukimiScript.Parser.Test\YukimiScript.Parser.Test.fsproj", "{AECFB897-2393-4A15-BCD5-5A042630F7E0}" 9 | EndProject 10 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "YukimiScript.CommandLineTool", "YukimiScript.CommandLineTool\YukimiScript.CommandLineTool.fsproj", "{F9F99EBD-36D4-462C-822E-6B5B55BDF736}" 11 | EndProject 12 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "YukimiScript.CodeGen", "YukimiScript.CodeGen\YukimiScript.CodeGen.fsproj", "{D3806F86-4B20-4728-A13D-87BC678369F6}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "YukimiScript.CodeGen.Bytecode", "YukimiScript.CodeGen.Bytecode\YukimiScript.CodeGen.Bytecode.fsproj", "{9926C290-A0B8-439B-9328-8569933AE8F4}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YukimiScript.Runtime.Bytecode.Parser", "YukimiScript.Runtime.Bytecode.Parser\YukimiScript.Runtime.Bytecode.Parser.csproj", "{384AB781-0A51-493A-9DD0-9C14ED2BB9E0}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Debug|x64 = Debug|x64 22 | Debug|x86 = Debug|x86 23 | Release|Any CPU = Release|Any CPU 24 | Release|x64 = Release|x64 25 | Release|x86 = Release|x86 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Debug|x64.ActiveCfg = Debug|Any CPU 31 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Debug|x64.Build.0 = Debug|Any CPU 32 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Debug|x86.ActiveCfg = Debug|Any CPU 33 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Debug|x86.Build.0 = Debug|Any CPU 34 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Release|x64.ActiveCfg = Release|Any CPU 37 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Release|x64.Build.0 = Release|Any CPU 38 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Release|x86.ActiveCfg = Release|Any CPU 39 | {A13293A1-9DA4-4552-ACEB-39C98BAC73B0}.Release|x86.Build.0 = Release|Any CPU 40 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Debug|x64.Build.0 = Debug|Any CPU 44 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Debug|x86.Build.0 = Debug|Any CPU 46 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Release|x64.ActiveCfg = Release|Any CPU 49 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Release|x64.Build.0 = Release|Any CPU 50 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Release|x86.ActiveCfg = Release|Any CPU 51 | {AECFB897-2393-4A15-BCD5-5A042630F7E0}.Release|x86.Build.0 = Release|Any CPU 52 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Debug|x64.ActiveCfg = Debug|Any CPU 55 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Debug|x64.Build.0 = Debug|Any CPU 56 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Debug|x86.Build.0 = Debug|Any CPU 58 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Release|x64.ActiveCfg = Release|Any CPU 61 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Release|x64.Build.0 = Release|Any CPU 62 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Release|x86.ActiveCfg = Release|Any CPU 63 | {F9F99EBD-36D4-462C-822E-6B5B55BDF736}.Release|x86.Build.0 = Release|Any CPU 64 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Debug|x64.ActiveCfg = Debug|Any CPU 67 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Debug|x64.Build.0 = Debug|Any CPU 68 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Debug|x86.ActiveCfg = Debug|Any CPU 69 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Debug|x86.Build.0 = Debug|Any CPU 70 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Release|x64.ActiveCfg = Release|Any CPU 73 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Release|x64.Build.0 = Release|Any CPU 74 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Release|x86.ActiveCfg = Release|Any CPU 75 | {D3806F86-4B20-4728-A13D-87BC678369F6}.Release|x86.Build.0 = Release|Any CPU 76 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Debug|x64.ActiveCfg = Debug|Any CPU 79 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Debug|x64.Build.0 = Debug|Any CPU 80 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Debug|x86.Build.0 = Debug|Any CPU 82 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Release|x64.ActiveCfg = Release|Any CPU 85 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Release|x64.Build.0 = Release|Any CPU 86 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Release|x86.ActiveCfg = Release|Any CPU 87 | {9926C290-A0B8-439B-9328-8569933AE8F4}.Release|x86.Build.0 = Release|Any CPU 88 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Debug|x64.ActiveCfg = Debug|Any CPU 91 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Debug|x64.Build.0 = Debug|Any CPU 92 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Debug|x86.Build.0 = Debug|Any CPU 94 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Release|x64.ActiveCfg = Release|Any CPU 97 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Release|x64.Build.0 = Release|Any CPU 98 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Release|x86.ActiveCfg = Release|Any CPU 99 | {384AB781-0A51-493A-9DD0-9C14ED2BB9E0}.Release|x86.Build.0 = Release|Any CPU 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(ExtensibilityGlobals) = postSolution 105 | SolutionGuid = {4EA87EBC-967B-4146-A04B-F04EA7134324} 106 | EndGlobalSection 107 | EndGlobal 108 | -------------------------------------------------------------------------------- /yukimiscript-syntax-highlight-vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /yukimiscript-syntax-highlight-vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /yukimiscript-syntax-highlight-vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "#", 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["<", ">"], 9 | ["[", "]"] 10 | ], 11 | // symbols that are auto closed when typing 12 | "autoClosingPairs": [ 13 | ["<", ">"], 14 | ["[", "]"], 15 | ["\"", "\""] 16 | ], 17 | // symbols that can be used to surround a selection 18 | "surroundingPairs": [ 19 | ["<", ">"], 20 | ["[", "]"], 21 | ["\"", "\""] 22 | ] 23 | } -------------------------------------------------------------------------------- /yukimiscript-syntax-highlight-vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yukimiscript-syntax-highlight", 3 | "displayName": "YukimiScript Syntax Highlight", 4 | "description": "YukimiScript Syntax Highlight", 5 | "publisher": "seng-jik", 6 | "version": "0.0.8", 7 | "engines": { 8 | "vscode": "^1.69.0" 9 | }, 10 | "categories": [ 11 | "Programming Languages" 12 | ], 13 | "contributes": { 14 | "languages": [{ 15 | "id": "yukimiscript", 16 | "aliases": ["Yukimi Script", "yukimiscript"], 17 | "extensions": [".ykm"], 18 | "configuration": "./language-configuration.json" 19 | }], 20 | "grammars": [{ 21 | "language": "yukimiscript", 22 | "scopeName": "text.ykm", 23 | "path": "./syntaxes/yukimiscript.tmLanguage.json" 24 | }] 25 | } 26 | } -------------------------------------------------------------------------------- /yukimiscript-syntax-highlight-vscode/syntaxes/yukimiscript.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Yukimi Script", 4 | "patterns": [ 5 | { "include": "#linecomment" }, 6 | { "include": "#toplevels" }, 7 | { "include": "#keywords" }, 8 | { "include": "#marks" }, 9 | { "include": "#lineop" }, 10 | { "include": "#inlineop" }, 11 | { "include": "#character"} 12 | ], 13 | "repository": { 14 | "linecomment": { 15 | "name": "comment.line", 16 | "match": "#.*$" 17 | }, 18 | "toplevels": { 19 | "begin": "^(-(\\s)*(macro|extern|scene))\\s*([a-zA-Z0-9_'.']+)?", 20 | "end": "$", 21 | "beginCaptures": { 22 | "1": { "name": "keyword.control" }, 23 | "0": { "name": "entity.name.function" } 24 | }, 25 | "patterns": [ 26 | { 27 | "name": "keyword.control", 28 | "match": "\\sinheirt\\s" 29 | }, 30 | { "include": "#strings" }, 31 | { "include": "#numbers" }, 32 | { "include": "#keywords" }, 33 | { "include": "#linecomment" }, 34 | { "include": "#symbol" }, 35 | { 36 | "match": "\\s([a-zA-Z0-9_'.']+)(=)?", 37 | "captures": { 38 | "1": { "name": "variable.parameter" }, 39 | "0": { "name": "keyword.operator" } 40 | } 41 | } 42 | ] 43 | }, 44 | "keywords": { 45 | "patterns": [ 46 | { 47 | "name": "constant.language", 48 | "match": "(\\s|(?<==))(true|false|null)(?=\\s)" 49 | } 50 | ] 51 | }, 52 | "strings": { 53 | "name": "string.quoted.double", 54 | "begin": "\\$?\"", 55 | "end": "\"", 56 | "patterns": [ 57 | { 58 | "name": "constant.character.escape", 59 | "match": "\\\\." 60 | } 61 | ] 62 | }, 63 | "marks": { 64 | "name": "keyword.control", 65 | "patterns": [ 66 | { 67 | "name": "keyword.control", 68 | "match": "<([A-Z]|[a-z]|[1-9]|\\.)+\\s" 69 | }, 70 | { 71 | "name": "keyword.control", 72 | "match": ">" 73 | } 74 | ] 75 | }, 76 | "symbol": { 77 | "name": "variable.name", 78 | "match": "([a-zA-Z0-9_\\.]+)" 79 | }, 80 | "args": { 81 | "patterns": [ 82 | { 83 | "match": "(--[a-zA-Z0-9_\\.]+)(?=(\\s+|\\]))", 84 | "name": "constant.regexp" 85 | }, 86 | { "include": "#strings" }, 87 | { "include": "#comments"}, 88 | { "include": "#numbers" }, 89 | { "include": "#keywords"}, 90 | { "include": "#symbol" } 91 | ] 92 | }, 93 | "lineop": { 94 | "begin": "@\\s*[A-Za-z0-9\\._]*", 95 | "end": "$", 96 | "beginCaptures": { 97 | "0": { "name": "entity.name.function" } 98 | }, 99 | "patterns": [ 100 | { "include": "#args" }, 101 | { "include": "#linecomment" } 102 | ] 103 | }, 104 | "inlineop": { 105 | "begin": "\\[[A-Za-z0-9\\._]*", 106 | "end": "\\]", 107 | "beginCaptures": { 108 | "0": { "name": "entity.name.function" } 109 | }, 110 | "patterns": [ 111 | { "include": "#args" }, 112 | { "include": "#linecomment" } 113 | ] 114 | }, 115 | "numbers": { 116 | "name": "constant.numeric", 117 | "match": "\\b(0x[0-9a-fA-F]+|[0-9]+(\\.[0-9]+)?)\\b" 118 | }, 119 | "character": { 120 | "name": "entity.name.type", 121 | "match": "^[^:]*:" 122 | } 123 | }, 124 | "scopeName": "text.ykm" 125 | } --------------------------------------------------------------------------------