├── README.md ├── assets ├── hvml-dark-h240.png ├── hvml-dark.png ├── hvml-light-h240.png ├── hvml-light.png ├── hvml-v-black.png ├── hvml-v-fill-white.png ├── hvml-v-squre.png ├── hvml-v.png └── hvml.png ├── cloudapp ├── figure-1.png ├── figure-2.png ├── figure-3.png ├── figure-4.png ├── figure-5.png └── zh.md ├── deprecated ├── hvml-spec-v0.9-zh.md └── setup-dev-env-using-docker.md ├── en ├── a-brief-introduction-to-hvml-en.md ├── embed-python-in-hvml-program-en.md ├── hvml-spec-predefined-variables-v1.0-en.md ├── hvml-spec-v1.0-en.md ├── learn-hvml-programming-in-30-minutes-en.md └── the-initial-idea-of-hvml-en.md ├── extensions └── vim │ ├── ftdetect │ └── hvml.vim │ └── syntax │ └── hvml.vim ├── interpreter ├── figure-1.png ├── figure-2.png ├── figure-3.png ├── figure-4.png ├── figure-5.png ├── figure-6.png └── zh.md ├── samples ├── bootstrap │ ├── assets │ │ ├── breaking-news-zh_CN.json │ │ └── messages-zh_CN.json │ └── modal.hvml ├── calculator │ ├── assets │ │ ├── buttons.json │ │ ├── calculator.css │ │ ├── expressions.json │ │ ├── zh.json │ │ └── zh_CN.json │ ├── calculator-1.hvml │ ├── calculator-2.hvml │ ├── calculator-3.hvml │ ├── calculator-4.hvml │ ├── calculator-5.hvml │ └── calculator.css ├── fibonacci │ ├── fibonacci-1.hvml │ ├── fibonacci-2.hvml │ ├── fibonacci-3.hvml │ ├── fibonacci-4.hvml │ ├── fibonacci-5.hvml │ └── fibonacci-6.hvml ├── loading │ ├── assets │ │ ├── breaking-news-zh_CN.json │ │ └── messages-zh_CN.json │ ├── loading-1.hvml │ ├── loading-2.hvml │ └── loading-3.hvml ├── readdir │ └── readdir-1.hvml └── template │ ├── assets │ └── messages-zh_CN.json │ ├── how-to-use-template-to-generate-html.hvml │ └── template │ └── news-list.html ├── screenshots ├── embed-python-animated-3d-random-walk.png ├── embed-python-to-find-primes-using-p-span-xgui-pro.png ├── embed-python-to-find-primes-using-p-span.png ├── embed-python-to-find-primes-using-ul-li.png ├── hello-world-with-style-foil.png └── hello-world-with-style-xgui-pro.png ├── tools └── README.md └── zh ├── a-brief-introduction-to-hvml-zh.md ├── calculator.css ├── calculator.png ├── embed-python-in-hvml-program-zh.md ├── hvml-spec-predefined-variables-v1.0-zh.md ├── hvml-spec-v1.0-zh.md ├── learn-hvml-programming-in-30-minutes-zh.md ├── the-initial-idea-of-hvml-zh.md └── write-a-calculator-by-using-hvml-step-by-step-zh.md /README.md: -------------------------------------------------------------------------------- 1 | # HVML Documents 2 | 3 | Articles and documents about HVML. 4 | 5 | This repository contains some articles, guides, and documents about HVML. 6 | 7 | ## Documents in English 8 | 9 | To quickly learn how to program with HVML, please refer to the tutorial (80% complete): 10 | 11 | - [Learn HVML Programming in 30 Minutes](en/learn-hvml-programming-in-30-minutes-en.md) 12 | 13 | For an detailed introduction to HVML, please refer to the article (10% complete): 14 | 15 | - [HVML, a Programable Markup Language](en/an-introduction-to-hvml-en.md) 16 | 17 | ## 中文文档 18 | 19 | ### 入门 20 | 21 | - [30 分钟学会 HVML 编程](zh/learn-hvml-programming-in-30-minutes-zh.md) 22 | - [用 HVML 写一个计算器](zh/writing-a-calculator-by-using-hvml-step-by-step.md) 23 | - [漫谈 HVML:它的由来和未来](zh/brief-introduction-to-hvml-zh.md) 24 | 25 | ### 规范 26 | 27 | 当前版本: 28 | 29 | - [HVML 规范(V1.0)](zh/hvml-spec-v1.0-zh.md) 30 | - [HVML 预定义变量(V1.0)](zh/hvml-spec-predefined-variables-v1.0-zh.md) 31 | 32 | 老的版本: 33 | 34 | - [HVML 规范(V0.9)](zh/hvml-spec-v0.9-zh.md) 35 | 36 | ### 设计 37 | 38 | - [HVML 解释器的设计:一种数据驱动的动态标记语言的解释器实现](interpreter/zh.md) 39 | - [基于 HVML 的远程应用:一种支持远程应用的方法及装置](cloudapp/zh.md) 40 | 41 | -------------------------------------------------------------------------------- /assets/hvml-dark-h240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-dark-h240.png -------------------------------------------------------------------------------- /assets/hvml-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-dark.png -------------------------------------------------------------------------------- /assets/hvml-light-h240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-light-h240.png -------------------------------------------------------------------------------- /assets/hvml-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-light.png -------------------------------------------------------------------------------- /assets/hvml-v-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-v-black.png -------------------------------------------------------------------------------- /assets/hvml-v-fill-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-v-fill-white.png -------------------------------------------------------------------------------- /assets/hvml-v-squre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-v-squre.png -------------------------------------------------------------------------------- /assets/hvml-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml-v.png -------------------------------------------------------------------------------- /assets/hvml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/assets/hvml.png -------------------------------------------------------------------------------- /cloudapp/figure-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/cloudapp/figure-1.png -------------------------------------------------------------------------------- /cloudapp/figure-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/cloudapp/figure-2.png -------------------------------------------------------------------------------- /cloudapp/figure-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/cloudapp/figure-3.png -------------------------------------------------------------------------------- /cloudapp/figure-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/cloudapp/figure-4.png -------------------------------------------------------------------------------- /cloudapp/figure-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/cloudapp/figure-5.png -------------------------------------------------------------------------------- /cloudapp/zh.md: -------------------------------------------------------------------------------- 1 | # 基于 HVML 的远程应用:一种支持远程应用的方法及装置 2 | 3 | 版权所有 © 2020, 2021 北京飞漫软件技术有限公司 4 | 保留所有权利 5 | 6 | 此文档不受 HVML 相关软件开源许可证的管辖。 7 | 8 | 飞漫软件公开此文档的目标,用于向开发者解释 HVML 相关设计原理或者相关规范。在未获得飞漫软件书面许可之前,任何人不得复制或者分发本文档的全部或部分内容,或利用本文档描绘的技术思路申请专利、撰写学术论文等。 9 | 10 | 本文提及的飞漫软件或其合作伙伴的注册商标或商标之详细列表,请查阅文档末尾。 11 | 12 | - [0) 基本信息](#0-基本信息) 13 | - [1) 背景技术](#1-背景技术) 14 | - [2) 现有技术的缺陷和不足](#2-现有技术的缺陷和不足) 15 | - [3) 技术方案描述](#3-技术方案描述) 16 | + [3.1) 云应用示例](#31-云应用示例) 17 | + [3.2) 整体结构](#32-整体结构) 18 | + [3.3) 隐藏的句柄属性](#33-隐藏的句柄属性) 19 | + [3.4) 从 eDOM 到 uDOM](#34-从-edom-到-udom) 20 | + [3.5) 处理 uDOM 事件](#35-处理-udom-事件) 21 | + [3.6) 会话管理](#36-会话管理) 22 | + [3.7) 云应用通讯协议](#37-云应用通讯协议) 23 | + [3.8) 用户代理](#38-用户代理) 24 | - [4) 本发明创造的优点](#4-本发明创造的优点) 25 | - [5) 替代方案](#5-替代方案) 26 | - [6) 本发明的关键点和欲保护点](#6-本发明的关键点和欲保护点) 27 | - [7) 附图及简要说明](#7-附图及简要说明) 28 | + [7.1) 附图1 解释器和用户代理的关系](#71-附图1-解释器和用户代理的关系) 29 | + [7.2) 附图2 云应用服务器和客户端的关系](#72-附图2-云应用服务器和客户端的关系) 30 | - [8) 有关专利查重结果的补充说明](#8-有关专利查重结果的补充说明) 31 | - [附:商标声明](#附商标声明) 32 | 33 | ## 0) 基本信息 34 | 35 | - 发明创造名称 36 | 一种支持远程应用的方法及装置 37 | - 权利人 38 | 北京飞漫软件技术有限公司 39 | - 发明人 40 | 魏永明、耿岳、薛淑明 41 | - 撰写人 42 | 魏永明 43 | 44 | ## 1) 背景技术 45 | 46 | 云应用的概念目前尚处于混乱状态,大部分文献中所指云应用是指云服务厂商提供的 SaaS 服务。本发明所指云应用(Cloud App),是指在控制应用逻辑的程序在云端运行,但用户通过终端设备(如智能手表、智能音箱等)和该应用交互的一种应用程序形式。这一概念和目前云服务厂商正在推广的云游戏类似。顾名思义,云游戏运行在云端,游戏玩家通过电脑、手机等连入云端服务器,使用电脑上的键盘、鼠标或者手机上的触摸屏和云端的服务器程序交互,云端服务器则将渲染好的场景(包括图像、声音等)传到电脑或者手机展示出来。 47 | 48 | 云游戏带来的好处是明显的。第一,游戏的渲染效果,尤其是复杂三维场景的渲染效果不再受制于玩家使用的电脑或者手机的硬件配置,因为所有的场景渲染均在云端完成;第二,玩家不需要下载安装体量巨大的游戏 App;第三,游戏开发者开发成本将大幅降低,不再需要针对不同的操作系统平台开发不同的版本,而只需要开发一个云端的版本即可;第四,对于云服务厂商来讲,有助于开拓新的收入来源。和云游戏类似,普通的应用也可以使用这种形式运行,从而获得和云游戏一样的好处。 49 | 50 | 另外,和本发明所指云应用类似的应用场景早在多年前就已存在。比如基于 Windows 或者 Unix 系统的远程桌面构造的瘦客户机。这类系统往往用于特定的办公场所,比如银行、学校等。用户通过一台瘦客户机连接到远程服务器上,用户通过瘦客户机运行的所有应用,比如桌面、办公软件等,均在服务器上运行,瘦客户机将用户的鼠标、键盘输入通过网络发送到服务器,而服务器将这些应用绘制的内容通过网络发送到瘦客户机,瘦客户机中运行的软件负责将绘制的内容展示在屏幕上。 51 | 52 | 目前云游戏的实现原理是将渲染好的游戏画面转换为视频流,加上声音等多媒体数据,以 H.264 等码流的形式发送到最终的设备上展示,由于 H.264 使用的有损压缩算法,故而会带来一定的图像清晰度损失。远程桌面的实现原理类似,但由于通常运行在局域网中,带宽并不是问题,所以传输的并不是压缩的多媒体流,而是更新的局部位图信息,但会使用无损压缩技术,如 GIF、PNG 图片格式或者自定义的位图格式。 53 | 54 | 本发明提出了一种全新的用于支持云应用的方法及装置,这一方法使用了改进的 Web 技术,相关的技术标准或规范罗列如下: 55 | 56 | - HTML 及其规范。HTML 和 CSS 等规范和标准是由 W3C 组织制定的,用来规范 Web 页面内容的编写和渲染行为。关键规范如下: 57 | * HTML:超文本标记语言(HyperText Markup Language),用于表述网页内容结构的标准。最新的发布标准是 HTML 5.3:; 58 | * CSS:级联样式表(Cascading Style Sheets),用于定义 HTML 页面元素布局、渲染效果等的规范。在 CSS 2.2 之后,CSS 规范开始按照模块划分,各模块分头演进,目前普遍支持到 Level 3。在如下网页中可以看到 CSS 各模块的规范进展情况:; 59 | * JavaScript/ECMAScript:一种符合 ECMAScript 规范的脚本编程语言,最初由网景公司设计给浏览器使用,用于操控 HTML 页面中的内容和渲染行为,现在由欧洲计算机制造商协会和国际标准化组织负责制定相关标准,最新的标准为 ECMA-262:。 60 | * DOM:文档对象模型(Document Object Model),用于 XML/HTML 文档结构的内部表达。一个 XML/HTML 文档,会被 XML/HTML 解析器解析并生成一个 DOM 树,XML/HTML 文档中的每个元素构成 DOM 树上的元素结点,而每个元素的子元素、属性、文本内容等,又构成了这个元素节点的子节点。有关 DOM 的最新的规范可见:。 61 | * JSON:JavaScript 对象表述法(JavaScript Object Notation)是一种轻量级的信息互换格式。最初被用于 JavaScript 对象的字符串表达,易于被 JavaScript 脚本代码使用,现在被广泛使用在不同编程语言之间的数据交换。有关 JSON 的描述,可见:。 62 | - 用户代理(User Agent)是 HTML 规范的一个术语,用来指代可以解析 HTML、CSS 等 W3C 规范,并对 HTML 文档内容进行渲染,进而呈现给用户并实现用户交互的计算机程序。我们熟知的浏览器就是用户代理。但用户代理不限于浏览器,可以是一个软件组件,也可以是一个应用框架。比如,内嵌到电子邮件客户端程序中,用以解析和渲染 HTML 格式邮件的软件组件,本质上也是 HTML 用户代理。 63 | - XML:可扩展标记语言(The Extensible Markup Language)是由 W3C 组织制定的,用来表述结构化信息的一种简单文本格式。和 HTML 相比,XML 使用类似的结构,但更加严格且更为通用。XML 是当今共享结构化信息的最广泛使用的格式之一,不论是在程序之间,人与人之间,计算机与人之间,也不论是在本地还是跨网络共享信息。有关 XML 的介绍和规范可参阅:。 64 | - 脚本语言。指类似 JavaScript 的高级计算机编程语言,通常解释执行,具有动态特征。除 JavaScript 之外,常见的脚本语言有 Python、Lua 等。 65 | 66 | 另外,本发明提出的支撑云应用运行的方法,采纳了近几年发展出来的虚拟 DOM 的思想。 67 | 68 | 从 2019 年开始,基于虚拟 DOM 技术的框架又受到前端开发者的青睐,比如著名的 React.js()、Vue.js()等。值得注意的是,微信小程序、快应用等,也不约而同使用了这种虚拟 DOM 技术来构建应用框架。 69 | 70 | 所谓“虚拟 DOM” 是指前端应用程序通过 JavaScript 来创建和维护一个虚拟的文档对象树,应用脚本并不直接操作真实的 DOM 树。在虚拟 DOM 树中,通过一些特别的属性实现了基于数据的一些流程控制,如条件、循环等。虚拟 DOM 技术提供如下一些好处: 71 | 72 | 1. 由于脚本并不使用脚本程序直接操作真实的 DOM 树,故而一方面通过现有的框架简化了前端开发的复杂性,另一方面通过优化对真实 DOM 数的操作而减少了由于动态修改页面内容而对 DOM 树的频繁操作,从而提高页面的渲染效率和用户体验。 73 | 1. 通过虚拟 DOM 技术,程序对某个数据的修改,可以直接反应到该数据绑定的页面内容上,开发者无需主动或直接地调用相关接口来操作 DOM 树。这种技术提供了所谓的“响应式”编程,极大降低了开发者的工作量。 74 | 75 | 为支撑本发明提出的方法,权利人提出了一种全新的数据驱动的编程语言,我们称为 HVML(Hybrid Virtual Markup Language)。HVML 采用类似 XML/HTML 的语法描述虚拟 DOM,可用于生成实际的 XML/HTML 文档内容,并处理页面产生的各种交互事件以及动态数据生成事件。具体的规范可见: 76 | 77 | 78 | 79 | ## 2) 现有技术的缺陷和不足 80 | 81 | ### 2.1) 云游戏方案 82 | 83 | 目前的云游戏实现原理是将渲染好的游戏画面转换为视频流,加上声音等多媒体数据,以 H.264 等码流的形式发送到最终的设备上展示。当我们使用云游戏方案来支撑云应用的运行室,存在如下弊端: 84 | 85 | - 由于多媒体码流通常采用有损压缩算法来压缩变化的画面。这对场景变化频繁的游戏画面来讲并不成为问题,但对于画面不频繁变化的普通图形用户界面应用来讲,则会在文字边缘等色彩变化剧烈的部位出现明显的马赛克效应,从而影响用户的体验。 86 | - 尽管我们可以使用 H.264 等高压缩比、低码流的视频流压缩算法,但多媒体流仍然要占据较高的带宽,这对使用 NBIoT 等窄带传输通讯的物联网设备来讲无法接受。 87 | - 设备端需要一个多媒体流,尤其是视频流的解码器,这通常意味着更高的成本以及更高的耗电,不适合智能手表等低功耗物联网产品。 88 | 89 | ### 2.2) 远程桌面方案 90 | 91 | 远程桌面方案主要用于高带宽的局域网环境,当我们在物联网场景中运行云应用时,存在带宽不足的问题。 92 | 93 | 当我们在互联网上使用远程桌面的技术方案来支撑云应用时,为降低带宽的使用,也可使用如云游戏一样的多媒体流形式来传输图形界面的画面,这时,其缺点或不足同云游戏方案。 94 | 95 | ### 2.3)基于 Web 技术 96 | 97 | 由于 Web 浏览器中显示的所有内容通常都来自运行在云端的 HTTP、WebSocket 等服务器,故而我们也可以将 Web 页面看作是一种云应用。比如谷歌 ChromeOS 使用的就是这种技术:所有的 Chrome App 本质上就是网页。 98 | 99 | 在过去的数十年间,浏览器的 JavaScript 脚本解释器有了重大的改进,其性能得到了数百倍的改善。然而,随之而来的问题则是浏览器本身占用的系统内存和体积也有了相应的增加。比如 Chrome 浏览器所使用的 V8 JavaScript 引擎,体积高达 30MB,运行时的内存占用则至少要占用 512MB(一个页面)。 100 | 101 | 因此,在设备侧运行 JavaScript 脚本,一方面需要较好的硬件配置和系统内存,才能获得较好的性能,另一方面,并不符合严格的云应用定义:即将所有的程序逻辑运行在云端。 102 | 103 | ## 3) 技术方案描述 104 | 105 | ### 3.1) 云应用示例 106 | 107 | 为了更好地理解本发明提出的方法和实现装置,我们首先看一个简单的示例。 108 | 109 | 我们假设一个智能手环上显示当前时间、当地气温、佩戴者的心跳信息和步数信息等信息,而这个智能手环通过 MQTT(一种轻量级消息通讯协议)和云端服务器交换信息,比如向云端服务器发送佩戴者的心跳和步数信息、地理位置信息,获得时间以及当前位置的气象条件等信息。在传统的实现方式中,我们一般需要开发一个在智能手环上运行的 GUI 系统,然后和云端通讯获得数据,界面的修改完全由设备端代码负责。如果要改变界面的样式,大部分情况下需要升级整个智能手环的固件(firmware)。 110 | 111 | 但如果我们使用 HVML,则可以通过云端来控制设备的界面显示。我们使用权利人提出的 HVML 编程语言来开发这个应用,则运行在云端的 HVML 代码如下所示: 112 | 113 | ```html 114 | 115 | 116 | 117 | 118 | 119 | 120 | [ 121 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 122 | ] 123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 | 131 | 132 | 133 |
134 | 135 |
136 | 137 | 138 | 139 |
140 | 141 |
142 | 143 | 144 | 145 |
146 | 147 |
148 | 149 | 150 | 151 |
152 | 153 | 154 | 155 | 156 | 157 |
158 | ``` 159 | 160 | 其要点如下: 161 | 162 | 1. 该代码运行在云端,云端的 HVML 解释器执行该代码生成 HTML 文档,并在数据和用户交互的驱动下,更新 HTML 文档内容。 163 | 1. 该代码生成的 HTML 文档或者对 HTML 文档的改变,将通过类似 WebSocket 的长连接发送给设备,设备根据此信息渲染用户界面或者更新用户界面。 164 | 1. 该代码监听智能手环(设备)通过 MQTT 发送给云端的数据,包括心跳、气温、步数等信息,并更新相应的标签内容。 165 | 1. 该代码设定了一个定时器,每隔 1 秒运行一次,并更新时钟对应的标签内容。 166 | 1. 该代码使用了一个外部选择执行器 `CDumpEvent` 将所有来自 `mqtt` 的事件转储到了云端数据库中。 167 | 168 | 这带来了如下显著的改变: 169 | 170 | 1. 复杂的逻辑代码将全部运行在云端,设备端只要有一个足够功能的 HTML/XML 用户代理即可,通常只需要包含一个根据 DOM 树和 CSS 渲染最终用户界面的渲染器。 171 | 1. 当我们需要调整设备端的显示效果或者功能时,我们只需要修改服务器端的 HVML 代码,而不需要更新设备端固件。 172 | 1. 我们还可以通过外部脚本,将运行在云端的其他功能,如数据库存储、数据的分析以及人工智能等要素有机整合在一起。 173 | 174 | ### 3.2) 整体结构 175 | 176 | 为了实现前述的云应用执行逻辑,我们将整个软件系统划分为两个主要的构成部分,分别运行在云端和设备端: 177 | 178 | - HVML 解释器。HVML 解释器的实现,在权利人提交的另外一个发明申请《一种数据驱动的动态标记语言的解释器实现》中阐述。 179 | - 简化的 HTML/XML 用户代理。该用户代理仅根据 HVML 解释器提供给它的最终 HTML/XML 文档和配套的 CSS 样式信息渲染 DOM 树并展示在屏幕上,而不会执行任何 JavaScript 脚本程序。这样,该 HTML/XML 用户代理相比全功能的浏览器来讲,是一个简化的用户代理,跟早期的字符终端一样,属于哑设备(dumb device)。由于不再在设备端运行 JavaScript 脚本,因此安全性也将大为提高。 180 | 181 | 附图1 给出了 HVML 解释器和设备端用户代理之间的关系。为方便描述,以下分别简称“解释器”和“用户代理”。 182 | 183 | ![解释器和用户代理的关系](figure-1.png) 184 | 185 | 云端解释器和设备端的用户代理之间的互操作过程大致可以描述为如下一个过程: 186 | 187 | - 由于解释器和用户代理运行在不同的计算机设备上,因此通过一个基于网络套接字的通讯机制建立联系,比如 WebSocket,或者 MQTT、XMPP 等消息通讯协议。我们将这类通讯协议统称为`桥梁`。 188 | - 解释器解析 HVML 定义的动作标签,根据动作标签规定的动作生成最终的 XML/HTML 文档(或片段),并通过桥梁将文档或者 DOM 树及其变化传递给用户代理。 189 | - 解释器负责监听在 HVML 头部中定义的数据源,并执行 `observe` 标签定义的操作。 190 | - 用户代理将用户在真实文档上产生的交互事件(比如按钮的单击事件)以及用户操作导致文档节点发生的变化(如输入框的内容变化、列表框的选择变化等),通过桥梁发送给在云端运行的解释器。 191 | - 解释器监听来自用户代理的交互事件或者文档节点的变化通知,并执行 `observe` 标签定义的操作。 192 | - 当 HVML 的操作修改了真实文档的某个节点时,解释器通过桥梁将该修改发送给用户代理,用户代理根据文档的改变信息重新渲染更新后的文档。 193 | 194 | 在上述过程中,关键点在于解释器和用户代理各自维护一个最终文档对应的 DOM 树,并通过桥梁同步两个 DOM 树之间的差异。 195 | 196 | 1. 解释器维护 HVML 对应的虚拟 DOM 树,其中包括模板标签、动作标签等定义的元素以及数据。为方便描述,我们将虚拟 DOM 称为 vDOM(virtual DOM)。 197 | 1. 解释器同时维护一个由其本身动态生成和修改的有效 DOM 树,其中不包含任何 HVML 标签定义的元素。为方便描述,我们将有效 DOM 称为 eDOM(effective DOM)。 198 | 1. 用户代理根据解释器动态生成或修改的文档信息维护一个最终的 DOM 树,用于渲染用户界面,并根据和用户的交互情况修改这个 DOM 树。为方便描述,我们将最终 DOM 称为 uDOM(ultimate DOM)。 199 | 1. 解释器和用户代理通过桥梁通讯保持 eDOM 和 uDOM 的一致性。 200 | 201 | ### 3.3) 隐藏的句柄属性 202 | 203 | 在 HVML 中,eDOM 树中的每个元素节点都会有一个隐藏的 `handle` 属性。这个属性相当于每个节点的标识符,用于唯一确定某个特定的元素节点,可以理解为句柄。用户代理需要在 uDOM 树中记录这个值,并在和解释器交换数据时提供这个属性值。这里,我们并不使用在 HTML 中常用的 `id` 属性,因为该属性值并不能保证唯一,且某些节点不存在这个属性值。 204 | 205 | 如下情形下,我们使用 `handle` 属性值: 206 | 207 | - 当解释器改变某个元素时,比如删除、修改属性值或内容等。 208 | - 当用户代理通知解释器某个元素上发生特定事件时,如用户点击按钮、输入框的内容变化、选择框的选中值发生变化、提交表单等。 209 | 210 | 解释器可使用下面的某个规则确定每个节点的 `handle` 属性值(或节点句柄): 211 | 212 | 1. 用于存储节点数据的内存地址,用十六进制字符串表达。在 C/C++ 语言中,相当于用来存储节点信息的数据结构之指针值。这样,我们利用这个值可以在解释器中快速定位节点对象。 213 | 1. 使用某种唯一性标识符,比如 UUID,为每一个新创建的节点赋予一个唯一的 UUID。 214 | 215 | 使用上述规则,只有新建一个节点时,才会产生新的 `handle` 值。注意,使用第一种方法时,由于内存通常使用堆(heap)来管理小单元内存,因此,删除一个节点然后再新建一个节点时,可能使用相同的 `handle` 属性值,但此时旧的节点已经被删除,因此在解释器这侧不会导致问题。但是,用户代理需要正确处理这种情形。 216 | 217 | ### 3.4) 从 eDOM 到 uDOM 218 | 219 | 当解释器第一次生成 eDOM 树之后,可以选择将 eDOM 转储(dump)为目标标记语言描述的源文档发送给用户代理,也可以选择把 eDOM 树通过桥梁整个传输给用户代理。而之后,解释器和用户代理之间仅就 DOM 树的改变进行通讯。 220 | 221 | 如果选择传输 eDOM 树而不是原始的 HTML/XML 源文档,可省去用户代理侧的文档解析工作,从而可以大大降低用户代理的工作量。这尤其适合用户代理运行在较低硬件配置(如物联网设备)的情形。另外,通过传输 eDOM 树的改变而不是完整的 eDOM 树,可节约桥梁上的传输负载,从而加快用户代理的响应时间。因此,本发明将重点描述通过桥梁传输 eDOM 树或者改变 eDOM 树的传输协议。 222 | 223 | 本质上,创建 DOM 树的过程就是从根节点开始创建一个个子节点的过程,而创建某个子节点的过程本身就是对 DOM 树的改变行为。因此,创建一个新的 DOM 树,归根结底就是在一个空的 DOM 树上做若干改变的过程。因此,从 eDOM 到 uDOM,我们如下设计传输协议: 224 | 225 | 1. 始终使用 JSON 格式来表示一个对 DOM 树的改变操作。 226 | 1. 始终使用由解释器生成的节点句柄来指代某个特定的节点。 227 | 1. 始终使用 `root` 来指代根节点的句柄。 228 | 229 | 比如,针对如下 HVML 文档: 230 | 231 | ```html 232 | 233 | 234 | 235 | Hello, world! 236 | 237 | 238 | 239 | 240 |

世界,您好!

241 |
242 | 243 | 244 |

Hello, world!

245 |
246 | 247 |
248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | No valid locale defined. 256 | 257 | 258 |
259 | 260 | 261 |
262 | ``` 263 | 264 | 当装载这个文档时传入的请求参数 `locale` 为 `zh_CN` 时,对应的 HTML 文档快照为: 265 | 266 | ```html 267 | 268 | 269 | 270 | Hello, world! 271 | 272 | 273 | 274 |
275 |

世界,您好!

276 |
277 | 278 | 279 | ``` 280 | 281 | 解释器向用户代理发送如下的数据: 282 | 283 | ```javascript 284 | { 285 | "session": 0, 286 | "sequence", 1, 287 | "handle": "root", 288 | "operation": "append", 289 | "payload" : [ 290 | { 291 | "handle": "40890000", 292 | "tag": "head", 293 | "children": [ 294 | { 295 | "handle": "40890100", 296 | "tag": "title", 297 | "children": [ 298 | { 299 | "handle": "40890200", 300 | "tag": "txt", 301 | "content": "Hello, world!" 302 | } 303 | ] 304 | } 305 | ] 306 | }, 307 | { 308 | "handle": "40890300", 309 | "tag": "body", 310 | "children": [ 311 | { 312 | "handle": "40890400", 313 | "tag": "div", 314 | "children": [ 315 | { 316 | "handle": "40890500", 317 | "tag": "p", 318 | "children": [ 319 | { 320 | "handle": "40890600", 321 | "tag": "txt", 322 | "content": "世界,您好!" 323 | } 324 | ] 325 | } 326 | ] 327 | } 328 | ] 329 | } 330 | ] 331 | } 332 | ``` 333 | 334 | 上述 JSON 对象由如下键值对组成: 335 | 336 | - `session`:用于表示会话标识符,零或正整数。用来表示操作 eDOM/uDOM 所属的会话。会话可用来管理在用户代理侧展示的多个页面,亦可用来在解释器侧识别不同的页面。 337 | - `sequence`:依次增加的序列号,正整数。 338 | - `handle`: 用于指定要操作的目标节点;这里取 `root` 表示在根节点上执行操作。 339 | - `operation`: 指定操作类型,字符串;这里取 `append` 表示在根节点上追加 `payload` 指定的节点。可取的操作类型有: 340 | - `append`:在指定节点内追加(append)一个子节点。 341 | - `prepend`:在指定节点内前置(prepend)一个子节点。 342 | - `insertBefore`:在指定节点之前插入一个节点。 343 | - `insertAfter`:在指定节点之后插入一个节点。 344 | - `displace`:置换指定节点的内容。 345 | - `update`:更改指定节点的属性或内容。 346 | - `remove`:删除指定节点,包括子节点。 347 | - `empty`:清空指定节点的内容,将删除所有子节点。当我们需要重置当前会话页面时,可在 `root` 节点上执行 `empty` 操作。 348 | - `payload`:指定操作的附加信息;上例中包括了要在根节点上创建的多个子元素,通常是一个字典数组。注意,上例中用于表述新创建子元素的 JSON 数据,和 HVML 规范文档中用来描述 HTML 片段的 JSON 表达一致。 349 | 350 | 需要注意的是,每个新创建的子节点均有一个全局唯一的句柄,通过 `handle` 指定。 351 | 352 | 之后,当我们需要在 `div` 标签中再增加一个 `p` 元素时,我们可以发送如下的数据给用户代理: 353 | 354 | ```javascript 355 | { 356 | "session": 0, 357 | "sequence", 2, 358 | "handle": "40890400", 359 | "operation": "append", 360 | "payload" : [ 361 | { 362 | "handle": "40890700", 363 | "tag": "p", 364 | "children": [ 365 | { 366 | "handle": "40890800", 367 | "tag": "txt", 368 | "content": "HVML,您好!" 369 | } 370 | ] 371 | } 372 | ] 373 | } 374 | ``` 375 | 376 | 再比如,当我们需要修改第一个 `p` 元素的 `class` 属性和文本内容时,我们可以发送如下的数据给用户代理: 377 | 378 | ```javascript 379 | { 380 | "session": 0, 381 | "sequence", 3, 382 | "handle": "40890500", 383 | "operation": "update", 384 | "payload" : [ 385 | { 386 | "handle": "40890500", 387 | "attr.class": "text-info", 388 | }, 389 | { 390 | "handle": "40890600", 391 | "content": "Hello, world!", 392 | } 393 | ] 394 | } 395 | ``` 396 | 397 | 注意,本节给出的基于 JSON 格式的 eDOM 变化表达方式,仅仅是一种可选途径,在实际的解释器和用户代理实现中,我们还可以使用不同于 JSON 的二进制表达来降低用户代理因解析 JSON 格式数据带来的负担,另外在某种程度上还可以降低数据跨网络的传输量。 398 | 399 | ### 3.5) 处理 uDOM 事件 400 | 401 | uDOM 事件是由用户代理和用户交互产生的通知,用于通知解释器执行可能的后续动作,比如当用户点击了页面上的一个按钮时,用户代理将发送一个事件到解释器。若解释器使用 `observe` 标签监听该按钮上的事件,则会触发后续的操作。 402 | 403 | 用户代理产生的事件可被打包成如下的 JSON 格式数据从用户代理发送到解释器。比如常见的按钮点击事件可以表述为: 404 | 405 | ```javascript 406 | { 407 | "session": 0, 408 | "sequence": 0, 409 | "handle": "40890F00", 410 | "event": "click", 411 | "payload": ... 412 | } 413 | ``` 414 | 415 | 其中, 416 | 417 | - `session`:会话标识符。 418 | - `sequence`: 当前会话中从用户代理发送到解释器的数据包序列号;从 0 依次增加。 419 | - `handle`:发生事件的节点句柄。 420 | - `event`:事件名称,字符串。 421 | - `payload`:事件数据。具体内容因事件而异。 422 | 423 | 再比如,当用户在一个输入框中输入内容时,将在该输入框上产生一个 `change` 事件,可用 JSON 表达为: 424 | 425 | ```javascript 426 | { 427 | "session": 0, 428 | "sequence": 10, 429 | "handle": "40890F00", 430 | "event": "change", 431 | "payload": { 432 | "content": "HVML is great!" 433 | } 434 | } 435 | ``` 436 | 437 | 在上面的事件数据中,使用了 `payload` 指定了当前输入框中的内容。需要注意的是,输入框事件的实现方式有两种。一种是如上所示,每次发送用户编辑后的完整内容;一种是发送修改信息,比如在某个位置增加一个字符,在某个位置删除一个或者多个字符等。前者比较简单,但不利于监控用户的每个编辑动作,后者比较复杂,但有利于监控用户的每个编辑动作,且在用户编辑大段文本时可以传递更少的数据量。在实际应用中,应优先使用第二种方案。 438 | 439 | 若采用第二种方案,输入框的改变事件可打包成如下的 JSON 数据包: 440 | 441 | ```javascript 442 | { 443 | "session": 0, 444 | "sequence": 10, 445 | "handle": "40890F00", 446 | "event": "insert", 447 | "payload": { 448 | "paragraph": 0, 449 | "position": 0, 450 | "content": "HVML" 451 | } 452 | } 453 | ``` 454 | 455 | 在上面事件数据中,我们使用 `insert` 作为事件名称,用来表示在指定输入框上的插入操作。在 `payload` 键值中,我们使用了 `paragraph` 表示被编辑的段落索引值,使用了 `position` 表示插入位置,`content` 表示插入的内容。 456 | 457 | 需要注意的是,当解释器和用户代理之间的通讯桥梁建立在低带宽情形下,并不能保证用户的所有编辑操作都可以精确发送,此种情况下,用户代理可能会在一个数据包中打包多个同样的编辑操作。比如,上面的编辑操作应该是用户分别键入了 H、V、M、L 四个字符,但被打包到了一个事件中发送。 458 | 459 | 类似地,我们可以将所有可能的用户交互事件通过上面的方式发送到解释器一侧,以便解释器可以根据 vDOM 中的 `observe` 元素执行后续的操作。 460 | 461 | 出于性能和节省网络带宽的考虑,我们不能将所有用户交互事件发送给解释器,比如用户移动鼠标光标移动到某个按钮上时,鼠标形状会发生变化。这种不导致后续动作的事件,应该通过 CSS 样式来控制,这样就可以由用户代理直接处理,而不是首先发送给解释器,由解释器处理后再通知用户代理做相应的改变。 462 | 463 | 解释器在收到来自用户代理的交互事件后,将做如下后续处理: 464 | 465 | 1. 如果需要,调整 eDOM 中相应节点的属性或内容。比如,对 HTML 的 `input` 元素,接收到输入改变时,要调整其 `value` 属性值。 466 | 1. 如果解释器监听了该节点上的相关事件,则根据 vDOM 做后续处理。 467 | 468 | ### 3.6) 会话管理 469 | 470 | 根据 HVML 规范,装载新的 HVML 文档可能会创建一个新的会话(类似 HTML 超链接的 `target` 属性为 `blank` 的情形),新的页面在新的会话中展示,其效果相当于在浏览器中打开一个新的窗口(或标签页)。此时,解释器可以创建一个新的进程或者线程来处理新的会话,而在用户代理侧,则会创建新的窗口或者新的标签页来渲染新的页面。 471 | 472 | 为方便起见,用户代理默认会创建一个会话,该会话的标识符始终为 0。上面的例子均使用了这一默认会话。 473 | 474 | 当解释器需要用户代理创建新的会话时,发送如下的指令: 475 | 476 | ```javascript 477 | { 478 | "sessionOp": "create", 479 | "sessionId": 1, 480 | "modal": [true | false], 481 | "parentId": 0, 482 | "title": "Window 1", 483 | } 484 | ``` 485 | 486 | 其中, 487 | 488 | - `sessionOp` 用来指定会话操作,可取如下值: 489 | + `create`:创建指定的会话。 490 | + `destroy`:销毁指定的会话。 491 | + `activate`:设置指定会话为活动会话。 492 | - `sessionId` 用来表示会话标识符,该会话标识符始终由解释器实例负责维护。 493 | - `modal` 用来表示是否为模态页面。 494 | - `parentId` 用来表示新会话的父会话,即新会话被销毁后默认返回的会话。 495 | - `title` 用来表示新会话的标题。 496 | 497 | 用户和用户代理的交互发生会话返回、切换等情形时,由用户代理通过 3.5) 中的事件通知解释器。 498 | 499 | ### 3.7) 云应用通讯协议 500 | 501 | 云应用的运行环境,不同于在同一台计算机上运行解释器和用户代理的情形,需要考虑一些特殊之处。 502 | 503 | 在针对 HTTP 这种无状态的协议设计云端服务器软件时,一般采取的方案是使用进程池或者线程池。服务器软件对来自设备侧的每个请求从进程池或者线程池中挑选一个空闲的进程来提供具体的请求处理,然后将来自请求处理进程的响应数据转发给设备侧。 504 | 505 | 然而,这种设计并不能满足云应用的情形。云应用服务器要处理的设备侧连接请求和 HTTP 等无状态的连接请求不同,云应用的连接是有状态的。理论上,每个在设备侧运行的用户代理都需要一个对应的解释器实例运行在云端。一般情况下,这个解释器实例以单个进程的形式存在,其中包含了当前的 vDOM、eDOM 快照以及当前的 vDOM 执行上下文信息。为应对海量设备的情形,云端服务器可以采取集群等方案提供服务。 506 | 507 | 但是,我们仍然要考虑一种情形,即云应用会话的中断和恢复,包括设备侧的启动、关机等情形,或者设备侧主动断开连接又恢复的情形(如设备进入休眠状态)。这些情况是云应用特有的。设想一个一直在云端运行的 HVML 程序,该程序不停地运行并更新设备侧界面上的时间,当设备侧和云端的网络断开时,对应的解释器进程以及设备侧该作何处理? 508 | 509 | 因此,云应用环境下,我们需要就如下情形做特殊处理: 510 | 511 | 1. 设备侧的启动、关机; 512 | 1. 设备侧主动断开、恢复连接; 513 | 1. 设备侧丢失连接时的停机处理; 514 | 1. 设备侧重新连接后的恢复处理。 515 | 516 | 我们可以将解释器实例看成一台物理的计算机系统,和物理计算机系统类似,我们可以对解释器实例执行如下四种操作: 517 | 518 | 1. 开机(start)。 519 | 1. 暂停(pause)。 520 | 1. 恢复(resume)。 521 | 1. 关机(shutdown)。 522 | 523 | 为此,我们引入云应用服务器(软件)作为设备和解释器实例之间的中介,其作用和 HTTP 服务器类似,负责如下事宜: 524 | 525 | 1. 监听来自设备侧的启动解释器实例的请求,验证设备身份,启动解释器实例(进程),装载指定的 HVML 文档。 526 | 1. 用封装的数据包转发解释器实例发送给设备侧用户代理的通讯数据。 527 | 1. 向设备侧发送心跳数据包。心跳数据包应该在解释器和用户代理之间无任何 eDOM/uDOM 同步数据时每隔固定的时间(如30秒)发送一次。 528 | 1. 处理设备侧发送来的数据包,将 uDOM/eDOM 同步数据转发给解释器实例。 529 | 1. 设备侧正常关机的情形下,通知解释器实例退出。 530 | 1. 在设备侧丢失连接的情形下,或者设备侧主动要求暂停的情形下,通知解释器实例转储当前的解释器状态到持久存储介质(如磁盘、数据库等)后退出。 531 | 1. 在设备侧重新连接的情形下,或者设备侧主动要求恢复的情形下,启动一个新的解释器实例,并使用已转储的解释器状态数据恢复当前的解释器,然后继续执行。 532 | 533 | 云应用服务器应该维护如下信息: 534 | 535 | 1. 设备标识符和活动解释器实例(进程标识符)之间的映射关系表。 536 | 1. 最近一次收到设备侧数据包的时间。当超过给定时间(如 60 秒)未收到来自设备侧的数据包(包括心跳应答数据包),则认为连接已丢失。 537 | 1. 设备状态(正常连接、连接丢失等)。 538 | 1. 所有从解释器实例发送给设备侧的、且未收到应答回复的 eDOM/uDOM 同步数据。一旦和云应用客户端的连接恢复,服务器应重新发送这些数据包给设备侧。 539 | 540 | 相应地,我们在设备侧引入云应用客户端(软件)作为设备侧用户代理和云应用服务器之间的中介,其负责如下事宜: 541 | 542 | 1. 在开机时向云应用服务器发送开机数据包,相当于启动解释器实例的请求。 543 | 1. 用封装的数据包转发用户代理发送给解释器的数据。 544 | 1. 处理云服务器发送的心跳数据包并应答。 545 | 1. 设备侧正常关机的情形下,发送关机数据包。 546 | 1. 通知用户代理处理丢失连接等异常情形。 547 | 548 | 云应用客户端需要维护如下信息: 549 | 550 | 1. 最近一次收到来自云应用服务器的数据包时间。当超过给定时间(如 60 秒)未收到来自云应用服务器的数据包(包括心跳数据包),则认为连接已丢失。 551 | 1. 所有发送给解释器、但未收到应答回复的 uDOM/eDOM 同步数据。一旦和云应用服务器的连接恢复,客户端应重新发送这些数据包。 552 | 553 | 附图2 给出了云应用服务器和客户端之间的关系。 554 | 555 | ![云应用服务器和客户端之间的关系](figure-2.png) 556 | 557 | 以上所述内容形成了云应用的通讯协议。该协议除了封装正常的解释器和用户代理之间的同步数据之外,还处理终端开关机、连接的丢失及恢复、暂停和恢复解释器实例等一系列的云应用特有操作。 558 | 559 | 作为示例,我们使用 JSON 格式来描述该协议,实际的实现可采用其他方便处理的格式。 560 | 561 | 从设备侧发送给云应用服务器的数据包具有如下固定的格式: 562 | 563 | ```javascript 564 | { 565 | "deviceId": "", 566 | "packageId": "", 567 | "action": "[connect | start | stop | pause | resume | update | pong | ack]", 568 | "status": , 569 | "extra": "", 570 | "data": XXX 571 | } 572 | ``` 573 | 574 | 其中各字段的含义解释如下: 575 | 576 | - `deviceId`:设备的唯一性标识符,通常使用字符串。 577 | - `packageId`:当前数据包的标识符,可在设备标识符、数据包生成时间、序列号、随机值等的基础上使用 MD5 等哈希算法生成。当服务器侧需要就某个动作做应答时,该数据包标识符将作为 `data` 字段发送回来,以表明应答对应的源数据包(source package)。 578 | - `action`:当前数据包代表的动作,可取 `connect`、`start`、`stop`、`pause`、`resume` 等控制解释器实例的动作;或者 `update`,用于更新 eDOM;或者 `pong`,用来应答心跳数据包;或者 `ack`,用于对来自解释器实例的会话管理请求以及 uDOM 更新请求做应答。 579 | - `status`:当 `action` 为 `ack` 时,用来表示状态码,和 HTTP 状态码类似,比如 200 表示正常,400 表示不存在的设备标识符,500 表示资源受限等。其他动作可取空值。 580 | - `extra`:当 `action` 为 `ack` 时,用来表示额外的可打印字符串消息,用于附加的状态信息。其他动作可取空值。 581 | - `data`:动作对应的数据。当 `action` 为 `update` 时,包含 3.5) 节中描述的用来同步 eDOM/uDOM 的数据或者交互事件数据;当 `action` 为 `ack` 时,包含源数据包标识符。其他动作情形下,该字段可取空值,或者包含用于验证设备身份的数据。 582 | 583 | 从服务器侧发送给云应用客户端的数据包具有如下固定的格式: 584 | 585 | ```javascript 586 | { 587 | "deviceId": "", 588 | "packageId": "", 589 | "action": "[response | create | destroy | activate | update | ping | ack]", 590 | "status": , 591 | "extra": "", 592 | "data": XXX 593 | } 594 | ``` 595 | 596 | 其中各字段的含义解释如下: 597 | 598 | - `deviceId`:设备的唯一性标识符,通常使用字符串。 599 | - `packageId`:是当前数据包的标识符,可在设备标识符、数据包生成时间、序列号、随机值等的基础上使用 MD5 等哈希算法生成。当设备侧需要就某个动作做应答时,该数据包标识符将作为 `data` 字段发送回来,以表明应答对应的源数据包(source package)。 600 | - `action`:当前数据包代表的动作,可取 `response`,用于对来自设备侧的解释器控制请求(`start`、`stop`、`pause`、`resume`)做出响应;或者取 `create`、`destory`、`activate` 之一,用来执行会话管理;或者取 `update`,用于更新 uDOM;或者 `ping` 表示心跳数据包;或者 `ack`,用于对来自设备侧的 eDOM 更新数据或者用户交互事件做应答。 601 | - `status`:当 `action` 为 `response` 或者 `ack` 时,用来表示响应或应答的状态码,和 HTTP 状态码类似,比如 200 表示正常,400 表示不存在的设备标识符,500 表示资源受限等。其他动作可取空值。 602 | - `extra`:当 `action` 为 `response` 或者 `ack` 时,用来表示额外的可打印字符串消息,用于附加的状态信息。其他动作可取空值。 603 | - `data`:动作对应的数据。当 `action` 为 `update` 时,包含 3.4) 节中描述的用来同步 eDOM/uDOM 的数据;当 `action` 为 `ack` 时,包含源数据包标识符。其他动作情形下,该字段可取空值。 604 | 605 | #### 3.7.1) 连接请求 606 | 607 | 连接请求数据包从设备侧发送到云应用服务器,该请求应该在和云服务器建立物理连接后立即发送,用于从云服务器端获取一个用来验证设备身份的令牌(token)。服务器侧收到该请求后,正常情况下可通过 `response` 数据包返回一个用于身份验证的令牌。当服务器遇到资源不足等情形时,可返回拒绝服务等响应。 608 | 609 | 用于身份验证的令牌通常是一个具有足够长度的随机字符串。在本方案中,我们使用非对称加密算法生成的签名来验证设备身份。在设备中,我们使用加密芯片保存一个在出厂时写入的密钥(非对称加密算法中的公钥),设备侧获得身份验证令牌后,使用公钥执行签名,并将签名信息通过随后的 `start`、`resume` 请求发送给服务器侧。服务器侧使用公钥对应的密钥对签名进行验证(验签)。如果签名验证通过,则可确认设备身份。如此,可完成设备的身份验证。 610 | 611 | #### 3.7.2) 开机请求 612 | 613 | 开机请求数据包从设备侧发送到云应用服务器,其中包括对身份验证令牌的签名信息,云应用服务器据此验证设备身份,可返回拒绝服务或者启动解释器实例进程并装载默认的 HVML 文档等,之后发送相应的 `response` 动作数据包给设备侧。 614 | 615 | 在部署云应用服务器时,可通过一个配置文件来指定需要装载的默认 HVML 文件位置,这通常是一个保存在云端的 HVML 文件路径,如使用 INI 文件格式来指定手环应用的默认 HVML 文件: 616 | 617 | ``` 618 | [bracelet] 619 | default_hvml=/srv/bracelet/hvml/index.hvml 620 | ``` 621 | 622 | #### 3.7.3) 关机请求 623 | 624 | 关机请求数据包从设备侧发送到云应用服务器,云应用服务器可根据设备身份验证信息做相应的处理,比如拒绝服务或者杀死解释器实例进程等,之后发送相应的 `response` 动作数据包给设备侧。 625 | 626 | #### 3.7.4) 暂停请求 627 | 628 | 暂停请求数据包从设备侧发送到云应用服务器,云应用服务器可根据设备身份验证信息做相应的处理,比如拒绝服务或者转储解释器实例进程的执行上下文信息并杀死解释器实例进程,之后发送相应的 `response` 动作数据包给设备侧。 629 | 630 | #### 3.7.5) 恢复请求 631 | 632 | 恢复请求数据包从设备侧发送到云应用服务器,云应用服务器可根据设备身份验证信息做相应的处理,比如拒绝服务或者启动新的解释器实例进程,装载已转储的执行上下文信息,之后发送相应的 `response` 动作数据包给设备侧。 633 | 634 | #### 3.7.6) 会话请求 635 | 636 | 会话请求数据包从云应用服务器发送到设备侧,用来创建、销毁或者激活某个会话,设备侧执行相应的操作之后发送相应的 `ack` 动作数据包给服务器。 637 | 638 | #### 3.7.7) eDOM 到 uDOM 同步数据 639 | 640 | 当解释器装载了默认的 HVML 文档,将产生 eDOM 的同步数据,服务器从解释器实例收到该数据后,封装为 `update` 数据包,然后由云应用服务器发送到设备侧,设备侧客户端应解开数据包并将其中的数据发送给用户代理,若用户代理工作正常,则发送相应的 `ack` 应答数据包给服务器。只有云服务器接受到来自设备侧对该源数据包的 `ack` 数据包之后,服务器才会发送下一个 `update` 数据包,否则新的 `update` 数据包将被服务器缓存到待发送队列中。 641 | 642 | #### 3.7.8) uDOM 到 eDOM 同步数据 643 | 644 | 当设备侧用户代理根据用户交互更新了 uDOM 之后,用户代理将发送同步数据到客户端,客户端封装为 `update` 数据包,然后由客户端发送给云应用服务器。云应用服务器在接收到 `update` 数据包之后,应解开数据包并将其中的同步数据发送给解释器实例,若解释器工作正常,则发送相应的 `ack` 应答数据包给设备侧。只有设备侧客户端接受到来自服务器侧对该源数据包的 `ack` 数据包之后,客户端才会发送下一个 `update` 数据包,否则新的 `update` 数据包将被客户端缓存到待发送队列中。 645 | 646 | #### 3.7.9) 心跳数据 647 | 648 | 当云应用服务器监测到和设备侧的最后一次通讯时间超过给定的时间(如 30 秒)时,将发送 `ping` 数据包给设备侧。设备侧在收到该数据包之后,应发送一个 `pong` 数据包。服务器收到 `pong` 数据包之后,更新和该设备的最近一次通讯时间。若超过给定时间(如 60 秒)未收到 `pong` 数据包,则可认为发生故障(如网络连接丢失,设备侧用户代理停止响应等)。 649 | 650 | 对设备侧,超过给定时间(如 60 秒)未收到来自服务器的 `update` 数据包或者 `ping` 数据包,亦可认为发生故障(如丢失网络连接,服务器故障等)。 651 | 652 | #### 3.7.10) 故障处理 653 | 654 | 在服务器侧,检测到网络故障时,服务器应: 655 | 656 | 1. 通知解释器实例暂停执行并转储当前执行上下文。 657 | 1. 杀死解释器实例进程。 658 | 1. 转储未收到 `ack` 数据包对应的 `update` 数据包。 659 | 1. 更新设备状态为暂停状态。 660 | 1. 监听恢复请求。 661 | 662 | 在服务器侧,当检测到由于资源受限(如内存不足)无法创建解释器实例等情形时,或者解释器意外退出等情形时,应向设备侧用户代理发送失败应答,用户代理展示故障信息页面。 663 | 664 | 在设备侧,当检测到网络故障时,设备侧客户端应: 665 | 666 | 1. 出现网络连接故障时,通知用户代理出现故障,正在尝试恢复。 667 | 1. 尝试重新建立到服务器的连接。 668 | 1. 若连接恢复,则发送 `resume` 请求,并重新发送未收到 `ack` 数据包的 `update` 数据包。 669 | 1. 若多次尝试后连接仍未恢复,则通知用户代理进入离线状态,等待用户主动发起重试操作。 670 | 671 | 在设备侧,当检测到由于资源受限(如内存不足)无法完成指定的 uDOM 更新操作或者会话操作时,应向解释器实例返回失败应答,必要时展示资源受限操作无法完成的信息。 672 | 673 | ### 3.8) 用户代理 674 | 675 | 如前所述,在使用 HVML 实现的云应用环境中,我们可以在开源浏览器引擎的基础上裁剪出一个用于设备侧的简化的 HTML 用户代理。 676 | 677 | 一个典型的 Web 浏览器包含有许多复杂的模块,如: 678 | 679 | - HTML/XML 解析器 680 | - JavaScript 解释及执行引擎 681 | - DOM 管理 682 | - CSS 解析器 683 | - 渲染引擎 684 | - 网络协议(如 HTTP 等)支持 685 | - 多媒体(音视频解码)支持 686 | 687 | 而简化后的用户代理可将 HTML/XML 解析器、JavaScript 解释及执行引擎移除掉: 688 | 689 | - DOM 管理(eDOM 和 uDOM 同步模块) 690 | - CSS 解析器 691 | - 渲染引擎 692 | - 网络协议(如 HTTP 等)支持,用于获取图片、CSS 文件等外部资源 693 | - 多媒体(音视频解码)支持 694 | - 内建故障展示及处理界面 695 | 696 | 其中,eDOM 和 uDOM 同步模块是新增的。另外,用户交互产生的超链接跳转、表单提交等事件,均应该做相应的调整发送给云应用服务器做处理,而不能由用户代理直接处理。 697 | 698 | 以 WebKit 为例,当我们将 JavaScript 引擎、HTML/XML 解析器移除之后,其编译后的二进制代码体积将缩小 30MB,而运行时内存占用可缩小 256MB(每页面)。此时的用户代理,本质上就是一个 DOM/CSS 的渲染器。 699 | 700 | 在设备侧,我们还可以在传统的 GUI 支持系统之上实现更为简单高效的用户代理,但需要做一些改进,如: 701 | 702 | - 使用 XML 描述界面。 703 | - 增加 eDOM 和 uDOM 同步模块,并支持界面的动态调整。 704 | - 使用类似 CSS 的技术来描述界面组件(Widget)的样式、布局、动画效果等。 705 | 706 | 由于用户代理的实现不属于本发明的关键点,故而本文档不做详细描述。 707 | 708 | # 4) 本发明创造的优点 709 | 710 | 本发明提出的基于 HVML 语言实现云应用的方法及其装置,其主要优点可总结如下: 711 | 712 | 1. 通过仅在云端和设备端交换 DOM 树节点及其变化,大幅度降低了使用云游戏、远程桌面等方案时存在的高带宽要求。在不需要处理多媒体视频流的情形下,终端设备无需集成高耗能、高成本的多媒体视频硬件解码器,这有助于降低终端设备的硬件成本。 713 | 1. 通过 eDOM 和 uDOM 之间的同步机制和事件传递机制,将 HTML/XML 解析器转移到了 HVML 解释器侧实现,从而降低了用户代理的复杂性。 714 | 1. 通过将交互事件的处理置于云端执行,移除了 HTML/XML 用户代理侧的脚本解释器,进一步降低了对终端硬件的要求。 715 | 716 | 总之,在物联网应用环境中,通过 eDOM 和 uDOM 的基于网络连接的同步和交互机制,提出了一种将用户代理和控制逻辑分别运行在设备端和云端的机制和方法,提供了一种有效实现云应用的装置,可有效降低物联网应用的开发工作量,并降低物联网终端的硬件成本。 717 | 718 | ## 5) 替代方案 719 | 720 | (暂无) 721 | 722 | ## 6) 本发明的关键点和欲保护点 723 | 724 | 1. vDOM、eDOM、uDOM 的互操作: 725 | - 使用隐藏 `handle` 属性保持 eDOM 和 uDOM 同步的方法和装置。 726 | - 用户在用户代理侧的交互事件到解释器的传递方法和装置。 727 | - eDOM 和 uDOM 保持一致性的同步协议。 728 | 1. 通过网络远程连接解释器和用户代理实现基于 HVML 的云应用方案: 729 | - 云应用服务器和设备侧客户端之间的通讯协议。 730 | - 云应用服务器处理解释器实例开机、停机、暂停、恢复等的处理。 731 | - 云应用服务器及设备侧客户端处理异常的方案。 732 | 733 | ## 7) 附图及简要说明 734 | 735 | ### 7.1) 附图1 解释器和用户代理的关系 736 | 737 | ![解释器和用户代理的关系](figure-1.png) 738 | 739 | 附图1 给出了 HVML 解释器和 XML/HTML 用户代理之间的关系: 740 | 741 | 1. 解释器维护 HVML 对应的 vDOM 树,其中包括模板标签、动作标签等定义的元素以及数据。 742 | 1. 解释器同时维护一个由其本身动态生成和修改的 eDOM 树,其中不包含任何 HVML 标签定义的元素。 743 | 1. 用户代理根据解释器动态生成或修改的文档信息维护一个 uDOM 树,用于渲染用户界面。 744 | 1. 解释器和用户代理通过通讯桥梁保持 eDOM 和 uDOM 的一致性。 745 | - 当 HVML 的操作修改了 eDOM 中的某个节点时,解释器通过桥梁将该修改发送给用户代理,用户代理更新 uDOM,并根据 uDOM 重新渲染整个或部分界面。 746 | - 用户代理将用户在真实文档上产生的交互事件(比如按钮的单击事件)以及用户操作导致文档节点发生的变化(如输入框的内容变化、列表框的选择变化等),通过桥梁发送给解释器。 747 | 1. 解释器负责监听在 HVML 头部中定义的数据源,并执行 `observe` 定义的操作。 748 | 1. 解释器监听来自用户代理的交互事件或者文档节点的变化通知,并执行 `observe` 标签定义的操作。 749 | 750 | ### 7.2) 附图2 云应用服务器和客户端的关系 751 | 752 | ![云应用服务器和客户端的关系](figure-2.png) 753 | 754 | 附图2 给出了云应用服务器和客户端的关系。 755 | 756 | 云应用服务器作为设备和解释器实例之间的中介,其作用和 HTTP 服务器类似,负责如下事宜: 757 | 758 | 1. 监听来自设备侧的启动解释器实例的请求,验证设备身份,启动解释器实例(进程),装载指定的 HVML 文档。 759 | 1. 用封装的数据包转发解释器实例发送给设备侧用户代理的通讯数据。 760 | 1. 向设备侧发送心跳数据包。心跳数据包应该在解释器和用户代理之间无任何 eDOM/uDOM 同步数据时每隔固定的时间(如30秒)发送一次。 761 | 1. 处理设备侧发送来的数据包,将 uDOM/eDOM 同步数据转发给解释器实例。 762 | 1. 设备侧正常关机的情形下,通知解释器实例退出。 763 | 1. 在设备侧丢失连接的情形下,或者设备侧主动要求暂停的情形下,通知解释器实例转储当前的解释器状态到持久存储介质(如磁盘、数据库等)后退出。 764 | 1. 在设备侧重新连接的情形下,或者设备侧主动要求恢复的情形下,启动一个新的解释器实例,并使用已转储的解释器状态数据恢复当前的解释器,然后继续执行。 765 | 766 | 设备侧的云应用客户端作为设备侧用户代理和云应用服务器之间的中介,负责如下事宜: 767 | 768 | 1. 在开机时向云应用服务器发送开机数据包,相当于启动解释器实例的请求。 769 | 1. 用封装的数据包转发用户代理发送给解释器的数据。 770 | 1. 处理云服务器发送的心跳数据包并应答。 771 | 1. 设备侧正常关机的情形下,发送关机数据包。 772 | 1. 通知用户代理处理丢失连接等异常情形。 773 | 774 | ## 8) 有关专利查重结果的补充说明 775 | 776 | ### 8.1) CN201610808984.5:网页资源加载方法及网页资源加载装置 777 | 778 | 该发明申请本质上提供了一种节约 HTML 页面内容传输量的方法。其原理是让服务器仅返回和预先保存的模板文件的差异。为此, 779 | 780 | 1. 该发明申请需要在设备端(客户终端)预先保存可能被用户访问的所有网页的一个副本(文档模板)。 781 | 1. 当用户请求某个网页的内容时,服务器返回和文档模板之间的差异,而不是整个网页内容。 782 | 783 | 于是达到了节约网页内容在网络上传输量的目的。该发明申请有如下不足: 784 | 785 | 1. 现代 Web 页面的内容繁多,除了 HTML 标记语言描述的部分之外,还有 CSS 以及 JavaScript 代码。仅传输 HTML 文档内容和预存模板的差异部分,对降低网络传输的流量所起到的作用有限。 786 | 1. 该方法仅适合于有限的应用场景,尤其是 CSS、JavaScript 等固定不变的情形。 787 | 788 | 该发明申请所述的预存模板和本发明所述的 vDOM 有本质不同。为了说明该发明专利的工作原理,看如下 HTML 文档: 789 | 790 | ```html 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 |

800 |

801 | 802 | 803 | ``` 804 | 805 | 该文档就是一个预存模板,而我们所需要的最终 HTML 文档可能是下面这个样子的: 806 | 807 | ```html 808 | 809 | 810 | 811 | 812 | 问候页 813 | 814 | 815 | 816 | 817 |

818 | 世界,您好! 819 |

820 | 821 | 822 | ``` 823 | 824 | 为了达到减少网络传输量的目的,该方面的方法是仅传输两个网页的差异部分。使用 Unix 中的 `diff` 命令,可获得前后两个 HTML 文档的差异部分: 825 | 826 | ``` 827 | 4a5 828 | > 问候页 829 | 9a11 830 | > 世界,您好! 831 | ``` 832 | 833 | 以上差异内容,表示最终的文档在预存模板的基础上,有如下改变: 834 | 835 | 1. `4a5` 表示预存模板文档的第 4 行和最终文档的第 5 行存在差异,增加了一行,内容为`问候页`。 836 | 1. `9a11` 表示预存模板文档的第 9 行和最终文档的第 11 行存在差异,增加了一行,内容为`世界,您好!`。 837 | 838 | 有了上面这个例子,就可以很清楚地理解`预存模板`和 vDOM 之间的区别: 839 | 840 | 1. 预存模板是静态的 HTML 文件;而 vDOM 是根据 HVML 构造的一个虚拟 DOM 树。 841 | 1. HTML 是文档的标记语言,不含有任何动态成分;而 HVML 是由权利人提出的一种新的数据驱动编程语言。 842 | 1. 预存模板是静态的;vDOM 表述的是可动态执行操作并生成对 eDOM 的更改指令。 843 | 844 | ### 8.2) CN201810679854.5:一种基于云端服务器的渲染方法及系统 845 | 846 | 该发明申请所述的方案,是将浏览器整个运行在云端,然后将浏览器渲染完成的页面图像编码为 PNG 或者 JPEG 格式,然后发送到客户端(电视棒)显示。 847 | 848 | 该方案和本发明申请 `2.1) 云游戏方案` 中描述的传输 H.264 编码的视频流本质上一样的。只是该方案传输的是一帧一帧的 JPEG 或者 PNG 图片,而云游戏传输的经过编码的运动图像码流。 849 | 850 | 顺便提一下该方案存在的不足: 851 | 852 | ### 8.3) CN201310099842.2:基于云计算的浏览器架构与解析方法 853 | 854 | 从发明要解决的问题上看,该发明申请所述的方案比较接近本发明申请。遗憾的是,该发明申请没有公开必要的技术细节,所以大概只能算是一种构思。 855 | 856 | 从有限的技术方案描述中可以看出,该发明申请(前者)和本发明申请(后者)所述方案之间主要存在如下差异: 857 | 858 | 1. 前者未描述经过云端浏览器解析后的`网页元素`具体包含哪些内容;而后者给出了 eDOM 到 uDOM 数据传递方法。 859 | 1. 前者未定义运行在本地终端的浏览器渲染模块,如何处理和用户的交互;而后者给出了 uDOM 到 eDOM 的交互事件处理方法。 860 | 861 | ### 8.4) 其他 862 | 863 | 有关补充建议,已在本技术交底书的相应内容中做了补充。主要见新增的 3.7.1) 小节及 3.7.2) 小节。 864 | 865 | ## 附:商标声明 866 | 867 | 本文提到的产品、技术或者术语名称,涉及北京飞漫软件技术有限公司在中国或其他地区注册的如下商标: 868 | 869 | 1) 飛漫 870 | 871 | ![飛漫](https://www.fmsoft.cn/application/files/cache/thumbnails/87f47bb9aeef9d6ecd8e2ffa2f0e2cb6.jpg) 872 | 873 | 2) FMSoft 874 | 875 | ![FMSoft](https://www.fmsoft.cn/application/files/cache/thumbnails/44a50f4b2a07e2aef4140a23d33f164e.jpg) 876 | 877 | 3) 合璧 878 | 879 | ![合璧](https://www.fmsoft.cn/application/files/4716/1180/1904/256132.jpg) 880 | ![合璧](https://www.fmsoft.cn/application/files/cache/thumbnails/9c57dee9df8a6d93de1c6f3abe784229.jpg) 881 | ![合壁](https://www.fmsoft.cn/application/files/cache/thumbnails/f59f58830eccd57e931f3cb61c4330ed.jpg) 882 | 883 | 4) HybridOS 884 | 885 | ![HybridOS](https://www.fmsoft.cn/application/files/cache/thumbnails/5a85507f3d48cbfd0fad645b4a6622ad.jpg) 886 | 887 | 5) HybridRun 888 | 889 | ![HybridRun](https://www.fmsoft.cn/application/files/cache/thumbnails/84934542340ed662ef99963a14cf31c0.jpg) 890 | 891 | 6) MiniGUI 892 | 893 | ![MiniGUI](https://www.fmsoft.cn/application/files/cache/thumbnails/54e87b0c49d659be3380e207922fff63.jpg) 894 | 895 | 6) xGUI 896 | 897 | ![xGUI](https://www.fmsoft.cn/application/files/cache/thumbnails/7fbcb150d7d0747e702fd2d63f20017e.jpg) 898 | 899 | 7) miniStudio 900 | 901 | ![miniStudio](https://www.fmsoft.cn/application/files/cache/thumbnails/82c3be63f19c587c489deb928111bfe2.jpg) 902 | 903 | 8) HVML 904 | 905 | ![HVML](https://www.fmsoft.cn/application/files/8116/1931/8777/HVML256132.jpg) 906 | 907 | 9) 呼噜猫 908 | 909 | ![呼噜猫](https://www.fmsoft.cn/application/files/8416/1931/8781/256132.jpg) 910 | 911 | 10) Purring Cat 912 | 913 | ![Purring Cat](https://www.fmsoft.cn/application/files/2816/1931/9258/PurringCat256132.jpg) 914 | 915 | -------------------------------------------------------------------------------- /deprecated/setup-dev-env-using-docker.md: -------------------------------------------------------------------------------- 1 | # 使用 docker 搭建 HVML 的开发和测试环境 2 | 3 | Linux(Ubuntu)是推荐的开发和测试环境,但是有些小伙伴平时使用的是 macOS 或者 Windows 系统。这里介绍 在macOS 上使用 docker 搭建 HVML 开发和测试环境的步骤。Windows 上的过程类似。 4 | 5 | ### 第一步,安装并启动 docker 6 | 7 | 下载 https://download.docker.com/mac/stable/Docker.dmg 8 | 9 | 安装并启动。 10 | 11 | ### 第二步,获取并运行 Ubuntu 镜像 12 | 13 | ``` 14 | # docker pull ubuntu 15 | # docker run -it ubuntu /bin/bash 16 | ``` 17 | 18 | 这个时间点获取的 Ubuntu 镜像是: 19 | 20 | ``` 21 | 20.04.1 LTS (Focal Fossa) 22 | ``` 23 | 24 | ### 第三步,安装开发和使用的软件包 25 | 26 | ``` 27 | # apt-get update 28 | # apt-get install -y git cmake g++ valgrind python3 29 | ``` 30 | 注意,如果不安装g++,会出现下述错误提示: 31 | ``` 32 | gcc: fatal error: cannot execute 'cc1plus': execvp: No such file or directory 33 | compilation terminated. 34 | ``` 35 | 随着项目的进展,可能会用到其他的工具,可以用上述命令安装。 36 | 37 | ### 第四步,开发测试 38 | 39 | 例如: 40 | ``` 41 | # git clone https://github.com/HVML/purring-cat.git 42 | # cd purring-cat/parser 43 | ``` 44 | 阅读该目录下的 README.md 文件,并尝试其中的步骤。 45 | 46 | -------------------------------------------------------------------------------- /en/the-initial-idea-of-hvml-en.md: -------------------------------------------------------------------------------- 1 | # Talk about HVML, Its Origin and Future 2 | 3 | Vincent Wei 4 | 5 | HVML is a new type of high-level programming language proposed by the author in the process of developing the combined operating system. The novel "Biography of Coding" introduces this language as follows: 6 | 7 | > The programming language invented by Lao Wei is officially called HVML, which is only one letter different from the web page markup language HTML we know. Some friends nicknamed it "Snoring Cat". HVML is completely different from many programming languages we know, such as Basic, Python, C/C++, etc. HVML proposes a data-driven concept. And there are no flow control statements such as if-then and do-while in the code, and all operations are based on data. For example, the input data of the program is an array, then we perform iterations on this array, and pick and process the elements in the array. and so on. 8 | > 9 | > —— "Biography of Coding" Chapter 10 10 | 11 | This article will tell you about the origin of HVML and the author's vision for its future. 12 | 13 | ### The Origin of HVML 14 | 15 | With the development of Internet technology and applications, the Web front-end development technology formed around HTML/CSS/JavaScript has developed rapidly, and it can even be described as "a thousand miles a day". Five years ago, front-end frameworks based on jQuery and Bootstrap became popular. And since 2019, frameworks based on virtual DOM technology have been favored by front-end developers, such as the famous React.js ( ), Vue.js (), etc. It is worth noting that WeChat applets, quick apps, etc. also use this virtual DOM technology to build application frameworks. 16 | 17 | The so-called "virtual DOM" means that the front-end application creates and maintains a virtual document object tree through JavaScript, and the application script does not directly operate the real DOM tree. In the virtual DOM tree, some process control based on variables is realized through some special attributes, such as conditions, loops, etc. Virtual DOM technology provides some benefits as follows: 18 | 19 | 1. Since scripts do not use script programs to directly operate the real DOM tree, on the one hand, the complexity of front-end development is simplified through the existing framework. On the other hand, frequent operations on the DOM tree for content, thereby improving page rendering efficiency and user experience. 20 | 1. Through the virtual DOM technology, the modification of a certain data by the program can be directly reflected on the content of the data-bound page, and the developer does not need to actively or directly call the relevant interface to operate the DOM tree. This technique provides so-called "reactive" programming, which greatly reduces the developer's workload. 21 | 22 | Front-end frameworks represented by React.js and Vue.js have achieved great success. It seems that Internet companies still have a lot of money, so they can keep building all kinds of wheels recklessly. 23 | 24 | However, application developers on embedded or IoT devices cannot enjoy the dividends brought by these technological advances. To develop applications on embedded or IoT devices, the programming language C/C++ is still used in most cases. Although there are some IoT operating systems that try to introduce JavaScript or Python language support into the system, only one programming language is not enough. For example, in the extensive and profound web front-end technology, JavaScript does play an important role, but the foundation of the web front-end technology is DOM and CSS: DOM provides a description of the elements in the document or interface, their attributes, and content. structure, while CSS describes the rendering characteristics of elements such as layout, style, and animation effects. In a word, the exquisite web page effect is not described by JavaScript, but determined by DOM and CSS. 25 | 26 | Therefore, if we want to use the convenience brought by Web front-end technology to develop GUI applications in a non-browser environment, the most important thing to introduce is DOM and CSS, not just support for a scripting language. 27 | 28 | In addition to developers of embedded or Internet of Things applications, other developers who use non-JavaScript languages, such as developers who use Python for artificial intelligence and big data analysis, cannot easily use the benefits of Web front-end technology. If you want to use it, you have to make a lot of troubles, for example, to visualize the calculation results, you must either use various GUI bindings of Python, or go around and feed data to the browser, and you must learn an additional scripting language. The hard work during this period is also too numerous to describe. 29 | 30 | The author has developed MiniGUI for more than 20 years, and knows how to use C/C++ to develop interfaces, and writing a few lines of C/C++ codes can also create many exquisite interfaces. But for a few years, the author has engaged in Web front-end development several times, and was impressed by the subtlety of the Web front-end technology development interface. Later, I was reluctant to use C/C++ language to develop the interface. If it wasn't for the money, who would want to develop a GUI in C/C++ in 2020? There are still people who want to replace Qt with a "domestic" C++ GUI system. Pooh! That was twenty years ago. If you want to engage in domestic substitution, can you look ahead first? 31 | 32 | On the other hand, in order to support complete Web front-end technology in embedded systems or Internet of Things devices, the required storage size and operating memory are relatively large, and GPU support is also required to obtain good results. Therefore, in the development of the hybrid operating system (HybridOS), we transformed the Web front-end technology through some small technical breakthroughs, so that developers can use less JavaScript code to complete more work. However, during the development of HybridOS, we realized that tinkering with the front-end technology of the Web can only be a stopgap measure—we need a new development framework, especially a programming language to break the existing framework and limitations of the front-end Web technology. limit. 33 | 34 | So there is HVML. If summed up, the birth of HVML has its historical mission: 35 | 36 | 1. Through a complete, self-consistent, and highly abstract new programming language, further summarize and summarize some technical attempts made by React.js, Vue.js, etc. around virtual DOM technology. 37 | 1. Break the coupling between Web front-end technology and JavaScript, so that other programming languages, such as Python, Lua, C/C++, etc., can also directly use the convenience brought by Web front-end technology. 38 | 1. Bring new changes to traditional GUI development, including design tools and development frameworks. 39 | 1. Bring new possibilities for IoT application development in the cloud environment. 40 | 41 | This is where HVML comes in. In the future, HVML will become the preferred programming language for HybridOS App development. 42 | 43 | ## HVML Overview 44 | 45 | The following uses a simple example to describe the basic appearance of HVML. For the detailed specification of HVML, interested readers can click the original link at the end of the article. 46 | 47 | The HTML page generated by the sample HVML code below will display three sets of information on the screen: 48 | 49 | 1. The system status bar displayed at the top of the page is used to display the current time, WiFi signal strength, battery power information, etc. This information will be dynamically updated by listening to status events from the system. 50 | 1. Display the user list in the middle of the page, and each user item includes user name, avatar and other information. The information comes from an array of dictionaries expressed in JSON. When the user clicks on a user's avatar, the HVML code will load a modal dialog with more information about that user. 51 | 1. Display a search engine link at the bottom of the page. The specific search engine is determined according to the language region (locale) information where the system is located. 52 | 53 | ```html 54 | 55 | 56 | 57 | 58 | [ 59 | { "id": "1", "avatar": "/img/avatars/1.png", "name": "Tom", "region": "en_US" }, 60 | { "id": "2", "avatar": "/img/avatars/2.png", "name": "Jerry", "region": "zh_CN" } 61 | ] 62 | 63 | 64 | 65 | [ 66 | { "id" : "foo", "interval" : 500, "active" : "yes" }, 67 | { "id" : "bar", "interval" : 1000, "active" : "no" }, 68 | ] 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
  • 77 | 78 | $?.name 79 |
  • 80 |
    81 | 82 | 83 | { 84 | "id": "$?.attr.data-value", "avatar": "$?.content[0].attr.src", 85 | "name": "$?.children[1].textContent", "region": "$?.attr.data-region" 86 | }, 87 | 88 | 89 |
    90 | 91 | 92 | 93 | 12:00 94 | > 95 |
    96 | 97 |
      98 | 99 | 100 | 101 | 102 | 103 | 104 |

      Bad user data!

      105 |
      106 |
      107 |
    108 | 109 | 110 |

    Baidu

    111 |
    112 | 113 | 114 |

    Bing

    115 |
    116 | 117 | 118 |

    Google

    119 |
    120 | 121 |
    122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |

    You forget to define the $global variable!

    134 |
    135 | 136 |

    Bad global data!

    137 |
    138 | 139 |

    Bad archetype data!

    140 |
    141 |
    142 |
    143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 |
    179 | ``` 180 | 181 | Readers familiar with HTML will find the above code familiar. Yes, like HTML, HVML uses tags; but unlike HTML, HVML is dynamic and expresses programs, while HTML is static and expresses documents. 182 | 183 | We compare some of the features of HVML with the above examples. 184 | 185 | The first is data-driven programming. Through operations such as data-based iteration, insertion, update, and clearing, developers can dynamically generate the final XML/HTML document without writing a program or only need to write a small amount of program. For example, the `iterate` tag in the sample code above iterates over the data represented by the `$users` variable (defined using the `init` tag in `header`), and then in the `ul` element of the final document Several `li` elements have been inserted. Instead, the properties of `li` elements (including child elements) are defined by an `archetype` tag using `$? ` to refer to a data unit in `$users` being iterated over. 186 | 187 | In the sample code above, we use the system built-in variable `$TIMERS` to define timers, and each timer has a global identifier, interval time and flag whether to activate. To activate a timer, we only need to use the `update` tag of HVML to modify the corresponding key value without calling a specific programming interface. This is another use of data-driven programming - we don't need to provide additional APIs for timers or other similar module operations. 188 | 189 | In addition, in the sample code above, we use the `observe` tag to observe new data or changes in the document itself and user interaction events, which can realize dynamic updates of XML/HTML documents or data. For example, in the last `observe` tag, a new `user.hvml` file is loaded by listening to the click event on the user's avatar, and the detailed information of the corresponding user is displayed in the form of a modal dialog box. 190 | 191 | The second is to completely decouple the interface, interaction, and data. Through the programming model and method introduced by HVML, the XML/HTML document content used to express the interface can be completely generated and dynamically adjusted by HVML, which avoids directly manipulating the data structure of the document (that is, the document object tree, or DOM for short) in the program code. tree), and the program only needs to focus on the generation and processing of the data itself. In this way, the decoupling of interface and data is realized. for example: 192 | - HVML can define the mapping relationship between data and DOM elements in the document fragment template or data template (such as the `archetype` or `archedata` tag in the sample code), without writing additional code to complete the data to DOM element attributes, Assignment operations such as content. 193 | - HVML separates the display of errors and exceptions from the program code. As long as the program generates appropriate errors or exceptions (such as `error` and `except` tags in the sample code), the handling of errors or exceptions is directly in the Defined in HVML, this not only isolates the program and the interface, but also improves the maintainability of the code. 194 | 195 | Again a JSON representation of the data. HVML provides a consistent interface for operations on documents and data. HVML requires that all external data be expressed in JSON format. JSON format is a form of data expression that can be read by humans and machines. It can express complex objects based on basic data units such as numbers, strings, arrays, and dictionaries. Since HTML/XML document fragments (DOM subtrees) can be represented as JSON-formatted data, HVML can also be used to manipulate data represented in JSON. In HVML, we also provide support for dynamic JSON objects. We can use external script programs to implement our own dynamic JSON objects, and we can perform functions similar to function calls on these objects. 196 | 197 | Finally, HVML uses action tags (usually some English verbs, such as `init`, `update`, `iterate`, etc.) and the associated preposition or adverb attributes to define the data on which the action tag depends and the target operation location and execute conditions to complete specific document operations. This is very different from common programming languages. The description method of HVML is closer to natural language, which can greatly reduce the learning threshold. 198 | 199 | Due to space limitations, we do not intend to introduce HVML in detail in this article. It is enough for readers to have a perceptual understanding of HVML. Readers who are interested in knowing the detailed specifications can refer to the link to the original text at the end of the article. But be patient, defining a complete and self-consistent programming language is not an easy task, so if you want to read it, you have to find a long time and read it patiently and carefully. 200 | 201 | ## How HVML Changes Traditional GUI Development 202 | 203 | We assume a GUI system that uses XML to describe the components (widgets) on the interface. Now, we want to use this GUI system to develop a simple file open dialog box. The general interface requirements are as follows: 204 | 205 | 1. There is a list box (Listbox), which lists the directories and files under the current path (collectively referred to as directory items). The user can use the mouse or keyboard to switch the currently selected item in the list box, and generate an event that the selected item changes. 206 | 1. At the top of the list box, there is a text label (Label), which shows the current path. 207 | 1. When the user clicks the "Open" button (Button) below the list box, if the currently selected item in the list box is a directory, enter this directory, modify the content of the text label used to display the current path, and use the new path Directory items populate the list box, if the currently selected item is a file, returns the selected file. 208 | 209 | For the above interface and interaction requirements, we can usually use the following XML file description: 210 | 211 | ```xml 212 | 213 | 216 | 217 | 218 | .. 219 | vincent 220 | david 221 | README.txt 222 | 223 | 224 | 227 | 228 | ``` 229 | 230 | In order to meet the above interaction processing requirements, we use HVML to describe the dynamic generation and interaction process of this interface: 231 | 232 | ```html 233 | 234 | 235 | 236 | 237 | { 238 | "curr_path": "/home/", 239 | "selected_type": "dir", 240 | "selected_name": "..", 241 | } 242 | 243 | 244 | 245 | 246 | 249 | 250 | 251 | $?.name 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | "$fileInfo.curr_path{$2.name}/" 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | ``` 294 | 295 | Let's make some explanations for the key parts of the above HVML code. 296 | 297 | First, the code uses a global `$fileInfo` variable to record the current path (initially `/home/`) and the type (initially `dir`) and name (initially `..` ). When the user selects a new directory item in the listbox, the `selected-item-changed` event is observed and the `selected_type` and `selected_name` keys in `$fileInfo` are updated. An example of the `payload` key value for this event is as follows: 298 | 299 | ```json 300 | { 301 | "type": "dir", 302 | "name": "david", 303 | } 304 | ``` 305 | 306 | Second, the code uses the `choose` element and an external executor (`CLASS: CDirEntries`) to get all directory entries in the current path. The returned result data is approximately: 307 | 308 | ```json 309 | [ 310 | { "type": "dir", "name": "david" }, 311 | { "type": "dir", "name": "vincent" }, 312 | { "type": "file", "name": "README.txt" }, 313 | ] 314 | ``` 315 | 316 | On top of the above results, populate the listbox with `iterate` elements. 317 | 318 | Finally, the above code observes the `clicked` event when the user clicks the `Open` button. When handling the event, do the work by checking `$fileInfo.selected_type`: 319 | 320 | - If the currently selected directory item type is a directory, switch to that directory. In this case, the list box is first emptied and then populated with the directory items under the new path. 321 | - If the currently selected directory item type is a file, use the `back` tag to return to the previous page and return `fileInfo` data. 322 | 323 | In the above code, the implementation of the external selector `CDirEntries` is very simple, which is to list the directory entries under the given path and return an array of dictionaries as required. It is very simple to implement in Python, so I will omit it here. 324 | 325 | If we use the extended URL schema (lcmd) mentioned in HybridOS to directly execute local system commands, we don't even need to write any code, but just use `request`: 326 | 327 | ```html 328 | 329 | 330 | 331 | 332 | 333 | ``` 334 | 335 | In this way, developers can implement a simple file browsing and opening dialog box without writing any programs. 336 | 337 | Obviously, if HVML is used, the development efficiency of traditional GUI applications will be greatly improved and the development cycle will be shortened. Of course, the traditional GUI support system needs to provide XML-based UI description support and CSS-like rendering support for layout, style, animation, etc. 338 | 339 | ## The Future of HVML: Cloud Applications 340 | 341 | HVML has more potential than the above examples suggest. In the future, we can even run the HVML code on the cloud, and control the interface display on the device through the cloud, thus forming a new cloud application solution. 342 | 343 | We assume that a smart bracelet displays information such as the current time, local temperature, wearer's heartbeat information and step information. And this smart bracelet exchanges information with the cloud server through MQTT (a lightweight message communication protocol), such as Send the wearer's heartbeat and step information, geographic location information to the cloud server, and obtain information such as time and weather conditions at the current location. In the traditional implementation method, we generally need to develop a GUI system running on the smart bracelet, and then communicate with the cloud to obtain data. And the modification of the interface is completely in charge of the device-side code. If you want to change the style of the interface, in most cases, you need to upgrade the firmware of the entire smart bracelet. 344 | 345 | But if we use HVML, we can control the interface display of the device through the cloud. The HVML code running on the cloud looks like this: 346 | 347 | ```html 348 | 349 | 350 | 351 | 352 | 353 | 354 | [ 355 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 356 | ] 357 | 358 | 359 | 360 | 361 | 362 | 363 |
    364 | 365 | 366 | 367 |
    368 | 369 |
    370 | 371 | 372 | 373 |
    374 | 375 |
    376 | 377 | 378 | 379 |
    380 | 381 |
    382 | 383 | 384 | 385 |
    386 | 387 | 388 | 389 | 390 | 391 |
    392 | ``` 393 | 394 | The main points are as follows: 395 | 396 | 1. The HTML document generated by the code or the changes to the HTML document will be sent to the device through a long connection similar to WebSocket. And the device will re-render the user interface based on this information. 397 | 1. The code monitors the data sent by the smart bracelet (device) to the cloud through MQTT, including heartbeat, temperature, steps and other information, and updates the corresponding label content. 398 | 1. The code sets a timer, runs every 1 second, and updates the label content corresponding to the clock. 399 | 1. The code uses an external selection executor `CDumpEvent` to dump all events from `mqtt` to the cloud database. 400 | 401 | This brings about the following notable changes: 402 | 403 | 1. The complex logic code will all run on the cloud. And the device only needs to have an HTML/XML user agent with sufficient functions, and usually only needs to include a renderer to render the final user interface according to the DOM tree and CSS. 404 | 1. When we need to adjust the display effect or function of the device, we only need to modify the HVML code without updating the firmware of the device. 405 | 1. We can also use external scripts to organically integrate other functions running on the cloud, such as database storage, data analysis, and artificial intelligence. 406 | 407 | Writing here, the author is really moved by the point of view he has always emphasized: **Programming language is the thing that determines the soul and genes of the operating system, and it is the jewel in the crown of the basic software ecology! ** 408 | 409 | ## HVML Reference Implementation Development Team 410 | 411 | Now, the appearance of HVML is roughly formed. HVML can be realized, which is a large software project no less than a browser engine. In addition, HVML can be used in various scenarios, and can be bound to different external scripting languages to form different systems. If we want to apply HVML to a cloud environment, we also need to develop an application server. 412 | 413 | As the author said in the novel "Biographic of Coding": **Like the old saying, once the key link is grasped, everything falls into place. The programming language is that key link.**. The changes brought about by a new programming language involve the reconstruction of basic software, changes in development models, changes in development tools, and the generation of new protocols and software. At the same time, it also involves changes in the upstream and downstream cooperation relationships in the industry. Just imagine, if the cloud applications described in this article become a reality, will the many efforts we are making in the IoT operating system be worthless? 414 | 415 | In order to let everyone see how HVML actually turns as soon as possible, the author organized a small open source collaborative team to develop a reference implementation of HVML, with the goal of providing a solution for the Python ecosystem that can directly use Web front-end technology. The development team is now working, and we will report back as soon as we have results. 416 | 417 | We expect more people or companies to join in the development of the HVML reference implementation. However, it takes a long period for a new programming language to mature from birth to maturity. Before seeing how HVML works, most people will hold a wait-and-see attitude. This is human nature. But opportunities must be reserved for conscientious people. 418 | 419 | - If you own a promising basic software company, a potential user of HVML, or an upstream operating system company, such as a chip company, you can join the HybridOS partner program (see for details) ), send several engineers to participate in the development of the HVML reference implementation. The author believes that your team and company will benefit greatly from this. 420 | - If you represent an individual, you can give a reward to the HVML reference implementation project (reward at the end of the article or go to the web page ). Your encouragement, no matter big or small, will become the driving force for us to move forward! 421 | 422 | Stay tuned, a revolution is slowly kicking off! 423 | -------------------------------------------------------------------------------- /extensions/vim/ftdetect/hvml.vim: -------------------------------------------------------------------------------- 1 | " ftdetect/hvml.vim 2 | autocmd BufNewFile,BufRead *.hvml setfiletype hvml 3 | -------------------------------------------------------------------------------- /extensions/vim/syntax/hvml.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: HVML 3 | " Maintainer: Vincent Wei 4 | " Last Change: 2025 Mar. 28 5 | 6 | " quit when a syntax file was already loaded 7 | if exists("b:current_syntax") 8 | finish 9 | endif 10 | 11 | if !exists("main_syntax") 12 | let main_syntax = 'html' 13 | endif 14 | 15 | runtime! syntax/html.vim 16 | unlet b:current_syntax 17 | 18 | syn case match 19 | 20 | syn match shTodo contained "\<\%(COMBAK\|FIXME\|TODO\|XXX\)\ze:\=\>" 21 | syn match shQuickComment /^#.*/ contains=shTodo,@Spell 22 | 23 | syn region hvmlString start=+"""+ end=+"""+me=s-1 contains=hvmlExpression,hvmlCompoundExpression,@htmlPreproc 24 | syn region hvmlString start=+'''+ end=+'''+ 25 | syn region hvmlString start=/"/ skip=/\\"/ end=/"/ oneline 26 | syn region hvmlString start=/'/ skip=/\\'/ end=/'/ oneline 27 | 28 | syn match hvmlCtxVariableInTag contained /\$\d*[@?!^:=%~<]/ 29 | syn match hvmlExpression "\$.*" contained contains=Identifier,hvmlCtxVariableInTag,hvmlString,htmlString,hvmlCompoundExpression 30 | syn match hvmlCEOperatorAnd "&&" contained 31 | syn match hvmlCEOperatorOR "||" contained 32 | syn region hvmlCompoundExpression start=+{{+ end=+}}+ contains=hvmlExpression,hvmlCEOperatorAnd,hvmlCEOperatorOR,hvmlCompoundExpression 33 | syn match hvmlValue contained "=[\t ]*[^'" \t>][^ \t>]*"hs=s+1 contains=javaScriptExpression,@htmlPreproc 34 | 35 | syn region hvmlEndTag start=++ contains=hvmlTagN,htmlTagError 36 | syn region hvmlTag start=+<[^/]+ end=+>+ fold contains=hvmlTagN,htmlString,hvmlString,hvmlPrepArg,hvmlValue,hvmlAdvArg,hvmlCtxVariableInTag,htmlTagError,@htmlPreproc,@htmlArgCluster 37 | syn match hvmlTagN contained +<\s*[-a-zA-Z0-9]\++hs=s+1 contains=htmlTagName,hvmlTagName,hvmlActionTagName,@hvmlTagNameCluster 38 | syn match hvmlTagN contained +/rs=e+1 end=// 44 | syn match hvmlUsrVariable /\$\<\h\w\+\>/ 45 | syn match hvmlCtxVariable /\$\d*[@?!^:=%~<]/ 46 | syn match hvmlProperty /\.\<\h\w\+\>/ 47 | syn match hvmlNumber "\<\d\+\>#\=" 48 | syn match hvmlNumber "\<\d\+L\>#\=" 49 | syn match hvmlNumber "\<\d\+UL\>#\=" 50 | syn match hvmlNumber "\<-\=\.\=\d\+\>#\=" 51 | syn match hvmlNumber "\<-\=\.\=\d\+FL\>#\=" 52 | 53 | syn region hvmlGetterCall start=/\.\<\h\w\+\>\s\+(/rs=e+1 end=/)/re=s-1 contains=hvmlUserVariable,hvmlString,hvmlCompoundExpression,hvmlGetterCall,hvmlSetterCall 54 | syn region hvmlSetterCall start=/\.\<\h\w\+\>\s\+(!/rs=e+1 end=/)/re=s-1 contains=hvmlUserVariable,hvmlString,hvmlCompoundExpression,hvmlGetterCall,hvmlSetterCall 55 | 56 | " HVML tag names 57 | syn keyword hvmlTagName contained hvml archetype archedata error except 58 | 59 | " HVML tag names 60 | syn keyword hvmlActionTagName contained init update erase clear test match differ 61 | syn keyword hvmlActionTagName contained choose iterate reduce sort define execute 62 | syn keyword hvmlActionTagName contained observe forget fire call return 63 | syn keyword hvmlActionTagName contained bind catch back request load exit inherit sleep 64 | 65 | syn keyword hvmlPrepArg contained in on from via for as at with to by against within onto onlyif idd-by 66 | syn keyword hvmlAdvArg contained synchronously asynchronously exclusively uniquely individually wholly once casesensitively caseinsensitively ascendingly descendingly silently temporarily nosetotail responsively noreturn concurrently constantly must-yield 67 | syn keyword hvmlAdvArg contained sync async excl uniq indv case caseless asc desc temp nose-to-tail resp no-return conc const 68 | 69 | " The default highlighting. 70 | hi def link shTodo Todo 71 | hi def link shQuickComment Comment 72 | 73 | hi def link hvmlTag Delimiter 74 | hi def link hvmlEndTag Delimiter 75 | hi def link hvmlPrepArg Operator 76 | hi def link hvmlAdvArg Exception 77 | hi def link hvmlString String 78 | hi def link hvmlActionTagName Include 79 | hi def link hvmlTagName htmlTagName 80 | 81 | hi def link hvmlExpression Function 82 | hi def link hvmlExpressionIn Function 83 | hi def link hvmlCompoundExpression Statement 84 | hi def link hvmlUsrVariable Identifier 85 | hi def link hvmlCtxVariable Identifier 86 | hi def link hvmlProperty Identifier 87 | hi def link hvmlCtxVariableInTag Special 88 | hi def link hvmlNumber Number 89 | " hi def link hvmlContent Special 90 | 91 | let b:current_syntax = "hvml" 92 | 93 | -------------------------------------------------------------------------------- /interpreter/figure-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/interpreter/figure-1.png -------------------------------------------------------------------------------- /interpreter/figure-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/interpreter/figure-2.png -------------------------------------------------------------------------------- /interpreter/figure-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/interpreter/figure-3.png -------------------------------------------------------------------------------- /interpreter/figure-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/interpreter/figure-4.png -------------------------------------------------------------------------------- /interpreter/figure-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/interpreter/figure-5.png -------------------------------------------------------------------------------- /interpreter/figure-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/interpreter/figure-6.png -------------------------------------------------------------------------------- /samples/bootstrap/assets/breaking-news-zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "中国代表团第三金!谷爱凌的历史夺金", 3 | "shortDesc": "中国代表团第三金!谷爱凌夺得自由式滑雪女子大跳台金牌", 4 | "longDesc": "2月8日,在北京冬奥会自由式滑雪大跳台比赛中,第一次参加冬奥会的谷爱凌发挥出色,最后一跳奠定胜局,以188.25分的总成绩为中国体育代表团再添一枚金牌。", 5 | "detailedUrl": "https://news.sina.com.cn/c/2022-02-08/doc-ikyamrmz9607121.shtml", 6 | "time": "2022-02-08 11:05" 7 | } 8 | -------------------------------------------------------------------------------- /samples/bootstrap/assets/messages-zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample - Bootstrap": "示例 - Bootstrap", 3 | "Modal": "模态框", 4 | "Read Details…": "阅读详情…", 5 | "Show the Breaking News!": "显示惊爆新闻!", 6 | "Copyright © FMSoft": "版权所有 © 飞漫软件" 7 | } 8 | -------------------------------------------------------------------------------- /samples/bootstrap/modal.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | https://files.fmsoft.cn/assets/bootstrap-5.1.3-dist 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | $T.get('Sample - Bootstrap') 22 | 23 | 24 | 25 | 26 | 27 |
    28 |

    29 | $T.get('Sample - Bootstrap') 30 | $T.get('Modal') 31 |

    32 |
    33 | 34 | 35 | 36 | 51 | 52 | 53 |
    54 | 56 | 57 | 58 | 61 |
    62 | 63 | 64 | { 65 | "title": "This is an absolute breaking news!", 66 | "shortDesc": "The Zhang family's rooster has laid eggs!", 67 | "longDesc": 'Yesterday, the second son of the Zhang family came to me and said, "My rooster has laid eggs!"', 68 | "detailedUrl": "#", 69 | "time": $DATETIME.time_prt 70 | } 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
    81 |

    $T.get('Copyright © FMSoft')

    82 |
    83 | 84 | 85 | 86 | 87 | 88 | 89 |
    90 | 91 | -------------------------------------------------------------------------------- /samples/calculator/assets/buttons.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "letters": "7", "class": "number" }, 3 | { "letters": "8", "class": "number" }, 4 | { "letters": "9", "class": "number" }, 5 | { "letters": "←", "class": "c_blue backspace" }, 6 | { "letters": "C", "class": "c_blue clear" }, 7 | { "letters": "4", "class": "number" }, 8 | { "letters": "5", "class": "number" }, 9 | { "letters": "6", "class": "number" }, 10 | { "letters": "×", "class": "c_blue multiplication" }, 11 | { "letters": "÷", "class": "c_blue division" }, 12 | { "letters": "1", "class": "number" }, 13 | { "letters": "2", "class": "number" }, 14 | { "letters": "3", "class": "number" }, 15 | { "letters": "+", "class": "c_blue plus" }, 16 | { "letters": "-", "class": "c_blue subtraction" }, 17 | { "letters": "0", "class": "number" }, 18 | { "letters": "00", "class": "number" }, 19 | { "letters": ".", "class": "number" }, 20 | { "letters": "%", "class": "c_blue percent" }, 21 | { "letters": "=", "class": "c_yellow equal" } 22 | ] 23 | -------------------------------------------------------------------------------- /samples/calculator/assets/calculator.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding: 0;margin: 0; 3 | background-color:#49C593 ; 4 | } 5 | #calculator{ 6 | position: absolute; 7 | width: 1200px;height: 620px; 8 | left: 50%;top: 50%; 9 | margin-left: -600px; 10 | margin-top: -310px; 11 | } 12 | #calculator #c_title { 13 | margin: auto; 14 | /*margin-left: 300px;*/ 15 | width: 800px; 16 | height: 80px; 17 | } 18 | #calculator #c_title h2{ 19 | text-align: center; 20 | font-size: 33px;font-family: "微软雅黑";color: #666; 21 | line-height: 30px; 22 | } 23 | #c_text{ 24 | width: 1000px; 25 | margin: auto; 26 | } 27 | #calculator #c_text #text{ 28 | /*margin-left: 200px;*/ 29 | padding-right: 10px; 30 | width: 1000px; 31 | height: 50px; 32 | font-size: 25px;font-family: "微软雅黑";color: #666666; 33 | text-align: right;border: 1px white; 34 | border: double 1px; 35 | } 36 | #calculator #c_value{ 37 | width: 1080px;height: 408px; 38 | /*margin-left: 160px;*/ 39 | margin: 20px auto; 40 | } 41 | #calculator #c_value ul{ 42 | margin: 0; 43 | } 44 | #calculator #c_value ul li{ 45 | margin: 10px; 46 | list-style: none;float: left; 47 | width: 180px;height: 80px;line-height: 80px; 48 | text-align: center;background-color: chartreuse; 49 | border: 1px solid black; 50 | font-size: 30px;font-family: "微软雅黑";color: #666; 51 | box-shadow: 5px 5px 30px rgba(0,0,0,0.4); 52 | -webkit-user-select: none; 53 | -ms-user-select: none; 54 | -moz-user-select: none; 55 | } 56 | #calculator #c_value ul li:active{ 57 | background-color: white; 58 | } 59 | #calculator #c_value ul li:hover{ 60 | opacity:0.8; 61 | cursor:pointer; 62 | } 63 | #calculator #c_value ul .c_blue{ 64 | background-color: cornflowerblue;color: #000000; 65 | } 66 | #calculator #c_value ul .c_yellow{ 67 | background-color: #f9f900;color: #000000; 68 | } 69 | -------------------------------------------------------------------------------- /samples/calculator/assets/expressions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "7 * 3 = ", 3 | "800 + 100 = ", 4 | "100 / 10 = ", 5 | "50C80 / 10 = ", 6 | "5←80 / 10 = ", 7 | "←←80 / 10 = " 8 | ] 9 | -------------------------------------------------------------------------------- /samples/calculator/assets/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "HVML Calculator": "HVML 計算器" 3 | "Current Time: ": "當前時間:" 4 | } 5 | -------------------------------------------------------------------------------- /samples/calculator/assets/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "HVML Calculator": "HVML 计算器", 3 | "Current Time: ": "当前时间:" 4 | } 5 | -------------------------------------------------------------------------------- /samples/calculator/calculator-1.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 计算器 5 | 6 | 7 | 8 | [ 9 | { "letters": "7", "class": "number" }, 10 | { "letters": "8", "class": "number" }, 11 | { "letters": "9", "class": "number" }, 12 | { "letters": "←", "class": "c_blue backspace" }, 13 | { "letters": "C", "class": "c_blue clear" }, 14 | { "letters": "4", "class": "number" }, 15 | { "letters": "5", "class": "number" }, 16 | { "letters": "6", "class": "number" }, 17 | { "letters": "×", "class": "c_blue multiplication" }, 18 | { "letters": "÷", "class": "c_blue division" }, 19 | { "letters": "1", "class": "number" }, 20 | { "letters": "2", "class": "number" }, 21 | { "letters": "3", "class": "number" }, 22 | { "letters": "+", "class": "c_blue plus" }, 23 | { "letters": "-", "class": "c_blue subtraction" }, 24 | { "letters": "0", "class": "number" }, 25 | { "letters": "00", "class": "number" }, 26 | { "letters": ".", "class": "number" }, 27 | { "letters": "%", "class": "c_blue percent" }, 28 | { "letters": "=", "class": "c_yellow equal" }, 29 | ] 30 | 31 | 32 | 33 | 34 |
    35 | 36 |
    37 |

    计算器

    38 |
    39 | 40 |
    41 | 42 |
    43 | 44 |
    45 | 46 |
  • $?.letters
  • 47 |
    48 | 49 |
      50 | 51 | 52 | 53 |

      Bad data!

      54 |
      55 |
      56 |
    57 |
    58 |
    59 | 60 | 61 |
    62 | 63 | -------------------------------------------------------------------------------- /samples/calculator/calculator-2.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | $T.get('HVML Calculator') 11 | 12 | 13 | [ 14 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 15 | ] 16 | 17 | 18 | 19 | 20 | 21 | 22 |
    23 | 24 |
    25 |

    $T.get('HVML Calculator') 26 | $T.get('Current Time: ')$DATETIME.time_prt() 27 |

    28 | 29 | 30 | 31 |
    32 | 33 |
    34 | 35 |
    36 | 37 |
    38 | 39 |
  • $?.letters
  • 40 |
    41 | 42 |
      43 | 44 | 45 | 46 |

      Bad data!

      47 |
      48 |
      49 |
    50 |
    51 |
    52 | 53 | 54 |
    55 | 56 | -------------------------------------------------------------------------------- /samples/calculator/calculator-3.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | $T.get('HVML Calculator') 11 | 12 | 13 | [ 14 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 15 | { "id" : "input", "interval" : 1500, "active" : "yes" }, 16 | ] 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    24 | 25 |
    26 |

    $T.get('HVML Calculator') 27 | $T.get('Current Time: ')$DATETIME.time_prt() 28 |

    29 | 30 | 31 | 32 |
    33 | 34 |
    35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | { "id" : "input", "active" : "no" } 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
    67 | 68 |
    69 | 70 |
  • $?.letters
  • 71 |
    72 | 73 |
      74 | 75 | 76 | 77 |

      Bad data!

      78 |
      79 |
      80 |
    81 |
    82 |
    83 | 84 | 85 |
    86 | 87 | -------------------------------------------------------------------------------- /samples/calculator/calculator-4.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | $T.get('HVML Calculator') 13 | 14 | 15 | [ 16 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 17 | { "id" : "input", "interval" : 1500, "active" : "yes" }, 18 | ] 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | { 37 | "chars" : $exp_chars[$SYS.random($DATA.count($exp_chars))], 38 | "index" : 0, 39 | } 40 | 41 | 42 |
    43 | 44 |
    45 |

    $T.get('HVML Calculator') 46 | $T.get('Current Time: ')$DATETIME.time_prt() 47 |

    48 | 49 | 50 | 51 |
    52 | 53 |
    54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | { "id" : "input", "active" : "no" } 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
    87 | 88 |
    89 | 90 |
  • $?.letters
  • 91 |
    92 | 93 |
      94 | 95 | 96 | 97 |

      Bad data!

      98 |
      99 |
      100 |
    101 |
    102 |
    103 | 104 | 105 |
    106 | 107 | -------------------------------------------------------------------------------- /samples/calculator/calculator-5.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | { 10 | exp: "", 11 | expr: "", 12 | } 13 | 14 | 15 | 16 | [ 17 | { "idx":"0", "id": "btn_7", "exp": "7", "letters": "7", "class": "btn number" }, 18 | { "idx":"1", "id": "btn_8", "exp": "8", "letters": "8", "class": "btn number" }, 19 | { "idx":"2", "id": "btn_9", "exp": "9", "letters": "9", "class": "btn number" }, 20 | { "idx":"3", "id": "btn_back", "exp": "←", "letters": "←", "class": "c_blue backspace" }, 21 | { "idx":"4", "id": "btn_clear", "exp": "C", "letters": "C", "class": "c_blue clear" }, 22 | { "idx":"5", "id": "btn_4", "exp": "4", "letters": "4", "class": "btn number" }, 23 | { "idx":"6", "id": "btn_5", "exp": "5", "letters": "5", "class": "btn number" }, 24 | { "idx":"7", "id": "btn_6", "exp": "6", "letters": "6", "class": "btn number" }, 25 | { "idx":"8", "id": "btn_mul", "exp": "*", "letters": "x", "class": "btn c_blue multiplication" }, 26 | { "idx":"9", "id": "btn_div", "exp": "/", "letters": "÷", "class": "btn c_blue division" }, 27 | { "idx":"10", "id": "btn_1", "exp": "1", "letters": "1", "class": "btn number" }, 28 | { "idx":"11", "id": "btn_2", "exp": "2", "letters": "2", "class": "btn number" }, 29 | { "idx":"12", "id": "btn_3", "exp": "3", "letters": "3", "class": "btn number" }, 30 | { "idx":"13", "id": "btn_plus", "exp": "+", "letters": "+", "class": "btn c_blue plus" }, 31 | { "idx":"14", "id": "btn_sub", "exp": "-", "letters": "-", "class": "btn c_blue subtraction" }, 32 | { "idx":"15", "id": "btn_0", "exp": "0", "letters": "0", "class": "btn number" }, 33 | { "idx":"16", "id": "btn_00", "exp": "00", "letters": "00", "class": "btn number" }, 34 | { "idx":"17", "id": "btn_dot", "exp": ".", "letters": ".", "class": "btn number" }, 35 | { "idx":"18", "id": "btn_pct", "exp": "%", "letters": "%", "class": "btn c_blue percent" }, 36 | { "idx":"19", "id": "btn_eq", "exp": "=", "letters": "=", "class": "c_yellow equal" }, 37 | ] 38 | 39 | 40 | $T.get('HVML Calculator') 41 | 42 | 43 | [ 44 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 45 | ] 46 | 47 | 48 | 49 | 60 | 61 | 62 | 63 |
    64 | 65 |
    66 |

    $T.get('HVML Calculator') 67 | $T.get('Current Time: ')$DATETIME.time_prt() 68 |

    69 | 70 | 71 | 72 | 73 |
    74 | 75 |
    76 | 77 |
    78 | 79 |
    80 | 81 |
  • $?.letters
  • 82 |
    83 | 84 |
      85 | 86 | 87 | 88 |

      Bad data!

      89 |
      90 |
      91 |
    92 |
    93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
    127 | 128 | 129 |
    130 | 131 | -------------------------------------------------------------------------------- /samples/calculator/calculator.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding: 0;margin: 0; 3 | background-color:#49C593 ; 4 | } 5 | #calculator{ 6 | position: absolute; 7 | width: 1200px;height: 620px; 8 | left: 50%;top: 50%; 9 | margin-left: -600px; 10 | margin-top: -310px; 11 | } 12 | #calculator #c_title { 13 | margin: auto; 14 | /*margin-left: 300px;*/ 15 | width: 800px; 16 | height: 80px; 17 | } 18 | #calculator #c_title h2{ 19 | text-align: center; 20 | font-size: 33px;font-family: "微软雅黑";color: #666; 21 | line-height: 30px; 22 | } 23 | #c_text{ 24 | width: 1000px; 25 | margin: auto; 26 | } 27 | #calculator #c_text #text{ 28 | /*margin-left: 200px;*/ 29 | padding-right: 10px; 30 | width: 1000px; 31 | height: 50px; 32 | font-size: 25px;font-family: "微软雅黑";color: #666666; 33 | text-align: right;border: 1px white; 34 | border: double 1px; 35 | } 36 | #calculator #c_text #expression{ 37 | /*margin-left: 200px;*/ 38 | padding-right: 10px; 39 | width: 1000px; 40 | height: 50px; 41 | font-size: 25px;font-family: "微软雅黑";color: #666666; 42 | text-align: right;border: 1px white; 43 | border: double 1px; 44 | } 45 | #calculator #c_value{ 46 | width: 1080px;height: 408px; 47 | /*margin-left: 160px;*/ 48 | margin: 20px auto; 49 | } 50 | #calculator #c_value ul{ 51 | margin: 0; 52 | } 53 | #calculator #c_value ul li{ 54 | margin: 10px; 55 | list-style: none;float: left; 56 | width: 180px;height: 80px;line-height: 80px; 57 | text-align: center;background-color: chartreuse; 58 | border: 1px solid black; 59 | font-size: 30px;font-family: "微软雅黑";color: #666; 60 | box-shadow: 5px 5px 30px rgba(0,0,0,0.4); 61 | -webkit-user-select: none; 62 | -ms-user-select: none; 63 | -moz-user-select: none; 64 | } 65 | #calculator #c_value ul li:active{ 66 | background-color: white; 67 | } 68 | #calculator #c_value ul li:hover{ 69 | opacity:0.8; 70 | cursor:pointer; 71 | } 72 | #calculator #c_value ul .c_blue{ 73 | background-color: cornflowerblue;color: #000000; 74 | } 75 | #calculator #c_value ul .c_yellow{ 76 | background-color: #f9f900;color: #000000; 77 | } 78 | -------------------------------------------------------------------------------- /samples/fibonacci/fibonacci-1.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fibonacci Numbers 5 | 6 | 7 | 8 |
    9 |

    Fibonacci Numbers less than 2000

    10 |

    Using named array variable ($fibonacci), $MATH, and $DATA

    11 |
    12 | 13 | 14 | [0, 1, ] 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 |
      23 | 24 |
    1. $?
    2. 25 |
      26 |
    27 |
    28 | 29 |
    30 |

    Totally $DATA.count($fibonacci) numbers.

    31 |
    32 | 33 | 34 |
    35 | -------------------------------------------------------------------------------- /samples/fibonacci/fibonacci-2.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fibonacci Numbers 5 | 6 | 7 | 8 |
    9 |

    Fibonacci Numbers less than 2000

    10 |

    Using temporary array variable ($!) and negative index

    11 |
    12 | 13 | 14 | [0, 1, ] 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 |
      23 | 24 |
    1. $?
    2. 25 |
      26 |
    27 |
    28 | 29 |
    30 |

    Totally $DATA.count($2!.fibonacci) numbers.

    31 |
    32 | 33 | 34 |
    35 | -------------------------------------------------------------------------------- /samples/fibonacci/fibonacci-3.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fibonacci Numbers 5 | 6 | 7 | 8 |
    9 |

    Fibonacci Numbers less than 2000

    10 |

    Using non-array temporary variables

    11 |
    12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 | 20 | 21 | 22 | 23 |
    $%
    24 |
    $?
    25 |
    26 |
    27 |
    28 | 29 |
    30 |

    Totally $2!.count numbers.

    31 |
    32 | 33 | 34 |
    35 | -------------------------------------------------------------------------------- /samples/fibonacci/fibonacci-4.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fibonacci Numbers 5 | 6 | 7 | 8 |
    9 |

    Fibonacci Numbers less than 2000

    10 |

    Using temporary variables in two levels

    11 |
    12 | 13 | 14 | 15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    $%
    26 |
    $?
    27 |
    28 |
    29 |
    30 | 31 |
    32 |

    Totally $2!.count numbers.

    33 |
    34 | 35 | 36 |
    37 | -------------------------------------------------------------------------------- /samples/fibonacci/fibonacci-5.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fibonacci Numbers 5 | 6 | 7 | 8 |
    9 |

    Fibonacci Numbers less than 2000

    10 |

    Using temporary variables and while/with attributes

    11 |
    12 | 13 | 14 | 15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    $%
    26 |
    $?
    27 |
    28 |
    29 |
    30 | 31 |
    32 |

    Totally $2!.count numbers.

    33 |
    34 | 35 | 36 |
    37 | -------------------------------------------------------------------------------- /samples/fibonacci/fibonacci-6.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $STREAM.stdout.writelines("# Fibonacci Numbers") 5 | 6 | 7 | 8 | 9 | {{ 10 | $STREAM.stdout.writelines("## Fibonacci Numbers less than 2000"); 11 | $STREAM.stdout.writelines('') 12 | }} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{ 21 | $STREAM.stdout.writelines(' 0: 0'); 22 | $STREAM.stdout.writelines(' 1: 1') 23 | }} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | $STREAM.stdout.writelines( 34 | $STR.join(' ', $MATH.add($%, 2), ': ', $?)) 35 | 36 | 37 | 38 | 39 | {{ 40 | $STREAM.stdout.writelines(''); 41 | $STREAM.stdout.writelines($STR.format_c("Totally %d numbers", $count)) 42 | }} 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/loading/assets/breaking-news-zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "中国代表团第三金!谷爱凌的历史夺金", 3 | "shortDesc": "中国代表团第三金!谷爱凌夺得自由式滑雪女子大跳台金牌", 4 | "longDesc": "2月8日,在北京冬奥会自由式滑雪大跳台比赛中,第一次参加冬奥会的谷爱凌发挥出色,最后一跳奠定胜局,以188.25分的总成绩为中国体育代表团再添一枚金牌。", 5 | "detailedUrl": "https://news.sina.com.cn/c/2022-02-08/doc-ikyamrmz9607121.shtml", 6 | "time": "2022-02-08 11:05" 7 | } 8 | -------------------------------------------------------------------------------- /samples/loading/assets/messages-zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample - Loading": "示例 - 装载", 3 | "Initializing named variable synchronously": "同步初始化命名变量", 4 | "Initializing named variable asynchronously": "异步初始化命名变量", 5 | "Overriding named variable asynchronously": "异步覆盖命名变量", 6 | "Breaking News!": "突发新闻!", 7 | "5 minutes ago": "五分钟之前", 8 | "Read Details…": "阅读详情…", 9 | "Failed to load content!": "装载内容时失败!" 10 | } 11 | -------------------------------------------------------------------------------- /samples/loading/loading-1.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | $T.get('Sample - Loading') 16 | 17 | 18 | 19 |
    20 |

    $T.get('Sample - Loading')

    21 |

    $T.get('Initializing named variable synchronously')

    22 |
    23 | 24 |
    25 |
    26 |
    27 | $T.get('Breaking News!') 28 |
    29 |
    30 |
    31 | 32 |
    33 |

    34 | 35 | 36 | 37 | 38 | 39 |

    40 | 41 | 42 |
    43 | 44 | 47 |
    48 |
    49 | 50 | 51 |
    $?.title
    52 |

    $?.shortDesc

    53 | $T.get('Read Details…') 54 |
    55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 |

    Copyright © FMSoft

    66 |
    67 | 68 | 69 |
    70 | 71 | -------------------------------------------------------------------------------- /samples/loading/loading-2.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | $T.get('Sample - Loading') 16 | 17 | 18 | 19 |
    20 |

    $T.get('Sample - Loading')

    21 |

    $T.get('Initializing named variable asynchronously')

    22 |
    23 | 24 |
    25 |
    26 |
    27 | $T.get('Breaking News!') 28 |
    29 |
    30 |
    31 | 32 |
    33 |

    34 | 35 | 36 | 37 | 38 | 39 |

    40 | 41 | 42 |
    43 | 44 | 47 |
    48 |
    49 | 50 | 51 |
    $?.title
    52 |

    $?.shortDesc

    53 | $T.get('Read Details…') 54 |
    55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
    67 |

    Copyright © FMSoft

    68 |
    69 | 70 | 71 |
    72 | 73 | -------------------------------------------------------------------------------- /samples/loading/loading-3.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | $T.get('Sample - Loading') 16 | 17 | 18 | 19 |
    20 |

    $T.get('Sample - Loading')

    21 |

    $T.get('Overriding named variable asynchronously')

    22 |
    23 | 24 |
    25 |
    26 |
    27 | $T.get('Breaking News!') 28 |
    29 | 30 |
    31 |
    32 | 33 | 36 |
    37 |
    38 | 39 | 40 |
    $?.title
    41 |

    $?.shortDesc

    42 | $T.get('Read Details…') 43 |
    44 | 45 | 46 | { 47 | "title": "This is an absolute breaking news!", 48 | "shortDesc": "The Zhang family's rooster has laid eggs!", 49 | "longDesc": 'Yesterday, the second son of the Zhang family came to me and said, "My rooster has laid eggs!"', 50 | "detailedUrl": "#", 51 | "time": $DATETIME.time_prt 52 | } 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |

    Copyright © FMSoft

    63 |
    64 | 65 | 66 |
    67 | 68 | -------------------------------------------------------------------------------- /samples/readdir/readdir-1.hvml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Read Directroy Entries 5 | 6 | 7 | 8 |
    9 |

    Entries in Directory

    10 |

    $REQ.dir

    11 |
    12 | 13 |
    14 | 15 | 16 | 17 | 18 |
      19 | 20 | 21 |
    1. Exception when calling '$FS.opendir($REQ.dir)'
    2. 22 |
      23 | 24 | 25 |
    3. $?.type: $?.name
    4. 26 | 27 | 28 |
      29 |
      30 |
    31 | 32 |

    Totally $!.count entries.

    33 | 34 | 35 |

    Not a directory or directory not readable: $REQ.dir

    36 |
    37 | 38 | 39 |

    Exception on calling $FS.file_is($REQ.dir, 'dir readable')

    40 |
    41 | 42 |
    43 | 44 |
    45 | 46 |
    47 |

    Copyright © 2025 FMSoft

    48 |
    49 | 50 | 51 |
    52 | 53 | -------------------------------------------------------------------------------- /samples/template/assets/messages-zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample - Template": "示例 - 模板", 3 | "Generating HTML contents by using a template file.": "通过使用模板文件生成HTML内容。", 4 | "Go for details": "查看详情" 5 | } 6 | -------------------------------------------------------------------------------- /samples/template/how-to-use-template-to-generate-html.hvml: -------------------------------------------------------------------------------- 1 | # This HVML program demonstrates how to use templates to generate HTML content 2 | # 3 | # Key features: 4 | # 1. Uses an external HTML template file (news-list.html) 5 | # 2. Loads and merges localized messages from a JSON file 6 | # 3. Demonstrates template inheritance with elements 7 | # 4. Shows dynamic content updates using placeholders 8 | # 5. Implements a news list layout with Bootstrap styling 9 | # 10 | # The program: 11 | # - Loads localized strings from a JSON file based on system locale 12 | # - Populates a page header with title and description 13 | # - Creates a news title list with sample items 14 | # - Renders news cards with images, descriptions and detail links 15 | # - Updates the copyright year dynamically 16 | # - Outputs the final HTML document to stdout 17 | 18 | 19 | 20 | 21 | $T.get('Sample - Template') 22 | 23 | 24 | 25 | 26 |

    $T.get('Sample - Template')

    27 |

    $T.get('Generating HTML contents by using a template file.')

    28 |
    29 | 30 | 31 | [ 32 | { title: '中国成功发射遥感三十九号卫星', description: '2024年3月,中国在酒泉卫星发射中心成功发射遥感三十九号卫星,卫星顺利进入预定轨道。', link: 'https://news.example.com/satellite-launch', img: 'https://news.example.com/images/satellite-launch.jpg'}, 33 | { title: '中国成功研制新型量子计算机', description: '2024年2月,中国科学家宣布成功研制出新型量子计算机,实现了重要技术突破。', link: 'https://news.example.com/quantum-computer', img: 'https://news.example.com/images/quantum-computer.jpg'}, 34 | { title: '美国洛杉矶发生大规模示威动乱', description: '2024年3月,洛杉矶市中心爆发大规模示威活动,造成多处设施损坏,当地警方已加强治安管控。', link: 'https://news.example.com/la-protests', img: 'https://news.example.com/images/la-protests.jpg'}, 35 | ] 36 | 37 | 38 | 39 |
  • $?.title
  • 40 |
    41 | 42 | 43 | 44 | 45 | 46 | 47 |
    48 | $?.title 49 |
    50 |
    $?.title
    51 |

    $?.description

    52 | $T.get('Go for details') 53 |
    54 |
    55 |
    56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
    66 | 67 | -------------------------------------------------------------------------------- /samples/template/template/news-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
    9 |
    10 | 11 | 15 | 16 |
    17 |
    18 | 19 |
    20 |

    Copyright © FMSoft

    21 |
    22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /screenshots/embed-python-animated-3d-random-walk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/screenshots/embed-python-animated-3d-random-walk.png -------------------------------------------------------------------------------- /screenshots/embed-python-to-find-primes-using-p-span-xgui-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/screenshots/embed-python-to-find-primes-using-p-span-xgui-pro.png -------------------------------------------------------------------------------- /screenshots/embed-python-to-find-primes-using-p-span.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/screenshots/embed-python-to-find-primes-using-p-span.png -------------------------------------------------------------------------------- /screenshots/embed-python-to-find-primes-using-ul-li.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/screenshots/embed-python-to-find-primes-using-ul-li.png -------------------------------------------------------------------------------- /screenshots/hello-world-with-style-foil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/screenshots/hello-world-with-style-foil.png -------------------------------------------------------------------------------- /screenshots/hello-world-with-style-xgui-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/screenshots/hello-world-with-style-xgui-pro.png -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | Use tools in `markdown/` of: 2 | 3 | 4 | 5 | or 6 | 7 | 8 | -------------------------------------------------------------------------------- /zh/calculator.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding: 0;margin: 0; 3 | background-color:#49C593 ; 4 | } 5 | #calculator{ 6 | position: absolute; 7 | width: 1200px;height: 620px; 8 | left: 50%;top: 50%; 9 | margin-left: -600px; 10 | margin-top: -310px; 11 | } 12 | #calculator #c_title { 13 | margin: auto; 14 | /*margin-left: 300px;*/ 15 | width: 800px; 16 | height: 80px; 17 | } 18 | #calculator #c_title h2{ 19 | text-align: center; 20 | font-size: 33px;font-family: "微软雅黑";color: #666; 21 | line-height: 30px; 22 | } 23 | #c_text{ 24 | width: 1000px; 25 | margin: auto; 26 | } 27 | #calculator #c_text #text{ 28 | /*margin-left: 200px;*/ 29 | padding-right: 10px; 30 | width: 1000px; 31 | height: 50px; 32 | font-size: 25px;font-family: "微软雅黑";color: #666666; 33 | text-align: right;border: 1px white; 34 | border: double 1px; 35 | } 36 | #calculator #c_value{ 37 | width: 1080px;height: 408px; 38 | /*margin-left: 160px;*/ 39 | margin: 20px auto; 40 | } 41 | #calculator #c_value ul{ 42 | margin: 0; 43 | } 44 | #calculator #c_value ul li{ 45 | margin: 10px; 46 | list-style: none;float: left; 47 | width: 180px;height: 80px;line-height: 80px; 48 | text-align: center;background-color: chartreuse; 49 | border: 1px solid black; 50 | font-size: 30px;font-family: "微软雅黑";color: #666; 51 | box-shadow: 5px 5px 30px rgba(0,0,0,0.4); 52 | -webkit-user-select: none; 53 | -ms-user-select: none; 54 | -moz-user-select: none; 55 | } 56 | #calculator #c_value ul li:active{ 57 | background-color: white; 58 | } 59 | #calculator #c_value ul li:hover{ 60 | opacity:0.8; 61 | cursor:pointer; 62 | } 63 | #calculator #c_value ul .c_blue{ 64 | background-color: cornflowerblue;color: #000000; 65 | } 66 | #calculator #c_value ul .c_yellow{ 67 | background-color: #f9f900;color: #000000; 68 | } 69 | -------------------------------------------------------------------------------- /zh/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HVML/HVML-Docs/22748dea4a7c6b8b777d03444ffa8d9508b8876a/zh/calculator.png -------------------------------------------------------------------------------- /zh/embed-python-in-hvml-program-zh.md: -------------------------------------------------------------------------------- 1 | # 在 HVML 程序中嵌入 Python 2 | 3 | - 作者:魏永明 4 | - 日期:2023 年 3 月 5 | 6 | **目录** 7 | 8 | [//]:# (START OF TOC) 9 | 10 | - [准备工作](#准备工作) 11 | - [快速了解 HVML](#快速了解-hvml) 12 | - [可装载动态对象 PY](#可装载动态对象-py) 13 | - [示例程序:寻找素数](#示例程序寻找素数) 14 | - [示例程序:三维随机游走](#示例程序三维随机游走) 15 | - [结语](#结语) 16 | 17 | [//]:# (END OF TOC) 18 | 19 | 20 | 2023 年 3 月,HVML 社区发布了 HVML 开源解释器 PurC 的 0.9.8 版本,其中增加了对 Python 的支持。 21 | 22 | 使用这一新的功能,我们可以非常方便地在 HVML 程序中调用 Python 模块,利用 Python 生态中的丰富软件包或模块开发自己的 HVML 应用。与此同时,由 HVML 提供的跨平台、统一 GUI/CLI 应用开发框架以及跨端运行的能力,将弥补 Python 生态和 Web 生态之间几十年来难以跨越的鸿沟,从而极大提升 Python 应用的表现力以及和用户交互的能力。 23 | 24 | 本文首先通过一个简单的寻找素数的程序,说明了在 HVML 中内嵌 Python 的基本方法,之后使用 NumPy 和 Matplotlib 实现了一个随机游走的三维动画 HVML 程序,介绍了这一增强的典型应用场景:科学计算可视化。 25 | 26 | ## 准备工作 27 | 28 | 截止目前,HVML 解释器 PurC 和图形渲染器 xGUI Pro 均支持在 Linux 或 macOS 桌面上运行。为完整执行本文提到的内嵌 Python 代码的 HVML 程序,需要提前安装好 Python 3.9+(Linux)或 Python 3.11+(macOS)运行时环境、开发时环境以及相关模块。 29 | 30 | 比如,在 Ubuntu Linux 20.04 或以上系统中,首先安装常用的开发工具(如 git、make 等),然后使用如下命令: 31 | 32 | ```console 33 | $ sudo apt install python3 python3-pip python3-dev 34 | $ sudo apt install libwebkit2gtk-4.0-dev 35 | $ pip3 install numpy matplotlib 36 | ``` 37 | 38 | 在 macOS 上,首先确保已安装 xCode 或者 xCode Command Line Tools,然后安装 macPorts。有关 macPorts 的安装,可访问 macPorts 官网:。 39 | 40 | 之后,在 macOS 的终端程序中,通过 macPorts 的 `port` 命令安装 Python 的运行时环境、开发时环境以及相关模块: 41 | 42 | ```console 43 | $ sudo port install python311 py-pip 44 | $ sudo port install webkit2-gtk-devel 45 | $ sudo port install xorg-server 46 | $ sudo pip3 install numpy matplotlib 47 | ``` 48 | 49 | 目前,需要开发者自行编译 HVML 的解释器 PurC 和图形渲染器 xGUI Pro。在做好以上准备工作之后,请访问如下开源代码仓库获取源代码并根据其中的描述构建这两款软件: 50 | 51 | - PurC: 52 | - xGUI Pro: 53 | 54 | 注意,为构建 PurC 和 xGUI Pro,你可能还需要安装如下开发工具或函数库: 55 | 56 | 1. 跨平台构建系统生成器:CMake 3.15 或更高版本 57 | 1. 兼容 C11 和 CXX17 的编译器:GCC 8+ 或 Clang 6+ 58 | 1. Zlib 1.2.0 或更高版本 59 | 1. Glib 2.44.0 或更高版本 60 | 1. BISON 3.0 或更高版本 61 | 1. FLEX 2.6.4 或更高版本 62 | 1. Ncurses 5.0 或更高版本(可选;`purc` 中的 Foil 渲染器需要此函数库) 63 | 64 | 请使用 Linux 发行版提供的包管理工具或者 macPorts 安装以上软件,并确保使用正确的版本。 65 | 66 | 下面是针对 macOS 系统的一些补充说明: 67 | 68 | - HVML 解释器需要 Python 3.9 以上版本来支持和 Python 代码的互操作,而在 macOS 上通过 macPorts 安装 Python 3.11 的原因,主要是为了避免和 xCode Command Line Tools 中包含的 Python 3.9 相冲突。 69 | - 在 macOS 上,如果不使用图形渲染器 xGUI Pro,而只使用 PurC 中内建的字符渲染器 Foil,也可以使用 Homebrew 系统来构建 PurC,而无需构建 xGUI Pro。但若要构建 xGUI Pro,则必须使用 macPorts。这主要是因为 Homebrew 未提供 WebKit2Gtk3 软件包。 70 | - 在使用 macPorts 构建 PurC 和 xGUI Pro 时,一定要通过 CMake 的 `-DCMAKE_INSTALL_PREFIX=/opt/local` 选项指定 PurC 和 xGUI Pro 的安装前缀为 `/opt/local`;若使用默认的 `/usr/local` 安装前缀,会出现找不到头文件的情形。 71 | - 在 macOS 上使用 xGUI Pro 时,需要使用 macPorts 安装 `xorg-server` 包,安装后需要重新登录才能生效。 72 | - 在 macOS 上编译 xGUI Pro 后,需要手工在安装 WebKit 扩展库的目录下(如 `/opt/local/xguipro/lib/webext`),创建一个后缀名为 `.so` 的符号链接指向构建好的 WebKit 扩展库: 73 | 74 | ```console 75 | $ cd /opt/local/xguipro/lib/webext 76 | $ sudo ln -s libWebExtensionHVML.so libWebExtensionHVML.dylib 77 | ``` 78 | 79 | 之所以要在 macOS 系统上手工创建一个符号链接,主要是因为 WebKit 在搜索其扩展共享库时,只会搜索并装载后缀名为 `.so` 的共享库文件,而在 macOS 系统上,共享库的后缀名通常为 `.dylib`。 80 | 81 | ## 快速了解 HVML 82 | 83 | 已经了解 HVML 特点的读者可以跳过本节。 84 | 85 | HVML 和其他编程语言之间的主要差异,在于 HVML 使用了类似 HTML 的标记语言来定义一个程序,故而被称为“可编程标记语言”。 86 | 87 | 作为一个简单的示例,我们用 HVML 解释器 PurC 来运行这段 HVML 程序: 88 | 89 | ```hvml 90 | 91 | 92 | $STREAM.stdout.writelines('Hello, world!') 93 | 94 | 95 |

    Hello, World!

    96 |

    This paragraph is generated by HVML, and it is in HTML.

    97 | 98 |
    99 | ``` 100 | 101 | 假定我们将上述这段程序保存为 `hello-world.hvml` 文件。如果我们不带任何参数在系统终端中运行 PurC 解释器的命令行程序 `purc`,则会获得如下结果: 102 | 103 | ```console 104 | $ purc hello-world.hvml 105 | Hello, world! 106 | ``` 107 | 108 | 我们看到终端上只是多了一条输出:`Hello, world!`。你大概可以想象到,这条输出是由 `$STREAM.stdout.writelines('Hello, world!')` 这条语句产生的,如同我们在 Python 程序中调用 `print()` 函数。 109 | 110 | 而如果我们调用 `purc` 时使用 `-c thread` 选项,则会得到如下结果: 111 | 112 | ```console 113 | $ purc -c thread hello-world.hvml 114 | Hello, world! 115 | 116 | Hello, World! 117 | 118 | This paragraph is generated by HVML, and it is in HTML. 119 | 120 | ``` 121 | 122 | 在笔者的 Linux 系统上,效果如下图所示: 123 | 124 | ![Hello, world!](/screenshots/hello-world-with-style-foil.png) 125 | 126 | 显然,相比第一次执行,我们看到了更多的内容。在支持颜色的终端程序中,你可以看到 `Hello, World!` 是红色的,而且居中显示。很明显,这些内容本质上是由 HVML 程序中夹杂的 `h1`、`p` 等元素定义的。我们还使用了 CSS 样式定义了 `h1` 元素的颜色(`color:red`)和文本居中对齐(`text-align:center`)样式。 127 | 128 | 和其他编程语言不同,HVML 将 `h1` 和 `p` 等元素视作动作执行,会将其内容插入到一个结构化的文档当中。而使用其他编程语言,我们可能需要通过特定的接口完成这些工作,比如在 Python 中使用类似 Jinja2 的模板引擎;而如果要展示模板引擎生成的内容,还要启动一个 Web 服务器,将数据喂给浏览器。 129 | 130 | 但 HVML 内置了这一功能,这也是本节要介绍的 HVML 的第一个重要特征:内建的结构化文档生成和操作能力。在不使用 `-c thread` 选项运行 `purc` 时,我们看不到任何文档相关的内容,但仍然可以通过 `-v` 选项让 `purc` 输出对应的文档结构: 131 | 132 | ```console 133 | $ purc -v hello-world.hvml 134 | purc 0.9.8 135 | Copyright (C) 2022, 2023 FMSoft Technologies. 136 | License GPLv3+: GNU GPL version 3 or later 137 | This is free software: you are free to change and redistribute it. 138 | There is NO WARRANTY, to the extent permitted by law. 139 | 140 | Executing HVML program from `file:///srv/devel/hvml/purc/build/hello-world.hvml`... 141 | Hello, world! 142 | 143 | The main coroutine exited. 144 | >> The document generated: 145 | 146 | 147 | 148 | 149 | 150 |

    Hello, World! 151 |

    152 |

    This paragraph is generated by HVML, and it is in HTML. 153 |

    154 | 155 | 156 | 157 | >> The executed result: 158 | 14 159 | ``` 160 | 161 | 以上的例子同时说明了 HVML 的第二个重要特征:解释器和渲染器分离。 162 | 163 | 当我们在执行 `purc` 命令时不使用 `-c thread` 选项,就会默认使用一个称为 `headless` 的渲染器。顾名思义,这个渲染器不会展示任何由 HVML 程序生成的文档内容。因此,我们看不到 `h1`、`p` 等元素定义的内容,而只能看到使用 `$STREAM.stdout.writelines()` 方法输出到终端上的内容。当我们使用 `-c thread` 选项执行 `purc` 命令时,将会使用内建于 `purc` 的一个字符渲染器,名叫 `Foil`(取著名科幻小说《三体》中“二向箔”之意)。和网页浏览器的工作原理类似,Foil 渲染器将解析这个 HVML 程序生成的 HTML 文档,并根据 CSS 样式信息格式化其中的内容展现到终端上。 164 | 165 | 你一定能想到,如果我们使用本文一开始提到的 xGUI Pro 图形渲染器,则可以在图形窗口中看到上述文档的内容。事实的确如此。启动 xGUI Pro,并在执行 `purc` 时将 `-c thread` 选项更换成 `-c socekt` 选项,便可以在窗口中看到以上内容。但是,因为目前我们编写的这个 HVML 程序在输出文档后立即退出,所以窗口会一闪而过。因此,我们还需要做一些额外的工作,在其中添加一些代码。 166 | 167 | ```hvml 168 | 169 | 170 | $STREAM.stdout.writelines('Hello, world!') 171 | 172 | 173 |

    Hello, World!

    174 |

    This paragraph is generated by HVML, and it is in HTML.

    175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 |
    184 | ``` 185 | 186 | 之后我们首先启动 xGUI Pro: 187 | 188 | ```console 189 | $ xguipro 190 | ``` 191 | 192 | 然后切换到另一个终端上使用 `-c socket` 执行 `purc` 命令: 193 | 194 | ```console 195 | $ purc -c socket hello-world.hvml 196 | Hello, world! 197 | ``` 198 | 199 | 该命令将创建一个窗口,其中会展示上述代码生成的文档内容。见下图。 200 | 201 | ![Hello, world!](/screenshots/hello-world-with-style-xgui-pro.png) 202 | 203 | 当我们关闭该窗口后,上述 `purc` 命令才会退出。而如果我们使用 `-c thread` 选项执行上述 HVML 程序,则执行效果和之前一样:程序会立即退出。显然,我们新增的如下代码起了作用: 204 | 205 | ```hvml 206 | 207 | 208 | 209 | 210 | 211 | ``` 212 | 213 | 和 `h1` 和 `p` 等元素不同,新增的代码使用 `test`、`observe` 和 `exit` 这三个英文动词单词定义的元素,我们称之为“动作元素”。顾名思义,动作元素定义程序的动作。比如 `test` 定义一个测试,而其中的属性 `with` 指定了用于测试的条件。类似地,`observe` 定义了一个观察(监听器),该监听器在数据 `$CTRN` 上监听 `rdrState:pageClosed` 事件,并在该事件到达时执行 `exit` 定义的动作,即退出该程序。 214 | 215 | 读者很容易想到,上面的代码中的 `$L`、`$RDR`、`$CRTN` 等使用前缀 `$` 的词元,表示一个变量。而按照 HVML 规范,使用全大写字母的变量,是系统定义的变量。这三个变量分别表示专门用于逻辑运算的对象、当前连接的渲染器以及当前执行的协程。在上面的代码中,通过访问 `$RDR.state`,我们可以获得当前渲染器的状态信息,而其上的 `comm` 属性,表示当前 HVML 程序和渲染器的通讯方法,对应的便是 `purc` 命令行中 `-c` 选项指定的 `thread` 或者 `socket`。 216 | 217 | 因此,以上新增代码的作用是:若当前渲染器的通讯方式为 `socket`,则监听当前协程的 `rdrState:pageClosed` 事件,当获得该事件时,终止当前协程的执行。 218 | 219 | 这给出了 HVML 的第三个重要特征:事件驱动。 220 | 221 | 另外,我们通过上面的简单 HVML 程序看到,我们可以使用一个类似 `$L.streq('caseless', $RDR.state.comm, 'socket')` 的表达式来设定元素的属性值。在 HVML 中,这类表达式称作混合求值表达式(Hybrid Evaluating Expression,HEE)。我们还可以使用由多条 HEE 组成的,含有一定逻辑控制能力的表达式,我们将这些表达式复合混合求值表达式(Compound Hybrid Evaluating Expression,CHEE),使用一对双花括号包围;比如 `{{ $L.gt($x, $y) && $x || $y }}`,表示对比 `$x` 和 `$y` 的值,取其中的较大者。 222 | 223 | 本质上,一个 HVML 程序由元素,其中包括 HVML 定义的动作元素或 HTML 等标记语言定义的外部元素,以及用于设定元素属性及其内容的混合求值表达式构成。 224 | 225 | 除了以上三个重要特征之外,HVML 还对模板定义和置换、异常处理、多协程、并发等现代编程技术提供了支持。更多详情,可参阅如下文章: 226 | 227 | - [漫谈 HVML,它的由来和未来](a-brief-introduction-to-hvml-zh.md) 228 | - [30 分钟学会 HVML 编程](learn-hvml-programming-in-30-minutes-zh.md) 229 | 230 | ## 可装载动态对象 PY 231 | 232 | 在 PurC 0.9.8 版本中,对 Python 的支持被实现为 HVML 的一个外部动态变体对象 `$PY`。利用该对象提供的功能,我们可以在 HVML 程序中完成如下功能: 233 | 234 | 1. 使用 `$PY.import()` 方法,可装载指定的 Python 模块并可在其上访问或调用已装载模块提供的子模块、属性或函数。 235 | 1. 使用 `$PY.run()` 方法,可执行一段 Python 代码、一个 Python 脚本或者一个指定的模块,并获得其结果。 236 | 1. 使用 `$PY.compile()` 方法,可编译一段 Python 代码,之后可在编译得到的 Python 代码对象上,在不同的名字空间中对其进行求值并获得求值结果。 237 | 1. 使用 `$PY.pythonize()` 方法,可将 HVML 字符串、数组、元组、集合、对象等数据转换为 Python 的内部对象,然后在其上执行这些 Python 内部对象支持的方法,或使用这些对象调用其他 Python 模块或函数。 238 | 1. 使用 `$PY.stringify()` 方法,可将 Python 内部对象转换为对应的 HVML 数据,或者获取对应的字符串表达,其作用类似 Python 的 `str()` 函数。 239 | 1. 使用 `$PY.global` 属性,可通过其获取器或者设置器访问当前 Python 解释器实例的内置 `__main__` 模块的全局变量。 240 | 1. 使用 `$PY.local` 属性,可通过其获取器或者设置器访问当前 Python 解释器实例内置 `__main__` 模块的局部变量。注意,局部变量名字空间将优先于全局变量。 241 | 242 | 下面我们使用一些 HVML 的复合混合求值表达式来说明 `$PY` 的用法。 243 | 244 | ```hee 245 | {{ 246 | $PY.import('math'); 247 | $PY.math.pow(2, 3) 248 | }} 249 | ``` 250 | 251 | 以上 CHEE 首先导入了 `math` 模块,然后调用了 `math` 模块的 `pow` 函数,其结果为 8。 252 | 253 | ```hee 254 | {{ 255 | $PY.import('datetime', ['datetime:dt', 'timedelta:td']); 256 | $PY.stringify($PY.dt.fromtimestamp(1429417200.0)) 257 | }} 258 | ``` 259 | 260 | 以上 CHEE 的第一条语句从 `datetime` 包中导入了子模块 `datetime` 和 `timedelta`,并分别命名为 `dt` 和 `td`。这条语句和 Python 的如下语句等价: 261 | 262 | ```python 263 | from datetime import datetime as dt, timedelta as td 264 | ``` 265 | 266 | 而上面 CHEE 的第二条语句根据给定的时间戳构造了一个 `datetime` 对象,然后在对象上使用 `$PY.stringify` 函数将其字符串化,其结果应该是:'2015-04-19 12:20:00'。 267 | 268 | ```hee 269 | {{ 270 | $PY.run('x = pow(2, 3)'); 271 | $PY.global.x; 272 | }} 273 | ``` 274 | 275 | 以上 CHEE 的第一条语句执行了一段 Python 代码,该代码将 `pow(2, 3)` 的结果赋给了全局变量 `x`。在 HVML 程序中,我们可以使用 `$PY` 的 `global` 属性访问全局变量。故而上述 CHEE 的求值结果就是 Python 中全局变量 `x` 的值:8。 276 | 277 | ```hee 278 | {{ 279 | $PY.local.x(! [1, 2, 2, 3] ); 280 | $PY.local.x.reverse(); 281 | $PY.local.x()() 282 | }} 283 | ``` 284 | 285 | 以上 CHEE 的第一条语句使用 HVML 数组设置了一个名为 `x` Python 局部变量,之后在其上调用了 Python 针对列表的 `reverse()` 方法,然后使用 `$PY.local.x()()` 这一用法调用了 `x` 本身的获取器,这将返回 Python 列表对象对应的 HVML 数组。因此,上述 CHEE 的执行结果是 [3, 2, 2, 1]。 286 | 287 | 其中,`$PY.local.x()` 返回的是一个代表 Python 复杂对象的 HVML 原生实体(native entity),在这个原生实体上再次调用其默认获取器,即 `$PY.local.x()()`,会执行数据类型的转换。该转换会将 Python 的 Unicode 字符串、字节数组(bytes 或 byte array)、列表(List)、字典(dictionary)、集合(set)构建为对应的 HVML 数据类型,分别是字符串(string)、字节序列(byte sequence)、数组(array)、对象(object)和一般性集合(generic set)。如果不做此类转换,这些 Python 对象在 HVML 程序中以原生实体动态对象的方式表达。而 Python 中的 None、True、False、整数和浮点数,则不做此类处理,直接等价于 HVML 的 null、true、false、longint 和 number 数据类型。对无法执行转换的情形,比如在一个自定义的 Python 类对象上执行默认的获取器,将等价于在其上调用 Python 的 `str()` 函数。 288 | 289 | 显然,通过 `$PY` 变量构造我们期望的混合求值表达式,将其用于 HVML 元素的属性值或者动作元素的内容,即可非常方便地将 Python 代码嵌入到 HVML 中,从而充分利用 Python 生态中丰富的模块及其功能。 290 | 291 | 在进入本文主题之前,我们再来看看 Python 异常的处理。若在执行 Python 代码或者调用 Python 解释器(当前使用 CPython)提供的接口时出现异常,HVML 会统一报告 `ExternalFailure` 异常,进一步的 Python 异常名称则由 `$PY.except` 给出。如下例所示: 292 | 293 | ```hee 294 | {{ 295 | $PY.run('2 / 0'); 296 | $PY.except 297 | }} 298 | ``` 299 | 300 | 上面的 CHEE 首先运行 Python 命令 `2 / 0`,这会抛出异常。如果我们捕获了该异常,然后再访问 `$PY.except`,将得到字符串:`ZeroDivisionError`。这是一个 Python 表示“被零除错误”的异常名称。 301 | 302 | ## 示例程序:寻找素数 303 | 304 | 这一小节给出了一个寻找素数的 HVML 程序,该程序使用了 Python 编写的一个函数: 305 | 306 | ```python 307 | def find_next_prime(start): 308 | if start < 2: 309 | start = 2 310 | 311 | while True: 312 | start += 1 313 | for j in range(2, start + 1): 314 | if start % j == 0: 315 | break 316 | if j == start: 317 | return start 318 | ``` 319 | 320 | 该函数名为 `find_next_prim`,如其名称所暗示,该函数返回比给定的参数大的第一个素数。比如,我们传入 2,将返回 3,而传入 5 将返回 7。 321 | 322 | 现在,我们尝试将该函数内嵌到 HVML 中,并使用 HTML 的 `ul` 和 `li` 元素列出调用上述 Python 函数获得的小于 100 的所有素数。代码如下,请注意其中的注释。 323 | 324 | ```hvml 325 | 329 | 330 | 331 | 332 | Embedded Python in HVML: Find Primes 333 | 334 | 335 | 336 | 342 | 343 | ''' 344 | def find_next_prime(start): 345 | if start < 2: 346 | start = 2 347 | 348 | while True: 349 | start += 1 350 | for j in range(2, start + 1): 351 | if start % j == 0: 352 | break 353 | if j == start: 354 | return start 355 | ''' 356 | 357 | 358 | 365 | 366 | {{ $PY.run($pyCode, 'source') }} 367 | 368 | 371 | 372 | $STREAM.stdout.writelines("A Python exception raised: $PY.except!") 373 | 374 | 375 | 376 | 377 | 378 |

    Embeding Python in HVML: Find Primes

    379 | 380 |
      381 | 390 | 392 | 393 | 396 |
    • $?
    • 397 |
      398 |
    399 | 400 | 403 | 404 | 405 | 406 | 407 | 408 | 409 |
    410 | ``` 411 | 412 | 如果我们执行上述 HVML 程序,并使用 Foil 字符渲染器,将列出小于 100 的所有素数,如下图所示。 413 | 414 | ![Find Primes (Using ul and li)](/screenshots/embed-python-to-find-primes-using-ul-li.png) 415 | 416 | 如果我们要列出小于 10000 的所有素数,以上程序当然也可以正常运行,但每行显示一个素数显然太浪费空间。因此,我们对上述代码生成文档部分稍作修改,使用 `p` 元素替代 `ul` 元素,使用 `span` 替代 `li` 元素,并交错使用不同的颜色来展示这些素数: 417 | 418 | ```hvml 419 | 420 |

    421 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | $?, 430 | 431 |

    432 | ``` 433 | 434 | 如果我们执行上述 HVML 程序,并使用 Foil 字符渲染器,将以紧凑形式列出小于 1000L 的所有素数,如下图所示(屏幕所限,仅显示后半部分): 435 | 436 | ![Find Primes (Using p and span)](/screenshots/embed-python-to-find-primes-using-p-span.png) 437 | 438 | 如果我们使用 xGUI Pro 图形渲染器,其效果如下所示(我们通过 `style="background-color:black"` 将 `p` 元素的背景颜色设置为黑色): 439 | 440 | ![Find Primes (Using p and span)](/screenshots/embed-python-to-find-primes-using-p-span-xgui-pro.png) 441 | 442 | 以上例子说明了在 HVML 中嵌入 Python 程序的巨大好处:利用 HVML,我们可以使用描述式的 HTML 和 CSS 来轻松改变 Python 程序的输出效果。同时,HVML 的解释器和渲染器分离设计,为我们的 GUI/CLI 设计带来非常多的便利。如内建的 Foil 字符渲染器和 xGUI Pro 图形渲染器表现的那样,我们可以通过 HVML 统一 CLI(命令行交互)和 GUI(图形用户交互)的开发。换句话说,今后我们在开发命令行程序时,也可以使用 HTML、CSS 等 Web 技术来展现内容并完成和用户的交互,而无需通过复杂而不易调试的方式来控制字符的颜色、位置、对齐等。更进一步,我们可以将渲染器运行在远程设备上,从而获得让一个 HVML 应用程序跨端(cross-end)执行的能力。有兴趣的读者可以尝试使用 xGUI Pro 提供的 Web Socket 通讯能力。 443 | 444 | ## 示例程序:三维随机游走 445 | 446 | 本节描述的三维随机游走程序,其原始版本来自 Matplotlib 官网的动画示例程序 Animated 3D random walk: 447 | 448 | 449 | 450 | 该程序使用 Python 生态中著名的 NumPy 和 Matplotlib 模块,实现了一个三维的随机游走程序。如果使用 Matplotlib 的交互式后端(backend),如 Tk、Gtk、Qt,可将其结果以动画形式展示在图形用户界面上。 451 | 452 | 该程序利用了 Matplotlib 的 animation 子模块,通过 `update_lines()` 函数周期性更新其中的线条,从而实现了动画效果。 453 | 454 | ```python 455 | import numpy as np 456 | import matplotlib.pyplot as plt 457 | import matplotlib.animation as animation 458 | 459 | ... 460 | 461 | def update_lines(num, walks, lines): 462 | for line, walk in zip(lines, walks): 463 | # NOTE: there is no .set_data() for 3 dim data... 464 | line.set_data(walk[:num, :2].T) 465 | line.set_3d_properties(walk[:num, 2]) 466 | return lines 467 | 468 | ... 469 | 470 | # Creating the Animation object 471 | ani = animation.FuncAnimation( 472 | fig, update_lines, num_steps, fargs=(walks, lines), interval=100) 473 | 474 | plt.show() 475 | ``` 476 | 477 | 上面给出了实现该动画的核心代码:`update_lines()` 函数以及创建动画的 `animation.FuncAnimation()` 函数调用。最终,程序调用 `plt.show()` 进入动画。若使用 Matplotlib 的交互式后端,则当用户关闭 `plt.show()` 展示的所有窗口之后,该程序才会继续往下执行。 478 | 479 | 如果我们希望在 Python 程序中和用户就动画进行交互,比如重新执行这一动画,Matplotlib 目前提供的动画框架很难实现。其中的主要原因在于 Python 本质上属于过程式编程语言,缺乏对事件驱动机制的内建支持。在 Python 中,若要实现这一交互,就需要 Matplotlib 的动画框架提供某种回调机制,并就各种可能的交互情形定义对应的事件,然后交由开发者在回调函数中处理这些事件。这显然不是一件简单的事情,而且,如果考虑到不同的交互式后端——如 Tk、Gtk、Qt——之间的差异,其工程量将非常庞大。 480 | 481 | 而 HVML 的方案则简洁而统一:界面的渲染和交互交给 HVML 处理,Python 只进行科学计算。就科学计算的可视化需求来讲,HVML 只需要 Matplotlib 生成 PNG 或者 SVG 图片就可以了。有了这个思路,我们对原始的 Python 程序稍作改动即可实现我们的目标。其要点如下: 482 | 483 | 1. 不使用 Matplotlib 的动画框架,改由 HVML 的定时器驱动。 484 | 1. 在 HVML 的定时器事件中,调用 Python 的 `update_lines()` 函数更新绘制的内容,并将结果保存为 PNG 或者 SVG 文件。 485 | 1. 通过修改 HVML 目标文档中的 `img` 元素之 `src` 属性来更新界面内容。 486 | 487 | 利用上述方案改造后的 HVML 程序,其主要框架和寻找素数的 HVML 程序类似,但有如下一些显著的不同: 488 | 489 | 1. 为获得更好的渲染效果,该程序将使用 Web 开发中常用的前端框架 Bootstrap 5.1。 490 | 1. 该程序创建了一个间隔为 100ms 的定时器,由定时器的到期事件驱动动画。 491 | 1. 该程序在界面上展示了一个“Run again” 的按钮,用户点击该按钮后,将重新执行该动画。 492 | 1. 为了用户界面更加美观,该程序使用更多的界面元素来美化页面的头部和尾部。 493 | 494 | 下面是该程序的完整源代码,请阅读其中的注释以理解该程序。 495 | 496 | ```hvml 497 | 498 | 499 | 500 | Embedded Python in HVML: Animated 3D Random Walk 501 | 502 | 503 | 504 | 505 | 506 | 509 | 510 | [ 511 | { "id" : "clock", "interval" : 100, "active" : "yes" }, 512 | ] 513 | 514 | 515 | 516 | 517 | 518 | 519 | ''' 520 | import numpy as np 521 | import matplotlib.pyplot as plt 522 | 523 | # Fixing random state for reproducibility 524 | np.random.seed(myseed) 525 | 526 | def random_walk(num_steps, max_step=0.05): 527 | """Return a 3D random walk as (num_steps, 3) array.""" 528 | start_pos = np.random.random(3) 529 | steps = np.random.uniform(-max_step, max_step, size=(num_steps, 3)) 530 | walk = start_pos + np.cumsum(steps, axis=0) 531 | return walk 532 | 533 | def update_lines(num, walks, lines): 534 | for line, walk in zip(lines, walks): 535 | # NOTE: there is no .set_data() for 3 dim data... 536 | line.set_data(walk[:num, :2].T) 537 | line.set_3d_properties(walk[:num, 2]) 538 | 539 | def update_walks(num_steps): 540 | np.random.seed(myseed) 541 | return [random_walk(num_steps) for index in range(40)] 542 | 543 | # Data: 40 random walks as (num_steps, 3) arrays 544 | num_steps = 30 545 | walks = update_walks(num_steps) 546 | 547 | # Attaching 3D axis to the figure 548 | fig = plt.figure() 549 | ax = fig.add_subplot(projection="3d") 550 | 551 | # Create lines initially without data 552 | lines = [ax.plot([], [], [])[0] for _ in walks] 553 | 554 | # Setting the axes properties 555 | ax.set(xlim3d=(0, 1), xlabel='X') 556 | ax.set(ylim3d=(0, 1), ylabel='Y') 557 | ax.set(zlim3d=(0, 1), zlabel='Z') 558 | ''' 559 | 560 | 561 | 568 | 569 | {{ 570 | $PY.global(! 'myseed', $SYS.time ); 571 | $PY.run($pyCode, 'source'); 572 | $PY.global.fig.canvas.draw_idle(); 573 | $PY.global.fig.savefig("frame-orig.svg"); 574 | }} 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 |
    583 |
    584 |

    Embeding Python in HVML: Animated 3D Random Walk
    585 | Powered by NumPy and Matplotlib

    586 |
    587 |
    588 | 589 | 590 |
    591 |
    592 | 593 |
    594 |
    595 | 600 | 601 |
    602 | 603 | 604 | 605 | 606 | 607 | 608 | {{ 609 | $STREAM.stdout.writelines("Going to handle Frame {$step}..."); 610 | $PY.global.update_lines($step, $PY.global.walks, $PY.global.lines); 611 | $PY.global.fig.canvas.draw_idle(); 612 | $PY.global.fig.savefig("frame-{$step}.svg"); 613 | $STREAM.stdout.writelines("File frame-{$step}.svg generated") 614 | }} 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 |

    A Python exception raised: $PY.except

    629 |
    630 |
    631 | 632 | 633 |
    634 |
    635 | 636 |
    637 |
    638 | 639 | 640 | 641 | 642 | {{ 643 | $PY.global.update_walks($DATA.arith('*', $step, 2)) 644 | }} 645 | 646 | 647 | 648 | 649 | 650 | 651 |
    652 |
    653 | 654 | 655 |
    656 |
    657 |
    658 | 659 | HVML logo 660 | 661 | © 2023 HVML Community 662 |
    663 | 664 | 669 |
    670 |
    671 | 672 | 673 | 674 | 675 | 676 | 677 |
    678 | ``` 679 | 680 | 注意,由于使用了 `img` 元素,该程序只能使用 xGUI Pro 图形渲染器(Foil 字符渲染器无法在字符终端中渲染图片)。下图给出了使用 xGUI Pro 渲染器时,该 HVML 程序的效果: 681 | 682 | ![Animated 3D Random Walk](/screenshots/embed-python-animated-3d-random-walk.png) 683 | 684 | 当用户按下“Run Again”按钮后,该程序将再次重新执行动画。 685 | 686 | ## 结语 687 | 688 | HVML 是一种全新种类的编程语言:可编程标记语言,本文介绍了这一编程语言的与众不同之处,同时通过两个示例程序展示了将 HVML 和 Python 结合在一起产生的奇妙“化学反应”。 689 | 690 | 利用 HVML 开源解释器 PurC 在 0.9.8 版本中引入的对内嵌 Python 的支持,开发者现在可以非常方便地在 HVML 程序中调用 Python 模块,从而利用 Python 生态中的丰富软件包或模块(比如在 AI 领域大热的 PyTorch 包)开发自己的 HVML 应用。对 Python 生态而言,利用 HVML 可以优雅解决 Python 难以用来开发交互式应用的问题。 691 | 692 | 另外,通过本文中的若干示例程序,我们还看到了 HVML 应用框架解耦解释器和渲染器带来的一项重大好处:一个跨平台、且有望统一 GUI/CLI 开发的全新应用框架。当然,要彻底实现这一目标还有很多要做的工作,比如 PurC 中的 Foil 渲染器还缺乏对表格、输入、表单等的支持。但这一切正在迅速改变当中。 693 | 694 | 作为 HVML 发明人以及 PurC 和 xGUI Pro 项目的创始人,笔者希望来自全世界的开源爱好者为 HVML 的快速成熟添砖加瓦! 695 | 696 | 最后,欢迎访问 HVML 开源解释器 PurC 项目仓库:,提交你的任何评论、建议、缺陷报告甚至代码合并请求! 697 | 698 | 699 | -------------------------------------------------------------------------------- /zh/the-initial-idea-of-hvml-zh.md: -------------------------------------------------------------------------------- 1 | # 漫谈 HVML,它的由来和未来 2 | 3 | 魏永明 4 | 5 | HVML 是笔者在开发合璧操作系统的过程中提出的一种新型的高级编程语言。小说《考鼎记》如此介绍这个语言: 6 | 7 | > 老魏发明的这编程语言,正式的名字叫 HVML,和我们知道的网页标记语言 HTML 只有一个字母之差,有朋友取了个昵称叫“呼噜猫”。HVML 和我们知道的很多编程语言,比如 Basic、Python、C/C++ 之类的完全不同,HVML 提出了一个数据驱动的概念,而代码里边也没有 if-then,do-while 之类的流程控制语句,所有的操作都基于数据。比如程序的输入数据是个数组,那我们就在这个数组上执行迭代,把数组里边的元素挑着处理一遍。如此等等。 8 | > 9 | > ——《考鼎记》第十回 10 | 11 | 本文就给大家讲讲 HVML 的由来以及笔者对它的未来的设想。 12 | 13 | ### HVML 的由来 14 | 15 | 随着互联网技术和应用的发展,围绕 HTML/CSS/JavaScript 形成的 Web 前端开发技术发展迅猛,甚至可以用“一日千里”来形容。五年前,基于 jQuery 和 Bootstrap 的前端框架大行其道,而从 2019 年开始,基于虚拟 DOM 技术的框架又受到前端开发者的青睐,比如著名的 React.js()、Vue.js()等。值得注意的是,微信小程序、快应用等,也不约而同使用了这种虚拟 DOM 技术来构建应用框架。 16 | 17 | 所谓“虚拟 DOM” 是指前端应用程序通过 JavaScript 来创建和维护一个虚拟的文档对象树,应用脚本并不直接操作真实的 DOM 树。在虚拟 DOM 树中,通过一些特别的属性实现了基于变量的一些流程控制,如条件、循环等。虚拟 DOM 技术提供如下一些好处: 18 | 19 | 1. 由于脚本并不使用脚本程序直接操作真实的 DOM 树,故而一方面通过现有的框架简化了前端开发的复杂性,另一方面通过优化对真实 DOM 树的操作而减少了由于动态修改页面内容而对 DOM 树的频繁操作,从而提高了页面的渲染效率和用户体验。 20 | 1. 通过虚拟 DOM 技术,程序对某个数据的修改,可以直接反映到该数据绑定的页面内容上,开发者无需主动或直接地调用相关接口来操作 DOM 树。这种技术提供了所谓的“响应式”编程,极大降低了开发者的工作量。 21 | 22 | 以 React.js、Vue.js 为代表的前端框架取得了巨大成功。看起来还是互联网公司钱多人多,所以可以肆无忌惮地不停地造各种各样的轮子。 23 | 24 | 然而,嵌入式或者物联网设备上的应用开发者却无法享受这些技术进步带来的红利。要开发嵌入式或者物联网设备上的应用,绝大多数情况下还是要使用 C/C++ 这种编程语言。虽说现在也有一些物联网操作系统尝试在系统中引入 JavaScript 或者 Python 语言的支持,但仅仅一个编程语言是不够的。比如博大精深的 Web 前端技术中,JavaScript 的确扮演了重要的角色,但 Web 前端技术赖以运转的基础却是 DOM 以及 CSS:DOM 提供了一种描述文档或者界面中的元素及其属性、内容的数据结构,而 CSS 描述了元素的布局、样式以及动画效果等渲染特性。一句话,那精妙的 Web 页面效果,不是通过 JavaScript 描述的,而是由 DOM 和 CSS 决定的。 25 | 26 | 所以,我们若想要在非浏览器环境中利用 Web 前端技术带来的便利来开发 GUI 应用,最该引入的首先应该是 DOM 和 CSS,而不仅仅是对一个脚本语言的支持。 27 | 28 | 除了嵌入式或者物联网应用的开发者之外,使用非 JavaScript 语言的其他开发者,比如使用 Python 做人工智能、大数据分析的开发者,也无法轻松使用 Web 前端技术带来的好处。若要用,非要折腾一番不可,比如要做个计算结果的可视化,要么使用 Python 的各种 GUI 绑定,要么绕一圈喂数据给浏览器,而且还要多学一门脚本语言。期间辛苦,也是罄竹难书。 29 | 30 | 笔者开发 MiniGUI 二十多年,知道使用 C/C++ 如何开发界面,多写几行 C/C++ 代码也能做出很多精妙的界面来。但有那么几年,笔者搞了几次 Web 前端开发,被 Web 前端技术开发界面的精妙之处所折服。后来再用 C/C++ 语言开发界面,笔者心里也是百般地不情愿。要不是为了钱,都 2020 年了,谁愿意用 C/C++ 开发 GUI?居然还有人要做“国产”的 C++ GUI 系统替代 Qt。呸!那都是二十年前的技术了。要搞国产替代,你先往前看看行吗? 31 | 32 | 另一方面,要想在嵌入式系统或者物联网设备中支持完整的 Web 前端技术,所需要的存储尺寸和运行内存都比较大,要获得好的效果,还要有 GPU 的支持。所以在合璧操作系统(HybridOS)的开发中,我们通过一些技术上的小突破来改造 Web 前端技术,使开发者可以用更少的 JavaScript 代码来完成更多的工作。但在 HybridOS 的开发过程中,我们意识到对 Web 前端技术的修修补补只能是权宜之计——我们需要一种新的开发框架,尤其是编程语言来打破 Web 前端技术现有的框框和限制。 33 | 34 | 于是就有了 HVML。如果总结一下的话,HVML 的诞生有其历史使命: 35 | 36 | 1. 通过完备、自恰、高度抽象的新编程语言来进一步归纳和总结 React.js、Vue.js 等围绕虚拟 DOM 技术所做一些技术尝试。 37 | 1. 打破 Web 前端技术和 JavaScript 的耦合,让其他编程语言,比如 Python、Lua、C/C++ 等,也可以直接使用 Web 前端技术带来的便利。 38 | 1. 为传统的 GUI 开发,包括设计工具、开发框架等带来新的变革。 39 | 1. 为云环境中的物联网应用开发带来新的可能。 40 | 41 | 这就是 HVML 的由来。未来,HVML 将成为 HybridOS 的 App 开发首选编程语言。 42 | 43 | ## HVML 概貌 44 | 45 | 下面用一个简单的例子来描述 HVML 的基本长相。有关 HVML 的详细规范,感兴趣的读者可点击文末的原文链接。 46 | 47 | 下面的示例 HVML 代码生成的 HTML 页面,将在屏幕上展示三组信息: 48 | 49 | 1. 在页面顶端显示的系统状态栏,用于展示当前时间、WiFi 信号强度、电池电量信息等。这些信息将通过监听来自系统的状态事件动态更新。 50 | 1. 在页面中间位置展示用户列表,每个用户项包括用户名称、头像等信息。这些信息来自 JSON 表达的一个字典数组。当使用者点击了某个用户头像后,该 HVML 代码将装载一个模态对话框,其中展示该用户的更多信息。 51 | 1. 在页面底部展示一个搜索引擎连接。具体的搜索引擎根据系统所在的语言地区(locale)信息确定。 52 | 53 | ```html 54 | 55 | 56 | 57 | 58 | [ 59 | { "id": "1", "avatar": "/img/avatars/1.png", "name": "Tom", "region": "en_US" }, 60 | { "id": "2", "avatar": "/img/avatars/2.png", "name": "Jerry", "region": "zh_CN" } 61 | ] 62 | 63 | 64 | 65 | [ 66 | { "id" : "foo", "interval" : 500, "active" : "yes" }, 67 | { "id" : "bar", "interval" : 1000, "active" : "no" }, 68 | ] 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
  • 77 | 78 | $?.name 79 |
  • 80 |
    81 | 82 | 83 | { 84 | "id": "$?.attr.data-value", "avatar": "$?.content[0].attr.src", 85 | "name": "$?.children[1].textContent", "region": "$?.attr.data-region" 86 | }, 87 | 88 | 89 |
    90 | 91 | 92 | 93 | 12:00 94 | > 95 |
    96 | 97 |
      98 | 99 | 100 | 101 | 102 | 103 | 104 |

      Bad user data!

      105 |
      106 |
      107 |
    108 | 109 | 110 |

    Baidu

    111 |
    112 | 113 | 114 |

    Bing

    115 |
    116 | 117 | 118 |

    Google

    119 |
    120 | 121 |
    122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |

    You forget to define the $global variable!

    134 |
    135 | 136 |

    Bad global data!

    137 |
    138 | 139 |

    Bad archetype data!

    140 |
    141 |
    142 |
    143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 |
    179 | ``` 180 | 181 | 熟悉 HTML 读者一定会觉得上面的代码很眼熟。是的,和 HTML 类似,HVML 使用标签(tag);但和 HTML 不同的是,HVML 是动态的,表述的是程序,而 HTML 是静态的,表述的是文档。 182 | 183 | 我们对照上述示例,介绍 HVML 的一些特点。 184 | 185 | 首先是数据驱动编程(data-driven programming)。通过基于数据的迭代、插入、更新、清除等操作,开发者不需要编写程序或者只要少量编写程序即可动态生成最终的 XML/HTML 文档。比如下面的上面示例代码中的 `iterate` 标签,就在`$users` 变量代表的数据(在 `header` 中使用 `init` 标签定义)上做迭代,然后在最终文档的 `ul` 元素中插入了若干 `li` 元素。而 `li` 元素的属性(包括子元素)由一个 `archetype` 标签定义,其中使用 `$?` 来指代被迭代的 `$users` 中的一个数据单元。 186 | 187 | 在上面的示例代码中,我们使用了系统内置变量 `$TIMERS` 来定义定时器,每个定时器有一个全局的标识符,间隔时间以及是否激活的标志。如果要激活一个定时器,我们只需要使用 HVML 的 `update` 标签来修改对应的键值,而不需要调用某个特定的编程接口。这是数据驱动编程的另一个用法——我们不需要为定时器或者其他的类似模块的操作提供额外的 API。 188 | 189 | 另外,在上面的示例代码中,我们通过 `observe` 标签观察新的数据或文档本身的变化以及用户交互事件,可实现 XML/HTML 文档或数据的动态更新。比如在最后一个 `observe` 标签中,通过监听用户头像上的点击事件来装载一个新的 `user.hvml` 文件,以模态对话框的形式展示对应用户的详细信息。 190 | 191 | 其次是彻底解除界面、交互和数据之间的耦合。通过 HVML 引入的编程模型和方法,用于表述界面的 XML/HTML 文档内容可完全由 HVML 生成和动态调整,这避免了在程序代码中直接操作文档的数据结构(即文档对象树,或简称 DOM 树),而程序只需要关注数据本身的产生和处理即可。这样,就实现了界面和数据的解耦。比如: 192 | - HVML 可在文档片段模板或者数据模板中定义数据和 DOM 元素之间的映射关系(如示例代码中的 `archetype` 或 `archedata` 标签),而无需编写额外的代码完成数据到 DOM 元素属性、内容等的赋值操作。 193 | - HVML 将错误和异常的展现和程序代码分离了开来,程序只要产生适当的错误或者异常(如示例代码中的 `error` 和 `except` 标签),而对错误或者异常的处理则直接在 HVML 中定义,这不仅仅将程序和界面隔离了开来,同时还提高了代码的可维护性。 194 | 195 | 再次是数据的 JSON 表达。HVML 对文档和数据的操作提供了一致接口。HVML 要求所有外部数据均使用 JSON 格式表述,JSON 格式是一种人机共读的数据表述形式,可在数值、字符串、数组、字典等基本数据单元的基础上表述复杂对象。由于 HTML/XML 文档片段(DOM 子树)可表述为 JSON 格式的数据,因此,HVML 亦可用于操作使用 JSON 表述的数据。在 HVML 中,我们还提供了对动态 JSON 对象的支持,我们可以使用外部脚本程序来实现自己的动态 JSON 对象,且可以在这些对象上执行类似函数调用一样的功能。 196 | 197 | 最后,HVML 通过动作标签(通常都是一些英文的动词,如 `init`、`update`、`iterate` 等)以及与之配合的介词或副词属性来定义动作标签所依赖的数据、目标操作位置以及执行条件来完成特定的文档操作。这和常见的编程语言有很大不同,HVML 的描述方式更加贴近自然语言,从而可以大幅降低学习门槛。 198 | 199 | 限于篇幅,我们不打算在本文中详细介绍 HVML,读者对 HVML 有个感性认识就可以了。有兴趣了解详细规范的读者,可以参阅文末的原文链接。不过要耐心点哦,定义一个完备、自恰的编程语言不是一件容易的事儿,所以要读,就要找个大段的时间,耐心点儿仔细地阅读。 200 | 201 | ## HVML 如何改变传统的 GUI 开发 202 | 203 | 我们假设有一个 GUI 系统,使用 XML 来描述界面上的构件(widget)。现在,我们要使用这个 GUI 系统开发一个简单的文件打开对话框,大致的界面需求如下: 204 | 205 | 1. 有一个列表框(Listbox),其中列出了当前路径下的目录及文件(统称为目录项)。用户可使用鼠标或者键盘切换列表框中的当前选中项,并产生一个选中项改变的事件。 206 | 1. 在该列表框的顶部,有一个文本标签(Label),其中显示了当前的路径。 207 | 1. 当用户点击列表框下面的“Open"按钮(Button)时,若列表框的当前选中项是目录,则进入这个目录,修改用于显示当前路径的文本标签内容,并使用新路径下的目录项填充列表框,若当前选中项是一个文件,则返回选中的这个文件。 208 | 209 | 对上述界面和交互需求,我们通常可以使用如下的 XML 文件描述: 210 | 211 | ```xml 212 | 213 | 216 | 217 | 218 | .. 219 | vincent 220 | david 221 | README.txt 222 | 223 | 224 | 227 | 228 | ``` 229 | 230 | 为满足以上的交互处理需求,我们使用 HVML 来描述这个界面的动态生成以及交互过程: 231 | 232 | ```html 233 | 234 | 235 | 236 | 237 | { 238 | "curr_path": "/home/", 239 | "selected_type": "dir", 240 | "selected_name": "..", 241 | } 242 | 243 | 244 | 245 | 246 | 249 | 250 | 251 | $?.name 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | "$fileInfo.curr_path{$2.name}/" 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | ``` 294 | 295 | 下面对上述 HVML 代码的重点部分做一些说明。 296 | 297 | 首先,该代码使用了一个全局的 `$fileInfo` 变量来记录当前路径(初始为 `/home/`)以及当前选中目录项的类型(初始为 `dir`)和名称(初始为 `..`)。当用户在列表框中选择新的目录项时,会观察到 `selected-item-changed` 事件,并更新 `$fileInfo` 中的 `selected_type` 和 `selected_name` 键值。该事件的 `payload` 键值示例如下: 298 | 299 | ```json 300 | { 301 | "type": "dir", 302 | "name": "david", 303 | } 304 | ``` 305 | 306 | 其次,该代码使用了 `choose` 元素以及一个外部执行器(`CLASS: CDirEntries`)来获得当前路径中的所有目录项。返回的结果数据大致为: 307 | 308 | ```json 309 | [ 310 | { "type": "dir", "name": "david" }, 311 | { "type": "dir", "name": "vincent" }, 312 | { "type": "file", "name": "README.txt" }, 313 | ] 314 | ``` 315 | 316 | 在上述结果之上,使用 `iterate` 元素填充列表框。 317 | 318 | 最后,当用户点击 `Open` 按钮时,上述代码会观察到 `clicked` 事件。在处理该事件时,通过检查 `$fileInfo.selected_type` 来完成相应的工作: 319 | 320 | - 如果当前选中的目录项类型是目录,则切换到该目录。此时,会首先清空列表框,然后再使用新路径下的目录项填充列表框。 321 | - 如果当前选中的目录项类型是文件,则使用 `back` 标签返回上个页面,同时返回 `fileInfo` 数据。 322 | 323 | 在上述代码中,外部选择器 `CDirEntries` 的实现非常简单,就是列出给定路径下的目录项,并按照要求返回一个字典数组。使用 Python 实现时非常简单,所以这里略去不谈。 324 | 325 | 如果我们使用 HybridOS 中提到的直接执行本地系统命令的扩展 URL 图式(lcmd),我们甚至都不需要编写任何代码,而只需要使用 `request`: 326 | 327 | ```html 328 | 329 | 330 | 331 | 332 | 333 | ``` 334 | 335 | 如此,开发者不需要编写任何程序,即可实现一个简单的文件浏览和打开对话框。 336 | 337 | 显然,如果使用 HVML,将大大提高传统 GUI 应用的开发效率,缩短开发周期。当然,传统的 GUI 支持系统,需要提供基于 XML 的 UI 描述支持以及类似 CSS 的布局、样式、动画等的渲染效果支持。 338 | 339 | ## HVML 的未来:云应用 340 | 341 | HVML 的潜力绝对不止上述示例所说的那样。在未来,我们甚至可以将 HVML 代码运行在云端,通过云端控制设备上的界面显示,从而形成一个新的云应用解决方案。 342 | 343 | 我们假设一个智能手环上显示当前时间、当地气温、佩戴者的心跳信息和步数信息等信息,而这个智能手环通过 MQTT(一种轻量级消息通讯协议)和云端服务器交换信息,比如向云端服务器发送佩戴者的心跳和步数信息、地理位置信息,获得时间以及当前位置的气象条件等信息。在传统的实现方式中,我们一般需要开发一个在智能手环上运行的 GUI 系统,然后和云端通讯获得数据,界面的修改完全由设备端代码负责。如果要改变界面的样式,大部分情况下需要升级整个智能手环的固件(firmware)。 344 | 345 | 但如果我们使用 HVML,则可以通过云端来控制设备的界面显示。运行在云端的 HVML 代码如下所示: 346 | 347 | ```html 348 | 349 | 350 | 351 | 352 | 353 | 354 | [ 355 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 356 | ] 357 | 358 | 359 | 360 | 361 | 362 | 363 |
    364 | 365 | 366 | 367 |
    368 | 369 |
    370 | 371 | 372 | 373 |
    374 | 375 |
    376 | 377 | 378 | 379 |
    380 | 381 |
    382 | 383 | 384 | 385 |
    386 | 387 | 388 | 389 | 390 | 391 |
    392 | ``` 393 | 394 | 其要点如下: 395 | 396 | 1. 该代码生成的 HTML 文档或者对 HTML 文档的改变,将通过类似 WebSocket 的长连接发送给设备,设备根据此信息重新渲染用户界面。 397 | 1. 该代码监听智能手环(设备)通过 MQTT 发送给云端的数据,包括心跳、气温、步数等信息,并更新相应的标签内容。 398 | 1. 该代码设定了一个定时器,每隔 1 秒运行一次,并更新时钟对应的标签内容。 399 | 1. 该代码使用了一个外部选择执行器 `CDumpEvent` 将所有来自 `mqtt` 的事件转储到了云端数据库中。 400 | 401 | 这带来了如下显著的改变: 402 | 403 | 1. 复杂的逻辑代码将全部运行在云端,设备端只要有一个足够功能的 HTML/XML 用户代理即可,通常只需要包含一个根据 DOM 树和 CSS 来渲染最终用户界面的渲染器。 404 | 1. 当我们需要调整设备端的显示效果或者功能时,我们只需要修改 HVML 代码,而不需要更新设备端的固件。 405 | 1. 我们还可以通过外部脚本,将运行在云端的其他功能,如数据库存储、数据的分析以及人工智能等要素有机整合在一起。 406 | 407 | 写到这里,笔者真的被自己一贯强调的观点打动了:**编程语言才是决定操作系统灵魂和基因的那个东西,才是基础软件生态皇冠上的那颗明珠!** 408 | 409 | ## HVML 参考实现开发小组 410 | 411 | HVML 长成什么样子,现在大概有了。可实现 HVML,则是一个不亚于浏览器引擎的大软件工程。另外,HVML 可以用在各种场景下,和不同的外部脚本语言绑定,就可以形成不同的系统。如果要将 HVML 应用到云环境中,我们还需要开发应用服务器。 412 | 413 | 如笔者在《考鼎记》小说中所讲:**所谓纲举目张,这编程语言就是那个纲**。一个新的编程语言带来的改变,涉及到基础软件的重构、开发模型的改变、开发工具的变化,以及新的协议和软件的产生。同时还涉及到产业界上下游合作关系的变化。试想,假如本文描述的云应用成为现实,我们现在在物联网操作系统当中所做的很多努力,是不是将一文不值? 414 | 415 | 为了尽快让大家看到 HVML 实际转起来的样子,笔者组织了一个小型的开源协作小组来开发 HVML 的参考实现,其目标是为 Python 生态提供可直接利用 Web 前端技术的解决方案。这个开发小组现在已经开始工作了,我们一旦有了成果,会立即向大家汇报。 416 | 417 | 我们期待更多的人或者企业加入到 HVML 参考实现的开发当中。但是,一个新编程语言从诞生到成熟,需要一个较长的周期。在还没有看到 HVML 运行起来的样子之前,大多数人会抱着围观和观望的态度。这是人之常情。但机会一定是留给有心人的。 418 | 419 | - 假如您是有雄心的基础软件企业、HVML 的潜在用户或者操作系统上游企业,如芯片公司,您可以加入到 HybridOS 的合作伙伴计划(详情见 )中,派几个工程师参与到 HVML 参考实现的开发当中。笔者相信,您的团队和企业,将因此获得莫大的收益。 420 | - 假如您代表个人,可以打个赏给 HVML 参考实现项目(文末打赏或到网页 )。您或大或小的鼓励,都将化成我们前进的绵绵动力! 421 | 422 | 敬请期待,一场变革正在徐徐拉开帷幕! 423 | 424 | 425 | -------------------------------------------------------------------------------- /zh/write-a-calculator-by-using-hvml-step-by-step-zh.md: -------------------------------------------------------------------------------- 1 | # 用 HVML 写一个计算器 2 | 3 | 本文用一个实际的例子描写了如何使用 HVML 一步步编写一个简单的计算器。 4 | 5 | - [Web 版本](#web-版本) 6 | - [一些说明](#一些说明) 7 | - [数据驱动生成 HTML 内容](#数据驱动生成-html-内容) 8 | - [响应式输出](#响应式输出) 9 | - [处理按钮事件](#处理按钮事件) 10 | - [交互过程的细节处理](#交互过程的细节处理) 11 | - [完整的计算器程序源代码](#完整的计算器程序源代码) 12 | 13 | ## Web 版本 14 | 15 | 我们以 CSDN 上一个 [Web 版计算器示例](https://blog.csdn.net/qq_34231010/article/details/77431070)作为开头,以方便理解需求。 16 | 17 | 这一 Web 版本的计算器,其运行原理如下: 18 | 19 | 1. 维护一个用户输入的运算表达式字符串,如 `50 * 3 - 20`; 20 | 1. 当用户点击 `=` 按钮时,使用 JavaScript 的内置 `eval` 函数来对运算表达式求值。 21 | 22 | 该示例在单个 HTML 文件中同时包含了 CSS 和 JavaScript 脚本代码,我们将其复制到本文中,以方便读者阅读,希望原作者不要告侵权。 23 | 24 | ```html 25 | 26 | 27 | 28 | 29 | 计算器 30 | 31 | 101 | 102 | 140 | 141 | 142 | 143 |
    144 |

    计算器

    145 |
    146 | 147 |
    148 |
    149 |
      150 |
    • 7
    • 151 |
    • 8
    • 152 |
    • 9
    • 153 |
    • 154 |
    • C
    • 155 |
    • 4
    • 156 |
    • 5
    • 157 |
    • 6
    • 158 |
    • ×
    • 159 |
    • ÷
    • 160 |
    • 1
    • 161 |
    • 2
    • 162 |
    • 3
    • 163 |
    • +
    • 164 |
    • -
    • 165 |
    • 0
    • 166 |
    • 00
    • 167 |
    • .
    • 168 |
    • %
    • 169 |
    • =
    • 170 |
    171 |
    172 |
    173 | 174 | 175 | ``` 176 | 177 | 该页面被渲染后的效果如下图所示: 178 | 179 | ![计算器渲染效果](calculator.png) 180 | 181 | ## 一些说明 182 | 183 | 上面的 HTML 代码,编写得算不上特别优美,比如大量使用了 `id` 属性,而其实使用 `class` 属性更为合适。 184 | 185 | 我们不打算仔细改造这些细节之处,所以最终的 HVML 程序中,会保留在头部声明的 `style` 标签内容,但由于内容过多,我们会省略其中的样式信息。 186 | 187 | ## 数据驱动生成 HTML 内容 188 | 189 | 首先,我们看到原始 HTML 中有 12 个用来输入数字的按钮,以及八个用于表示四则运算、删除字符、清零的功能按钮。我们可以使用 HVML 的 `iterate` 动作标签完成这些重复性的 HTML 内容的生成。为此,我们首先准备一个全局的数据: 190 | 191 | ```hvml 192 | 193 | 194 | 195 | 计算器 196 | 199 | 200 | 201 | [ 202 | { "idx":"0", "id": "btn_7", "exp": "7", "letters": "7", "class": "btn number" }, 203 | { "idx":"1", "id": "btn_8", "exp": "8", "letters": "8", "class": "btn number" }, 204 | { "idx":"2", "id": "btn_9", "exp": "9", "letters": "9", "class": "btn number" }, 205 | { "idx":"3", "id": "btn_back", "exp": "←", "letters": "←", "class": "c_blue backspace" }, 206 | { "idx":"4", "id": "btn_clear", "exp": "C", "letters": "C", "class": "c_blue clear" }, 207 | { "idx":"5", "id": "btn_4", "exp": "4", "letters": "4", "class": "btn number" }, 208 | { "idx":"6", "id": "btn_5", "exp": "5", "letters": "5", "class": "btn number" }, 209 | { "idx":"7", "id": "btn_6", "exp": "6", "letters": "6", "class": "btn number" }, 210 | { "idx":"8", "id": "btn_mul", "exp": "*", "letters": "x", "class": "btn c_blue multiplication" }, 211 | { "idx":"9", "id": "btn_div", "exp": "/", "letters": "÷", "class": "btn c_blue division" }, 212 | { "idx":"10", "id": "btn_1", "exp": "1", "letters": "1", "class": "btn number" }, 213 | { "idx":"11", "id": "btn_2", "exp": "2", "letters": "2", "class": "btn number" }, 214 | { "idx":"12", "id": "btn_3", "exp": "3", "letters": "3", "class": "btn number" }, 215 | { "idx":"13", "id": "btn_plus", "exp": "+", "letters": "+", "class": "btn c_blue plus" }, 216 | { "idx":"14", "id": "btn_sub", "exp": "-", "letters": "-", "class": "btn c_blue subtraction" }, 217 | { "idx":"15", "id": "btn_0", "exp": "0", "letters": "0", "class": "btn number" }, 218 | { "idx":"16", "id": "btn_00", "exp": "00", "letters": "00", "class": "btn number" }, 219 | { "idx":"17", "id": "btn_dot", "exp": ".", "letters": ".", "class": "btn number" }, 220 | { "idx":"18", "id": "btn_pct", "exp": "%", "letters": "%", "class": "btn c_blue percent" }, 221 | { "idx":"19", "id": "btn_eq", "exp": "=", "letters": "=", "class": "c_yellow equal" }, 222 | ] 223 | 224 | 225 | 226 | 227 | ... 228 | 229 | 230 | ``` 231 | 232 | 我们在全局的 `buttons` 这个 JSON 对象数组上执行迭代,即可生成所有的按钮。注意,为方便其后的编程,我们为不同种类的按钮新增了一个用于功能的类名,如 `number`、`plus` 等。 233 | 234 | 对应的 HVML `body` 标签内容如下: 235 | 236 | ```hvml 237 | 238 |
    239 | 240 |
    241 |

    $T.get('HVML Calculator') 242 | $T.get('Current Time: ')$DATETIME.time_prt() 243 |

    244 | 245 | 246 | 247 | 248 |
    249 | 250 |
    251 | 252 |
    253 | 254 |
    255 | 256 |
  • $?.letters
  • 257 |
    258 | 259 |
      260 | 261 | 262 | 263 |

      Bad data!

      264 |
      265 |
      266 |
    267 |
    268 | 269 | ... 270 | ``` 271 | 272 | 注意,在上述代码中,除了使用了 HVML `iterate` 动作标签之外,我们还使用了 `archetype` 标签,用于定义一个 HTML 片段模板。 273 | 274 | ## 处理表达式输入 275 | 276 | 随着用户点击按钮,解释器输出框中的内容将随之改变。为此,我们设定一个全局的字符串变量,用于保存计算器输出框中的字符串,当用户点击按钮时,该字符串的值将发生变化,而计算器输出框中显示的内容也将相应发生变化。 277 | 278 | 这一改变涉及两处: 279 | 280 | - 在头部新增一个全局的 `myResult` 变量。 281 | - 监听具有类名 `.btn` 元素之 `click` 事件,更新表达式并重置输入框的文本内容。 282 | 283 | 如下所示: 284 | 285 | ```hvml 286 | 287 | 288 | ... 289 | 290 | 291 | { 292 | exp: "", 293 | expr: "", 294 | } 295 | 296 | 297 | 298 | 299 |
    300 | ... 301 |
    302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | ``` 310 | 311 | ## 处理其他按钮事件 312 | 313 | 接下来,我们使用 HVML 的 `observe` 标签处理其他按钮上的事件。其中清除(C)按钮是最容易处理的: 314 | 315 | ```hvml 316 | 317 | 318 | 319 | 320 | 321 | ``` 322 | 323 | 回退(backspace)按钮的处理,要稍微麻烦一些。对字符串操作,我们通常使用由 HVML 解释器实现的内置动态对象(如 `$STR`),通过调用该对象提供的动态方法 `substr` 实现: 324 | 325 | ```hvml 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | ``` 338 | 339 | 对 `=` 按钮的处理,我们直接使用预定义变量 `$MATH` 提供的 `eval` 方法: 340 | 341 | ```hvml 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | ``` 352 | 353 | ## 完整的计算器程序源代码 354 | 355 | 见下面的代码清单。 356 | 357 | 注:为节省篇幅,我们把 CSS 部分保存到了单独的外部文件中。 358 | 359 | ```hvml 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | { 369 | exp: "", 370 | expr: "", 371 | } 372 | 373 | 374 | 375 | [ 376 | { "idx":"0", "id": "btn_7", "exp": "7", "letters": "7", "class": "btn number" }, 377 | { "idx":"1", "id": "btn_8", "exp": "8", "letters": "8", "class": "btn number" }, 378 | { "idx":"2", "id": "btn_9", "exp": "9", "letters": "9", "class": "btn number" }, 379 | { "idx":"3", "id": "btn_back", "exp": "←", "letters": "←", "class": "c_blue backspace" }, 380 | { "idx":"4", "id": "btn_clear", "exp": "C", "letters": "C", "class": "c_blue clear" }, 381 | { "idx":"5", "id": "btn_4", "exp": "4", "letters": "4", "class": "btn number" }, 382 | { "idx":"6", "id": "btn_5", "exp": "5", "letters": "5", "class": "btn number" }, 383 | { "idx":"7", "id": "btn_6", "exp": "6", "letters": "6", "class": "btn number" }, 384 | { "idx":"8", "id": "btn_mul", "exp": "*", "letters": "x", "class": "btn c_blue multiplication" }, 385 | { "idx":"9", "id": "btn_div", "exp": "/", "letters": "÷", "class": "btn c_blue division" }, 386 | { "idx":"10", "id": "btn_1", "exp": "1", "letters": "1", "class": "btn number" }, 387 | { "idx":"11", "id": "btn_2", "exp": "2", "letters": "2", "class": "btn number" }, 388 | { "idx":"12", "id": "btn_3", "exp": "3", "letters": "3", "class": "btn number" }, 389 | { "idx":"13", "id": "btn_plus", "exp": "+", "letters": "+", "class": "btn c_blue plus" }, 390 | { "idx":"14", "id": "btn_sub", "exp": "-", "letters": "-", "class": "btn c_blue subtraction" }, 391 | { "idx":"15", "id": "btn_0", "exp": "0", "letters": "0", "class": "btn number" }, 392 | { "idx":"16", "id": "btn_00", "exp": "00", "letters": "00", "class": "btn number" }, 393 | { "idx":"17", "id": "btn_dot", "exp": ".", "letters": ".", "class": "btn number" }, 394 | { "idx":"18", "id": "btn_pct", "exp": "%", "letters": "%", "class": "btn c_blue percent" }, 395 | { "idx":"19", "id": "btn_eq", "exp": "=", "letters": "=", "class": "c_yellow equal" }, 396 | ] 397 | 398 | 399 | $T.get('HVML Calculator') 400 | 401 | 402 | [ 403 | { "id" : "clock", "interval" : 1000, "active" : "yes" }, 404 | ] 405 | 406 | 407 | 408 | 419 | 420 | 421 | 422 |
    423 | 424 |
    425 |

    $T.get('HVML Calculator') 426 | $T.get('Current Time: ')$DATETIME.time_prt() 427 |

    428 | 429 | 430 | 431 | 432 |
    433 | 434 |
    435 | 436 |
    437 | 438 |
    439 | 440 |
  • $?.letters
  • 441 |
    442 | 443 |
      444 | 445 | 446 | 447 |

      Bad data!

      448 |
      449 |
      450 |
    451 |
    452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 |
    486 | 487 | 488 |
    489 | ``` 490 | --------------------------------------------------------------------------------