Ton的编译过程(上)

系列文章目录

FunC编写初始准备


文章目录

  • 系列文章目录
  • 预先准备
  • 第一个FunC合约
  • 深入compileFunc的内部
    • compileFunc初探
    • 艾丽卡的疑惑
    • package.json
  • 初览index.js


预先准备

首先请大家跟着艾丽卡一步一步的完成FunC编写初始准备 这里面环境的搭建。
接下来,请做好下面的步骤,如果这里面有任何疑惑先别担心,我们后面会慢慢介绍:

让我们开始我们的项目设置之旅。首先,我们要为我们的项目创建一个文件夹。
请注意这里是我的linux环境,如果你们是windows环境下面的命令不起效果,也可以直接在vscode中创建文件

mkdir my_first_contract && cd my_first_contract

在这里插入图片描述看到这个左上角鼠标放置的地方,new file是创建文件,旁边的那个是创建文件夹,慢慢摸索就会了。

接下来,我们用一个包管理器来初始化一个package.json文件。艾丽卡将使用yarn,木森更喜欢使用npm,我会在接下来都演示一遍,注意,大家只需要使用一个工具就ok啦

yarn init

or

npm init

系统会提示你输入一些参数,但你可以简单地在每个提示时按回车键。完成后,我们的项目目录中应该会有一个package.json文件,它包含以下默认内容:

{"name": "my_first_contract","version": "1.0.0","main": "index.js", "license": "MIT"
}

现在,我们来安装一些库。这些库都与TypeScript相关。

yarn add typescript ts-node @types/node @swc/core --dev

or

npm install typescript ts-node @types/node @swc/core --dev

接下来,我们在项目根目录创建一个tsconfig.json文件,并在其中放置以下配置:

{"compilerOptions": {"target": "es2020","module": "commonjs","esModuleInterop": true,"forceConsistentCasingInFileNames": true,"strict": true,"skipLibCheck": true,"resolveJsonModule": true},"ts-node": {"transpileOnly": true,"transpiler": "ts-node/transpilers/swc"}
}

上面的内容有些多,但其实他们就是在编程中,一些关于语法上面的规定,比如说:

"resolveJsonModule": true

这个家伙,如果没有他的话,你将不能够在你的代码中轻松的引用josn文件的包
比如说未来的某一天会遇到这个

import {hex} from "../build/main.compiled.json"

你看这个里面的main.compiled.json可以被引用就是"resolveJsonModule": true的个功劳,剩下的我们会慢慢解释的。。。

我们还需要安装三个与TON区块链相关的库:

  • ton-core:实现了TON区块链的低级原语的核心库。
  • ton-crypto:用于构建TON区块链应用的加密原语。
  • @ton-community/func-js:TON FunC编译器。

安装这些库的命令如下:

yarn add @ton/core ton-crypto @ton-community/func-js --dev

or

npm install @ton/core ton-crypto @ton-community/func-js --dev

艾丽卡,现在我们已经为编写和编译我们的智能合约做好了准备。接下来,我们可以开始编写我们的FunC代码,并使用这些工具来编译它。这将是我们的魔法之旅的第一步!

第一个FunC合约

艾丽卡(专注地):“木森,这些符文和符号看起来好复杂啊。我们真的能通过这本魔法书来编写我们的智能合约吗?”

木森(认真地):“当然可以,艾丽卡。只要我们跟着这本魔法书的指引一步步来,就能编写出我们的智能合约。首先,我们要做的是理解这些符文的含义和如何正确地组合它们。”

艾丽卡(点头):“好的,木森。那我们先从哪里开始呢?”

木森(指着魔法书):“我们先从创建一个名为contracts的魔法空间开始,然后在里面放置我们的main.fc卷轴,这将是我们编写合约的载体。”

艾丽卡(兴奋地):“就像我们在魔法工作台上准备材料一样!那我们快开始吧,我已经迫不及待想要看到我们的合约变成现实了。”

木森(微笑):“没问题,艾丽卡。跟着我,我们一步步来。首先,我们要用这个咒语来创建我们的魔法空间和卷轴。”

他们一起念出了咒语,很快,contracts文件夹和main.fc文件就出现在了他们面前。木森和艾丽卡相视一笑,知道他们已经迈出了成功的第一步。接下来,他们将开始在main.fc卷轴上书写他们的智能合约符文。

mkdir contracts && cd contracts && touch main.fc

接下来,我们打开main.fc文件,并输入一些简单的FunC代码。这段代码定义了一个智能合约可以接收消息的函数。

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {}

现在,我们要编写一个编译脚本,这个脚本会使用@ton-community/func-js库来编译我们的智能合约代码。

首先,我们创建一个scripts文件夹,并在其中创建一个名为compile.ts的TypeScript文件。

mkdir scripts && cd scripts && touch compile.ts

然后,我们在项目的package.json文件中添加一个快捷方式,这样我们就可以通过一个命令来运行我们的编译脚本。

{//...your previous package.json contents"scripts": {"compile": "ts-node ./scripts/compile.ts"}
}

现在,我们打开compile.ts文件,并开始编写编译脚本。我们会导入一些必要的模块,比如fs用于文件操作,process用于控制脚本执行过程,Cell用于存储合约的字节码,以及compileFunc用于实际的编译功能。

import * as fs from "fs";
import process from "process";
import { Cell } from "@ton/core";
import { compileFunc } from "@ton-community/func-js";async function compileScript() {}compileScript();

接下来,我们使用compileFunc函数来编译我们的智能合约。我们传递给它合约文件的路径,并在编译出错时退出脚本。

async function compileScript() {const compileResult = await compileFunc({targets: ["./contracts/main.fc"],sources: (x) => fs.readFileSync(x).toString("utf8"),});if (compileResult.status === "error") {process.exit(1);}
}
compileScript();

深入compileFunc的内部

compileFunc初探

  const compileResult = await compileFunc({targets: ["./contracts/main.fc"],sources: (x) => fs.readFileSync(x).toString("utf8"),});

让我们逐步解释这段代码:

  1. const compileResult = await compileFunc({ ... });:这里使用await关键字等待compileFunc函数的执行结果。await只能在异步函数(用async关键字声明的函数)中使用。compileResult变量将会存储编译过程的结果。

  2. targets: ["./contracts/main.fc"]:这个属性指定了编译的目标文件,即需要编译的FunC语言文件的路径。在这个例子中,文件路径是./contracts/main.fc,表示文件位于contracts目录下,并且文件名为main.fc

  3. sources: (x) => fs.readFileSync(x).toString("utf8"),:这是一个函数,作为compileFuncsources属性的值。这个函数负责提供编译器需要的源代码文件的内容。当编译器需要读取文件内容时,它会调用这个函数,并将文件路径作为参数x传递给它。

    • fs.readFileSync(x):使用Node.js的fs模块同步地读取文件内容。这里的x是文件路径。
    • .toString("utf8"):将读取到的文件内容(通常是一个Buffer对象)转换为UTF-8编码的字符串。

综合来看,这段代码的意思是:
1.等待compileFunc函数完成编译工作
2.编译的目标是./contracts/main.fc文件
3.编译过程中,编译器会通过sources函数获取需要编译的源代码文件的内容。

艾丽卡的疑惑

艾丽卡(眼睛闪闪发光):“木森,我有个想法!我们为什么不直接把源代码放到compileFunc里面呢?这样不是更方便吗?”

木森(微笑):“艾丽卡,你的想象力总是这么丰富。通常,compileFunc函数确实是通过文件路径来读取源代码的。这是因为编译器需要知道代码文件的确切位置。”

艾丽卡(好奇地):“但是我们能不能尝试一下直接传递代码呢?就像我们用魔法棒直接施法一样!”

木森(思考):“这的确是个有趣的点子。虽然compileFunc默认是设计来读取文件的,但我们或许可以找到一种方法,把源代码作为字符串传递给它。”

艾丽卡(兴奋地):“那我们快试试吧!我喜欢冒险和尝试新事物!”

木森(点头):“好主意!让我们开始这个新的探索。或许可以尝试创建一个临时文件,或者看看compileFunc是否有其他方式可以接受字符串输入。这样我们的编译过程可能会变得更加灵活和方便。”

艾丽卡(跳起来):“耶!一起探索新的魔法吧,木森!我们一定能找到答案的!”

木森(笑着):“但是,任何事物可不能盲目尝试,我们可以试着去阅读他的底层真正的了解他。”

艾丽卡(疑惑):“可是要怎么阅读呢?他就这么几句话。”

木森(一本正经):“从哪里来?到哪里去!”
将鼠标移动到我们引入compileFunc的那段包上面,选中他,便可以看到它的路径
在这里插入图片描述然后,按下F12,奇迹出现了。。

package.json

突然,屏幕一闪,他们的视野仿佛穿透了代码的表面,直接进入了compileFunc函数的底层世界
node_modules/@ton-community/func-js/dist/index.ts文件下。

艾丽卡(好奇地):“木森,我有点困惑。为什么我们导入的是@ton-community/func-js,但是实际上我们却来到了node_modules/@ton-community/func-js/dist/index.ts呢?”

木森(耐心地):“艾丽卡,这是因为在Node.js的世界里,每个包(package)都可以指定一个主入口文件。这个入口文件是当你导入包时,实际上会加载的文件。”

艾丽卡(挠头):“哦?那这个是怎么决定的呢?”

木森(指向package.json文件):“看,这里。每个包都有一个package.json文件,它包含了包的元数据。在这个文件中,有一个字段叫做main,它指定了包的主入口文件。”

艾丽卡(凑近看):“哇,我看到了,这里写着"main": "dist/index.js"。所以当我们导入@ton-community/func-js时,实际上是导入了这个dist/index.js文件。”

木森(点头):“没错,艾丽卡。这就是Node.js和npm(或yarn)的工作方式。它们会根据package.json中的main字段来确定加载哪个文件。”

艾丽卡(恍然大悟):“原来如此!那这个dist文件夹又是什么呢?”

木森(解释):“distdistribution的缩写,它通常用于存放构建或编译后的代码。在这个案例中,@ton-community/func-js包的作者可能将编译后的JavaScript代码放在了dist文件夹中,以便用户可以直接使用。”

艾丽卡(兴奋地):“这真是太神奇了!就像我们的魔法书,每一页都有它的作用,而这个package.json就像是目录,告诉我们该去哪里找到我们想要的魔法。”

木森(微笑):“Node.js包的内部结构就是这样来找到正确的入口文件。”

艾丽卡(好奇地):“木森,这个项目里既有JavaScript文件又有TypeScript文件,我们应该看哪一个呢?”

木森(耐心地):“艾丽卡,虽然TypeScript提供了类型安全和更现代的语法特性,但是在Node.js环境中,最终运行的代码都是JavaScript。因此,阅读JavaScript文件可以帮助我们更直接地理解代码是如何工作的。”

艾丽卡(思考):“那么,TypeScript文件就不重要了吗?”

木森(微笑):“并不是的。TypeScript文件在编译时会被转换成JavaScript。它提供了额外的类型检查和更清晰的代码结构,这对于开发大型应用程序和团队协作非常有帮助。但是,如果你想要快速理解代码的运行逻辑,直接阅读JavaScript文件会更直观。”

初览index.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileFunc = exports.compilerVersion = exports.latestCompiler = exports.FuncCompiler = exports.sourcesResolver = exports.arraySourceResolver = exports.mapSourceResolver = void 0;
const path_1 = require("./path");
const utils_1 = require("./utils");
require('./funcfiftlib.js');
const func_js_bin_1 = require("@ton-community/func-js-bin");
const mapSourceResolver = (map) => {return (path) => {if (path in map) {return map[path];}throw new Error(`Cannot find source file \`${path}\``);};
};
exports.mapSourceResolver = mapSourceResolver;
const arraySourceResolver = (arr) => {return (path) => {const entry = arr.find(e => e.filename === path);if (entry === undefined)throw new Error(`Cannot find source file \`${path}\``);return entry.content;};
};
exports.arraySourceResolver = arraySourceResolver;
const sourcesResolver = (sources) => {if (typeof sources === 'function')return sources;if (Array.isArray(sources))return (0, exports.arraySourceResolver)(sources);return (0, exports.mapSourceResolver)(sources);
};
exports.sourcesResolver = sourcesResolver;
const copyToCString = (mod, str) => {const len = mod.lengthBytesUTF8(str) + 1;const ptr = mod._malloc(len);mod.stringToUTF8(str, ptr, len);return ptr;
};
const copyToCStringPtr = (mod, str, ptr) => {const allocated = copyToCString(mod, str);mod.setValue(ptr, allocated, '*');return allocated;
};
const copyFromCString = (mod, ptr) => {return mod.UTF8ToString(ptr);
};
class FuncCompiler {constructor(funcWASMObject) {this.createModule = async () => await this.module({ wasmBinary: this.wasmBinary });this.compilerVersion = async () => {const mod = await this.createModule();const versionJsonPointer = mod._version();const versionJson = copyFromCString(mod, versionJsonPointer);mod._free(versionJsonPointer);return JSON.parse(versionJson);};this.validateVersion = async () => {const v = await this.compilerVersion();return v.funcVersion === this.inputFuncVersion;};this.compileFunc = async (compileConfig) => {const resolver = (0, exports.sourcesResolver)(compileConfig.sources);let targets = compileConfig.targets;if (targets === undefined && Array.isArray(compileConfig.sources)) {targets = compileConfig.sources.map(s => s.filename);}if (targets === undefined) {throw new Error('`sources` is not an array and `targets` were not provided');}const entryWithNoSource = targets.find(filename => {try {resolver(filename);return false;}catch (e) {return true;}});if (entryWithNoSource) {throw new Error(`The entry point \`${entryWithNoSource}\` was not provided in sources.`);}const mod = await this.createModule();const allocatedPointers = [];const sourceMap = {};const sourceOrder = [];const callbackPtr = mod.addFunction((_kind, _data, contents, error) => {const kind = copyFromCString(mod, _kind);const data = copyFromCString(mod, _data);if (kind === 'realpath') {const path = (0, path_1.normalize)(data);allocatedPointers.push(copyToCStringPtr(mod, path, contents));}else if (kind === 'source') {const path = (0, path_1.normalize)(data);try {const source = resolver(path);sourceMap[path] = { content: source, included: false };sourceOrder.push(path);allocatedPointers.push(copyToCStringPtr(mod, source, contents));}catch (err) {const e = err;allocatedPointers.push(copyToCStringPtr(mod, 'message' in e ? e.message : e.toString(), error));}}else {allocatedPointers.push(copyToCStringPtr(mod, 'Unknown callback kind ' + kind, error));}}, 'viiii');const configStr = JSON.stringify({sources: targets,optLevel: compileConfig.optLevel || 2,});const configStrPointer = copyToCString(mod, configStr);allocatedPointers.push(configStrPointer);const resultPointer = mod._func_compile(configStrPointer, callbackPtr);allocatedPointers.push(resultPointer);const retJson = copyFromCString(mod, resultPointer);// CleanupallocatedPointers.forEach(ptr => mod._free(ptr));mod.removeFunction(callbackPtr);const snapshot = [];for (let i = sourceOrder.length - 1; i >= 0; i--) {const path = sourceOrder[i];if (sourceMap[path].included)continue;snapshot.push({filename: path,content: sourceMap[path].content,});}const ret = JSON.parse(retJson);return {...ret,snapshot,};};if (!('schemaVersion' in funcWASMObject))throw new Error('FunC WASM Object does not contain schemaVersion');if (funcWASMObject.schemaVersion !== 1)throw new Error('FunC WASM Object is of unknown schemaVersion ' + funcWASMObject.schemaVersion);const normalObject = funcWASMObject;this.module = normalObject.module;this.wasmBinary = (0, utils_1.base64Decode)(normalObject.wasmBase64);this.inputFuncVersion = normalObject.funcVersion;}
}
exports.FuncCompiler = FuncCompiler;
exports.latestCompiler = new FuncCompiler(func_js_bin_1.object);
exports.compilerVersion = exports.latestCompiler.compilerVersion;
exports.compileFunc = exports.latestCompiler.compileFunc;

exports.compileFunc = exports.latestCompiler.compileFunc;艾里卡结尾是这个

木森(认真地):“艾丽卡,这行代码告诉我们compileFunc实际上是从另一个模块 latestCompiler 中导出的。这意味着compileFunc函数的实现是在latestCompiler这个模块里。”

艾丽卡(好奇地):“那么,latestCompiler是什么呢?”

木森(解释):“latestCompiler很可能是一个包含了最新编译器实现的模块。在这个上下文中,exports.compileFunc = exports.latestCompiler.compileFunc; 这行代码表示我们将latestCompiler模块中的compileFunc函数导出为当前模块的一个公共接口。”

艾丽卡(思考):“所以,当我们在代码中调用compileFunc时,实际上是在调用latestCompiler中的compileFunc?”

木森(点头):“正是这样。这是一种常见的模块化编程技巧,它允许我们将功能封装在不同的模块中,然后在需要的时候将它们组合起来。”

艾丽卡(兴奋地):“那么,我们怎样才能了解更多关于latestCompiler的信息呢?”

木森(指导):“我们可以尝试查看latestCompiler模块的文档或者源代码。通常,这些信息可以在模块的package.json文件中找到,或者在模块的根目录下有相关的文档文件。”

艾丽卡(兴奋地):“木森,我找到latestCompiler的定义了!它是通过创建一个新的FuncCompiler实例来实现的,代码是exports.latestCompiler = new FuncCompiler(func_js_bin_1.object);。”

木森(点头):“很好,艾丽卡!这行代码的意思是,我们正在使用FuncCompiler类来创建一个编译器的实例,并将其赋值给latestCompiler。这样,我们就可以使用这个实例来调用编译功能了。”

艾丽卡(好奇地):“那func_js_bin_1.object又是什么呢?”

木森(解释):“func_js_bin_1.object通常是一个包含编译器所需的WebAssembly模块和其他信息的对象。这个对象可能是在@ton-community/func-js-bin包中定义的,它提供了编译器的底层实现。”

艾丽卡(思考):“所以,latestCompiler就是我们用来编译FunC代码的工具,而FuncCompiler类则负责处理编译的具体逻辑,对吗?”

木森(微笑):“正是如此,艾丽卡。通过创建FuncCompiler的实例,我们可以调用它的方法,比如compileFunc,来编译我们的智能合约。”

随后艾丽卡使用F12点开了func_js_bin_1.object

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.object = void 0;
exports.object = {schemaVersion: 1,funcVersion: '0.4.4',module: require('./funcfiftlib.js'),wasmBase64: require('./funcfiftlib.wasm.js').FuncFiftLibWasm,
};

艾丽卡(眼睛一亮):“木森,我找到了@ton-community/func-js-bin包里的宝藏!这里有FuncWASMObject类型和object常量。”

木森(微笑):“太棒了,艾丽卡!让我们来仔细看看这些宝藏是什么。”

艾丽卡(好奇地):“这个FuncWASMObject类型定义了什么呢?”

木森(解释):“FuncWASMObject类型定义了一个对象的结构,这个对象包含了编译器需要的所有信息。它有以下几个属性:

schemaVersion:这个数字表示对象结构的版本,这里固定为1,表示我们使用的是这个特定版本的结构。funcVersion:这是一个字符串,表示编译器的版本。module:这个属性是一个任意类型(any),它可能包含了与编译器模块相关的一些底层实现或引用。wasmBase64:这是一个字符串,包含了编译器WebAssembly模块的Base64编码数据。”

艾丽卡(恍然大悟):“我明白了!那么object常量就是FuncWASMObject类型的一个实例,它提供了这些信息给FuncCompiler。”

木森(点头):“没错,艾丽卡。object常量就是FuncCompiler类在创建实例时所需要的那个func_js_bin_1.object。它实际上是一个已经准备好的编译器对象,我们可以直接用它来编译FunC代码。”

艾丽卡(困惑地):“木森,这个funcfiftlib.wasm.js文件里的内容看起来好奇怪啊,这串长长的编码是什么?它不像是我们平时看到的代码。”

module.exports = { FuncFiftLibWasm: 'AGFzbQEAAAAB0gZhYAF/AGABfwF/YAJ/fwF/YAN/f38Bf2ACf38AYAR/f39/AX9gA39/fwBgBX9/f39/AX9gBH9/f38AYAZ/f39/f38Bf2AHf39/f39/fwF/YAABf2AFf39/f38AYAZ/f39/f38AYAAAYAh/f39/f39/fwF/YAd/f39/f39/AGAJf39/f39/f39/AX9gAn9/AX5gAX8BfmAIf39/f39/f38AYAp/f39/f39/f39/AX9gAn9+AGAFf39+f38AYAJ/fgF/YAV/f39/fgF/YAt/f39/f39/f39/fwF/YAN/fn8Bf2ADf39+AGAKf39/f39/f39/fwBgBX9+fn5+AGAMf39/f39/f39/f39/AX9gCX9/f39/f39/fwBgAAF+YAN+f38AYAN/fn8BfmADf39+AX9gBH9/f34AYAF+AX5gBH9/f34Bf2AFf39/f3wBf2AAAXxgBH9+fn8AYAN/f38BfmADf35/AGAEf39/fwF+YAt/f39/f39/f39/fwBgAn5/AX5gAn5+AX5gA39+fgF+YAF+AX9gAX8BfGAGf39/f35/AX9gBn98f39/fwF/YAJ+fwBgBn9/f35+fgBgBX9/f35+AGAEf39+fgBgBn9/fn5/fwBgD39/f39/f39/f39/f39/fwBgB39/f39/fn4Bf2AGf39/f35+AX9gEH9/f39/f39/f39/f39/f38AYAR/f39/AXxgBH9/f38BfWAGf39/f398AX9gCH9/f39/fn9/AGAGf39+f39/AX9gEH9/f39/f39/f39/f39/f38Bf2ADf35+AGAEf39+fwF/YAd/f39/f35/AX9gBH9/fn8AYAJ/fABgDX9/f39/f39/f39/f38Bf2AOf39/f39/f39/f39/f38Bf2AEfn5+fgF/YAJ+fwF/YAp/fn5+fn5+fn5+AX9gAn5+AXxgBH9/f34BfmABfAF+YAl/f39/fH9/f38Bf2AJf39/f35/f39/AX9gBX9/f39/AX5gBn9/f39/fgF/YAJ+fgF9YAN+fn4Bf2ACfH8BfGARf39/f39/f39/f39/f39/f38Bf2ADf398AX9gBX9/f35/AX9gC39/f39+fn5/f39/AX9gAXwBf2ACf34BfmADf39/AXxgA39

木森(耐心地):“艾丽卡,你发现的这个其实是WebAssembly模块的Base64编码。这个编码是将二进制的WebAssembly模块转换成了文本格式,这样它就可以被方便地在网络中传输了。”

艾丽卡(好奇地):“但是,我们怎么从这个编码中得到真正的编译器呢?”

木森(解释):“在Node.js中,我们可以使用内置的Buffer类来解码这个Base64字符串,将其转换回二进制格式,然后加载到WebAssembly实例中。这个过程通常是由@ton-community/func-js库内部处理的。”

艾丽卡在index中找到了类似的base64的代码
this.wasmBinary = (0, utils_1.base64Decode)(normalObject.wasmBase64);
在里面他发现了编码的详细内容

"use strict";
// Credits: https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_–_rewriting_atob_and_btoa_using_typedarrays_and_utf-8  
Object.defineProperty(exports, "__esModule", { value: true });
exports.base64Decode = void 0;
function b64ToUint6(nChr) {return nChr > 64 && nChr < 91? nChr - 65: nChr > 96 && nChr < 123? nChr - 71: nChr > 47 && nChr < 58? nChr + 4: nChr === 43? 62: nChr === 47? 63: 0;
}
function base64Decode(sBase64) {const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");const nInLen = sB64Enc.length;const nOutLen = (nInLen * 3 + 1) >> 2;const taBytes = new Uint8Array(nOutLen);let nMod3;let nMod4;let nUint24 = 0;let nOutIdx = 0;for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {nMod4 = nInIdx & 3;nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));if (nMod4 === 3 || nInLen - nInIdx === 1) {nMod3 = 0;while (nMod3 < 3 && nOutIdx < nOutLen) {taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;nMod3++;nOutIdx++;}nUint24 = 0;}}return taBytes;
}
exports.base64Decode = base64Decode;

木森(微笑):“艾丽卡,这段代码是一个用来将Base64编码的字符串转换成二进制数据的函数。让我们一步步来看看它是如何工作的。”

艾丽卡(好奇地):“好的,木森。这个base64Decode函数是做什么的?”

木森(解释):“base64Decode函数接受一个Base64编码的字符串作为输入,然后返回相应的二进制数据。这个二进制数据被存储在一个Uint8Array类型的数组中。”

艾丽卡(思考):“那么,这个转换过程具体是怎么进行的呢?”

木森(指着代码):“首先,这个函数定义了一个辅助函数b64ToUint6,它将Base64编码中的每个字符映射到一个6位的数字上。Base64编码使用64个字符,所以每个字符可以表示6位信息。”

艾丽卡(专注地):“我看到了,那么这些字符是怎么被映射的呢?”

木森(继续解释):“这个映射是根据Base64的编码规则来的。大写字母A-Z映射到0-25,小写字母a-z映射到26-51,数字0-9映射到52-61,+映射到62,/映射到63。”

艾丽卡(点头):“原来是这样。那么,主函数base64Decode是怎么使用这个映射的呢?”

木森(耐心地):“在base64Decode函数中,首先会移除输入字符串中的任何非Base64字符,比如换行符或空格。然后,它会计算输出数组的长度,并创建一个Uint8Array数组来存储转换后的二进制数据。”

艾丽卡(好奇):“那么,它是如何将Base64字符转换为二进制数据的呢?”

木森(微笑):“这个过程是通过一系列的位操作来完成的。函数会遍历输入字符串的每个字符,使用b64ToUint6函数获取每个字符的6位数值,并将这些值组合成一个24位的整数。然后,它会将这个24位的整数拆分成三个字节,并将它们存储到输出数组中。”

艾丽卡(恍然大悟):“哦,我明白了!那么,如果输入字符串的长度不是3的倍数,它是怎么处理的呢?”

木森(点头):“在Base64编码中,如果输入数据的长度不是3的倍数,会在编码的字符串的末尾添加一个或两个=字符作为填充。在解码过程中,如果遇到这种情况,函数会忽略这些填充字符。”

艾丽卡(兴奋地):“这真是太神奇了!我们可以用这个函数来解码任何Base64编码的字符串,得到原始的二进制数据。”

看完了编译器的大致流程,让我们在回到index.js中

这段代码是一个编译函数的核心部分,它处理编译配置、解析源文件、并调用底层模块进行编译。让我们一步步地分析这个函数的工作原理,特别是它是如何处理source的。

  1. 定义解析器
const resolver = (0, exports.sourcesResolver)(compileConfig.sources);

这行代码创建了一个解析器函数,它用于从编译配置中提供的源文件信息中获取实际的源代码。这个解析器函数通常是一个高级函数,能够根据文件名或其他标识符读取源文件的内容。

  1. 确定编译目标
let targets = compileConfig.targets;
if (targets === undefined && Array.isArray(compileConfig.sources)) {targets = compileConfig.sources.map(s => s.filename);
}
if (targets === undefined) {throw new Error('`sources` is not an array and `targets` were not provided');
}

这部分代码首先尝试从编译配置中获取targets,如果未定义且sources存在,它会尝试从sources数组中提取文件名作为目标。如果两者都未定义,将抛出错误

  1. 检查源文件是否存在
const entryWithNoSource = targets.find(filename => {try {resolver(filename);return false;}catch (e) {return true;}
});
if (entryWithNoSource) {throw new Error(`The entry point \`${entryWithNoSource}\` was not provided in sources.`);
}

这里,代码检查每个目标文件是否都可以通过解析器找到。如果有任何目标文件无法找到,将抛出错误。

  1. 设置编译环境
const mod = await this.createModule();
const allocatedPointers = [];
const sourceMap = {};
const sourceOrder = [];

这部分代码初始化编译模块,准备一些辅助变量,如用于存储指针的数组、源文件映射和源文件顺序列表。

  1. 定义回调函数
const callbackPtr = mod.addFunction((_kind, _data, contents, error) => {// 省略具体实现...
}, 'viiii');

这里定义了一个回调函数,它将被底层模块调用来处理各种编译时的事件,如请求源文件内容。

  1. 配置和启动编译
const configStr = JSON.stringify({sources: targets,optLevel: compileConfig.optLevel || 2,
});
const configStrPointer = copyToCString(mod, configStr);
allocatedPointers.push(configStrPointer);
const resultPointer = mod._func_compile(configStrPointer, callbackPtr);
allocatedPointers.push(resultPointer);
const retJson = copyFromCString(mod, resultPointer);

这部分代码将编译配置转换为字符串,并将其传递给底层模块以启动编译过程。

  1. 清理和返回结果
allocatedPointers.forEach(ptr => mod._free(ptr));
mod.removeFunction(callbackPtr);
const snapshot = [];
for (let i = sourceOrder.length - 1; i >= 0; i--) {const path = sourceOrder[i];if (sourceMap[path].included)continue;snapshot.push({filename: path,content: sourceMap[path].content,});
}
const ret = JSON.parse(retJson);
return {...ret,snapshot,
};

最后,代码清理分配的资源,构建一个包含所有源文件内容的快照,并返回编译结果。

这个函数是一个完整的编译流程实现,它处理源文件的解析、编译配置的设置、编译过程的启动和结果的处理。

我们现在返回头,来看一下艾丽卡最关心的如何编译

const sourcesResolver = (sources) => {if (typeof sources === 'function')return sources;if (Array.isArray(sources))return (0, exports.arraySourceResolver)(sources);return (0, exports.mapSourceResolver)(sources);
};

木森(耐心地):“艾丽卡,这段代码定义了一个名为sourcesResolver的函数,它的作用是根据提供的sources参数的不同形态,返回一个合适的解析器函数。”

艾丽卡(好奇地):“哦?那sources有哪些不同的形态呢?”

木森(解释):“sources参数可以有两种不同的形态:

  1. 函数:如果sources是一个函数,那么sourcesResolver会直接返回这个函数。这种情况下,函数应该接受一个文件路径作为参数,并返回该路径对应的源代码内容。”

艾丽卡(思考):“也就是说,如果我已经有一个函数可以读取文件内容,sourcesResolver就会使用它?”

木森(点头):“没错,这样你就可以直接利用现有的函数来解析源代码,而不需要额外的转换。”

艾丽卡(继续询问):“那第二种形态是什么呢?”

木森(微笑):“2. 数组或对象:如果sources是一个数组或对象,sourcesResolver会根据数组或对象中的信息来创建一个解析器函数。

  • 数组:如果sources是一个数组,sourcesResolver会调用arraySourceResolver函数。这个函数会根据数组中的文件信息来创建一个解析器,这个解析器可以接受文件路径作为参数,并从数组中找到对应的文件内容。”

艾丽卡(恍然大悟):“我明白了,那如果sources是一个对象呢?”

木森(详细解释):“- 对象:如果sources是一个对象,sourcesResolver会调用mapSourceResolver函数。这个函数会根据对象中的键值对来创建一个解析器,这个解析器可以接受文件路径作为参数,并从对象中找到对应的文件内容。”

艾丽卡(兴奋地):“这真是太方便了!无论我们的源代码信息是函数、数组还是对象,sourcesResolver都能帮我们处理。”

是的,所以接下来我们只要修改一下前面的逻辑,让他不用判断target和source,而是直接传入源代码就好啦!

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileFunc = exports.compilerVersion = exports.latestCompiler = exports.FuncCompiler = exports.sourcesResolver = exports.arraySourceResolver = exports.mapSourceResolver = void 0;const path_1 = require("./path");
const utils_1 = require("./utils");
const func_js_bin_1 = require("@ton-community/func-js-bin");// 定义 source resolver 函数
const mapSourceResolver = (map) => {return (path) => {if (path in map) {return map[path];}throw new Error(`Cannot find source file \`${path}\``);};
};
exports.mapSourceResolver = mapSourceResolver;const arraySourceResolver = (arr) => {return (path) => {const entry = arr.find(e => e.filename === path);if (entry === undefined)throw new Error(`Cannot find source file \`${path}\``);return entry.content;};
};
exports.arraySourceResolver = arraySourceResolver;const sourcesResolver = (sources) => {if (typeof sources === 'function') {return sources;}if (Array.isArray(sources)) {return exports.arraySourceResolver(sources);}return exports.mapSourceResolver(sources);
};
exports.sourcesResolver = sourcesResolver;// 定义辅助函数
const copyToCString = (mod, str) => {const len = mod.lengthBytesUTF8(str) + 1;const ptr = mod._malloc(len);mod.stringToUTF8(str, ptr, len);return ptr;
};const copyToCStringPtr = (mod, str, ptr) => {const allocated = copyToCString(mod, str);mod.setValue(ptr, allocated, '*');return allocated;
};const copyFromCString = (mod, ptr) => {return mod.UTF8ToString(ptr);
};class FuncCompiler {constructor() {this.wasmBinary = (0, utils_1.base64Decode)(func_js_bin_1.object.wasmBase64);this.inputFuncVersion = func_js_bin_1.object.funcVersion;this.module = null; // 这里应该是初始化 WebAssembly 模块的逻辑}async createModule() {// 这里应该是加载和初始化 WebAssembly 模块的逻辑// 返回模块实例return this.module;}async compilerVersion() {const mod = await this.createModule();const versionJsonPointer = mod._version();const versionJson = copyFromCString(mod, versionJsonPointer);mod._free(versionJsonPointer);return JSON.parse(versionJson);}async validateVersion() {const v = await this.compilerVersion();return v.funcVersion === this.inputFuncVersion;}async compileFunc(sourceCode) {const mod = await this.createModule();const allocatedPointers = [];const sourceMap = { "main.fc": sourceCode };const sourceOrder = ["main.fc"];const callbackPtr = mod.addFunction((_kind, _data, contents, error) => {const kind = copyFromCString(mod, _kind);const data = copyFromCString(mod, _data);if (kind === 'realpath' || kind === 'source') {const path = "main.fc"; // Assuming the source code is in 'main.fc'allocatedPointers.push(copyToCStringPtr(mod, sourceMap[path], contents));} else {allocatedPointers.push(copyToCStringPtr(mod, 'Unknown callback kind ' + kind, error));}}, 'viiii');const configStr = JSON.stringify({sources: [{ filename: "main.fc", content: sourceCode }],optLevel: 2,});const configStrPointer = copyToCString(mod, configStr);allocatedPointers.push(configStrPointer);const resultPointer = mod._func_compile(configStrPointer, callbackPtr);allocatedPointers.push(resultPointer);const retJson = copyFromCString(mod, resultPointer);// CleanupallocatedPointers.forEach(ptr => mod._free(ptr));mod.removeFunction(callbackPtr);const snapshot = sourceOrder.map(path => ({filename: path,content: sourceMap[path],}));const ret = JSON.parse(retJson);return {...ret,snapshot,};}
}exports.FuncCompiler = FuncCompiler;
exports.latestCompiler = new FuncCompiler();
exports.compilerVersion = exports.latestCompiler.compilerVersion;
exports.compileFunc = exports.latestCompiler.compileFunc;

艾丽卡,激动的运行,可是仍然报错了
在这里插入图片描述
这是什么原因呢?原来是因为虽然修改了js代码,但是和它同名的ts代码并没有修改,而编码仍然要检查类型。
当你在项目中同时使用 JavaScript (JS) 和 TypeScript (TS) 时,确保两者的接口和实现保持一致是非常重要的。如果你修改了 JS 代码但没有相应地更新 TS 代码,可能会出现类型不匹配的问题,因为 TypeScript 在编译时会进行类型检查。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1534112.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

通过python提取PDF文件指定页的图片

整体思路 要从 PDF 文件中提取指定页和指定位置的图片&#xff0c;可以分几个步骤来实现&#xff1a; 1.1 准备所需工具与库 在 Python 中处理 PDF 和图像时&#xff0c;需要使用几个库&#xff1a; PyMuPDF (fitz)&#xff1a;用于读取和处理 PDF 文件&#xff0c;可以精确…

Android 测试机

要测手机应用&#xff0c;直接挂电脑上跑虚拟机的话&#xff0c;怀疑电脑都要起火了。 eBay 上买了个新的机器&#xff0c;也才 100 美元多点&#xff0c;机器都没有拆过&#xff0c;电池是完全无电的状态。 操作系统是 Android 12 的版本&#xff0c;升级到 Android 14 后&am…

表格标记<table>

一.表格标记、 1table&#xff1a;表格标记 2.caption:表单标题标记 3.tr:表格行标记 4.td:表格中数据单元格标记 5.th:标题单元格 table标记是表格中最外层标记&#xff0c;tr表示表格中的行标记&#xff0c;一对<tr>表示表格中的一行&#xff0c;在<tr>中可…

Spring Boot集成Akka Stream快速入门Demo

1.什么是Akka Stream&#xff1f; Akka Streams是一个用于处理和传输元素序列的库。它建立在Akka Actors之上&#xff0c;使流的摄入和处理变得简单。由于它是建立在Akka Actors之上的&#xff0c;它为Akka现有的actor模型提供了一个更高层次的抽象。Akka流由3个主要部分组成-…

Linux 入门:简单的基础操作

“批判他人总是想的太简单 剖析自己总是想的太困难” 文章目录 前言Linux 入门&#xff1a;从基础操作到 WSL2 安装文章有误敬请斧正 不胜感恩&#xff01;1. 什么是 Linux&#xff1f;2. Linux 和其他系统有啥不同&#xff1f;3. Linux 的主要组成4. 常见 Linux 发行版5. 基本…

DNS查询报文分析

目录 1. 用 tcpdump工具监听抓包 2. 用 host 工具获取域名对应的IP地址 3. 分析DNS以太网查询数据帧 3.1 linux下查询DNS服务器IP地址 3.2 DNS以太网查询数据帧 &#xff08;1&#xff09;数据链路层 &#xff08;2&#xff09;网络层 &#xff08;3&#xff09;传输层…

【C++】—— list 的了解与使用

【C】—— list 的了解与使用 1 list 的函数接口2 迭代器2.1 简单使用 list 的迭代器2.2 迭代器的划分2.3 不同迭代器的使用场景2.3.1 sort2.3.2 reverse2.3.3 find 3 emplace_back4 操作函数4.1 sort4.1.1 list中sort介绍4.1.2 list 中 sort 与算法库中 sort 效率比较 4.2 mer…

Web:HTTP包的相关操作

目录 一、请求包修改页面来源 二、Cookie身份认证 三、XXF修改本地访问 四、向页面同时发出GET和POST请求 一、请求包修改页面来源 题目提示要从 http://localhost:8080/flag3cad.php?a1&#xff0c;请求包中没有指定请求来源&#xff0c;需要指定。 而表示页面来源的字段…

华南医电科技集团受邀出席中马建交50周年高级别经贸合作交流活动

左:马来西亚第九任首拿督斯里 伊斯迈尔沙必里雅各布; 右:华南医电董事长陈广元 在庆祝中国和马来西亚建交50周年的辉煌时刻,中马两国间的经贸合作不仅承载着历史的重任,更展望着未来无限的广阔前景。2024年,作为这一重要里程碑的纪念之年,中马两国政府及商界精英携手举办了一…

解决项目启动时报“找不到符号”问题

前言 在Java开发过程中&#xff0c;遇到“找不到符号”的错误是非常常见的现象。这种错误往往意味着编译器无法识别你所引用的某个类、方法或变量。本文旨在提供一套详细的排查和解决思路&#xff0c;帮助开发者快速定位并解决此类问题。 问题描述 “找不到符号”错误通常出…

Ubuntu下安装最新版本Apache2文件服务器

文章目录 1.最新版本Apache2安装2. Apache2配置2.1 端口配置2.2 创建软连接,生成文件服务2.3 隐藏Apache2服务版本号2.4 添加用户&#xff0c;设置Apache2文件服务密码2.5 重启Apache2服务3. 执行后效果 1.最新版本Apache2安装 注意&#xff1a;安装最新版本必须升级Ubuntu为20…

网络药理学:15、草稿暂存区

TCMSP 韦恩图在线网站 https://bioinfogp.cnb.csic.es/tools/venny/index.html String数据库参数详解&#xff1a;https://www.bilibili.com/video/BV1q64y1k7Zf?p16&vd_sourceaed4c634975918b14b7354ec93ce5389 David数据库可以用基因ID或者基因名。 KEGG数据库使用&am…

linux环境下手动安装mysql

没想到兜兜转转这么些年&#xff0c;今天申请个云服务器用来搭建求生2服务器&#xff0c;先用mysql来测试&#xff0c;结果还是花了相当久的时间。 基本所有单节点部署应用到linux环境&#xff0c;都三个流程&#xff1a; 1 下载安装包 2 解压修改配置文件 3 运行启动脚本 我们…

2024年最新软件测试学习路线图(从入门到精通)

六维全息课程注重综合能力培养&#xff0c;从入学到职后一站式服务测试开发人才。2024年最新软件测试学习路线图&#xff0c;从入门到精通一应俱全。 9阶段专业课11大专项测试项目 适应互联网企业测试开发需求。 对于想入行学软件测试的新手来说&#xff0c;首先就需要一个高效…

Qt自定义信号、带参数的信号、lambda表达式和信号的使用

整个部分知识通过一个跳转窗口的项目来体现 第一个页面 #include "test.h" #include <qdebug.h> test::test(QWidget *parent): QDialog(parent) {ui.setupUi(this);/** &s 信号发出者* &subWidget::mySignals 处理的信号&#xff0c; &发送者类…

携手鲲鹏,长亮科技加速银行核心系统升级

新经济周期下&#xff0c;银行净息差持续收窄、盈利压力加大、市场竞争日趋加剧。同时&#xff0c;国家相关政策不断出台&#xff0c;对金融科技的自主创新与安全可控提出了更高要求。 在这样的大背景下&#xff0c;银行业的数字化转型已经步入深水区。其中&#xff0c;核心系统…

Games101学习 - 光栅化

Games101中讲解的光栅化的基础知识&#xff0c;本文就来梳理一下。 在UE中使用UTexture2D可以逐像素绘制纹理&#xff1a; https://blog.csdn.net/grayrail/article/details/142165442 1.绘制三角形 这里可以通过101中讲解的叉积法逐像素绘制三角形&#xff1a; 绘制效果&a…

表单标记form

1.form:表单域标记&#xff0c;表示表单范围&#xff0c;所有的表单元素必须放进form标记中 2.input:用来设置表单输入元素&#xff0c;<input>元素根据不同的属性&#xff0c;可以有多种形式&#xff0c;如文本框&#xff08;text&#xff09;,密码框&#xff08;passw…

信息安全数学基础(9)素数的算数基本定理

前言 在信息安全数学基础中&#xff0c;素数的算数基本定理&#xff08;也称为唯一分解定理或算术基本定理&#xff09;是一个极其重要的定理&#xff0c;它描述了正整数如何唯一地分解为素数的乘积。这个定理不仅是数论的基础&#xff0c;也是许多密码学算法&#xff08;如RSA…

Java面试篇基础部分-Java泛型详解

导语   Java中泛型的本质是参数化类型,泛型提供了编译时类型的安全检测机制。泛型机制允许程序在编译的时候检测非法的类型,例如要实现一个对于字符串、整型、浮点型、对象类型等比较其大小的方法,就可以使用泛型,在使用的时候在明确所要比较的数据类型就可以了。 当然如…