├── .babelrc ├── .gitignore ├── LICENSE.txt ├── README.md ├── README_zh.md ├── actions ├── Comment.js ├── Exp.js ├── Expression.js ├── LogicBlock.js ├── arithmetic.js ├── base.js ├── index.js ├── keyvalue.js └── story.js ├── index.js ├── libs ├── block.js ├── parser.js └── variable.js ├── ohm └── bks.ohm.js ├── package.json ├── test.js ├── test └── parser.test.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["latest"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storyscript 2 | 3 | [中文版请点击此处 (View Chinese Edition)](README_zh.md) 4 | 5 | ## Introduction and Design Ideas 6 | 7 | Storygscript is a set of scripting systems for expressing AVG stories and is one of the official modules of AVG.js. Similar to the scripting system of other AVG engines, it is executed in the order of text; grammatically, it is quite similar to BKEngine's scripting system, but simpler. 8 | 9 | The previous AVG script, including the entire game of all the program logic, but relatively Lua, Javasciprt and other mature programming language, the markup language syntax is very simple, on this basis to achieve the program logic, whether programmers or ordinary enthusiasts Are a troubled. 10 | 11 | AVG.js AVG script has been stripped off the game UI, performance effects and other strong logic, and the rest is only a simple instructional code, which is the origin of the name Storyscript. 12 | 13 | Storyscript aims to reduce the difficulty of learning, so that non-programmers can fully grasp their use, so that designers and other personnel to complete the production of game content and reduce unnecessary communication links. 14 | 15 | For more information about AVG.js and Storyscript, please visit 16 | 17 | ## Installation and Usage 18 | 19 | Storyscript has been built into the AVG.js project as the initial module. If you want to use it in AVG.js, please refer to the AVG.js tutorial. 20 | 21 | If used in other projects, as follows: 22 | 23 | **Installation** 24 | 25 | ```shell 26 | npm install avg-storyscript 27 | ``` 28 | 29 | **Usage** 30 | 31 | ```javasciprt 32 | import Story from 'avg-storyscript'; 33 | const story = new StoryScript(); 34 | story.load(scriptString); 35 | for (const line of story) { 36 | // do something 37 | } 38 | ``` 39 | 40 | Refer to the test.js file for details. 41 | 42 | ## Syntax 43 | 44 | StoryScript consists of **Content Script** and **Logical Script**. 45 | 46 | Content script: changes in the game plot, such as print dialogue text, display characters, switch the background picture, play sound, etc. 47 | 48 | Logic Script: Controls the direction of the game plot or provides convenient programming logic such as variable assignment, conditional branching, loops, etc. 49 | 50 | ### Content Script 51 | 52 | **Basic** 53 | 54 | ```shell 55 | [command flag param="value"] 56 | ``` 57 | 58 | For example 59 | 60 | ```shell 61 | [bgm autoplay loop file="abc.ogg" volume=100] 62 | ``` 63 | 64 | Will be parsed as 65 | 66 | ```javasciprt 67 | { 68 | command: 'bgm', 69 | flags: ['autoplay', 'loop'], 70 | params: { 71 | file: "abc.ogg", 72 | volume: 100 73 | } 74 | } 75 | ``` 76 | 77 | ### Logical Script 78 | 79 | #### Statement 80 | 81 | **LET** 82 | 83 | ``` 84 | #let foo = 123 // standard way 85 | #bar = 456 // omit let 86 | #let foobar // can not assign value (value will be null), thus `let` can not be omitted 87 | ``` 88 | 89 | **If** 90 | 91 | ``` 92 | #if foo > bar 93 | // do something 94 | #elseif foo == bar 95 | // do something 96 | #else 97 | // do something 98 | #end 99 | ``` 100 | 101 | Among them, `elseif` and`else` are optional. 102 | 103 | **While** 104 | 105 | ``` 106 | #while i < 10 107 | // do something 108 | #end 109 | ``` 110 | 111 | Currently `break` and`continue` are not supported, but they are in TODO list :) 112 | 113 | **Foreach** 114 | 115 | ``` 116 | #foreach child in children 117 | // do something 118 | #end 119 | ``` 120 | 121 | `children` is an array, there are some special circumstances, please read on. 122 | 123 | #### Variables 124 | 125 | Support for simple variable assignment and modify operations, and to meet the needs of AVG, different variables in the archive have different treatment. 126 | 127 | **Global Archive Variables** 128 | 129 | Variables that begin with `$` will be treated as global archive variables, meaning that once assigned, they will be read in any case, either by reading a new archive or by using a previous archive. You can use it to control the unlock CG appreciation, or support _New Game+_. 130 | 131 | ``` 132 | #let $gameclear = true; 133 | ``` 134 | 135 | **Single archive variable** 136 | 137 | Variables that start with `%` are treated as single archive variables, meaning that they will only be valid in certain archives and will be overwritten when other archives are read. Usually used to control the route or favorability. 138 | 139 | ``` 140 | #let %girl_favor_num = 1; 141 | ``` 142 | 143 | **Common variables** 144 | 145 | In other cases, the variable name is a normal variable, in the archive, only the archive point where the "block" and the parent "block" common variables will be saved (see below). Common variables are used only for single-file use. Do not use them to save favorability. 146 | 147 | ``` 148 | #let x = 0; 149 | ``` 150 | 151 | **Scopes** 152 | 153 | Global archive variables and single archive variables are valid at any location, and are therefore considered global variables. 154 | 155 | (NOTE: The global archive variable is completely unrelated to the global variable, the former is about how it is saved in the archive, while the other is about where it can be access in the game script) 156 | 157 | For a normal variable, its scope is "block", such as an IF branch or the contents of the While statement, are a "block." The largest "block" is the "file", that is, even if you do not declare variables such as IF While, but outside of them, variables in the scope of the work is limited to the current script file. 158 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # Storyscript 2 | 3 | ## 简介及设计思想 4 | 5 | Storyscript 是一套用于表达 AVG 剧情的脚本系统,是 AVG.js 的官方模块之一。与其他 AVG 引擎的脚本系统类似,它也是依照文本顺序依次执行;语法上,与 BKEngine 的脚本系统颇为类似,但更为简单。 6 | 7 | 以往的 AVG 脚本,包含了整个游戏全部的程序实现,但相对 Lua、Javasciprt 等成熟的程序语言,其类似标记语言的语法却十分简陋,在这个基础上实现程序逻辑无论对程序员还是普通的爱好者都是一份困扰。 8 | 9 | AVG.js 已经将游戏UI、演出效果等强逻辑内容从 AVG 脚本中剥离,剩下的只有简单的指令性代码,这便是 Storyscript 之名的由来。 10 | 11 | Storyscript 致力于降低学习难度,让非程序员完全能够掌握其使用方法,使策划、剧本、演出师等人员能够自行完成游戏内容的制作,减少不必要的沟通环节。 12 | 13 | 有关 AVG.js 及 Storyscript 的详细信息请访问 14 | 15 | ## 安装与使用 16 | 17 | Storyscript 已作为初始模块内置于 AVG.js 工程中,若你想要在 AVG.js 中使用它,请参考 AVG.js 相关教程。 18 | 19 | 下面只介绍在其他工程中的使用方法: 20 | 21 | **安装** 22 | 23 | ```shell 24 | npm install avg-storyscript 25 | ``` 26 | 27 | **使用** 28 | 29 | ```javasciprt 30 | import Story from 'avg-storyscript'; 31 | const story = new StoryScript(); 32 | story.load(scriptString); 33 | for (const line of story) { 34 | // do something 35 | } 36 | ``` 37 | 38 | 详细请参考 test.js 文件 39 | 40 | ## 语法简介 41 | 42 | Storyscript 脚本系统分为**内容脚本**和**逻辑脚本**。 43 | 44 | 内容脚本:表示游戏剧情的变化,如打印对话、显示立绘、切换背景图片、播放声音等 45 | 46 | 逻辑脚本:控制游戏剧情的走向或提供一些方便的程序功能,如变量赋值、条件分支、循环等 47 | 48 | ### 内容脚本 49 | 50 | **基本语法** 51 | 52 | ```shell 53 | [command flag param="value"] 54 | ``` 55 | 56 | command: 指令名
57 | flag: 标记
58 | param: 参数 59 | 60 | 例如 61 | 62 | ```shell 63 | [bgm autoplay loop file="abc.ogg" volume=100] 64 | ``` 65 | 66 | 将被解析为 67 | 68 | ```javasciprt 69 | { 70 | command: 'bgm', 71 | flags: ['autoplay', 'loop'], 72 | params: { 73 | file: "abc.ogg", 74 | volume: 100 75 | } 76 | } 77 | ``` 78 | 79 | ### 逻辑脚本 80 | 81 | #### 语句 82 | 83 | **LET** 84 | 85 | ``` 86 | #let foo = 123 // 标准方式 87 | #bar = 456 // 可省略 let 88 | #let foobar // 可不赋值(值为null),此时 let 不可省略 89 | ``` 90 | 91 | **If** 92 | 93 | ``` 94 | #if foo > bar 95 | // do something 96 | #elseif foo == bar 97 | // do something 98 | #else 99 | // do something 100 | #end 101 | ``` 102 | 103 | 其中,`elseif` 和 `else` 都是可选的。 104 | 105 | **While** 106 | 107 | ``` 108 | #while i < 10 109 | // do something 110 | #end 111 | ``` 112 | 113 | 暂不支持 `break` 和 `continue`,它们还在 TODO 列表中 : ) 114 | 115 | **Foreach** 116 | 117 | ``` 118 | #foreach child in children 119 | // do something 120 | #end 121 | ``` 122 | 123 | `children` 是一个数组,这里有一些特殊的情况,请往下阅读。 124 | 125 | #### 变量 126 | 127 | 支持简单的变量赋值和修改操作,同时为适应 AVG 游戏的需求,不同的变量在存档时有不同的处理。 128 | 129 | **全局存档变量** 130 | 131 | 以 `$` 开头的变量将被视为全局存档变量,意思是说,它一旦被赋值将在任何情况下都能被读取,无论是读取了新的档案还是使用以前的档案。你可以用它来控制CG鉴赏的解锁,或是标明周目数。 132 | 133 | ``` 134 | #let $gameclear = true; 135 | ``` 136 | 137 | **单存档变量** 138 | 139 | 以 `%` 开头的变量将被视为单存档变量,意思是说,它将只在某些特定的存档中有效,读取其他档案后将被覆盖。通常用来控制路线或好感度。 140 | 141 | ``` 142 | #let %girl_favor_num = 1; 143 | ``` 144 | 145 | **普通变量** 146 | 147 | 其他情况下的变量名都是普通变量,在存档中,只有存档点所在的「块」及父「块」中的普通变量将被保存(见下)。普通变量仅用于单文件内使用,请勿用于保存好感度等。 148 | 149 | ``` 150 | #let x = 0; 151 | ``` 152 | 153 | **作用域** 154 | 155 | 全局存档变量和单存档变量在任何位置都有效,故可视为全局变量(注:全局存档变量和全局变量完全没有联系,前者是说其在存档中的保存方式,后者是说其在游戏脚本中何处可以读取) 156 | 157 | 对于普通变量,它的作用域是「块」,如一个 IF 分支中的内容或 While 语句中的内容,均为一个「块」。最大的「块」是「文件」,也就是说,即使你不在 IF While 等语句块中声明变量,而是在它们的外面,变量起作用的范围也仅限于当前的脚本文件。 158 | -------------------------------------------------------------------------------- /actions/Comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | Comment_single(head, text) { 19 | return { 20 | type: 'comment', 21 | value: text.parse() 22 | } 23 | }, 24 | Comment_multi(head, text, foot) { 25 | return { 26 | type: 'comment', 27 | value: text.parse() 28 | } 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /actions/Exp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | Scripts(n) { 19 | var ret = []; 20 | for (var child of n.children) { 21 | ret.push(child.parse()) 22 | } 23 | return ret; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /actions/Expression.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | Exp_bool(JudgeExp, booleanOperator, Exp) { 19 | return { 20 | type: 'expression', 21 | value: { 22 | left: JudgeExp.parse(), 23 | operator: booleanOperator.parse(), 24 | right: Exp.parse() 25 | } 26 | } 27 | }, 28 | JudgeExp_judge(left, operator, right) { 29 | return { 30 | type: 'expression', 31 | value: { 32 | left: left.parse(), 33 | operator: operator.parse(), 34 | right: right.parse() 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /actions/LogicBlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | LogicBlock_IF(IF, LogicBlock1, ELSEIFs, LogicBlock2s, ELSE, LogicBlock3, END) { 19 | // get conditions 20 | var conditions = [IF.parse()]; 21 | for (var ELSEIF of ELSEIFs.children) { 22 | conditions.push(ELSEIF.parse()); 23 | } 24 | 25 | // get stroy block 26 | var blocks = []; 27 | var block1 = []; 28 | for (var LogicBlock of LogicBlock1.children) { 29 | block1.push(LogicBlock.parse()); 30 | } 31 | blocks.push(block1); 32 | for (var LogicBlock2 of LogicBlock2s.children) { 33 | var block2 = []; 34 | for (var LogicBlock of LogicBlock2.children) { 35 | block2.push(LogicBlock.parse()); 36 | } 37 | blocks.push(block2); 38 | } 39 | var block3 = []; 40 | if (LogicBlock3.child(0)) { 41 | for (var LogicBlock of LogicBlock3.child(0).children) { 42 | block3.push(LogicBlock.parse()); 43 | } 44 | } 45 | blocks.push(block3); 46 | 47 | return { 48 | type: 'logic', 49 | name: 'if', 50 | conditions: conditions, 51 | blocks: blocks 52 | } 53 | }, 54 | LogicBlock_WHILE(WHILE, LogicBlocks, END) { 55 | var condition = WHILE.parse(); 56 | var block = []; 57 | for (var LogicBlock of LogicBlocks.children) { 58 | block.push(LogicBlock.parse()); 59 | } 60 | return { 61 | type: 'logic', 62 | name: 'while', 63 | condition: condition, 64 | block: block 65 | } 66 | }, 67 | LogicBlock_FOREACH(FOREACH, LogicBlocks, END) { 68 | var condition = FOREACH.parse(); 69 | var block = []; 70 | for (var LogicBlock of LogicBlocks.children) { 71 | block.push(LogicBlock.parse()); 72 | } 73 | return { 74 | type: 'logic', 75 | name: 'foreach', 76 | child: condition.child, 77 | children: condition.children, 78 | block: block 79 | } 80 | }, 81 | IF(head, Expression) { 82 | // condtion Object 83 | return Expression.parse(); 84 | }, 85 | ELSEIF(head, Expression) { 86 | // condtion Object 87 | return Expression.parse(); 88 | }, 89 | WHILE(head, Expression) { 90 | // condtion Object 91 | return Expression.parse(); 92 | }, 93 | FOREACH(head, childVar, _in, childrenVar) { 94 | return { 95 | child: childVar.parse(), 96 | children: childrenVar.parse() 97 | } 98 | }, 99 | LET_assign(head, variable, operator, Exp) { 100 | var explicit = head.parse().length > 1; 101 | return { 102 | type: 'logic', 103 | name: 'let', 104 | explicit: explicit, 105 | left: variable.parse(), 106 | right: Exp.parse() 107 | } 108 | }, 109 | LET_nonAssign(head, variable) { 110 | return { 111 | type: 'logic', 112 | name: 'let', 113 | explicit: true, 114 | left: variable.parse(), 115 | right: { type: 'value', value: null } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /actions/arithmetic.js: -------------------------------------------------------------------------------- 1 | function mathExp(left, operator, right) { 2 | return { 3 | type: 'expression', 4 | value: { 5 | left: left.parse(), 6 | operator: operator.parse(), 7 | right: right.parse() 8 | } 9 | } 10 | } 11 | 12 | 13 | module.exports = { 14 | variable(prefix, n) { 15 | return { 16 | type: 'variable', 17 | prefix: prefix.parse() || null, 18 | value: n.parse() 19 | }; 20 | }, 21 | AddExp_add: mathExp, 22 | MulExp_mul: mathExp, 23 | ExpExp_power: mathExp, 24 | PriExp_paren(head, MathExp, foot) { 25 | return MathExp.parse(); 26 | }, 27 | PriExp_neg(neg, PriExp) { 28 | return { 29 | type: 'expression', 30 | value: { 31 | left: { 32 | type: 'value', 33 | value: 0 34 | }, 35 | operator: '-', 36 | right: PriExp.parse() 37 | } 38 | } 39 | }, 40 | PriExp_pos(pos, PriExp) { 41 | return { 42 | type: 'expression', 43 | value: { 44 | left: { 45 | type: 'value', 46 | value: 0 47 | }, 48 | operator: '+', 49 | right: PriExp.parse() 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /actions/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function parseSignedHexNumber(string) { 18 | const char = string[0]; 19 | if (char === '-') { 20 | return - Number(string.substr(1)); 21 | } else { 22 | return Number(string.substr(1)); 23 | } 24 | } 25 | 26 | module.exports = { 27 | value(n) { 28 | var value; 29 | switch (n.ctorName) { 30 | case 'string': value = n.parse(); break; 31 | case 'number': value = Number(n.parse()) || parseSignedHexNumber(n.parse()); break; 32 | case 'boolean': value = (n.parse().toLowerCase() === 'true'); break; 33 | case 'array': value = n.parse(); break; 34 | default: value = null; 35 | } 36 | return { 37 | type: 'value', 38 | value: value 39 | }; 40 | }, 41 | number_sign(sign, number) { 42 | return sign.parse() + number.parse(); 43 | }, 44 | number_fract(number, dot, decimal) { 45 | return number.parse() + '.' + decimal.parse(); 46 | }, 47 | number_hex(head, octdigit) { 48 | return '0x' + octdigit.parse(); 49 | }, 50 | array(head, list, foot) { 51 | return list.parse().map(item => item.value); 52 | }, 53 | nonemptyListOf(a, b, c) { 54 | return [a.parse(), ...c.parse()]; 55 | }, 56 | string_doubleQuote(quoteA, stringContent, quoteB) { 57 | return stringContent.parse() 58 | }, 59 | string_singleQuote(quoteA, stringContent, quoteB) { 60 | return stringContent.parse() 61 | }, 62 | _iter(children) { 63 | var ret = []; 64 | var hasObject = false; 65 | for (var child of children) { 66 | const value = child.parse(); 67 | hasObject = hasObject || (typeof value === 'object'); 68 | ret.push(value); 69 | } 70 | return hasObject ? ret : ret.join(''); 71 | }, 72 | _terminal() { 73 | return this.primitiveValue 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /actions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var base = require('./base'); 18 | var arithmetic = require('./arithmetic'); 19 | var keyvalue = require('./keyvalue'); 20 | var story = require('./story'); 21 | var Expression = require('./Expression'); 22 | var Comment = require('./Comment'); 23 | var LogicBlock = require('./LogicBlock'); 24 | var Exp = require('./Exp'); 25 | 26 | module.exports = Object.assign({}, base, arithmetic, keyvalue, story, Expression, Comment, LogicBlock, Exp); 27 | -------------------------------------------------------------------------------- /actions/keyvalue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | content_mul(kv, space, content) { 19 | var ret = { 20 | flags: [], 21 | params: {} 22 | }; 23 | var result = kv.parse(); 24 | if (result.length === 1) { 25 | ret.flags.push(result[0]); 26 | } else { 27 | ret.params[result[0]] = result[1]; 28 | } 29 | let ret2 = content.parse(); 30 | ret.flags = ret.flags.concat(ret2.flags); 31 | Object.assign(ret.params, ret2.params); 32 | return ret; 33 | }, 34 | content_base(kv) { 35 | var ret = { 36 | flags: [], 37 | params: {} 38 | }; 39 | var result = kv.parse(); 40 | if (result.length === 1) { 41 | ret.flags.push(result[0]); 42 | } else { 43 | ret.params[result[0]] = result[1]; 44 | } 45 | return ret; 46 | }, 47 | keyValue_param(key, syntex, value) { 48 | return [key.parse(), value.parse()] 49 | }, 50 | keyValue_flag(key) { 51 | return [key.parse()] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /actions/story.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | StoryLine_formatA(head, command, content, foot) { 19 | console.warn('[Deprecated] Command beginning with `@` will no longer be supported.') 20 | var content = content.parse(); 21 | return { 22 | type: 'content', 23 | command: command.parse(), 24 | flags: content.flags, 25 | params: content.params, 26 | } 27 | }, 28 | StoryLine_formatB(head, command, content, foot) { 29 | var content = content.parse(); 30 | return { 31 | type: 'content', 32 | command: command.parse(), 33 | flags: content.flags, 34 | params: content.params, 35 | } 36 | }, 37 | StoryLine_formatC(head, command, foot) { 38 | console.warn('[Deprecated] Command beginning with `@` will no longer be supported.') 39 | return { 40 | type: 'content', 41 | command: command.parse(), 42 | flags: [], 43 | params: {}, 44 | } 45 | }, 46 | StoryLine_formatD(head, command, foot) { 47 | return { 48 | type: 'content', 49 | command: command.parse(), 50 | flags: [], 51 | params: {}, 52 | } 53 | }, 54 | StoryLine_formatE(text) { 55 | var textContent = text.parse(); 56 | return { 57 | type: 'content', 58 | command: '*', 59 | flags: [], 60 | params: { raw: { type: 'value', value: textContent } }, 61 | } 62 | }, 63 | command(key) { 64 | return key.parse(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var parser = require('./libs/parser'); 18 | var variable = require('./libs/variable'); 19 | var { IfBlock, WhileBlock, ForeachBlock } = require('./libs/block'); 20 | 21 | export default class StoryScript { 22 | constructor(onGlobalChanged) { 23 | this.BLOCKSTACK = []; 24 | this.CURRENTBLOCK = null; 25 | 26 | this.onGlobalChanged = onGlobalChanged; 27 | } 28 | load(string) { 29 | const result = parser.parse(string); 30 | const system = new IfBlock(result); 31 | this.CURRENTBLOCK = system; 32 | this.BLOCKSTACK = []; 33 | // variable.reset(); 34 | } 35 | getBlockData() { 36 | const blocks = []; 37 | for (const [node, block] of [...this.BLOCKSTACK, this.CURRENTBLOCK].reverse().entries()) { 38 | let blockData = block.getData(); 39 | blockData.scope = variable.getScope(node); 40 | blocks.push(blockData); 41 | } 42 | return blocks.reverse(); 43 | } 44 | getGlobalScope() { 45 | return variable.getGlobalScope(); 46 | } 47 | getSaveScope() { 48 | return variable.getSaveScope(); 49 | } 50 | // @deprecated 51 | getData() { 52 | console.warn('[Storyscript] getData() has been deprecated!'); 53 | return { 54 | blocks: this.getBlockData(), 55 | globalScope: this.getGlobalScope(), 56 | saveScope: this.getSaveScope() 57 | } 58 | } 59 | setGlobalScope(scope) { 60 | variable.setGlobalScope(scope); 61 | } 62 | setSaveScope(scope) { 63 | variable.setSaveScope(scope); 64 | } 65 | setBlockData(blocks) { 66 | const scopes = [blocks[0].scope]; 67 | variable.setScopes(scopes); 68 | this.CURRENTBLOCK.setCurrentLine(blocks[0].currentLine); 69 | 70 | if (blocks.length === 1) { 71 | return true 72 | } 73 | 74 | for (let i = 0; i < blocks.length; i++) { 75 | const block = blocks[i]; 76 | const nextBlock = blocks[i + 1]; 77 | const lastLine = block.currentLine - 1; 78 | const line = this.CURRENTBLOCK.getLine(lastLine); 79 | if (line.name === nextBlock.type) { 80 | switch (line.name) { 81 | case 'if': 82 | const ifBlock = new IfBlock(line.blocks[nextBlock.blockIndex], nextBlock.blockIndex); 83 | ifBlock.setCurrentLine(nextBlock.currentLine); 84 | variable.pushScope(nextBlock.scope); 85 | // variable.popScope(); 86 | this.BLOCKSTACK.push(this.CURRENTBLOCK); 87 | this.CURRENTBLOCK = ifBlock; 88 | break; 89 | case 'while': 90 | const whileBlock = new WhileBlock(line.block, line.condition); 91 | whileBlock.setCurrentLine(nextBlock.currentLine); 92 | variable.pushScope(nextBlock.scope); 93 | // variable.popScope(); 94 | this.BLOCKSTACK.push(this.CURRENTBLOCK); 95 | this.CURRENTBLOCK = whileBlock; 96 | break; 97 | case 'foreach': 98 | const foreachBlock = new ForeachBlock(line.block, line.child, line.children); 99 | foreachBlock.setCurrentLine(nextBlock.currentLine); 100 | variable.pushScope(nextBlock.scope); 101 | // variable.popScope(); 102 | this.BLOCKSTACK.push(this.CURRENTBLOCK); 103 | this.CURRENTBLOCK = foreachBlock; 104 | break; 105 | default: 106 | throw 'Bad savedata'; 107 | } 108 | } else { 109 | throw 'Bad savedata'; 110 | } 111 | } 112 | } 113 | // @deprecated 114 | setData(object) { 115 | console.warn('[Storyscript] setData() has been deprecated!'); 116 | this.setGlobalScope(object.globalScope); 117 | this.setSaveScope(object.saveScope); 118 | this.setBlockData(object.blocks); 119 | } 120 | [Symbol.iterator]() { 121 | return this; 122 | } 123 | next() { 124 | let {value, done} = this.CURRENTBLOCK.next(); 125 | if (done) { 126 | var CURRENTBLOCK = this.BLOCKSTACK.pop(); 127 | if (CURRENTBLOCK) { 128 | this.CURRENTBLOCK = CURRENTBLOCK; 129 | variable.popScope(); 130 | return this.next(); 131 | } else { 132 | return { done: true } 133 | } 134 | } else { 135 | const retValue = this.handleScript(value); 136 | if (retValue) { 137 | return { value: retValue, done: false} 138 | } else { 139 | // handleLogic will return undefined, so should exec next line 140 | return this.next(); 141 | } 142 | } 143 | } 144 | handleScript(argLine) { 145 | // deep copy 146 | const line = Object.assign({}, argLine); 147 | 148 | if (line.type === 'content') { 149 | return this.handleContent(line); 150 | } else if (line.type === 'logic') { 151 | return this.handleLogic(line); 152 | } else if (line.type === 'comment') { 153 | return null; 154 | } else { 155 | throw `Unrecognized type ${line.type}`; 156 | } 157 | } 158 | 159 | handleContent(line) { 160 | const params = line.params; 161 | const keys = Object.keys(params); 162 | for (const key of keys) { 163 | params[key] = params[key].value; 164 | } 165 | return line; 166 | } 167 | 168 | handleLogic(line) { 169 | switch (line.name) { 170 | case 'if': return this.handleLogic_IF(line);break; 171 | case 'while': return this.handleLogic_WHILE(line);break; 172 | case 'foreach': return this.handleLogic_FOREACH(line);break; 173 | case 'let': return this.handleLogic_LET(line);break; 174 | default: throw `Unrecognized name ${line.name}`; 175 | } 176 | } 177 | 178 | handleLogic_IF(line) { 179 | let blockIndex = 0; 180 | for (const condition of line.conditions) { 181 | if (variable.calc(condition)) { 182 | break; 183 | } else { 184 | blockIndex++; 185 | } 186 | } 187 | this.BLOCKSTACK.push(this.CURRENTBLOCK); 188 | const blockData = line.blocks[blockIndex]; 189 | const block = new IfBlock(blockData, blockIndex); 190 | this.CURRENTBLOCK = block; 191 | // variable.pushScope(); 192 | } 193 | 194 | handleLogic_WHILE(line) { 195 | const result = variable.calc(line.condition); 196 | if (result) { 197 | this.BLOCKSTACK.push(this.CURRENTBLOCK); 198 | const blockData = line.block; 199 | const block = new WhileBlock(blockData, line.condition); 200 | this.CURRENTBLOCK = block; 201 | } 202 | // variable.pushScope(); 203 | } 204 | 205 | handleLogic_FOREACH(line) { 206 | const children = variable.calc(line.children); 207 | if (children instanceof Array) { 208 | this.BLOCKSTACK.push(this.CURRENTBLOCK); 209 | const blockData = line.block; 210 | const block = new ForeachBlock(blockData, line.child, line.children); 211 | this.CURRENTBLOCK = block; 212 | } else { 213 | throw '[Foreach] Children must be a array'; 214 | } 215 | // variable.pushScope(); 216 | } 217 | 218 | handleLogic_LET(line) { 219 | if (line.left.prefix === '$') { 220 | this.onGlobalChanged && this.onGlobalChanged(); 221 | } 222 | variable.assign(line.left.value, line.left.prefix, line.right, line.explicit); 223 | } 224 | } 225 | 226 | // module.exports = StoryScript; 227 | -------------------------------------------------------------------------------- /libs/block.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var variable = require('./variable'); 18 | 19 | class IfBlock { 20 | constructor(data, blockIndex) { 21 | this.reset(); 22 | this.data = data; 23 | this.blockIndex = blockIndex; 24 | variable.pushScope(); 25 | } 26 | reset() { 27 | this.data = []; 28 | this.currentLine = 0; 29 | this.done = false; 30 | } 31 | getData() { 32 | return { 33 | type: 'if', 34 | currentLine: this.currentLine, 35 | blockIndex: this.blockIndex 36 | } 37 | } 38 | setCurrentLine(no) { 39 | this.currentLine = no; 40 | } 41 | getLine(no) { 42 | return this.data[no]; 43 | } 44 | [Symbol.iterator]() { 45 | return this; 46 | } 47 | next() { 48 | if (this.currentLine < this.data.length) { 49 | let line = this.data[this.currentLine++]; 50 | return { value: line, done: false }; 51 | } else { 52 | // !this.done && variable.popScope(); 53 | // this.done = true; 54 | return { done: true }; 55 | } 56 | } 57 | } 58 | 59 | class WhileBlock { 60 | constructor(data, condition) { 61 | this.reset(); 62 | this.data = data; 63 | this.condition = condition; 64 | variable.pushScope(); 65 | } 66 | reset() { 67 | this.data = []; 68 | this.currentLine = 0; 69 | this.done = false; 70 | } 71 | getData() { 72 | return { 73 | type: 'while', 74 | currentLine: this.currentLine 75 | } 76 | } 77 | setCurrentLine(no) { 78 | this.currentLine = no; 79 | } 80 | getLine(no) { 81 | return this.data[no]; 82 | } 83 | [Symbol.iterator]() { 84 | return this; 85 | } 86 | next() { 87 | if (this.currentLine < this.data.length) { 88 | let line = this.data[this.currentLine++]; 89 | return { value: line, done: false }; 90 | } else { 91 | if (variable.calc(this.condition)) { 92 | this.currentLine = 0; 93 | variable.popScope(); 94 | variable.pushScope(); 95 | return this.next(); 96 | } else { 97 | // !this.done && variable.popScope(); 98 | // this.done = true; 99 | return { done: true }; 100 | } 101 | } 102 | } 103 | } 104 | 105 | class ForeachBlock { 106 | constructor(data, child, children) { 107 | this.reset(); 108 | this.data = data; 109 | this.child = child; 110 | this.children = variable.calc(children); 111 | this.index = 0; 112 | variable.pushScope(); 113 | variable.assign(this.child.value, this.child.prefix, 114 | { type: 'value', value: this.children[this.index] }, true); 115 | } 116 | reset() { 117 | this.data = []; 118 | this.currentLine = 0; 119 | this.done = false; 120 | } 121 | getData() { 122 | return { 123 | type: 'foreach', 124 | currentLine: this.currentLine 125 | } 126 | } 127 | setCurrentLine(no) { 128 | this.currentLine = no; 129 | } 130 | getLine(no) { 131 | return this.data[no]; 132 | } 133 | [Symbol.iterator]() { 134 | return this; 135 | } 136 | next() { 137 | if (this.currentLine < this.data.length) { 138 | let line = this.data[this.currentLine++]; 139 | return { value: line, done: false }; 140 | } else { 141 | if (this.index < this.children.length - 1) { 142 | this.currentLine = 0; 143 | this.index++; 144 | variable.popScope(); 145 | variable.pushScope(); 146 | variable.assign(this.child.value, this.child.prefix, 147 | { type: 'value', value: this.children[this.index] }, true); 148 | return this.next(); 149 | } else { 150 | // !this.done && variable.popScope(); 151 | // this.done = true; 152 | return { done: true }; 153 | } 154 | } 155 | } 156 | } 157 | 158 | module.exports = { IfBlock, WhileBlock, ForeachBlock }; 159 | -------------------------------------------------------------------------------- /libs/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var ohm = require('ohm-js'); 18 | // var fs = require('fs'); 19 | var actions = require('../actions'); 20 | var contents = require('../ohm/bks.ohm'); 21 | 22 | var myGrammar = ohm.grammar(contents); 23 | var mySemantics = myGrammar.createSemantics(); 24 | mySemantics.addOperation('parse', actions); 25 | 26 | exports.parse = function (string) { 27 | var m = myGrammar.match(string); 28 | if (m.succeeded()) { 29 | return mySemantics(m).parse(); 30 | } else { 31 | throw m.message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libs/variable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | let GLOBAL = {}, 18 | SAVE = {}, 19 | SCOPES = []; 20 | let CURRENTSCOPE = {}; 21 | 22 | function calculate(exp, node=0) { 23 | switch (exp.type) { 24 | case 'expression': 25 | const value = exp.value; 26 | return calc_expression(calculate(value.left), value.operator, calculate(value.right)); 27 | case 'variable': 28 | return calc_variable(exp.value, exp.prefix, node); 29 | case 'value': 30 | return exp.value; 31 | default: throw `Unrecognized type ${type}`; 32 | } 33 | } 34 | 35 | function calc_expression(left, operator, right) { 36 | switch (operator) { 37 | case '&&': return left && right; break; 38 | case '||': return left || right; break; 39 | case '==': return left == right; break; 40 | case '>=': return left >= right; break; 41 | case '<=': return left <= right; break; 42 | case '>': return left > right; break; 43 | case '<': return left < right; break; 44 | case '+': return left + right; break; 45 | case '-': return left - right; break; 46 | case '*': return left * right; break; 47 | case '/': return left / right; break; 48 | case '^': return Math.pow(left, right); break; 49 | case '%': return left % right; break; 50 | default: throw `Unrecognized operator ${operator}`; 51 | } 52 | } 53 | 54 | function calc_variable(name, prefix, node) { 55 | switch (prefix) { 56 | case null: return findVariableValue(name, node); break; 57 | case '$': return GLOBAL[name]; break; 58 | case '%': return SAVE[name]; break; 59 | default: throw `Unrecognized prefix ${prefix}`; 60 | } 61 | } 62 | 63 | function findVariableValue(name, node=0) { 64 | let ret = null; 65 | const _SCOPES = [...SCOPES, CURRENTSCOPE]; 66 | for (let i = _SCOPES.length - 1 - node; i > -1; i--) { 67 | const scope = _SCOPES[i]; 68 | ret = scope[name] || null; 69 | if (ret) { 70 | break; 71 | } 72 | } 73 | return ret; 74 | } 75 | 76 | function assign(name, prefix, value, explicit) { 77 | if (prefix) { 78 | if (prefix === '$') { 79 | GLOBAL[name] = value; 80 | } else if (prefix === '%') { 81 | SAVE[name] = value; 82 | } 83 | } else if (explicit) { 84 | const scope = CURRENTSCOPE; 85 | if (scope[name]) { 86 | throw `Identifier '${name}' has already been declared`; 87 | } else { 88 | scope[name] = value; 89 | } 90 | } else { 91 | let defined = false; 92 | let scope = null; 93 | const _SCOPES = [...SCOPES, CURRENTSCOPE]; 94 | for (let i = _SCOPES.length - 1; i > -1; i--) { 95 | scope = _SCOPES[i]; 96 | if (scope.hasOwnProperty(name)) { 97 | defined = true; 98 | break; 99 | } 100 | } 101 | if (defined) { 102 | scope[name] = value; 103 | } else { 104 | throw `${name} is not defined`; 105 | } 106 | } 107 | } 108 | 109 | module.exports = { 110 | load() { 111 | GLOBAL = {}; 112 | SAVE = {}; 113 | SCOPES = []; 114 | CURRENTSCOPE = {}; 115 | }, 116 | dump() { 117 | return { 118 | GLOBAL: GLOBAL, 119 | SAVE: SAVE, 120 | SCOPES: SCOPES, 121 | CURRENTSCOPE: CURRENTSCOPE 122 | } 123 | }, 124 | getGlobalScope() { 125 | return GLOBAL; 126 | }, 127 | getSaveScope() { 128 | return SAVE; 129 | }, 130 | getScope(node) { 131 | return [...SCOPES, CURRENTSCOPE][SCOPES.length - node] 132 | }, 133 | setGlobalScope(scope) { 134 | GLOBAL = scope; 135 | }, 136 | setSaveScope(scope) { 137 | SAVE = scope; 138 | }, 139 | setScopes(scopes) { 140 | SCOPES = SCOPES; 141 | this.popScope(); 142 | }, 143 | pushScope(scope={}) { 144 | SCOPES.push(CURRENTSCOPE); 145 | CURRENTSCOPE = scope; 146 | }, 147 | popScope() { 148 | CURRENTSCOPE = SCOPES.pop(); 149 | }, 150 | calc(exp, node) { 151 | return calculate(exp); 152 | }, 153 | assign(name, prefix, right, explicit) { 154 | return assign(name, prefix, calculate(right), explicit); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /ohm/bks.ohm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Icemic Jia 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = ` 18 | BKS { 19 | Scripts 20 | = LogicBlock* 21 | 22 | LogicBlock 23 | = Comment 24 | | IF LogicBlock* (ELSEIF LogicBlock*)* (ELSE LogicBlock*)? END -- IF 25 | | WHILE LogicBlock* END -- WHILE 26 | | FOREACH LogicBlock* END -- FOREACH 27 | | LET -- LET 28 | | StoryLine -- Story 29 | 30 | Comment = "//" comment_single -- single 31 | | "/*" comment_multi "*/" -- multi 32 | 33 | comment_single = (~("\\n" | "\\r") any)+ 34 | comment_multi = (~("*/") any)+ 35 | 36 | StoryLine 37 | = "[" command content "]" -- formatB 38 | | "@" command content ("\\r"|"\\n"|end) -- formatA 39 | | "@" command ("\\r"|"\\n"|end) -- formatC 40 | | "[" command "]" -- formatD 41 | | text -- formatE 42 | 43 | text = (~("[" | "@" | "#" | "\\n" | "\\r" | "//" | "/*") any)+ 44 | 45 | command = key 46 | 47 | content = keyValue " " content -- mul 48 | | keyValue -- base 49 | 50 | keyValue = key "=" value -- param 51 | | key -- flag 52 | 53 | key = (letter | number | "_")+ 54 | 55 | value = string | number | boolean | "null" | array 56 | 57 | array = "[" listOf "]" 58 | 59 | string = "\\"" doubleQuoteStringContent* "\\"" -- doubleQuote 60 | | "\\'" singleQuoteStringContent* "\\'" -- singleQuote 61 | 62 | // ~("\\'" | "\\\\" ) any -- nonEscaped 63 | 64 | singleQuoteStringContent = ~("\\'") any -- nonEscaped 65 | | "\\\\" escapeCharacter -- escaped 66 | 67 | doubleQuoteStringContent = ~("\\"") any -- nonEscaped 68 | | "\\\\" escapeCharacter -- escaped 69 | 70 | singleEscapeCharacter = "'"|"\\""|"\\\\"|"b"|"f"|"n"|"r"|"t"|"v" 71 | escapeCharacter = singleEscapeCharacter | "x" | "u" 72 | 73 | quote = "\\"" | "\\'" 74 | 75 | boolean = ("true" | "false") ~variable 76 | 77 | number (a number) 78 | = ("-"|"+") number -- sign 79 | | digit* "." digit+ -- fract 80 | | "0x" hexdigit+ -- hex 81 | | digit+ -- whole 82 | 83 | hexdigit 84 | = "a".."f" | "A".."F" | digit 85 | 86 | variable = ~number ("$" | "%")? (letter | number | "_")+ 87 | 88 | IF 89 | = "#if" Exp 90 | 91 | LET 92 | = ("#let" | "#") variable "=" Exp -- assign 93 | | "#let" variable -- nonAssign 94 | 95 | END 96 | = "#end" 97 | 98 | ELSE 99 | = "#else" 100 | 101 | ELSEIF 102 | = "#elseif" Exp 103 | 104 | WHILE 105 | = "#while" Exp 106 | 107 | FOREACH 108 | = "#foreach" variable "in" variable 109 | 110 | Exp 111 | = JudgeExp booleanOperator Exp -- bool 112 | | JudgeExp 113 | 114 | booleanOperator = "&&" | "||" 115 | 116 | JudgeExp 117 | = AddExp judgeOperator AddExp -- judge 118 | | AddExp 119 | 120 | judgeOperator = "==" | ">=" | "<=" | ">" | "<" 121 | 122 | // MathExp 123 | // = MathExp mathOperator MathExp -- math 124 | // | PriExp 125 | 126 | // mathOperator = "+" | "-" | "*" | "/" | "^" | "%" 127 | 128 | AddExp 129 | = AddExp ("+" | "-") MulExp -- add 130 | // | AddExp "-" MulExp -- minus 131 | | MulExp 132 | 133 | MulExp 134 | = MulExp ("*" | "/" | "%") ExpExp -- mul 135 | // | MulExp "/" ExpExp -- divide 136 | // | MulExp "%" ExpExp -- mod 137 | | ExpExp 138 | 139 | ExpExp 140 | = PriExp "^" ExpExp -- power 141 | | PriExp 142 | 143 | PriExp 144 | = "(" Exp ")" -- paren 145 | | "+" PriExp -- pos 146 | | "-" PriExp -- neg 147 | | value 148 | | variable 149 | 150 | } 151 | `; 152 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "avg-storyscript", 3 | "version": "0.2.1", 4 | "description": "Build-in Story Script System for AVG.js.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "prepublish": "npm run build", 8 | "build": "webpack --progress --colors", 9 | "test": "mocha" 10 | }, 11 | "keywords": [ 12 | "AVG", 13 | "Game", 14 | "AVG.js", 15 | "script" 16 | ], 17 | "author": "Icemic Jia ", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/avgjs/storyscript.git" 21 | }, 22 | "license": "Apache-2.0", 23 | "dependencies": { 24 | "babel-core": "^6.17.0", 25 | "babel-loader": "^6.2.5", 26 | "babel-preset-latest": "^6.16.0", 27 | "ohm-js": "^0.12.3", 28 | "webpack": "^1.13.2" 29 | }, 30 | "devDependencies": { 31 | "chai": "^3.5.0", 32 | "mocha": "^3.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const StoryScript = require('./index'); 2 | const variable = require('./libs/variable'); 3 | 4 | // var userInput = ` 5 | // [bg file="kodoyuri/data.xp3/bgimage/white.png" trans] 6 | // [flow wait time=200] 7 | // #if x>1 && ((x < 10 && z >= 100) || z) || n == 'xxx' 8 | // [text set bgfile="kodoyuri/data.xp3/image/massage_bg2.png" color=0xffffff] 9 | // [text set speed=50] 10 | // #elseif y <= 0x11 11 | // [bg file="kodoyuri/data.xp3/bgimage/h01.png" trans] 12 | // #elseif y == (x + (1 - 1) * -2) / +4 13 | // [bg file="kodoyuri/data.xp3/bgimage/white.png" trans] 14 | // #else 15 | // #end 16 | // #while x < 0 17 | // [flow wait time=200] 18 | // #end 19 | // 20 | // #foreach child in children 21 | // [text set speed=50] 22 | // #end 23 | // 24 | // [text show trans]`; 25 | // 26 | var userInput = ` 27 | #let x = 1 28 | #$open = false 29 | #let xxx = "sdfdsf" 30 | #if x > 0 && xxx == 'sdfdsf' 31 | #let x = 2 32 | #let $open = 123 33 | [name flagA] 34 | #x = 3 35 | #else 36 | [name flagB] 37 | #end 38 | 39 | #let i = 0 40 | #while i < 5 41 | [name flagX] 42 | #i = i + 1 43 | #end 44 | 测试 一下 sdjsdfj /c /* 注释 */ 45 | // [wb]545第二行 46 | #let y = false 47 | /* 48 | * 其他测试 49 | 123 50 | */ 51 | #let aaaa = 11 52 | [name flagC] 53 | #aaaa = aaaa ^ 2 54 | ` 55 | 56 | const story = new StoryScript(); 57 | story.load(userInput); 58 | 59 | let i = 0; 60 | for (var value of story) { 61 | if (i === 0) { 62 | console.log(JSON.stringify(story.getData(), null, ' ')) 63 | } 64 | i++ 65 | console.log(value) 66 | } 67 | 68 | console.log(variable.dump()) 69 | console.log(story.getData()) 70 | -------------------------------------------------------------------------------- /test/parser.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var parse = require('../libs/parser').parse; 4 | 5 | describe('Parser', () => { 6 | describe('Content Script', () => { 7 | it('parse script starts with `@`', () => { 8 | expect(parse('@name flag')) 9 | .to.eql([{ 10 | type: 'content', 11 | command: 'name', 12 | flags: ['flag'], 13 | params: {} 14 | }]); 15 | }); 16 | 17 | it('parse script wrapped with `[]`', () => { 18 | expect(parse('[name flag]')) 19 | .to.eql([{ 20 | type: 'content', 21 | command: 'name', 22 | flags: ['flag'], 23 | params: {} 24 | }]); 25 | }); 26 | 27 | it('parse no parameter', () => { 28 | expect(parse('[name]')) 29 | .to.eql([{ 30 | type: 'content', 31 | command: 'name', 32 | flags: [], 33 | params: {} 34 | }]); 35 | }); 36 | 37 | it('parse parameter value of ascii string', () => { 38 | expect(parse('[name param="string"]')) 39 | .to.eql([{ 40 | type: 'content', 41 | command: 'name', 42 | flags: [], 43 | params: { param: { type: 'value', value: 'string' } } 44 | }]); 45 | }); 46 | it('parse parameter value of non-ascii string', () => { 47 | expect(parse('[name param="中文测试,日本語の分析テスト" param2=\'中a文s\\测**|/试%……%\']')) 48 | .to.eql([{ 49 | type: 'content', 50 | command: 'name', 51 | flags: [], 52 | params: { 53 | param: { type: 'value', value: '中文测试,日本語の分析テスト'}, 54 | param2: { type: 'value', value: '中a文s\\测**|/试%……%'} 55 | } 56 | }]); 57 | }); 58 | it('parse parameter value of number', () => { 59 | expect(parse('[name param1=123 param2=00123 param3=0x123 param4=-10 param5=+0x20 param6=10.02 param7=.4]')) 60 | .to.eql([{ 61 | type: 'content', 62 | command: 'name', 63 | flags: [], 64 | params: { 65 | param1: { type: 'value', value: 123}, 66 | param2: { type: 'value', value: 123}, 67 | param3: { type: 'value', value: 0x123}, 68 | param4: { type: 'value', value: -10}, 69 | param5: { type: 'value', value: 0x20}, 70 | param6: { type: 'value', value: 10.02}, 71 | param7: { type: 'value', value: 0.4}, 72 | } 73 | }]); 74 | }); 75 | it('parse parameter value of boolean', () => { 76 | expect(parse('[name param=true param2=false]')) 77 | .to.eql([{ 78 | type: 'content', 79 | command: 'name', 80 | flags: [], 81 | params: { 82 | param: { type: 'value', value: true}, 83 | param2: { type: 'value', value: false} 84 | } 85 | }]); 86 | }); 87 | it('parse parameter value of null', () => { 88 | expect(parse('[name param=null param2=false]')) 89 | .to.eql([{ 90 | type: 'content', 91 | command: 'name', 92 | flags: [], 93 | params: { 94 | param: { type: 'value', value: null}, 95 | param2: { type: 'value', value: false} 96 | } 97 | }]); 98 | }); 99 | it('parse parameter value of array', () => { 100 | expect(parse('[name param1=[1,2,null,4] param2=[1,false,"test",[1,2,null]]]')) 101 | .to.eql([{ 102 | type: 'content', 103 | command: 'name', 104 | flags: [], 105 | params: { 106 | param1: { type: 'value', value: [1,2,null,4]}, 107 | param2: { type: 'value', value: [1,false,"test",[1,2,null]]} 108 | } 109 | }]); 110 | }); 111 | 112 | it('throw when wrong syntex', () => { 113 | expect(() => parse('[name param1=xxx]')).to.throw(/Line 1, col 14/); 114 | expect(() => parse('[name param1="string]')).to.throw(/Line 1, col 22/); 115 | expect(() => parse('[name param1=123true]')).to.throw(/Line 1, col 17/); 116 | expect(() => parse('[name param1= 123]')).to.throw(/Line 1, col 14/); 117 | expect(() => parse('@name param1=xxx')).to.throw(/Line 1, col 14/); 118 | expect(() => parse('@name param1="string')).to.throw(/Line 1, col 21/); 119 | expect(() => parse('@name param1=123true')).to.throw(/Line 1, col 17/); 120 | expect(() => parse('@name param1= 123')).to.throw(/Line 1, col 14/); 121 | }); 122 | 123 | it('parse multi lines', () => { 124 | expect(parse(` 125 | [name param=123] 126 | [name flag] 127 | `)) 128 | .to.eql([ 129 | { type: 'content', command: 'name', flags: [], params: { param: { type: 'value', value: 123} } }, 130 | { type: 'content', command: 'name', flags: ['flag'], params: {} } 131 | ]); 132 | }); 133 | }); 134 | 135 | describe('Logic Script', () => { 136 | it('parse IF-ELSEIF-ELSE', () => { 137 | expect(parse(` 138 | #if x > 1 139 | [name flagA] 140 | #elseif y == 2 141 | #elseif y <= 300 142 | #else 143 | [name flagB] 144 | #end 145 | [name flagC] 146 | `)).to.eql([ 147 | { 148 | type: 'logic', name: 'if', 149 | conditions: [ 150 | { type: 'expression', value: { left: { type: 'variable', prefix: null, value: 'x' }, operator: '>', right: { type: 'value', value: 1 } }}, 151 | { type: 'expression', value: { left: { type: 'variable', prefix: null, value: 'y' }, operator: '==', right: { type: 'value', value: 2 } }}, 152 | { type: 'expression', value: { left: { type: 'variable', prefix: null, value: 'y' }, operator: '<=', right: { type: 'value', value: 300 } }} 153 | ], 154 | blocks: [ 155 | [{ type: 'content', command: 'name', flags: ['flagA'], params: {} }], 156 | [],[], 157 | [{ type: 'content', command: 'name', flags: ['flagB'], params: {} }] 158 | ] 159 | }, 160 | { type: 'content', command: 'name', flags: ['flagC'], params: {} } 161 | ]) 162 | }); 163 | it('parse WHILE', () => { 164 | expect(parse(` 165 | [name flagA] 166 | #while x > 1 167 | [name flagB] 168 | #end 169 | [name flagC] 170 | `)).to.eql([ 171 | { type: 'content', command: 'name', flags: ['flagA'], params: {} }, 172 | { 173 | type: 'logic', name: 'while', 174 | condition: { 175 | type: 'expression', 176 | value: { 177 | left: { type: 'variable', prefix: null, value: 'x' }, 178 | operator: '>', 179 | right: { type: 'value', value: 1 } 180 | } 181 | }, 182 | block: [{ type: 'content', command: 'name', flags: ['flagB'], params: {} }] 183 | }, 184 | { type: 'content', command: 'name', flags: ['flagC'], params: {} } 185 | ]) 186 | }); 187 | it('parse FOREACH', () => { 188 | expect(parse(` 189 | [name flagA] 190 | #foreach child in children 191 | [name flagB] 192 | #end 193 | [name flagC] 194 | `)).to.eql([ 195 | { type: 'content', command: 'name', flags: ['flagA'], params: {} }, 196 | { 197 | type: 'logic', name: 'foreach', 198 | child: { type: 'variable', prefix: null, value: 'child' }, 199 | children: { type: 'variable', prefix: null, value: 'children' }, 200 | block: [{ type: 'content', command: 'name', flags: ['flagB'], params: {} }] 201 | }, 202 | { type: 'content', command: 'name', flags: ['flagC'], params: {} } 203 | ]) 204 | }); 205 | it('parse LET', () => { 206 | expect(parse(` 207 | [name flagA] 208 | #let variable = "123" 209 | #let variable2 = variable 210 | #let variable3 211 | [name flagB] 212 | #variable4 = true 213 | `)).to.eql([ 214 | { type: 'content', command: 'name', flags: ['flagA'], params: {} }, 215 | { 216 | type: 'logic', name: 'let', 217 | explicit: true, 218 | left: { type: 'variable', prefix: null, value: 'variable' }, 219 | right: { type: 'value', value: '123' }, 220 | }, 221 | { 222 | type: 'logic', name: 'let', 223 | explicit: true, 224 | left: { type: 'variable', prefix: null, value: 'variable2' }, 225 | right: { type: 'variable', prefix: null, value: 'variable' }, 226 | }, 227 | { 228 | type: 'logic', name: 'let', 229 | explicit: true, 230 | left: { type: 'variable', prefix: null, value: 'variable3' }, 231 | right: { type: 'value', value: null}, 232 | }, 233 | { type: 'content', command: 'name', flags: ['flagB'], params: {} }, 234 | { 235 | type: 'logic', name: 'let', 236 | explicit: false, 237 | left: { type: 'variable', prefix: null, value: 'variable4' }, 238 | right: { type: 'value', value: true }, 239 | } 240 | ]) 241 | }); 242 | 243 | it('parse computation', () => { 244 | expect(parse(`#let x = 1 - 22.3 + 4`)).to.eql([ 245 | { 246 | type: "logic", 247 | name: "let", 248 | explicit: true, 249 | left: { 250 | prefix: null, 251 | type: "variable", 252 | value: "x" 253 | }, 254 | right: { 255 | type: "expression", 256 | value: { 257 | left: { 258 | type: "expression", 259 | value: { 260 | left: { 261 | type: "value", 262 | value: 1 263 | }, 264 | operator: "-", 265 | right: { 266 | type: "value", 267 | value: 22.3 268 | } 269 | } 270 | }, 271 | operator: "+", 272 | right: { 273 | type: "value", 274 | value: 4 275 | } 276 | } 277 | } 278 | } 279 | ]); 280 | expect(parse(`#let x = 1 + 2 * 3 + 4 % 2`)).to.eql([ 281 | { 282 | type: "logic", 283 | name: "let", 284 | explicit: true, 285 | left: { 286 | prefix: null, 287 | type: "variable", 288 | value: "x" 289 | }, 290 | right: { 291 | type: "expression", 292 | value: { 293 | left: { 294 | type: "expression", 295 | value: { 296 | left: { 297 | type: 'value', 298 | value: 1 299 | }, 300 | operator: '+', 301 | right: { 302 | type: 'expression', 303 | value: { 304 | left: { 305 | type: 'value', 306 | value: 2 307 | }, 308 | operator: '*', 309 | right: { 310 | type: 'value', 311 | value: 3 312 | } 313 | } 314 | } 315 | } 316 | }, 317 | operator: "+", 318 | right: { 319 | type: "expression", 320 | value: { 321 | left: { 322 | type: "value", 323 | value: 4 324 | }, 325 | operator: '%', 326 | right: { 327 | type: "value", 328 | value: 2 329 | } 330 | } 331 | } 332 | } 333 | } 334 | } 335 | ]) 336 | }); 337 | 338 | it('parse complex logic expression', () => { 339 | expect(parse(` 340 | #while x > 1 + 1 && ((x == 'test' || y >= 30) && a) || (b + 2) * -10 341 | [name] 342 | 这是一句话,哈哈~! 343 | [name flagB] 344 | Some words! 345 | #end 346 | `)).to.eql([ 347 | { 348 | type: 'logic', name: 'while', 349 | condition: { 350 | type: 'expression', 351 | value: { 352 | left: { 353 | type: 'expression', 354 | value: { 355 | left: { type: 'variable', prefix: null, value: 'x' }, 356 | operator: '>', 357 | right: { 358 | type: 'expression', 359 | value: { 360 | left: { type: 'value', value: 1 }, 361 | operator: '+', 362 | right: { type: 'value', value: 1 } 363 | } 364 | } 365 | } 366 | }, 367 | operator: '&&', 368 | right: { 369 | type: 'expression', 370 | value: { 371 | left: { 372 | type: 'expression', 373 | value: { 374 | left: { 375 | type: 'expression', 376 | value: { 377 | left: { 378 | type: 'expression', 379 | value: { 380 | left: { type: 'variable', prefix: null, value: 'x' }, 381 | operator: '==', 382 | right: { type: 'value', value: 'test' } 383 | } 384 | }, 385 | operator: '||', 386 | right: { 387 | type: 'expression', 388 | value: { 389 | left: { type: 'variable', prefix: null, value: 'y' }, 390 | operator: '>=', 391 | right: { type: 'value', value: 30 } 392 | } 393 | } 394 | } 395 | }, 396 | operator: '&&', 397 | right: { type: 'variable', prefix: null, value: 'a' }, 398 | } 399 | }, 400 | operator: '||', 401 | right: { 402 | type: 'expression', 403 | value: { 404 | left: { 405 | type: 'expression', 406 | value: { 407 | left: { type: 'variable', prefix: null, value: 'b' }, 408 | operator: '+', 409 | right: { type: 'value', value: 2 } 410 | } 411 | }, 412 | operator: '*', 413 | right: { 414 | type: 'expression', 415 | value: { 416 | left: { type: 'value', value: 0 }, 417 | operator: '-', 418 | right: { type: 'value', value: 10 } 419 | } 420 | } 421 | } 422 | } 423 | } 424 | } 425 | } 426 | }, 427 | block: [ 428 | { type: 'content', command: 'name', flags: [], params: {} }, 429 | { type: 'content', command: '*', flags: [], params: { raw: { type: 'value', value: '这是一句话,哈哈~!' } } }, 430 | { type: 'content', command: 'name', flags: ['flagB'], params: {} }, 431 | { type: 'content', command: '*', flags: [], params: { raw: { type: 'value', value: 'Some words!' } } } 432 | ] 433 | } 434 | ]) 435 | }); 436 | }); 437 | }) 438 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: "./index.js", 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | filename: "index.js", 9 | libraryTarget: 'umd' 10 | }, 11 | resolve: { 12 | extensions: ['', '.js'] 13 | }, 14 | module: { 15 | loaders: [ 16 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}, 17 | ] 18 | }, 19 | plugins: [ 20 | new webpack.BannerPlugin(`Copyright 2016 Icemic Jia 21 | 22 | Licensed under the Apache License, Version 2.0 (the "License"); 23 | you may not use this file except in compliance with the License. 24 | You may obtain a copy of the License at 25 | 26 | http://www.apache.org/licenses/LICENSE-2.0 27 | 28 | Unless required by applicable law or agreed to in writing, software 29 | distributed under the License is distributed on an "AS IS" BASIS, 30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | See the License for the specific language governing permissions and 32 | limitations under the License.`), 33 | ], 34 | devServer: { 35 | historyApiFallback: true, 36 | hot: true, 37 | inline: true, 38 | progress: true, 39 | } 40 | }; 41 | --------------------------------------------------------------------------------