hvml-spec-v1.0-zh.md 402 KB
Newer Older
Vincent Wei's avatar
Vincent Wei committed
1
# HVML 规范
Vincent Wei's avatar
Vincent Wei committed
2

Vincent Wei's avatar
Vincent Wei committed
3
Subject: HVML Specification  
Vincent Wei's avatar
Vincent Wei committed
4
Version: 1.0-RC5  
Vincent Wei's avatar
Vincent Wei committed
5
Author: Vincent Wei  
Vincent Wei's avatar
Vincent Wei committed
6
Category: Language Specification  
Vincent Wei's avatar
Vincent Wei committed
7
Creation Date: July, 2020  
Vincent Wei's avatar
Vincent Wei committed
8
Last Modified Date: June 1, 2022  
Vincent Wei's avatar
tune    
Vincent Wei committed
9
Status: Release Candidate  
Vincent Wei's avatar
cleanup    
Vincent Wei committed
10
Release Name: 硕鼠  
Vincent Wei's avatar
Vincent Wei committed
11
12
13
14
Language: Chinese

*Copyright Notice*

Vincent Wei's avatar
Vincent Wei committed
15
版权所有 © 2020, 2021, 2022 魏永明  
Vincent Wei's avatar
Vincent Wei committed
16
版权所有 © 2021, 2022 北京飞漫软件技术有限公司  
Vincent Wei's avatar
Vincent Wei committed
17
18
19
20
保留所有权利

此文档不受 HVML 相关软件开源许可证的管辖。

Vincent Wei's avatar
Vincent Wei committed
21
版权所有人公开此文档的目标,用于向开发者解释 HVML 相关设计原理或者相关规范。在未获得版权所有人书面许可之前,任何人不得复制或者分发本文档的全部或部分内容,或利用本文档描绘的技术思路申请专利、撰写学术论文等。
Vincent Wei's avatar
Vincent Wei committed
22

Vincent Wei's avatar
Vincent Wei committed
23
本文提及的版权所有人相关注册商标或商标之详细列表,请查阅文档末尾。
Vincent Wei's avatar
Vincent Wei committed
24

Vincent Wei's avatar
tune    
Vincent Wei committed
25
26
27
28
**目录**

[//]:# (START OF TOC)

Vincent Wei's avatar
Vincent Wei committed
29
30
31
32
33
- [1) 介绍](#1-介绍)
   + [1.1) 背景技术](#11-背景技术)
   + [1.2) 问题](#12-问题)
   + [1.3) 目的](#13-目的)
   + [1.4) 应用框架](#14-应用框架)
Vincent Wei's avatar
Vincent Wei committed
34
- [2) HVML 详解](#2-hvml-详解)
Vincent Wei's avatar
Vincent Wei committed
35
   + [2.1) 基本原理](#21-基本原理)
Vincent Wei's avatar
TOC    
Vincent Wei committed
36
37
38
39
      * [2.1.1) 程序结构](#211-程序结构)
      * [2.1.2) 基本数据类型](#212-基本数据类型)
      * [2.1.3) 扩展数据类型](#213-扩展数据类型)
      * [2.1.4) 任意数据类型的强制转换规则](#214-任意数据类型的强制转换规则)
40
41
42
43
44
         - [2.1.4.1) 数值化](#2141-数值化)
         - [2.1.4.2) 布尔化](#2142-布尔化)
         - [2.1.4.3) 字符串化](#2143-字符串化)
         - [2.1.4.4) 序列化](#2144-序列化)
         - [2.1.4.5) 键值对象](#2145-键值对象)
Vincent Wei's avatar
TOC    
Vincent Wei committed
45
      * [2.1.5) 可变数据和不可变数据](#215-可变数据和不可变数据)
46
47
48
      * [2.1.6) 变量](#216-变量)
         - [2.1.6.1) `$REQUEST`](#2161-request)
         - [2.1.6.2) `$SYSTEM`](#2162-system)
49
50
51
52
53
54
         - [2.1.6.3) `$HVML`](#2163-hvml)
         - [2.1.6.4) `$DOC`](#2164-doc)
         - [2.1.6.5) `$TIMERS`](#2165-timers)
         - [2.1.6.6) `$L`](#2166-l)
         - [2.1.6.7) `$T`](#2167-t)
         - [2.1.6.8) `$EJSON`](#2168-ejson)
55
         - [2.1.6.9) `$STREAM`](#2169-stream)
56
57
58
         - [2.1.6.10) `$RDR`](#21610-rdr)
         - [2.1.6.11) 集合变量](#21611-集合变量)
         - [2.1.6.12) 表达式变量](#21612-表达式变量)
Vincent Wei's avatar
Vincent Wei committed
59
      * [2.1.7) 栈式虚拟机](#217-栈式虚拟机)
Vincent Wei's avatar
Vincent Wei committed
60
61
62
63
      * [2.1.8) 框架元素](#218-框架元素)
      * [2.1.9) 模板元素](#219-模板元素)
      * [2.1.10) 动作元素](#2110-动作元素)
         - [2.1.10.1) 用来操作数据的动作元素](#21101-用来操作数据的动作元素)
Vincent Wei's avatar
Vincent Wei committed
64
         - [2.1.10.2) 用于操控执行栈的动作元素](#21102-用于操控执行栈的动作元素)
Vincent Wei's avatar
Vincent Wei committed
65
         - [2.1.10.3) 其他动作元素](#21103-其他动作元素)
Vincent Wei's avatar
Vincent Wei committed
66
      * [2.1.11) 错误和异常的处理](#2111-错误和异常的处理)
67
68
69
70
      * [2.1.12) 介词属性](#2112-介词属性)
      * [2.1.13) 副词属性](#2113-副词属性)
      * [2.1.14) 引用元素或数据](#2114-引用元素或数据)
      * [2.1.15) JSON 求值表达式](#2115-json-求值表达式)
Vincent Wei's avatar
Vincent Wei committed
71
      * [2.1.16) 协程和虚拟机状态](#2116-协程和虚拟机状态)
Vincent Wei's avatar
Vincent Wei committed
72
73
      * [2.1.17) 文档片段的 JSON 数据表达](#2117-文档片段的-json-数据表达)
      * [2.1.18) MIME 类型](#2118-mime-类型)
74
75
      * [2.1.19) HVML URI 图式](#2119-hvml-uri-图式)
         - [2.1.19.1) `hvml` 图式](#21191-hvml-图式)
Vincent Wei's avatar
Vincent Wei committed
76
         - [2.1.19.2) `hvml+cor` 图式](#21192-hvmlcor-图式)
Vincent Wei's avatar
Vincent Wei committed
77
   + [2.2) 规则、表达式及方法的描述语法](#22-规则表达式及方法的描述语法)
78
79
80
      * [2.2.1) 规则描述语法](#221-规则描述语法)
      * [2.2.2) JSON 求值表达式的语法](#222-json-求值表达式的语法)
      * [2.2.3) 常见的被指名词法单元](#223-常见的被指名词法单元)
Vincent Wei's avatar
Vincent Wei committed
81
      * [2.2.4) 动态对象方法的描述语法](#224-动态对象方法的描述语法)
Vincent Wei's avatar
Vincent Wei committed
82
   + [2.3) 框架标签详解](#23-框架标签详解)
83
84
85
      * [2.3.1) `hvml` 标签](#231-hvml-标签)
      * [2.3.2) `head` 标签](#232-head-标签)
      * [2.3.3) `body` 标签](#233-body-标签)
Vincent Wei's avatar
Vincent Wei committed
86
   + [2.4) 模板标签详解](#24-模板标签详解)
87
88
89
90
      * [2.4.1) `archetype` 标签](#241-archetype-标签)
      * [2.4.2) `archedata` 标签](#242-archedata-标签)
      * [2.4.3) `error` 标签](#243-error-标签)
      * [2.4.4) `except` 标签](#244-except-标签)
Vincent Wei's avatar
Vincent Wei committed
91
   + [2.5) 动作标签详解](#25-动作标签详解)
92
93
94
95
96
97
      * [2.5.1) `init` 标签](#251-init-标签)
      * [2.5.2) `update` 标签](#252-update-标签)
         - [2.5.2.1) 指定目标位置](#2521-指定目标位置)
         - [2.5.2.2) 更新集合](#2522-更新集合)
      * [2.5.3) `erase` 标签](#253-erase-标签)
      * [2.5.4) `clear` 标签](#254-clear-标签)
98
99
100
      * [2.5.5) `test`、 `match` 和 `differ` 标签](#255-test-match-和-differ-标签)
         - [2.5.5.1) 多分支处理](#2551-多分支处理)
         - [2.5.5.2) 二选一处理](#2552-二选一处理)
101
102
      * [2.5.6) `choose` 标签](#256-choose-标签)
      * [2.5.7) `iterate` 标签](#257-iterate-标签)
Vincent Wei's avatar
Vincent Wei committed
103
104
         - [2.5.7.1) 使用迭代执行器](#2571-使用迭代执行器)
         - [2.5.7.2) 不使用迭代执行器](#2572-不使用迭代执行器)
105
106
      * [2.5.8) `reduce` 标签](#258-reduce-标签)
      * [2.5.9) `sort` 标签](#259-sort-标签)
107
108
109
110
111
112
113
114
      * [2.5.10) `define` 和 `include` 标签](#2510-define-和-include-标签)
      * [2.5.11) `observe`、 `forget` 和 `fire` 标签](#2511-observe-forget-和-fire-标签)
      * [2.5.12) `call` 和 `return` 标签](#2512-call-和-return-标签)
      * [2.5.13) `bind` 标签](#2513-bind-标签)
      * [2.5.14) `catch` 标签](#2514-catch-标签)
      * [2.5.15) `back` 标签](#2515-back-标签)
      * [2.5.16) `request` 标签](#2516-request-标签)
      * [2.5.17) `load` 和 `exit` 标签](#2517-load-和-exit-标签)
Vincent Wei's avatar
Vincent Wei committed
115
      * [2.5.18) `inherit` 标签](#2518-inherit-标签)
116
      * [2.5.19) `sleep` 标签](#2519-sleep-标签)
117
118
119
120
121
122
123
124
125
126
127
   + [2.6) 执行器](#26-执行器)
      * [2.6.1) 内建执行器](#261-内建执行器)
         - [2.6.1.1) `KEY` 执行器](#2611-key-执行器)
         - [2.6.1.2) `RANGE` 执行器](#2612-range-执行器)
         - [2.6.1.3) `FILTER` 执行器](#2613-filter-执行器)
         - [2.6.1.4) 用于字符串的内建执行器](#2614-用于字符串的内建执行器)
         - [2.6.1.5) 用于数值的内建执行器](#2615-用于数值的内建执行器)
         - [2.6.1.6) `SQL` 执行器](#2616-sql-执行器)
         - [2.6.1.7) `TRAVEL` 执行器](#2617-travel-执行器)
         - [2.6.1.8) 内建执行器的使用](#2618-内建执行器的使用)
      * [2.6.2) 外部执行器](#262-外部执行器)
Vincent Wei's avatar
cleanup    
Vincent Wei committed
128
129
         - [2.6.2.1) 外部函数执行器](#2621-外部函数执行器)
         - [2.6.2.2) 外部类执行器](#2622-外部类执行器)
130
      * [2.6.3) 执行器规则表达式的处理](#263-执行器规则表达式的处理)
Vincent Wei's avatar
Vincent Wei committed
131
   + [2.7) 响应式更新](#27-响应式更新)
Vincent Wei's avatar
Vincent Wei committed
132
- [3) HVML 语法](#3-hvml-语法)
Vincent Wei's avatar
tune    
Vincent Wei committed
133
134
135
   + [3.1) 书写 HVML 文档](#31-书写-hvml-文档)
      * [3.1.1) DOCTYPE](#311-doctype)
      * [3.1.2) 元素](#312-元素)
136
137
138
         - [3.1.2.1) 起始标签](#3121-起始标签)
         - [3.1.2.2) 终止标签](#3122-终止标签)
         - [3.1.2.3) 属性](#3123-属性)
Vincent Wei's avatar
Vincent Wei committed
139
140
141
         - [3.1.2.4) 动作元素属性](#3124-动作元素属性)
         - [3.1.2.5) 可选标签](#3125-可选标签)
         - [3.1.2.6) 裸文本元素和可转义裸文本元素的内容限制](#3126-裸文本元素和可转义裸文本元素的内容限制)
142
         - [3.1.2.7) JSONTEXT 和 JSONSTR](#3127-jsontext-和-jsonstr)
143
144
      * [3.1.3) 文本](#313-文本)
         - [3.1.3.1) 新行](#3131-新行)
Vincent Wei's avatar
tune    
Vincent Wei committed
145
         - [3.1.3.2) 扩展 JSON 语法](#3132-扩展-json-语法)
146
147
148
      * [3.1.4) 字符引用](#314-字符引用)
      * [3.1.5) CDATA 段落](#315-cdata-段落)
      * [3.1.6) 注释](#316-注释)
Vincent Wei's avatar
Vincent Wei committed
149
   + [3.2) 解析 HVML 文档](#32-解析-hvml-文档)
Vincent Wei's avatar
Vincent Wei committed
150
151
152
153
- [4) 应用示例](#4-应用示例)
   + [4.1) 使用 HVML 开发传统 GUI 应用](#41-使用-hvml-开发传统-gui-应用)
   + [4.2) 云应用](#42-云应用)
- [5) 总结](#5-总结)
Vincent Wei's avatar
Vincent Wei committed
154
- [附录](#附录)
Vincent Wei's avatar
Vincent Wei committed
155
   + [附.1) 修订记录](#附1-修订记录)
Vincent Wei's avatar
Vincent Wei committed
156
157
158
159
160
161
162
      * [RC5) 220701](#rc5-220701)
         - [RC5.1) 调整对 `include` 标签的描述](#rc51-调整对-include-标签的描述)
         - [RC5.2) 调整 `request` 标签](#rc52-调整-request-标签)
         - [RC5.3) 调整 `load` 和 `call` 标签](#rc53-调整-load-和-call-标签)
         - [RC5.4) HVML URI 图式及协程描述符](#rc54-hvml-uri-图式及协程描述符)
         - [RC5.5) 增强 `sort` 标签](#rc55-增强-sort-标签)
         - [RC5.6) 调整 `observe` 标签](#rc56-调整-observe-标签)
Vincent Wei's avatar
Vincent Wei committed
163
164
165
166
      * [RC4) 220601](#rc4-220601)
         - [RC4.1) 重构`基本原理`一节](#rc41-重构基本原理一节)
         - [RC4.2) MIME 类型和数据](#rc42-mime-类型和数据)
         - [RC4.3) `inherit` 标签](#rc43-inherit-标签)
167
168
169
         - [RC4.4) `sleep` 标签](#rc44-sleep-标签)
         - [RC4.5) 调整上下文变量](#rc45-调整上下文变量)
         - [RC4.6) 元素及属性的调整](#rc46-元素及属性的调整)
170
         - [RC4.7) `differ` 标签](#rc47-differ-标签)
Vincent Wei's avatar
Vincent Wei committed
171
      * [RC3) 220501](#rc3-220501)
Vincent Wei's avatar
Vincent Wei committed
172
         - [RC3.1) 调整动作标签](#rc31-调整动作标签)
Vincent Wei's avatar
Vincent Wei committed
173
         - [RC3.2) HVML 程序的运行状态](#rc32-hvml-程序的运行状态)
Vincent Wei's avatar
Vincent Wei committed
174
         - [RC3.3) 可使用元素的锚点名称定位前置栈帧](#rc33-可使用元素的锚点名称定位前置栈帧)
Vincent Wei's avatar
RC3.5    
Vincent Wei committed
175
         - [RC3.5) eJSON 语法增强](#rc35-ejson-语法增强)
176
         - [RC3.6) `$STREAM` 预定义变量](#rc36-stream-预定义变量)
Vincent Wei's avatar
tune    
Vincent Wei committed
177
         - [RC3.7) 调整动态对象方法的描述语法](#rc37-调整动态对象方法的描述语法)
178
         - [RC3.8) 事件名称的命名规则](#rc38-事件名称的命名规则)
Vincent Wei's avatar
Vincent Wei committed
179
         - [RC3.9) 简化外部执行器](#rc39-简化外部执行器)
Vincent Wei's avatar
Vincent Wei committed
180
         - [RC3.10) 协程及其状态](#rc310-协程及其状态)
Vincent Wei's avatar
tune    
Vincent Wei committed
181
      * [RC2) 220401](#rc2-220401)
Vincent Wei's avatar
Vincent Wei committed
182
183
         - [RC2.1) 用户自定义临时变量的初始化和重置方法](#rc21-用户自定义临时变量的初始化和重置方法)
         - [RC2.2) 调整动态对象方法的描述语法](#rc22-调整动态对象方法的描述语法)
Vincent Wei's avatar
Vincent Wei committed
184
185
         - [RC2.3) 上下文变量的增强和调整](#rc23-上下文变量的增强和调整)
         - [RC2.4) `iterate` 元素的增强](#rc24-iterate-元素的增强)
Vincent Wei's avatar
Vincent Wei committed
186
         - [RC2.5) 调整第一章的内容](#rc25-调整第一章的内容)
187
188
         - [RC2.6) 异常相关增强](#rc26-异常相关增强)
         - [RC2.7) 可命名一个 `observe`](#rc27-可命名一个-observe)
Vincent Wei's avatar
Vincent Wei committed
189
190
         - [RC2.8) 增强 `request`](#rc28-增强-request)
         - [RC2.9) 调整介词属性](#rc29-调整介词属性)
Vincent Wei's avatar
Vincent Wei committed
191
192
193
194
         - [RC2.10) 调整响应式处理的语法](#rc210-调整响应式处理的语法)
         - [RC2.11) 增强 `bind` 标签](#rc211-增强-bind-标签)
         - [RC2.12) CJSONEE](#rc212-cjsonee)
         - [RC2.13) 调整布尔化规则](#rc213-调整布尔化规则)
Vincent Wei's avatar
Vincent Wei committed
195
      * [RC1) 220209](#rc1-220209)
Vincent Wei's avatar
Vincent Wei committed
196
197
198
199
200
201
         - [RC1.1) 上下文变量的调整](#rc11-上下文变量的调整)
         - [RC1.2) `init` 标签的增强](#rc12-init-标签的增强)
         - [RC1.3) 针对数值执行器的附加说明](#rc13-针对数值执行器的附加说明)
         - [RC1.4) `observe` 标签的增强](#rc14-observe-标签的增强)
         - [RC1.5) 骨架元素的增强](#rc15-骨架元素的增强)
         - [RC1.6) 属性值操作符的增强](#rc16-属性值操作符的增强)
Vincent Wei's avatar
Vincent Wei committed
202
      * [BRC) 其他](#brc-其他)
Vincent Wei's avatar
Vincent Wei committed
203
   + [附.2) 待定内容](#附2-待定内容)
204
205
      * [TBD 1) 扩展数据类型](#tbd-1-扩展数据类型)
         - [TBD 1.1) 扩展数据类型](#tbd-11-扩展数据类型)
206
207
208
      * [TBD2) 动作元素](#tbd2-动作元素)
         - [TBD2.1) `pipe` 标签](#tbd21-pipe-标签)
         - [TBD2.2) `connect`、 `send` 和 `disconnect` 标签](#tbd22-connect-send-和-disconnect-标签)
Vincent Wei's avatar
cleanup    
Vincent Wei committed
209
210
         - [TBD2.3) 外部函数更新器](#tbd23-外部函数更新器)
         - [TBD2.4) 杂项](#tbd24-杂项)
Vincent Wei's avatar
Vincent Wei committed
211
212
   + [附.3) 贡献者榜单](#附3-贡献者榜单)
   + [附.4) 商标声明](#附4-商标声明)
Vincent Wei's avatar
Vincent Wei committed
213

Vincent Wei's avatar
tune    
Vincent Wei committed
214
[//]:# (END OF TOC)
215

Vincent Wei's avatar
Vincent Wei committed
216
## 1) 介绍
Vincent Wei's avatar
Vincent Wei committed
217

Vincent Wei's avatar
Vincent Wei committed
218
219
220
### 1.1) 背景技术

本文涉及的背景技术及其最新规范如下:
Vincent Wei's avatar
Vincent Wei committed
221
222

- HTML 及其规范。HTML 和 CSS 等规范和标准是由 W3C <https://www.w3.org> 组织制定的,用来规范 Web 页面内容的编写和渲染行为。关键规范如下:
Vincent Wei's avatar
tune    
Vincent Wei committed
223
224
   * HTML:超文本标记语言(HyperText Markup Language),用于表述网页内容结构的标准。HTML 最新规范:<https://html.spec.whatwg.org/>
   * CSS:级联样式表(Cascading Style Sheets),用于定义 HTML 页面元素布局、渲染效果等的规范。在 CSS 2.2 <https://www.w3.org/TR/CSS22/> 之后,CSS 规范开始按照模块划分,各模块分头演进,目前普遍支持到 Level 3。在如下网页中可以看到 CSS 各模块的规范进展情况:<https://drafts.csswg.org>
Vincent Wei's avatar
Vincent Wei committed
225
226
227
228
229
   * JavaScript/ECMAScript:一种符合 ECMAScript 规范的脚本编程语言,最初由网景公司设计给浏览器使用,用于操控 HTML 页面中的内容和渲染行为,现在由欧洲计算机制造商协会和国际标准化组织负责制定相关标准,最新的标准为 ECMA-262:<http://www.ecma-international.org/publications/standards/Ecma-262.htm>
   * DOM:文档对象模型(Document Object Model),用于 XML/HTML 文档结构的内部表达。一个 XML/HTML 文档,会被 XML/HTML 解析器解析并生成一个 DOM 树,XML/HTML 文档中的每个元素构成 DOM 树上的元素结点,而每个元素的子元素、属性、文本内容等,又构成了这个元素节点的子节点。有关 DOM 的最新的规范可见:<https://dom.spec.whatwg.org/>
   * JSON:JavaScript 对象表述法(JavaScript Object Notation)是一种轻量级的信息互换格式。最初被用于 JavaScript 对象的字符串表达,易于被 JavaScript 脚本代码使用,现在被广泛使用在不同编程语言之间的数据交换。有关 JSON 的描述,可见:<https://json.org/>
- 用户代理(User Agent)是 HTML 规范的一个术语,用来指代可以解析 HTML、CSS 等 W3C 规范,并对 HTML 文档内容进行渲染,进而呈现给用户并实现用户交互的计算机程序。我们熟知的浏览器就是用户代理。但用户代理不限于浏览器,可以是一个软件组件,也可以是一个应用框架。比如,内嵌到电子邮件客户端程序中,用以解析和渲染 HTML 格式邮件的软件组件,本质上也是 HTML 用户代理。
- XML:可扩展标记语言(The Extensible Markup Language)是由 W3C 组织制定的,用来表述结构化信息的一种简单文本格式。和 HTML 相比,XML 使用类似的结构,但更加严格且更为通用。XML 是当今共享结构化信息的最广泛使用的格式之一,不论是在程序之间,人与人之间,计算机与人之间,也不论是在本地还是跨网络共享信息。有关 XML 的介绍和规范可参阅:<https://www.w3.org/standards/xml/>
Vincent Wei's avatar
Vincent Wei committed
230
231
- 脚本语言。指类似 JavaScript 的高级计算机编程语言,通常解释执行,具有动态特征。除 JavaScript 之外,常见的脚本语言有 Python、Lua、PHP 等。
- 低级编程语言。类似 C、C++、Java、C# 的编程语言,通常编译执行,直接运行在计算机硬件之上或者虚拟机之上。
Vincent Wei's avatar
Vincent Wei committed
232
233
- SQL:结构化查询语言(Structured Query Language),用于关系型数据库的数据操作语言,目前几乎所有的关系数据库均支持 SQL。和一般的编程语言不同,SQL 具有非过程性特征,基本的 SQL 代码中不包括 if-else 这种流程控制语句。

Vincent Wei's avatar
Vincent Wei committed
234
235
### 1.2) 问题

Vincent Wei's avatar
tune    
Vincent Wei committed
236
随着互联网技术和应用的发展,围绕 HTML/CSS/JavaScript 发展的 Web 前端开发技术发展迅猛,甚至可以用“一日千里”来形容。五年前,基于 jQuery 和 Bootstrap 的前端框架大行其道,而从 2019 年开始,基于虚拟 DOM 技术的框架又受到前端开发者的青睐,比如著名的 [React.js]、[Vue.js] 等。值得注意的是,微信小程序、快应用等,也不约而同使用了这种虚拟 DOM 技术来构建应用框架。
Vincent Wei's avatar
Vincent Wei committed
237

Vincent Wei's avatar
Vincent Wei committed
238
所谓“虚拟 DOM”是指前端应用程序通过 JavaScript 来创建和维护一个虚拟的文档对象树,程序脚本并不直接操作真实的 DOM 树。在虚拟 DOM 树中,通过一些特别的属性实现了基于数据的一些流程控制,如条件、循环等。
Vincent Wei's avatar
Vincent Wei committed
239

Vincent Wei's avatar
Vincent Wei committed
240
241
242
243
另一方面,大量图形用户界面(GUI)应用,仍然在使用 C、C++、Java、C# 等编程语言开发。这些传统的 GUI 应用,其程序框架无外乎直接调用 C/C++ 或其他编程语言提供的接口,在一个事件循环中完成创建 GUI 元素,响应用户交互的工作。为了方便 GUI 应用的开发,业界存在诸多大同小异的 GUI Toolkit 库,比如早期运行在 Unix 图形工作站上的 Motif,Windows 上的 Win32、MFC,Linux 桌面发展出的 Gtk+,跨平台的 Qt,针对嵌入式的 MiniGUI 等等。

这些 GUI Toolkit 库,为提高图形用户界面应用的开发效率提供了一定的帮助,但限于编程语言的表达能力,开发者经常会陷入到操控 GUI 元素及其属性的大量细节当中。就算有可视化的界面设计器帮助开发者,其开发效率也很难和上面提到的 Web 前端技术相比。

Vincent Wei's avatar
tune    
Vincent Wei committed
244
那么,我们能否将 Web 前端技术带到浏览器之外?比如可以让 C、C++、Java 程序,甚至 Python 这类脚本语言也能轻松地使用 Web 前端技术来开发 GUI 应用?
Vincent Wei's avatar
Vincent Wei committed
245

Vincent Wei's avatar
cleanup    
Vincent Wei committed
246
为了将 Web 前端技术引入到通用的 GUI 应用的开发中,开源社区也做了一些探索性工作,比如 Electron 开源项目,将 Chromium + Node.js 打包在一起,让 Web 后端跑在本机上,从而方便本地 GUI 应用的开发。但 Electron 的软件栈过于复杂,限制了其应用领域。
Vincent Wei's avatar
Vincent Wei committed
247
248

另外,以 React.js、Vue.js 为代表的前端框架取得了巨大成功,但存在如下缺陷和不足:
Vincent Wei's avatar
Vincent Wei committed
249

Vincent Wei's avatar
cleanup    
Vincent Wei committed
250
251
252
253
254
255
1. 这些技术建立在已有成熟的 Web 标准之上,需要完整支持相关前端规范的浏览器才能运行(或者说,只能运行在浏览器之内),很难和已有基于 C/C++ 等编程语言开发的功能模块整合。
1. 由于先天局限性,在网页中使用 JavaScript 语言,一直存在开发者诟病的如下问题:
   - 安全性差。一方面业务逻辑有关的代码在最终用户的浏览器上执行,任何人都可以看到 JavaScript 程序的源代码,从而可能泄露敏感信息;另一方面,恶意代码可能在最终用户的浏览器上执行,从而导致用户敏感数据的泄露。
   - 对性能的负面影响。在浏览器中运行大量业务逻辑相关的 JavaScript 代码,会引发页面渲染和业务逻辑竞争处理器资源的问题,这是浏览器页面渲染能力和传统 GUI 开发的界面存在显著差异的一个原因。
   - 较低的代码可维护性。开发者经常会在网页的不同地方随意嵌入零散的 JavaScript 代码,这降低了整个应用系统代码的可维护性。
1. 这些技术通过引入 `v-if``v-else``v-for` 等虚拟属性实现了基于数据的条件和循环流程控制,割裂了代码的逻辑,破坏了代码的可读性。如下面的一个示例:
Vincent Wei's avatar
Vincent Wei committed
256
257
258
259
260
261
262
263
264
265

```html
<div v-if="Math.random() > 0.5">
  Now you see "{{ name }}"
</div>
<div v-else>
  Now you don't
</div>
```

Vincent Wei's avatar
Vincent Wei committed
266
267
### 1.3) 目的

Vincent Wei's avatar
tune    
Vincent Wei committed
268
269
[HybridOS](合璧操作系统)的开发过程中,[魏永明](https://github.com/VincentWei)提出了一套完备、通用、易学的新式编程语言,称为 HVML。HVML 是 Hybrid Virtual Markup Language 的缩写,中文名称为“混合虚拟标记语言”,简称“呼噜猫”。

Vincent Wei's avatar
Vincent Wei committed
270
我们将 HVML 定义为一种可编程标记语言(Programmable Markup Language)。和 HTML 类似,HVML 使用标记语言来定义程序的结构和数据,但和 HTML 不同的是,HVML 是可编程的、动态的。HVML 通过有限的几个动作标签以及可用于定义属性和内容的动态 JSON 表达式,实现了 XML/HTML 文档的动态生成和更新功能;HVML 还提供了和已有编程语言,如 C/C++、Python、Lua 等进行结合的方法,从而为这些编程语言在浏览器之外利用 Web 前端技术提供了强有力的技术支撑。从这个角度讲,HVML 也可以视作是一种胶水语言。
Vincent Wei's avatar
Vincent Wei committed
271

Vincent Wei's avatar
Vincent Wei committed
272
273
本质上,HVML 提供了一种新的思路来解决前面的那个问题:第一,将 Web 前端技术(主要是 DOM、CSS 等)引入到其他编程语言中,而不是用 JavaScript 替代其他编程语言。第二,采用类似 HTML 的标记语言来操控 Web 页面中的元素、属性和样式,而非 JavaScript。另外,在设计 HVML 的过程中,我们有意地使用了数据驱动的概念,使得 HVML 可以非常方便地和其他编程语言以及各种网络连接协议,如数据总线、消息协议等结合在一起。这样,开发者熟悉哪种编程语言,就使用这种编程语言来开发应用的非 GUI 部分,而所有操控 GUI 的功能,交给 HVML 来完成,它们之间,通过模块间流转的数据来驱动,而 HVML 提供了对数据流转过程的抽象处理能力。

Vincent Wei's avatar
tune    
Vincent Wei committed
274
尽管设计 HVML 的最初目标是为了提高 GUI 应用的开发效率,但其实可用于更加通用的场景——只要程序的输出可被抽象为使用一个或者多个树状结构来表达,就可以使用 HVML;甚至我们可以像普通脚本语言那样来使用 HVML。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
275
276
277

本质上,HVML 是一种抽象层级高于 JavaScript 或者 Python 这类脚本语言的编程语言,其主要特点有:

Vincent Wei's avatar
tune    
Vincent Wei committed
278
1. 极简设计。HVML 仅仅使用二十多个特有的标签,定义了用于操作一个抽象的栈式虚拟机的完备指令。通过符合英语表达习惯的动词标签以及介词属性、副词属性,让每一行代码都具有清晰的语义。这将帮助开发者编写具有极佳可读性的程序代码。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
279
1. 数据驱动。一方面,HVML 提供了通过操控数据来实现功能的方法。比如,我们使用更新动作操控定时器数组中的某个字段就可以开启或者关闭一个定时器,而无需调用对应的接口。另一方面,HVML 语言致力于通过统一的数据表达方式将系统中的不同模块串接起来,而不是通过复杂的接口调用来实现模块间的互操作。这两个手段可有效避免传统编程语言存在的接口爆炸问题。为了实现以上目标,HVML 在 JSON 这一广泛使用的抽象数据表达方式之上,提供了扩展的数据类型以及灵活的表达式处理能力。
Vincent Wei's avatar
Vincent Wei committed
280
1. 固有事件驱动机制。和其他编程语言不同,HVML 语言提供了观察数据、事件甚至观察一个表达式结果发生变化的语言级机制。在此基础上,开发者无需关心底层的实现细节便可以轻松实现其他编程语言中难以管理的并发或异步编程。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
281
1. 全新的应用框架。通过 HVML 独有的应用框架,我们将性能关键的数据处理交由外部程序或服务器实现,和用户的交互由独立的渲染器负责,HVML 程序则负责粘合这些不同的系统组件。一方面,HVML 解决了使用不同编程语言开发的系统组件之间难以高效互操作的问题,从而可以充分发挥各个组件的优势,保护已有软件资产的价值;另一方面,采用 HVML 提供的应用框架开发应用,可最大程度地解除不同组件之间的耦合问题。
Vincent Wei's avatar
Vincent Wei committed
282

Vincent Wei's avatar
cleanup    
Vincent Wei committed
283
总之,HVML 提供了不同于传统编程语言的编程模式,在数据驱动的思想基础之上,HVML 提供了更加系统和完备的低代码(low code,指使用更少的代码来编写程序)编程方法。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
284
285

在未来,HVML 将成为 HybridOS 的应用开发首选编程语言。
Vincent Wei's avatar
Vincent Wei committed
286

Vincent Wei's avatar
Vincent Wei committed
287
### 1.4) 应用框架
Vincent Wei's avatar
Vincent Wei committed
288

Vincent Wei's avatar
Vincent Wei committed
289
围绕 HVML 形成的应用框架,和传统的 GUI 应用框架以及 Web 前端技术都有显著的不同。传统的 GUI 应用,代码设计模式无外乎直接调用 C/C++ 或其他编程语言提供的接口,在一个事件循环中完成创建界面元素、响应用户交互的工作。Web 前端技术和传统 GUI 应用的最大区别在于描述式的 HTML 和 CSS 语言,但程序框架在本质上是一样的——事件循环,而且必须使用 JavaScript 语言。
Vincent Wei's avatar
Vincent Wei committed
290

Vincent Wei's avatar
Vincent Wei committed
291
但 HVML 提供了一个完全不一样的应用框架。
Vincent Wei's avatar
Vincent Wei committed
292

Vincent Wei's avatar
tune    
Vincent Wei committed
293
在完整的基于 HVML 的应用框架中,通常包含一个独立运行的用户界面渲染器(UI renderer)。开发者通过编写 HVML 程序来操控描述用户界面的页面内容,而页面内容最终由渲染器处理并展示到屏幕上。HVML 程序在 HVML 解释器中运行,可以和其他已有的编程语言的运行时环境结合在一起,接收由其他编程语言程序生成的数据,并按照 HVML 程序的指令,将其转换为户界面的描述信息或者变更信息。通过这样的设计,我们将所有涉及到图形用户界面的应用程序分开成两个松散的模块:
Vincent Wei's avatar
Vincent Wei committed
294

Vincent Wei's avatar
Vincent Wei committed
295
第一,一个和用户界面无关的数据处理模块,开发者可以使用任何其熟悉的编程语言和开发工具开发这个模块。比如,涉及到人工智能处理时,开发者选择 Python;在 Python 代码中,除了装载 HVML 程序之外,开发者无需考虑任何和界面渲染及交互相关的东西,比如创建一个按钮或者点击一个菜单后完成某项工作等的这类工作,开发者只需要在 Python 代码中准备好渲染用户界面所需要的数据即可,而这些数据通常使用 HTML/XML/CSS 等描述式语言来表示。我们将这些数据处理模块统称为“外部程序(external program)”。
Vincent Wei's avatar
Vincent Wei committed
296
297
298

第二,一个或者多个使用 HVML 语言编写的程序(HVML 程序),用来完成对用户界面的操控。HVML 程序根据数据处理模块提供的数据生成用户界面的描述信息,并根据用户的交互或者从数据处理模块中获得的计算结果来更新用户界面,或者根据用户的交互驱动数据处理模块完成某些工作。

Vincent Wei's avatar
tune    
Vincent Wei committed
299
如此,HVML 应用框架将操控界面元素的代码从原先调用 C、C++、Java、C# 等接口的设计模式中解放了出来,转而使用 HVML 代码来处理。而 HVML 使用类似 HTML 的标签语言来操控界面元素,通过隐藏大量细节,降低了直接使用低级编程语言操控界面元素导致的程序复杂度。
Vincent Wei's avatar
Vincent Wei committed
300

Vincent Wei's avatar
tune    
Vincent Wei committed
301
通常,我们将界面渲染器设计为类似字符控制台的哑设备,这样,我们就可以将 HVML 程序和应用的其他模块从控制界面元素展现行为的细节中解放出来。举个例子。我们在字符终端程序的开发中,可以使用一些转义控制指令来设置字符的颜色、是否闪烁等,而字符终端程序无需包含任何处理字符颜色以及闪烁的代码——因为这些细节字符控制台(可能是硬件,也可能是一个伪终端程序)帮我们默默处理了。HVML 的界面渲染器也遵循同样的设计思路,HVML 程序创建好一个按钮,至于这个按钮显示出来是什么样子的,用户如何跟它交互,这些统统无需 HVML 程序来操心——一切由渲染器在给定的描述式语言(如 HTML、CSS)的控制下运转。这带来另一个好处,由于在界面渲染器中不包含任何的应用运行逻辑代码和敏感的数据,从某种程度上讲,这带来了安全性的提高。
Vincent Wei's avatar
Vincent Wei committed
302

303
304
在 HVML 应用框架中,一个应用可以同时启动多个并行的任务,我们将这些并行的任务称为“行者(runner)”(注:不同的行者可能使用不同的编程语言开发)。使用 HVML 语言开发的行者在一个独立的 HVML 解释器实例中运行。一个解释器实例可以是一个独立运行的系统进程,也可以是解释器进程中的独立线程;这取决于解释器的具体实现方式。但不论解释器如何实现,HVML 要求单个行者可以同时装载多个 HVML 程序运行,而且解释器应该始终以独立的协程(coroutine)形式执行同一行者装载的这些 HVML 程序实例。因此,我们在本文档中使用“协程(coroutine)”一词来指代一个运行中的 HVML 程序实例。

Vincent Wei's avatar
Vincent Wei committed
305
306
这种设计带来了如下好处:

Vincent Wei's avatar
tune    
Vincent Wei committed
307
1. 相比线程或进程提供的并发机制,协程提供了一种低成本实现并发的机制。同一行者创建的多个协程属于同一个 HVML 会话,在独立的解释器实例中运行;这些协程在解释器的协调下在单个线程或者进程中轮换执行,因此协程间的数据交换不需要考虑竞态(race-condition)问题,几乎都是无锁(lock-free)操作,这减低了处理并发的成本,从而提升了整体性能。
Vincent Wei's avatar
Vincent Wei committed
308
309
310
1. 通过将负责不同业务逻辑的代码分离到不同的协程当中实现,我们可以提高整个项目的模块化程度,从而提升项目的可测试性以及可维护性,进而提升整个项目的软件质量。

有了这样的应用框架设计,HVML 可以让几乎所有的编程语言,不论是 C/C++ 这类传统编程语言,还是 Python 这类脚本语言,都可以使用统一的模式来开发 GUI 应用。
Vincent Wei's avatar
Vincent Wei committed
311
312
313

## 2) HVML 详解

Vincent Wei's avatar
Vincent Wei committed
314
为方便描述,本文档使用如下术语:
Vincent Wei's avatar
Vincent Wei committed
315

Vincent Wei's avatar
Vincent Wei committed
316
317
318
1. `数据(data)`。指可使用 JSON 这类记法描述的数据,可用来描述如文本、数值这类基本的数据,也可以用来描述数组、键值对、树形等抽象的数据。
1. `数据项(data item 或 datum)``数据成员(data member)`。对数组而言,每个数组单元就是一个数据项;对对象数据而言,其中的某个键值对就是一个数据项。为防止混淆,我们避免使用 `元素(element)` 一词来指代数据项或者数据成员。
1. `标签(tag)`。在 HTML/XML/HVML 文档中,用来定义一个元素节点的元素类型名称。
Vincent Wei's avatar
tune    
Vincent Wei committed
319
320
321
322
323
1. `目标文档(destination document)`。指 HVML 程序生成的 XML/HTML 文档。
1. `元素(element)`。指文档对象模型中,使用某个标签(tag)定义的元素节点;一个文档元素可包含一个或多个属性(attribute)以及属性值,还可以包含内容(content);一个元素可包含文本内容、数据内容或者使用标签定义的单个或多个子元素。
1. `文档片段(document fragment)`。指 XML/HTML 文档中的一个片段,可作为模板被克隆(clone)到目标文档的指定位置。
1. `文本内容(text content)`。指使用文本定义的元素内容。
1. `数据内容(data content)`。指使用抽象数据表达定义的元素内容。
Vincent Wei's avatar
tune    
Vincent Wei committed
324
1. `元素汇集(element collection)`。指使用选择器选择的零个或者多个元素。这里避免使用“集合”这个术语,是为了防止和`集合(set)`数据类型混淆。
Vincent Wei's avatar
tune    
Vincent Wei committed
325
1. `程序(program)`。一段或者一组可执行的代码集合。若无特别说明,本文档中特指 HVML 程序。
Vincent Wei's avatar
Vincent Wei committed
326
327
1. `解释器(interpreter)`。若无特别说明,本文档中特指用来解析并执行 HVML 程序的程序。
1. `协程(coroutine)`。若无特别说明,本文档中特指一个运行中的 HVML 程序实例。
Vincent Wei's avatar
tune    
Vincent Wei committed
328
329
330
331
332
1. `码点(code point)`。指一个表述为 `U+` 和四到六个 ASCII 大写十六进制数字形式的 Unicode 码点,范围在 U+0000 到 U+10FFFF(含)。有时候,我们会在码点之后包含码点的名称以及包含在小括号中的该码点的渲染形式,且高亮或加粗显示该码点的渲染形式。对无法渲染的码点,本文档会给出其码点名称。有关 Unicode 字符的更多术语解释如下:
   - 码点的名称由 Unicode 标准定义并以 ASCII 大写形式表述,如 `CR` 指 Carriage Return(回车)。
   - `替代符(surrogate)`是范围在 U+D800 到 U+DFFF(含)的码点。
   - 不是替代符的码点被称为`标量值(scalar value)`
   - U+FDD0 到 U+FDEF(含)范围内的码点,以及如下码点被称为`非字符(noncharacter)`
Vincent Wei's avatar
Vincent Wei committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
      + U+FFFE、 U+FFFF
      + U+1FFFE、 U+1FFFF
      + U+2FFFE、 U+2FFFF
      + U+3FFFE、 U+3FFFF
      + U+4FFFE、 U+4FFFF
      + U+5FFFE、 U+5FFFF
      + U+6FFFE、 U+6FFFF
      + U+7FFFE、 U+7FFFF
      + U+8FFFE、 U+8FFFF
      + U+9FFFE、 U+9FFFF
      + U+AFFFE、 U+AFFFF
      + U+BFFFE、 U+BFFFF
      + U+CFFFE、 U+CFFFF
      + U+DFFFE、 U+DFFFF
      + U+EFFFE、 U+EFFFF
      + U+FFFFE、 U+FFFFF
      + U+10FFFE、 U+10FFFF.
Vincent Wei's avatar
tune    
Vincent Wei committed
350
351
352
353
354
355
   - `ASCII 码点`是指范围在 U+0000 NULL 到 U+007F DELETE(含)的码点。
   - `ASCII 制表符或者新行符`指 U+0009 TAB、 U+000A LF 或 U+000D CR。
   - `ASCII 空白字符`是指 U+0009 TAB、 U+000A LF、 U+000C FF、 U+000D CR、或 U+0020 SPACE。也常常简称为`空白字符`
   - `C0 控制字符`是范围在 U+0000 NULL 到 U+001F INFORMATION SEPARATOR ONE(含)的码点。
   - `C0 控制字符或空格`指 C0 控制字符或 U+0020 SPACE。
   - `控制字符`是指一个`C0 控制字符`或者范围在 U+007F DELETE 到 U+009F APPLICATION PROGRAM COMMAND(含)的码点。
Vincent Wei's avatar
tune    
Vincent Wei committed
356
   - `ASCII 数字`是范围在 U+0030(`0`)到 U+0039(`9`)(含)的字符。
Vincent Wei's avatar
tune    
Vincent Wei committed
357
358
359
360
361
362
363
   - `ASCII 大写十六进制数字`要么是一个 ASCII 数字,要么是一个范围在 U+0041(`A`)到 U+0046(`F`)(含)的码点。
   - `ASCII 小写十六进制数字`要么是一个 ASCII 数字,要么是一个范围在 U+0061(`a`)到 U+0066(`f`)(含)的码点。
   - `ASCII 十六进制数字`要么是一个 ASCII 大写十六进制数字,要么是一个 ASCII 小写十六进制数字。
   - `ASCII 大写字母`是一个范围在 U+0041(`A`)到 U+005A(`Z`)(含)的码点。
   - `ASCII 小写字母`是一个范围在 U+0061(`a`)到 U+007A(`z`)(含)的码点。
   - `ASCII 字母`要么是一个 ASCII 大写字母,要么是一个 ASCII 小写字母。
   - `ASCII 字母数字`要么是一个 ASCII 数字,要么是一个 ASCII 字母。
Vincent Wei's avatar
Vincent Wei committed
364

Vincent Wei's avatar
cleanup    
Vincent Wei committed
365
比如,表情符号 🤔 的码点是 U+1F914,可表述为 U+1F914(🤔),也可以表述为 U+1F914 THINKING FACE(🤔)。
Vincent Wei's avatar
Vincent Wei committed
366

Vincent Wei's avatar
Vincent Wei committed
367
368
### 2.1) 基本原理

Vincent Wei's avatar
Vincent Wei committed
369
370
在详细了解 HVML 的设计细节之前,我们通过一些代码片段描述 HVML 的关键特征,以期读者可以快速一览其概貌:

Vincent Wei's avatar
Vincent Wei committed
371
第一,在广泛使用的 JSON 表述方法之上,HVML 使其具有了动态处理能力以及参数化表述数据的能力。JSON 是一种人机共读的数据表述形式,可在数值、字符串、数组、字典等基本数据单元的基础上表述复杂对象。HVML 扩展了 JSON 表述方法,使之支持更多数据类型,并通过使用动态值和原生实体这两类动态数据,定义了从底层系统获得数据或者实现某种功能的方法,并在此基础上实现了灵活的表达式求值能力。这可以帮助我们利用已有的系统能力,也可以帮助开发者快速扩展 HVML 程序的功能和能力。
Vincent Wei's avatar
Vincent Wei committed
372
373
374
375

比如下面的 HVML 代码片段,通过表达式 `$STR.substr($SYSTEM.locale, 0, 2)` 取系统区域字符串(如 `zh_CN`)的前两个字符作为结果,设置了 `lang` 这个属性的属性值(`zh`):

```html
376
377
<hvml target="html"
        lang="$STR.substr($SYSTEM.locale, 0, 2)">
Vincent Wei's avatar
Vincent Wei committed
378
379
380
381
    ...
</hvml>
```

Vincent Wei's avatar
Vincent Wei committed
382
第二,HVML 使用类似 XML 的标记语言来定义程序结构。HVML 定义了为数不多的十多个动作标签,可用来定义变量,操作数据或者控制程序的执行路径。和这些动作标签配合,HVML 使用介词属性和副词属性来定义动作依赖的源数据、目标数据以及执行条件,从而获得更加贴近自然语言的程序表达和书写效果。这一方面降低了开发者的学习门槛,另一方面可以大幅提高了代码的可读性。
Vincent Wei's avatar
Vincent Wei committed
383
384
385
386
387

比如下面的 HVML 代码片段生成小于 100 的偶数数列,其中使用了 `init``iterate``update` 这三个动作标签,分别实现了初始化一个数组变量、迭代计算偶数并将每个迭代结果追加到数组中的功能:

```html
    <init as="evenNumbers" with=[0,] >
388
389
        <iterate on=$?[0] onlyif=$L.lt($0<,100)
                with=$MATH.add($0<,2) nosetotail>
Vincent Wei's avatar
Vincent Wei committed
390
            <update on="$evenNumbers" to="append" with="$?" />
Vincent Wei's avatar
Vincent Wei committed
391
392
393
394
395
396
397
398
399
        </iterate>
    </init>
```

这种语法还允许该我们混合使用外部标签来定义程序结构,这通过赋予未知的非 HVML 标签一个统一的动作而实现。比如下面的代码,通过 `iterate` 元素生成了由 HTML 的 `ul``li` 标签定义的偶数列表:

```html
    <ul>
        <init as="evenNumbers" with=[0,] temp >
400
401
            <iterate on=$?[0] onlyif=$L.lt($0<,100)
                    with=$MATH.add($0<,2) nosetotail>
Vincent Wei's avatar
Vincent Wei committed
402
403
404
405
406
407
                <li>$?</li>
            </iterate>
        </init>
    </ul>
```

Vincent Wei's avatar
Vincent Wei committed
408
第三,数据驱动编程。在 HVML 中,一切编程行为围绕要处理的数据进行,比如选择数据,在数据上迭代,执行规约操作,观察数据的变化等等,甚至我们通过更改数据来操控某个功能的实现,比如增加一个定时器,我们不需要调用某个编程接口,而是在一个集合中新增一个数据项。我们将这种编程方式称为数据驱动的编程(data-driven programming)。
Vincent Wei's avatar
Vincent Wei committed
409
410
411
412

比如下面的 HVML 代码片段,使用 `update` 动作标签,添加了一个激活的定时器,随后使用 `choose``update` 使之无效:

```html
Vincent Wei's avatar
Vincent Wei committed
413
    <!-- 新增标识符为 `foo` 的定时器,间隔 3000 ms,激活状态 -->
Vincent Wei's avatar
Vincent Wei committed
414
415
416
417
418
419
    <update on="$TIMERS" to="append">
        { "id" : "foo", "interval" : 3000, "active" : "yes" }
    </update>

    ...

Vincent Wei's avatar
Vincent Wei committed
420
    <!-- 使标识符为 `foo` 的定时器失效 -->
Vincent Wei's avatar
Vincent Wei committed
421
422
423
424
425
    <choose on="$TIMERS" by="FILTER: AS 'foo'">
        <update on="$?" at=".active" with="no" />
    </choose>
```

Vincent Wei's avatar
Vincent Wei committed
426
第四,灵活的模板应用和统一的文档或数据表达。通过使用参数化的扩展 JSON 表达式构成的模板,用于表述树状结构的目标文档内容可由 HVML 程序生成和更新,这避免了在程序代码中调用特定接口来修改目标文档,HVML 程序只需要关注数据本身的产生和处理即可。这样,就实现了界面和数据的解耦。另外,HVML 对文档和数据的操作提供了一致的操作动作,因此,HVML 操作数据的动作元素,不仅可用于操作数据,也可以可用来操作目标文档。
Vincent Wei's avatar
Vincent Wei committed
427
428
429
430
431
432

比如,下面的 HVML 程序片段,使用 `archetype` 元素定义了一个模板,然后使用 `iterate``update` 元素,将 `users` 数组中的数据通过模板置换处理为目标文档的片段,然后追加到目标文档中:

```html
    <init as="users">
        [
433
434
435
436
            { "id": "1", "avatar": "/img/avatars/1.png", "name": "Tom",
                "region": "en_US", "age": 2 },
            { "id": "2", "avatar": "/img/avatars/2.png", "name": "Jerry",
                "region": "zh_CN", "age": 3 }
Vincent Wei's avatar
Vincent Wei committed
437
438
439
440
        ]
    </init>

    <archetype name="user_item">
441
442
        <li class="user-item" id="user-$?.id"
                data-value="$?.id" data-region="$?.region">
Vincent Wei's avatar
Vincent Wei committed
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
            <img class="avatar" src="$?.avatar" />
            <span>$?.name</span>
        </li>
    </archetype>

    <ul class="user-list">
        <iterate on="$users" by="RANGE: FROM 0">
            <update on="$@" to="append" with="$user_item" />
        </iterate>
    </ul>
```

第五,独特的栈式虚拟机。HVML 程序在一个抽象的栈式虚拟机上执行。一个 HVML 程序被解析后,会生成代表该程序结构的 vDOM(即虚拟 DOM)树,解释器从根元素开始以深度优先的顺序执行 vDOM 上的每个元素。每执行一个子孙元素,虚拟机的执行栈上会压入(push)一个新的栈帧(stack frame),直到 vDOM 的叶子元素为止,此时,解释器执行完叶子元素之后,会弹出(pop)最后的栈帧,上一个栈帧变为最后的栈帧,然后尝试执行该栈帧对应元素的下个兄弟元素。在 HVML 的栈式虚拟机上,我们可实现高效的数据管理模型,一部分数据在执行操作时作为中间数据存在,将被自动释放,另外一部分数据在栈帧中维护,从而可以自动分配和释放这些数据,只有少部分数据以静态形式存在。通过这样的管理机制,我们不需要在解释器中使用昂贵的垃圾收集器。另外,基于可动态替换的操作组,HVML 提供了就地执行、函数调用的功能,并实现了类似闭包的特性。

比如下面的 HVML 代码片段,展示了根据当前的目标文档类型就地执行不同操作组的能力:

```html
    <!-- 该元素定义了一个操作组,该操作组输出 HTML 片段。-->
    <define as="output_html">
        <h1>HVML</h1>
        <p>$?</p>
    </define>

Vincent Wei's avatar
Vincent Wei committed
466
    <!-- 该元素定义了一个操作组,该操作组向系统的标准输出打印文本。-->
Vincent Wei's avatar
Vincent Wei committed
467
468
469
470
471
472
473
474
475
476
477
478
479
480
    <define as="output_void">
        <inherit>
            $STREAM.stdout.writelines($?)
        </inherit>
    </define>

    <!-- 该元素根据当前 `hvml` 元素的 `target` 属性值就地执行不同的操作组。-->
    <include with=${output_$HVML.target} on="$T.get('Hello, world!')" />
```

第六,内置事件驱动。HVML 在语言层面提供了对数据、变量和表达式的观察能力。只需要一个 `observe` 元素,我们就可以观察一个数据上的特定事件,变量状态的变化,甚至一个表达式值的变化。

比如,下面的代码片段观察标识符为 `foo` 的定时器的到期事件:

Vincent Wei's avatar
Vincent Wei committed
481
```html
Vincent Wei's avatar
Vincent Wei committed
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
    <observe on="$TIMERS" for="expired:foo" >
        ...
    </observe>
```

再比如,下面的代码片段将一个表达式绑定为一个变量,从而可以观察这个表达式值的变化:

```html
    <bind on="$SYSTEM.time" as="rtClock" />

    <observe on="$rtClock" for="change">
       ...
    </observe>
```

Vincent Wei's avatar
Vincent Wei committed
497
由于 `$SYSTEM.time` 返回的是以秒为单位的 Unix 时间戳数值,故而 `observe` 元素定义的操作组,将每秒执行一次。
Vincent Wei's avatar
Vincent Wei committed
498

499
第七,轻松实现异步及并发编程。在栈式虚拟机基础上,HVML 允许在一个虚拟机之上同时运行多个 HVML 程序,并要求解释器使用协程管理同一个虚拟机上同时运行的多个 HVML 程序实例。同时,HVML 允许并发调用操作组,从而在另一个虚拟机实例上执行一段代码。这些机制可以帮助开发者轻松实现异步及并发编程,而在其他编程语言中,异步和并发编程通常需要足够的技巧才能良好驾驭。
Vincent Wei's avatar
Vincent Wei committed
500

501
比如下面的代码片段,异步地并发调用了 `collectAllDirEntriesRecursively` 操作组,该操作组递归遍历指定的目录,收集其下的所有目录项,然后使用 `observe` 来观察这个并发调用的状态。显然,这个操作组是一项耗时操作。在该操作组返回结果之前,调用者可以继续执行完成其他的工作。
Vincent Wei's avatar
Vincent Wei committed
502
503

```html
504
505
    <call as="my_task" on="$collectAllDirEntriesRecursively" with="/"
            within="myRunner" concurrently asynchronously />
Vincent Wei's avatar
Vincent Wei committed
506
507
508
509
510
511
512
    <observe on="$my_task" for="callState:success">
        <iterate on="$?" in="#entries" by="RANGE: FROM 0">
            <update on="$@" to="append" with="$dir_entry" />
        </iterate>
    </observe>
```

Vincent Wei's avatar
Vincent Wei committed
513
除此之外,HVML 还允许两个协程之间互相发送请求,且两个协程可以处于不同的虚拟机实例,这提供了一种统一和高效的跨协程及虚拟机的通讯机制。
Vincent Wei's avatar
Vincent Wei committed
514

Vincent Wei's avatar
Vincent Wei committed
515
516
#### 2.1.1) 程序结构

Vincent Wei's avatar
Vincent Wei committed
517
下面用一个简单的例子来说明 HVML 的程序结构。这个 HVML 程序生成一个 HTML 页面并根据用户的交互更新这个 HTML 页面;这个 HTML 页面,将在屏幕上展示三组信息:
Vincent Wei's avatar
Vincent Wei committed
518
519
520
521
522
523

1. 在页面顶端显示的系统状态栏,用于展示当前时间、WiFi 信号强度、电池电量信息等。这些信息将动态更新。
1. 在页面中间位置展示用户列表,每个用户项包括用户名称、头像等信息。这些信息来自 JSON 表达的一个字典数组。
1. 在页面底部展示一个搜索引擎连接。具体的搜索引擎根据系统所在的语言地区(locale)信息确定。

```html
Vincent Wei's avatar
tune    
Vincent Wei committed
524
<!DOCTYPE hvml SYSTEM "v: MATH">
Vincent Wei's avatar
tune    
Vincent Wei committed
525
<hvml target="html" lang="$STR.substr($SYSTEM.locale, 0, 2)">
Vincent Wei's avatar
Vincent Wei committed
526
    <head>
Vincent Wei's avatar
Vincent Wei committed
527
528
529
    </head>

    <body>
Vincent Wei's avatar
Vincent Wei committed
530
531
532
533
534
535
        <init as="global">
            { "locale" : "zh_CN" }
        </init>

        <init as="users">
            [
536
537
                { "id": "1", "avatar": "/img/avatars/1.png", "name": "Tom", "region": "en_US", "age": 2 },
                { "id": "2", "avatar": "/img/avatars/2.png", "name": "Jerry", "region": "zh_CN", "age": 3 }
Vincent Wei's avatar
Vincent Wei committed
538
539
540
            ]
        </init>

Vincent Wei's avatar
tune    
Vincent Wei committed
541
        <init as="databus" with=$STREAM.open('unix:///var/tmp/hibus.sock','default','hiBus') >
Vincent Wei's avatar
Vincent Wei committed
542

Vincent Wei's avatar
tune    
Vincent Wei committed
543
        <archetype name="user_item">
Vincent Wei's avatar
Vincent Wei committed
544
545
546
547
548
549
            <li class="user-item" id="user-$?.id" data-value="$?.id" data-region="$?.region">
                <img class="avatar" src="$?.avatar" />
                <span>$?.name</span>
            </li>
        </archetype>

Vincent Wei's avatar
tune    
Vincent Wei committed
550
        <archedata name="item_user">
Vincent Wei's avatar
Vincent Wei committed
551
            {
Vincent Wei's avatar
Vincent Wei committed
552
553
                "id": "$?.attr[data-value]", "avatar": "$?.content[0].attr.src",
                "name": "$?.children[1].textContent", "region": "$?.attr[data-region]"
Vincent Wei's avatar
Vincent Wei committed
554
555
556
557
558
559
560
561
562
563
564
565
            },
        </archedata>

        <header id="theStatusBar">
            <img class="mobile-status" src="" />
            <span class="mobile-operator"></span>
            <img class="wifi-status" src="" />
            <span class="local-time">12:00</span>
            <img class="battery-status" />
        </header>

        <ul class="user-list">
566
567
            <iterate on="$users" by="CLASS: IUser">
                <update on="$@" to="append" with="$user_item" />
568
                <except type="NoData">
Vincent Wei's avatar
Vincent Wei committed
569
                    <img src="wait.png" />
570
                </except>
Vincent Wei's avatar
tune    
Vincent Wei committed
571
                <except type="NotIterable">
Vincent Wei's avatar
Vincent Wei committed
572
573
574
575
576
                    <p>Bad user data!</p>
                </except>
            </iterate>
        </ul>

Vincent Wei's avatar
tune    
Vincent Wei committed
577
        <archetype name="footer_cn">
Vincent Wei's avatar
Vincent Wei committed
578
579
580
            <p><a href="http://www.baidu.com">Baidu</a></p>
        </archetype>

Vincent Wei's avatar
tune    
Vincent Wei committed
581
        <archetype name="footer_tw">
Vincent Wei's avatar
Vincent Wei committed
582
583
584
            <p><a href="http://www.bing.com">Bing</a></p>
        </archetype>

Vincent Wei's avatar
tune    
Vincent Wei committed
585
        <archetype name="footer_def">
Vincent Wei's avatar
Vincent Wei committed
586
587
588
589
590
            <p><a href="http://www.google.com">Google</a></p>
        </archetype>

        <footer id="the-footer">
            <test on="$global.locale" in='the-footer'>
591
592
                <match for="AS 'zh_CN'" exclusively>
                    <update on="$@" to="displace" with="$footer_cn" />
Vincent Wei's avatar
Vincent Wei committed
593
                </match>
594
595
                <match for="AS 'zh_TW'" exclusively>
                    <update on="$@" to="displace" with="$footer_tw" />
Vincent Wei's avatar
Vincent Wei committed
596
                </match>
Vincent Wei's avatar
Vincent Wei committed
597
                <match for="ANY">
598
                    <update on="$@" with="$footer_def" />
Vincent Wei's avatar
Vincent Wei committed
599
                </match>
600
                <except type="NoData" raw>
Vincent Wei's avatar
Vincent Wei committed
601
                    <p>You forget to define the $global variable!</p>
602
                </except>
Vincent Wei's avatar
tune    
Vincent Wei committed
603
                <except type="NoSuchKey">
Vincent Wei's avatar
Vincent Wei committed
604
605
                    <p>Bad global data!</p>
                </except>
Vincent Wei's avatar
Vincent Wei committed
606
                <except type="IdentifierError">
Vincent Wei's avatar
Vincent Wei committed
607
608
609
610
611
                    <p>Bad archetype data!</p>
                </except>
            </test>
        </footer>

Vincent Wei's avatar
cleanup    
Vincent Wei committed
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
        <define as="onBatteryChanged">
            <test on="$?.level">
                <match for="GE 100" exclusively>
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-full.png" />
                </match>
                <match for="GT 90" exclusively>
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-90.png" />
                </match>
                <match for="GT 70" exclusively>
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-70.png" />
                </match>
                <match for="GT 50" exclusively>
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-50.png" />
                </match>
                <match for="GT 30" exclusively>
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-30.png" />
                </match>
                <match for="GT 10" exclusively>
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-10.png" />
                </match>
                <match for="ANY">
                    <update on="> img.mobile-status" at="attr.src" with="/battery-level-low.png" />
                </match>
            </test>
        </define>

638
        <choose on=$databus.subscribe('@localhost/cn.fmsoft.hybridos.settings/powerd/BATTERYCHANGED')>
Vincent Wei's avatar
cleanup    
Vincent Wei committed
639
            <observe on="$databus" for="event:$?" in="#theStatusBar" with=$onBatteryChanged />
640
        </choose>
Vincent Wei's avatar
Vincent Wei committed
641

642
        <observe on=".avatar" for="click">
643
            <load from="user.hvml" as="userProfile" onto="user" async />
644
645
                {'id': $@.attr[data-value]}
            </load>
Vincent Wei's avatar
Vincent Wei committed
646
647
648
649
650
        </observe>
    </body>
</hvml>
```

651
首先,HVML 采用了类似 HTML 的标签来定义文档的整体结构:
Vincent Wei's avatar
Vincent Wei committed
652

Vincent Wei's avatar
tune    
Vincent Wei committed
653
- 在文档的开头,我们使用 `<!DOCTYPE hvml>` 来标记文档类型为 `hvml`。我们还使用了 `DOCTYPE``SYSTEM` 标识符来定义该 HVML 文档使用的标签前缀以及需要预先装载的外部模块。
Vincent Wei's avatar
Vincent Wei committed
654
- `hvml` 标签用于定义整个 HVML 文档。可包含如下属性:
Vincent Wei's avatar
Vincent Wei committed
655
   1. `target`:定义 HVML 文档的目标标记语言,取 `html``xml``void` 等值。
Vincent Wei's avatar
Vincent Wei committed
656
   1. 其他属性(如 `lang` 属性,用来定义语言,取值如 `en``zh` 等),将被解释器在求值后克隆到目标文档的根元素。
Vincent Wei's avatar
Vincent Wei committed
657
- `head` 标签用于定义头部信息,其中可包含:
Vincent Wei's avatar
tune    
Vincent Wei committed
658
   1. 可被原样保留到目标文档的标签,如 HTML 文档的 `<meta>``<link>` 标签。
Vincent Wei's avatar
Vincent Wei committed
659
   1. 全局数据的初始化或重置;使用 `init` 标签定义。
Vincent Wei's avatar
Vincent Wei committed
660
661
662
663
   1. 全局动态对象;使用 `init` 标签定义。
   1. 全局模板;使用 `archedata``archetype` 标签定义。
- `body` 标签用于定义文档的本体内容。在 HVML 文档中,可以定义多个 `body` 本地内容,使用 `id` 属性区别不同的本体内容。在执行过程中,可通过 `load` 元素装载不同的本体内容。

664
其次,从上面的 HVML 程序看出,HVML 使用了类似 HTML 的标签(tag),同时也可混用 HTML 的标签。两者的区别在于:
Vincent Wei's avatar
Vincent Wei committed
665

666
1. HVML 是动态的,表述的是程序,而 HTML 是静态的,表述的是文档。
Vincent Wei's avatar
tune    
Vincent Wei committed
667
1. HVML 的大部分标签用来定义动作元素。每个动作元素执行一个或者一组操作,所以这些标签使用英文中的动词;而 HTML 标准通常使用名词或其简写作为标签名称,如 HTML 的常见标签 `p` 是 paragraph(段落)的简写。
Vincent Wei's avatar
Vincent Wei committed
668
1. HVML 的其他标签用来定义程序的框架或者一个模板,所以使用名词作为标签名称,如 `head``archetype`,分别用来定义一个框架元素和模板元素。
Vincent Wei's avatar
Vincent Wei committed
669
1. HVML 可混合使用 HTML、XML 等目标标记语言的标签,使用非 HVML 的标签用于定义一个外部元素。HVML 赋予所有的外部元素一个统一的操作:克隆该元素的属性和内容到当前的目标文档位置并隐式调整 HVML 程序的上下文数据。
670
1. HVML 的标签名称、属性名称、变量名称和规则关键词是区分大小写的,这主要是为了和 XML 相关规范保持一致。
Vincent Wei's avatar
Vincent Wei committed
671
1. HVML 元素支持如下几类属性:
Vincent Wei's avatar
Vincent Wei committed
672
673
674
675
   - 通用属性。目前只有 `id` 一个通用属性;该属性用来指定元素的标识符。在 HVML 中,元素的标识符主要用于定位元素或者栈帧的位置。在外部元素中使用该属性时,则该属性及其属性值将被克隆到目标文档中。
   - 名词属性。如 `name` 等,通常在模板元素中使用。
   - 介词属性。如 `on``with` 等,在动词元素中使用。
   - 副词属性。如 `uniquely` 等,在动词元素中使用。
676

Vincent Wei's avatar
tune    
Vincent Wei committed
677
现在对照上面的例子介绍 HVML 的一些特点。本文档其他章节也会引用这个例子的片段。
678

Vincent Wei's avatar
Vincent Wei committed
679
首先是数据驱动编程(data-driven programming)。通过基于数据的迭代、插入、更新、清除等操作,开发者不需要编写程序或者只要少量编写程序即可动态生成最终的 XML/HTML 文档。比如上面示例代码中的 `iterate` 标签,就在`$users` 变量代表的数据(在 `head` 中使用 `init` 标签定义)上做迭代,然后在目标文档的 `ul` 元素中插入了若干 `li` 元素。而 `li` 元素的属性(包括子元素)由一个 `archetype` 标签定义,其中使用 `$?` 来指代被迭代的 `$users` 中的一个数据成员。
680

Vincent Wei's avatar
Vincent Wei committed
681
在上面的示例代码中,我们使用了系统内置变量 `$TIMERS` 来定义定时器,每个定时器有一个全局的标识符、间隔时间以及是否激活的标志。如果要激活一个定时器,我们只需要使用 HVML 的 `update` 标签来修改对应的键值,而不需要调用某个特定的编程接口。这是数据驱动编程的另一个用法——我们不需要为定时器或者其他的类似模块的操作提供额外的应用编程接口(API)。
682

Vincent Wei's avatar
Vincent Wei committed
683
另外,在上面的示例代码中,我们通过 `observe` 标签观察新的数据或文档本身的变化以及用户交互事件,可实现 XML/HTML 文档或数据的动态更新。比如在最后一个 `observe` 标签中,通过监听用户头像上的点击事件来装载一个新的 `user.hvml` 程序,在新的渲染器页面展示对应用户的详细信息。
684
685
686

其次是彻底解除界面、交互和数据之间的耦合。通过 HVML 引入的编程模型和方法,用于表述界面的 XML/HTML 文档内容可完全由 HVML 生成和动态调整,这避免了在程序代码中直接操作文档的数据结构(即文档对象树,或简称 DOM 树),而程序只需要关注数据本身的产生和处理即可。这样,就实现了界面和数据的解耦。比如:

Vincent Wei's avatar
Vincent Wei committed
687
688
- HVML 可在文档片段模板或者数据模板中定义数据和 DOM 元素之间的映射关系(如示例代码中的 `archetype``archedata` 元素),而无需编写额外的代码完成数据到 DOM 元素属性、内容等的赋值操作。
- HVML 将错误和异常的展现和程序代码分离了开来,程序只要定义适当的错误或者异常模板(如示例代码中的 `error``except` 元素),即可定义好错误或者异常情形下要展示的内容,而无需更多的程序处理。
689

Vincent Wei's avatar
Vincent Wei committed
690
再次是数据的 JSON 表达。HVML 对文档和数据的操作提供了一致接口。HVML 要求所有外部数据均使用 JSON 格式表述,JSON 格式是一种人机共读的数据表述形式,可在数值、字符串、数组、字典等基本数据单元的基础上表述复杂对象。由于 HTML/XML 文档片段(DOM 子树)可表述为 JSON 格式的数据,因此,HVML 亦可用于操作目标文档。在 HVML 中,我们还提供了对动态对象的支持,我们可以使用外部程序来实现自己的动态对象,且可以在这些动态对象上执行类似函数调用一样的功能。
691

Vincent Wei's avatar
Vincent Wei committed
692
最后,HVML 通过动作标签(通常都是一些英文的动词,如 `init``update``iterate` 等)以及与之配合的介词或副词属性来定义动作标签所依赖的数据、目标操作位置以及执行条件来完成特定的操作。这和常见的编程语言有很大不同,HVML 的描述方式更加贴近自然语言,从而可以大幅降低学习门槛。
Vincent Wei's avatar
Vincent Wei committed
693

Vincent Wei's avatar
Vincent Wei committed
694
695
为方便区分,我们将解析 HVML 程序之后生成的 DOM 树称为 vDOM(虚拟 DOM),而目标文档对应的 DOM 树,称为 eDOM(有效 DOM)。

Vincent Wei's avatar
Vincent Wei committed
696
#### 2.1.2) 基本数据类型
697

Vincent Wei's avatar
Vincent Wei committed
698
HVML 定义如下基本数据项(basic datum)类型:
699
700

- 空值(null)。
Vincent Wei's avatar
tune    
Vincent Wei committed
701
- 布尔值(boolean)。用于表示真假逻辑值,可取 `true``false` 两种值。
702
- 数值(number)。用于表达整数或浮点数,一般情况下,等价于 C 语言的 `double` 类型(双精度浮点数)。
Vincent Wei's avatar
tune    
Vincent Wei committed
703
- 字符串(string)。用于表达文本,始终使用 UTF-8 编码。
704
705
706

HVML 定义如下基本容器(basic container)类型:

Vincent Wei's avatar
tune    
Vincent Wei committed
707
708
- 数组(array)。可使用索引引用的多个数据项。
- 对象(object)。用单个或多个键值对(key-value pair)表示,亦称字典、关联数组等;键值对也常被称作属性(property)。
709

710
基本数据项类型和基本容器类型统称为基本数据类型,其表达方式兼容 [JSON]。
711

Vincent Wei's avatar
Vincent Wei committed
712
#### 2.1.3) 扩展数据类型
713
714
715

本规范要求 HVML 解释器要实现如下扩展的数据类型以及两种特殊数据类型:

Vincent Wei's avatar
Vincent Wei committed
716
- 未定义(undefined)。该数据仅在内部使用,用来表示数据尚未就绪,或在更新容器数据时移除一个成员。
Vincent Wei's avatar
Vincent Wei committed
717
- 异常(exception)。该数据仅在内部使用,用来表示异常。
Vincent Wei's avatar
Vincent Wei committed
718
719
- 有符号长整数(longint),应实现为 64 位有符号整数。
- 无符号长整数(ulongint),应实现为 64 位无符号整数。
Vincent Wei's avatar
tune    
Vincent Wei committed
720
721
722
723
724
- 长浮点数(longdouble),对应 C 语言 long double 类型。
- 字节序列(bsequence)。
- 集合(set),特殊的数组,其中的成员可根据其值或者对象数组上的唯一性键值确保唯一性。

以上扩展数据类型的表达方式使用本文档定义的 [3.1.3.2) 扩展 JSON 语法](#3132-扩展-json-语法)
725
726
727

HVML 还定义有如下两种特殊数据类型:

728
729
- 动态值(dynamic value)。动态值本质上由 `获取器(getter)``设置器(setter)` 方法构成,读取时,由获取器返回对应的值,设置时,由设置器完成对应的工作。
- 原生实体(native entity)。由底层实现的原生实体,通常用于代表一些可执行复杂操作的抽象数据,如读写流、长连接等。这些复杂操作包括实现虚拟属性上的获取器或设置器方法,实现对原生对象的观察(observe)等。
Vincent Wei's avatar
tune    
Vincent Wei committed
730

Vincent Wei's avatar
tune    
Vincent Wei committed
731
上述特殊数据类型属于内部数据类型,仅在运行时有效,在 HVML 代码中可通过变量和表达式访问。
732
733
734

动态值或者原生实体均可以作为对象的属性值存在,从而构成我们常说的动态对象。

Vincent Wei's avatar
Vincent Wei committed
735
在 HVML 中,我们扩展了对象的属性使之具有动态特性。一个动态属性,通常由 HVML 解释器或者外部程序定义或实现,要么是一个动态值,要么是一个原生实体。
736
737
738

从 HVML 文档的角度看,访问一个动态属性的方法和访问一个常规属性的方法并无二致。比如,我们通过访问 `$SYSTEM.time` 可获得当前的 UNIX 时间戳。但是,在不同的时刻访问 `$SYSTEM.time`,获得的值将会不同。这是因为这里的 `time` 就是一个动态属性。

Vincent Wei's avatar
tune    
Vincent Wei committed
739
作为动态属性的另一个特性,我们可以将某个特定的属性视作对象而在其上提供虚拟的属性,比如当我们访问 `$SYSTEM.uname_prt.default` 时,将获得当前的操作系统内核名称(如 `Linux`)。
740

Vincent Wei's avatar
tune    
Vincent Wei committed
741
742
743
更进一步,我们还可以将某个特定的属性当作函数使用,通过传递参数来获得不同的返回值,或者对该属性设置特定的值。比如在 `$SYSTEM` 对象上,如果我们要获取当前操作系统的内核名称以及发布版本号,则可以使用 `$SYSTEM.uname_prt('kernel-name kernel-release')`,这时,我们将获得类似 `Linux 5.4.0-107-generic` 的字符串。

除了使用 `( )` 这种类似函数调用的方式之外,我们还可以使用 `(! )`,后者用于设置某个属性。比如,使用 `$SYSTEM.cwd` 可以获得当前工作目录,而使用 `$SYSTEM.cwd(! '/tmp' )` 可设置当前工作目录。
744

745
这里,我们引入了两种运算符:`( )``(! )`。本质上,前者对应于动态属性的获取器方法,后者对应于动态属性的设置器方法。
746

Vincent Wei's avatar
tune    
Vincent Wei committed
747
除了内置的 `$SYSTEM` 动态对象或者通过 `DOCTYPE` 预先装载的动态对象之外,我们也可以通过外部程序模块实现自定义的动态对象,并通过 `init` 标签将这个动态对象和某个变量绑定在一起,如:
748
749

```html
750
    <init as="math" from="purc_dvobj_math" via="LOAD" />
751
752
753
754
```

之后,当我们访问 `$math.pi` 时,将返回 PI 的值,如果访问 `$math.sin($math.pi)` 将返回 `0.0`

Vincent Wei's avatar
Vincent Wei committed
755
当我们引用一个动态对象上并不存在的动态属性,或者不存在的虚拟子属性,或者无法在该属性上执行函数操作时,HVML 解释器将抛出异常。
756
757
758

通过这样的设计,我们可以方便有效地扩展 HVML 的功能,并通过动态对象和外部模块交换数据,或者调用外部模块的功能。

Vincent Wei's avatar
Vincent Wei committed
759
#### 2.1.4) 任意数据类型的强制转换规则
Vincent Wei's avatar
cleanup    
Vincent Wei committed
760

761
762
##### 2.1.4.1) 数值化

763
在需要将任意数据强制转换为数值的情形下,按如下规则转换为数值:
Vincent Wei's avatar
cleanup    
Vincent Wei committed
764

Vincent Wei's avatar
tune    
Vincent Wei committed
765
1. `null``undefined``false` 值转换为 0。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
766
767
768
1. `true` 值转换为 1。
1. 空字符串按 0 处理;非空字符串按照 EJSON 数值的规则进行转换,比如 `123.34` 将转换为实数,`abcd` 转换为 0。
1. 空字节序列按 0 处理;非空字节序列取最高 64 位(最长 8 字节)按小端字节序转为有符号的长整数,再转为数值。
Vincent Wei's avatar
Vincent Wei committed
769
1. 动态值,若不存在获取器方法,则按 0 处理;若存在获取器方法,则不传递任何参数调用获取器方法,若返回值为无效值则取 0,若返回值为数值型,则取其数值,若返回值为非数值型,按本规则递归处理。
770
1. 原生实体,尝试获取 `__number` 键名的获取器方法。若存在该方法,则不传递任何参数调用这个获取器,参考动态值处理;若不存在该方法,则取 0。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
771
772
773
1. 数组的数值,累加所有数组单元,若数组单元不是数值型,按本规则递归处理。
1. 字典的数值,累加所有键值,若某键值不是数值型,按本规则递归处理。

774
以上操作称为“数值化(numberify)”。
775
776
777
778

##### 2.1.4.2) 布尔化

当需要获得任何数据的布尔逻辑真假值时,按如下规则转换为布尔值:
Vincent Wei's avatar
cleanup    
Vincent Wei committed
779
780

1. 对如下情形始终视作 `false`
Vincent Wei's avatar
Vincent Wei committed
781
782
783
784
785
786
787
788
789
790
   - `null``undefined``false`
   - 数值: 0。
   - 空字符串。
   - 空数组。
   - 空对象。
   - 空集合。
   - 长度为零的字节序列。
1. 动态值:若不存在获取器方法,则按 `false` 处理;若存在,则不传递任何参数调用获取器方法,按本规则判断返回值。
1. 原生实体:尝试获取 `__boolean` 键名的获取器方法,若存在该方法,则不传递任何参数调用这个获取器,按本规则递归判断返回值;若不存在该方法,则取 `false`
1. 其他情形始终视作 `true`,包括:
Vincent Wei's avatar
Vincent Wei committed
791
792
   - `true`
   - 非零数值。
Vincent Wei's avatar
Vincent Wei committed
793
794
795
796
797
   - 非空字符串。
   - 非空数组。
   - 非空对象。
   - 非空集合。
   - 长度不为零的字节序列。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
798

799
800
801
802
803
804
以上操作称为“布尔化(booleanize)”。

##### 2.1.4.3) 字符串化

在需要将任意数据强制转换为字符串的情形下,按如下规则转换:

Vincent Wei's avatar
tune    
Vincent Wei committed
805
1. `null``undefined``true``false` 等,使用对应的关键词字符串。
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
1. 数值(包括长整形等),格式化输出,具体输出形式,由解释器确定。
1. 字符串,使用 UTF-8 编码串行化。
1. 字节序列,使用字节序列的十六进制表达方式,全大写。
1. 动态值,统一输出为 `<dynamic: %getter, %setter>`,其中 `%getter``%setter` 分别表示对动态值两个方法的格式化输出,具体形式,由解释器确定。
1. 原生实体,统一输出为 `<native: %entity>`,其中 `%entity` 表示对原生实体的格式化输出,具体形式,由解释器确定。
1. 数组:连接所有成员字符串化之后的字符串,每个成员之后追加(+U000A NEWLINE)字符。
1. 字典:连接所有属性键名对应的字符传以及键值对应的字符串,两者之间使用冒号(+U003A COMMA)分隔,每个属性之后追加(+U000A NEWLINE)字符。

以上操作称为“字符串化(stringify)”。

比如,下面的 JSON 数据,

```json
    [
        { "id": "1", "name": "Tom", "age": 2, "male": true },
        { "id": "2", "name": "Jerry", "age": 3, "male": true }
    ]
```

将被字符串化为:

```
id:1
name:Tom
age:2
male:true

id:2
name:Jerry
age:3
male:true
837

838
839
840
841
```

字符串化数据的目的,是为了按照字符串对多个数据进行对比、排序等操作。

Vincent Wei's avatar
tune    
Vincent Wei committed
842
注意,“字符串化”不同于“序列化”,后者指按照 JSON 或 EJSON 格式化数据。
843
844
845

##### 2.1.4.4) 序列化

Vincent Wei's avatar
tune    
Vincent Wei committed
846
“序列化(serialize)”指按照 JSON 或者 EJSON 格式化任意数据。EJSON 格式,可见本文档 [3.1.3.2) 扩展 JSON 语法](#3132-扩展-json-语法)
847

Vincent Wei's avatar
tune    
Vincent Wei committed
848
序列化时,若按照 JSON 格式输出,则对 EJSON 扩展类型的数据,应统一输出为 `null` 或特定格式的字符串。
849

Vincent Wei's avatar
tune    
Vincent Wei committed
850
851
852
1. `undefined`
1. 动态值。
1. 原生实体。
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888

##### 2.1.4.5) 键值对象

在 HVML 中,我们可以将对象中的一个键值对,使用一个新的对象表示,如:

```json
{
    "nrUsers" : 2,
    "names" : [ "Tom", "Jerry" ],
}
```

这个对象中的键值对 `"nrUsers" : 2` 可表达为一个新的对象:

```json
{
    "k": "nrUsers",
    "v": 2
}
```

我们将上面这种对象称为 `键值对象`(key-value object)。

而上面的对象可以转换为一个键值对象数组:

```json
[
    { "k": "nrUsers", "v": 2 },
    { "k": "names", "v": [ "Tom", "Jerry" ] }
]
```

键值对象数组本质上形成了一个以 `k` 为唯一性键的集合。

需要说明的是,HVML 解释器会根据要求隐式地将对象的一个属性转换为键值对象,我们通常不需要显式执行这种转换。

Vincent Wei's avatar
Vincent Wei committed
889
#### 2.1.5) 可变数据和不可变数据
890
891
892
893
894
895
896

在 HVML 中,我们将如下数据类型称为不可变数据(immutable data):

- 未定义(undefined)。
- 空值(null)。
- 真值(true)。
- 假值(false)。
Vincent Wei's avatar
tune    
Vincent Wei committed
897
898
899
900
901
- 数值(number)。
- 字符串(string)。
- 字节序列(byte sequence)。
- 动态值(dynamic value)。
- 原生实体(native entity)。
902
903
904
905
906

不可变数据的含义是,我们不能在运行时改变这个数据的值,而只能构造一个新的数据来表示新的值。

我们将如下数据类型称为可变数据(mutable data):

Vincent Wei's avatar
tune    
Vincent Wei committed
907
908
- 数组(array)。
- 对象(object)。
909
- 集合(set)。
910
911
912

和不可变数据相反,可变数据的含义是,我们可以在运行时改变这个数据的值。本质上,可变数据都是容器类数据,也就是数组、对象和集合。我们改变这些数据的值,本质上改变的是这些数据所包含的内容,比如删除其中的一个数据项。

Vincent Wei's avatar
Vincent Wei committed
913
在 HVML 中,我们可以在可变数据上使用 `update``clear``erase` 等标签执行更新、清空或移除操作,这些操作本质上修改的是其中的数据项。
Vincent Wei's avatar
tune    
Vincent Wei committed
914

Vincent Wei's avatar
Vincent Wei committed
915
我们也可以在原生实体上执行更新、清空或者移除操作,但这种情况下,操作的是原生实体代表的内部数据,而不是原生实体本身。比如,对一个代表目录项的原生实体,在其上执行 `clear` 操作,将清空该目录项中的所有的文件和子目录,而在其上执行 `erase` 操作,将移除这个目录项。
916

917
HVML 不提供任何操作可以用来改变不可变数据,但开发者可以使用 `init` 标签重置一个变量为其他数据。
918
919

#### 2.1.6) 变量
Vincent Wei's avatar
Vincent Wei committed
920
921
922

除了上述用于定义文档整体结构的标签外,HVML 提供了如下用于定义数据的标签:

Vincent Wei's avatar
Vincent Wei committed
923
- `init`:该标签初始化一个变量;我们将有名字的数据称为变量。在 HVML 文档的头部(由 `head` 标签定义)使用 `init` 标签,将默认初始化一个全局变量。在 HVML 文档的正文(由 `body` 标签定义)内使用 `init` 标签,将定义一个仅在其所在父元素定义的子树中有效的局部变量。我们可以直接将 JSON 数据嵌入到 `init` 标签内,亦可通过 HTTP 等协议加载外部内容而获得。若通过 HTTP 请求时,使用 `from` 属性定义请求的 URL,`with` 属性定义请求的参数,`via` 属性定义请求的方法(如 `GET``POST`)。
924
925
- `bind`:该标签用于定义一个表达式变量。

Vincent Wei's avatar
tune    
Vincent Wei committed
926
在 HVML 中,当我们引用变量时,我们使用 `$` 前缀,比如 `$global``$users``$?` 等。当我们要指代普通的 `$` 字符时,我们使用 `\` 做转义字符。
Vincent Wei's avatar
tune    
Vincent Wei committed
927

Vincent Wei's avatar
RC4.4    
Vincent Wei committed
928
`$global`、 `$users` 这种变量称为命名变量(named variables),又分为静态(static)命名变量和临时(temporary)命名变量;`$?` 这类使用特殊字符的变量称为上下文变量(context variables),根据 HVML 解释器的运行上下文(执行栈)确定其值。需要说明的是,在 HVML 相关文档中,程序的执行栈是从顶向下延伸的,第一个被压入的栈帧(stack frame)称为最顶(topmost)栈帧。
Vincent Wei's avatar
tune    
Vincent Wei committed
929

930
在 HVML 中,上下文变量和静态变量的管理机制不同:
Vincent Wei's avatar
Vincent Wei committed
931

932
1. 上下文变量存在于 HVML 程序的执行栈上,随着相应栈帧的弹出而自动销毁,本质上是临时变量。
Vincent Wei's avatar
Vincent Wei committed
933
1. 静态命名变量是静态的,全局存在的,和 vDOM 树中的某个元素节点关联。除非在 `init` 动作中使用 `undefined` 值重置一个静态命名变量,否则该变量会一直存在。
Vincent Wei's avatar
cleanup    
Vincent Wei committed
934
935
1. 临时命名变量本质上就是一个特殊的上下文变量。
1. 若在程序中不使用上下文变量特有的符号而使用命名变量的名称引用一个变量时,解释器将优先在当前执行栈中查找匹配该名称的临时变量,然后再在当前 vDOM 位置的父元素以及祖先元素中查找匹配该名称的静态变量。
Vincent Wei's avatar
Vincent Wei committed
936

Vincent Wei's avatar
tune    
Vincent Wei committed
937
HVML 定义的上下文变量罗列如下:
Vincent Wei's avatar
tune    
Vincent Wei committed
938

Vincent Wei's avatar
RC4.4    
Vincent Wei committed
939
940
941
942
943
944
- `$?`:指前置操作的结果数据(result data)。
- `$@`:指当前文档操作位置,由 eDOM 元素汇集(target element collection)表达,通常由前置操作中的介词属性 `in` 定义。
- `$!`:前置操作执行栈中保存的用户自定义数据(user data),用于定义临时数据。
- `$^`:指前置操作的内容数据(content data),仅针对动作元素,其他情形下为未定义。
- `$:`:若前置操作的结果数据是一个键值对象,则该变量表示键名,其他情形下为未定义。
- `$=`:若前置操作的结果数据是一个键值对象,则该变量表示键值,其他情形下为未定义。
Vincent Wei's avatar
tune    
Vincent Wei committed
945

946
以下上下文变量专用于迭代时(其他情形下为未定义):
Vincent Wei's avatar
tune    
Vincent Wei committed
947

Vincent Wei's avatar
RC4.4    
Vincent Wei committed
948
949
- `$%`:当前迭代索引值(iteration index),比如第一次迭代,该变量的值为 0,第二次迭代,该变量的值为 1,以此类推。
- `$<`:当前迭代数据(iteration data),由介词属性 `on` 定义。
Vincent Wei's avatar
tune    
Vincent Wei committed
950
951
952

我们还可以在上下文变量的符号之前添加一个正整数来引用从当前向上回溯 `<N>` 级的上下文数据:

953
- `$<N><SYMB>`,如 `$1<`、 `$1?` 指从当前上下文向上回溯 `<N>` 级的上下文数据;这里的 `<N>` 必须是零或者正整数。这个上下文变量主要用于访问执行栈上前置栈帧中的上下文数据。当 `<N>` 为零时,指访问当前栈帧中的上下文变量。
Vincent Wei's avatar
Vincent Wei committed
954
955
956
957

有关当前栈帧中的上下文变量,说明如下:

1. 所有上下文变量初始定义为 `undefined`。访问未定义的上下文变量,会产生 `NoData` 异常。
958
1. 上下文变量的设置和某些属性有关,比如 `$0<` 初始来自 `on` 属性值,`$0@` 来自 `in` 属性值或者继承自前一个栈帧。因此,引用这些上下文变量时,要注意动作元素规定的属性值求值顺序。
Vincent Wei's avatar
tune    
Vincent Wei committed
959

960
961
为提高代码的可读性,我们还可以使用元素的 `id` 属性定义的锚点(anchor)来定义要引用的上下文变量:

962
- `$#<ANCHOR><SYMB>`,如 `$#theAnchor<`、 `$#theAnchor?` 指从当前栈帧沿执行栈向上回溯并使用 `<ANCHOR>` 匹配对应 vDOM 元素的 `id` 属性值,从而定位到执行栈上某个栈帧的上下文数据。
963

Vincent Wei's avatar
Vincent Wei committed
964
965
在 HVML 中,我们通常使用 `as` 属性来给数据命名,但 HVML 保留若干变量名称用于预定义场合,我们称为预定义全局变量,习惯上全部使用大写形式。

Vincent Wei's avatar
cleanup    
Vincent Wei committed
966
按是否含有动态对象划分,HVML 的预定义变量可分为:
967
968
969
970

1. 动态变量,即变量对应的数据提供动态方法。
1. 非动态变量,即变量对应的数据不提供动态方法。

Vincent Wei's avatar
cleanup    
Vincent Wei committed
971
按变量对应数据的作用域,HVML 的预定义变量可分为:
972
973
974
975

1. 会话级变量。指该变量对应的数据对当前解释器实例中的所有 HVML 协程可见。也就是说,同一会话中的不同协程对应同一个数据副本。
1. 协程级变量。指该变量对应的数据仅对当前解释器实例中的单个 HVML 协程可见。也就是说,不同的 HVML 协程有一个自己的数据副本。

Vincent Wei's avatar
cleanup    
Vincent Wei committed
976
按变量是否必须提供,HVML 的预定义变量可分为:
977
978
979
980

1. 必要(required)变量。解释器必须实现该变量。
1. 可选(optional)变量。该变量的实现是可选的。

Vincent Wei's avatar
cleanup    
Vincent Wei committed
981
作为 HVML 规范集合的一部分,文档 [HVML 预定义变量](hvml-spec-predefined-variables-v1.0-zh.md) 详细规定了所有的预定义变量及其接口,解释器应根据规范要求做相应的实现。
Vincent Wei's avatar
Vincent Wei committed
982
983

下面简单介绍一些关键的预定义变量。
Vincent Wei's avatar
Vincent Wei committed
984

985
##### 2.1.6.1) `$REQUEST`
Vincent Wei's avatar
Vincent Wei committed
986

987
`$REQUEST`:主要用来表述装载文档时,由其他模块提供的请求数据,一般由 HVML 解释器在装载 HVML 文档时生成。比如下面的 Python 脚本装载一个 HVML 文档,并传递了 `nrUsers` 参数:
Vincent Wei's avatar
Vincent Wei committed
988
989
990
991
992

```python
hvml.load ("a.hvml", { "nrUsers" : 10 })
```

993
在 HVML 文档中,我们可使用 `$REQUEST.nrUsers` 来引用上述脚本代码传入的值(`10`)。
Vincent Wei's avatar
Vincent Wei committed
994

995
996
`$REQUEST` 变量本质上是一个必要的协程级非动态对象。

997
##### 2.1.6.2) `$SYSTEM`
Vincent Wei's avatar
Vincent Wei committed
998

Vincent Wei's avatar
Vincent Wei committed
999
`$SYSTEM`:一个用于访问系统基本功能的动态对象,可用于提供系统时间、当前语言地区(区域)、时区、随机序列、机器名称等。比如,我们要获得当前的 Unix 时间戳,可直接使用 `$SYSTEM.time`,如果要获得一个随机序列,可使用 `$SYSTEM.random_sequence`,如果我们要获得当前的机器名称,可使用 `$SYSTEM.uname`,如果要获取当前语言地区信息,可使用 `$SYSTEM.locale`。
Vincent Wei's avatar
Vincent Wei committed
1000

1001
`$SYSTEM` 变量本质上是一个必要的会话级动态对象。
Vincent Wei's avatar
Vincent Wei committed
1002

Vincent Wei's avatar
Vincent Wei committed
1003
1004
##### 2.1.6.3) `$HVML`

Vincent Wei's avatar
Vincent Wei committed
1005
`$HVML` 是一个动态对象,该对象表述的是当前正在执行的 HVML 程序实例(协程)本身,用以设置当前协程相关的参数。比如:
Vincent Wei's avatar
Vincent Wei committed
1006
1007

1. `$HVML.base`:获取或设置 HVML 程序的默认 URL 位置(类似 HTML 的 `base` 标签)。
Vincent Wei's avatar
Vincent Wei committed
1008
1009
1010
1. `$HVML.max_iteration_count`:获取或设置 HVML 程序在执行 `iteration` 元素时的最大迭代次数;用于检测可能的死循环。
1. `$HVML.max_recursion_depth`:获取或设置 HVML 程序在递归执行某个功能时的最大递归深度,以防止栈溢出。
1. `$HVML.max_embedded_levels`:获取或设置 HVML 程序在解析或者处理嵌套的容器数据时,允许的最大嵌套层级。
Vincent Wei's avatar
Vincent Wei committed
1011
1. `$HVML.timeout`:获取或设置获取外部数据时的超时值。
Vincent Wei's avatar
Vincent Wei committed
1012

1013
1014
1015
1016
1017
1018
1019
另外,我们还可以通过 `$HVML` 对象观察一些全局事件以及当前协程渲染状态的变化,从而优雅地处理渲染器页面被用户关闭或者渲染器丢失等的情形。这些事件有:

- `idle`:当前 HVML 程序在事件轮询阶段未获得任何事件。
- `rdrState:closed`:协程对应的渲染器页面被用户强制关闭。
- `rdrState:lost`:协程所在会话丢失渲染器的连接。
- `rdrState:suppressed`:协程和渲染器的交互(包括页面的更新以及接受来自渲染器的交互事件)被压制。
- `rdrState:reload`:当前协程的文档内容重新装载到渲染器,渲染器状态调整为 `regular`。
Vincent Wei's avatar
Vincent Wei committed
1020

1021
1022
`$HVML` 变量本质上是一个必要的协程级动态对象。

1023
##### 2.1.6.4) `$DOC`
1024

Vincent Wei's avatar
tune    
Vincent Wei committed
1025
`$DOC` 是一个动态对象,该对象表述的是 HVML 生成的目标文档。我们可以使用该对象上的特定键名以及 `query` 方法通过 CSS 选择器获取目标文档上的元素汇集,如:
Vincent Wei's avatar