Ton的编译过程(上)

news/2024/9/18 20:46:21/ 标签: 智能合约, 区块链

系列文章目录

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.ppmy.cn/news/1525565.html

相关文章

不用禁用 iptables 来解决 UFW 和 Docker 的安全问题

UFW 是 Ubuntu 上很流行的一个 iptables 前端&#xff0c;可以非常方便的管理防火墙的规则。但是当安装了 Docker&#xff0c;UFW 无法管理 Docker 发布出来的端口了。 解决 UFW 和 Docker 的问题 目前新的解决方案只需要修改一个 UFW 配置文件即可&#xff0c;Docker 的所有…

堆叠沙漏网络(stacked hourglass network)学习

定义 Stacked Hourglass Networks是2016年密歇根大学提出的经典网络架构。是曾经最具代表性的姿态识别SOTA之一。 hourglass network hourglass network 本身其实可以理解成是一个encoder-decoder的结构&#xff0c;encoder最大程度的提取图像在每一个scale的特征以及空间信…

漫谈设计模式 [21]:备忘录模式

引导性开场 菜鸟&#xff1a;老鸟&#xff0c;我最近在一个项目中遇到了一个问题。我需要实现一个功能&#xff0c;能够让用户在修改数据后撤销或恢复到之前的状态。你有什么好的建议吗&#xff1f; 老鸟&#xff1a;这听起来像是一个很经典的问题。你有没有听说过设计模式中…

个性化、持续性阅读 学生英语词汇量自然超越标准

2024年秋季新学年&#xff0c;根据2022版《义务教育英语课程标准》全新修订的英语新版教材开始投入使用&#xff0c;标志着我国英语教育迈入了一个以应用为导向、注重综合素养培养的新阶段。 新版教材的变革不仅仅是一次词汇量的简单增加&#xff0c;更是一场从应试到应用的深…

Windows Python 指令补全方法

网络上搜集的补全代码 # python startup file import sys import readline import rlcompleter import atexit import os# tab completion readline.parse_and_bind(tab: complete) # history file histfile os.path.join(os.environ[HOMEPATH], .pythonhistory) try:readline…

数学分析原理答案——第三章 习题18

【第三章 习题18】 把习题16中的递推公式换成 x n 1 p − 1 p x n α p x n − p 1 x_{n 1} \frac{p - 1}{p}x_{n} \frac{\alpha}{p}x_{n}^{- p 1} xn1​pp−1​xn​pα​xn−p1​ 这里 p p p是固定的正整数&#xff0c;描述该序列的性质 【解】 若 x 1 > x p x…

Linux命令分享 三 (ubuntu 16.04)

1、‘>’ >>输出重定向 用法&#xff1a;命令 参数 > 文件 ls > a.txt ‘>’ 将一个命令的结果不输出到屏幕上&#xff0c;输出到文件中&#xff0c;如果文件不存在就创建文件&#xff0c;如果存在就覆盖文件。 ls >> a.txt ‘>>’ 如果文件不存…

注册安全分析报告:熊猫频道

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

JS设计模式之装饰者模式:优雅的给对象增添“魔法”

引言 在前端开发中&#xff0c;我们经常会遇到需要在不修改已有代码的基础上给对象添加新的行为或功能的情况。而传统的继承方式并不适合这种需求&#xff0c;因为继承会导致类的数量急剧增加&#xff0c;且每一个子类都会固定地实现一种特定的功能扩展。 装饰者模式则提供了…

使用Let’s Encrypt 配置 SSL 证书去除浏览器不安全告警

Let’s Encrypt是什么 https://letsencrypt.org/zh-cn/about/如何操作进行配置实现ssl认证 使用 certbot 获取 Let’s Encrypt 的免费 SSL 证书 更新系统软件包 sudo yum update -y安装 EPEL 仓库(Certbot 通常位于 EPEL 仓库中): sudo yum

使用Pandas高效读取和处理Excel数据

目录 引言 安装必要的库 示例代码 注 引言 在数据科学和数据分析领域&#xff0c;Excel文件是一种常见的数据存储格式。由于其易于编辑和分享的特点&#xff0c;Excel成为了许多企业和组织中数据记录的标准工具。然而&#xff0c;在进行大规模的数据分析时&#xff0c;手动处理…

栈OJ题——用栈实现队列

文章目录 一、题目链接二、解题思路三、解题代码 一、题目链接 用栈实现队列 二、解题思路 三、解题代码 class MyQueue {public Stack<Integer> stack1 ;public Stack<Integer> stack2;public MyQueue() {stack1 new Stack<>();stack2 new Stack<&g…

网络药理学:2、文章基本思路、各个数据库汇总与比对、其他相关资料(推荐复现的文章、推荐学习视频、论文基本框架、文献基本知识及知网检索入门)

一、文章基本思路&#xff08;待更&#xff09; 一篇不含分子对接和实验的纯网络药理学文章思路如下&#xff1a; 即如下&#xff1a; 二、 各个数据库&#xff08;待更&#xff09; 三、其他相关资料 1.推荐复现的文章 纯网络药理学分子对接&#xff1a;知网&#xff1…

[项目] - Calc计算器

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天来尝试模拟windows 下的clac计算器 绘制计算器 拖动工具箱的Edit Control输入框、Button按钮 制作计算器界面需要将Edit Control输入框 拉长&#xff0c;将多行、只读 设置为True整体计算机的控件ID&#xff1a;I…

算法题:找出一个数组中,出现次数为奇数次的数。详细解析,加个人理解。

数组中&#xff0c;出现奇数次的数 从一个数组中找出出现了奇数次的数字&#xff0c;要求&#xff1a; 时间复杂度&#xff1a;O(n)空间复杂度&#xff1a;O(1) 题目 从数组中找出一个出现奇数次的数字&#xff0c;如&#xff1a; {1, 2, 2, 1, 3, 1, 1, 3, 3}&#xff0c;结…

如何下载各个版本的tomcat-比如tomcat9

1&#xff0c;找到tomcat官网https://tomcat.apache.org/ Apache Tomcat - Welcome! 找到tomcat9&#xff0c;或者archives 1.1&#xff0c;找到对应版本 1.2&#xff0c;找到小版本 1.3&#xff0c;找到bin 2&#xff0c;Index of /dist/tomcat/tomcat-9/v9.0.39/bin 2.1&a…

【网络】TCP/IP五层网络模型:应用层

互联网中&#xff0c;主流的是 TCP/IP 五层协议 5 G/4 G 上网&#xff0c;是有自己的协议栈&#xff0c;要比 TCP/IP 更复杂&#xff08;能够把 TCP/IP 的一部分内容给包含进去了&#xff09; 应用层 可以代表我们所编写的应用程序&#xff0c;只要应用程序里面用到了网络通…

2024桥梁科技两江论坛——第二届桥梁工程安全与韧性学术会议

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网&#xff1a;https://ais.cn/u/vEbMBz提交检索&#xff1a;EI Compendex、IEEE Xplore、Scopus 三、大会介绍 2024年桥梁科技两江论坛——第二届桥梁工程…

用Postman调试是英文导致系统语言变成英文,SQL语句查询不出来对应的字段,出现SAP系统里面调试是有值的,但是外部调用是没有值的!

用Postman调试是英文导致系统语言变成英文&#xff0c;SQL语句查询不出来对应的字段&#xff0c;出现SAP系统里面调试是有值的&#xff0c;但是外部调用是没有值的&#xff01;后面调试了非常久&#xff0c;一直以为是有特殊字符导致的&#xff0c;后面处理了特殊字符之后还是不…

视觉检测中的深度学习应用

引言 视觉检测是计算机视觉的一个重要领域&#xff0c;涉及到对图像或视频流进行分析和理解。随着深度学习技术的迅猛发展&#xff0c;视觉检测领域发生了革命性的变化。深度学习通过使用复杂的神经网络模型&#xff0c;尤其是卷积神经网络&#xff08;CNNs&#xff09;&#…