@wllama/wllama
Version:
WebAssembly binding for llama.cpp - Enabling on-browser LLM inference
116 lines (101 loc) • 3.27 kB
JavaScript
import fs from 'fs';
import path from 'path';
const inputPath = './cpp/glue.hpp';
const outputPath = './src/glue/messages.ts';
const snakeToCamel = (str) =>
str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase());
const snakeToPascal = (str) => {
let camelCase = snakeToCamel(str);
let pascalCase = camelCase[0].toUpperCase() + camelCase.substr(1);
return pascalCase;
};
const cppContent = fs.readFileSync(inputPath, 'utf8');
const lines = cppContent.split('\n');
const protos = [];
let insideStruct = false;
let currentProto = { name: '', className: '', structName: '', fields: [] };
let version = 0;
for (const line of lines) {
if (line.startsWith('#define GLUE_VERSION')) {
version = parseInt(line.split(' ').at(-1));
}
if (line.startsWith('struct glue_msg_')) {
insideStruct = true;
const structName = line.split(' ')[1];
currentProto = {
name: '',
structName,
className: snakeToPascal(structName),
fields: [],
};
}
const lineTrimed = line.trim();
if (insideStruct) {
if (lineTrimed.startsWith('GLUE_HANDLER')) {
const name = lineTrimed.split('(')[1].split(')')[0];
currentProto.name = JSON.parse(name);
if (currentProto.name.length !== 8) {
console.error(
'Invalid name (must be 8 characters):',
currentProto.name
);
process.exit(1);
}
}
if (lineTrimed.startsWith('GLUE_FIELD')) {
const isNullable = lineTrimed.startsWith('GLUE_FIELD_NULLABLE');
const fieldParts = lineTrimed.split('(')[1].split(')')[0].split(',');
currentProto.fields.push({
type: fieldParts[0].trim(),
name: fieldParts[1].trim(),
isNullable,
});
}
if (line.startsWith('};')) {
insideStruct = false;
protos.push(currentProto);
}
}
}
//console.log(JSON.stringify(protos, null, 2));
const typeMapping = {
bool: 'boolean',
int: 'number',
float: 'number',
str: 'string',
raw: 'Uint8Array',
arr_bool: 'bool[]',
arr_int: 'number[]',
arr_float: 'number[]',
arr_str: 'string[]',
arr_raw: 'Uint8Array[]',
};
const protoMap = {};
for (const proto of protos) {
protoMap[proto.name] = proto;
}
const outputContent = `// This file is generated by cpp/generate_glue_prototype.js
// Do not edit this file directly
import type { GlueMessageProto } from './glue';
export const GLUE_VERSION = ${version};
export const GLUE_MESSAGE_PROTOTYPES: { [name: string]: GlueMessageProto } = ${JSON.stringify(protoMap, null, 2)};
${protos
.map((proto) => {
let interfaceCode = `// struct ${proto.structName}\n`;
interfaceCode += `export interface ${proto.className} {\n`;
interfaceCode += ` _name: "${proto.name}";\n`;
for (const field of proto.fields) {
const mappedType = typeMapping[field.type];
if (!mappedType) {
console.error('Unknown type:', field.type);
process.exit(1);
}
interfaceCode += ` ${field.name}${field.isNullable ? '?' : ''}: ${mappedType}${field.isNullable ? ' | undefined' : ''};\n`;
}
interfaceCode += '}\n';
return interfaceCode;
})
.join('\n')}
export type GlueMsg = ${protos.map((proto) => proto.className).join(' | ')};
`;
fs.writeFileSync(outputPath, outputContent);