molstar
Version:
A comprehensive macromolecular library.
152 lines (151 loc) • 6.38 kB
JavaScript
/**
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Task } from '../../../mol-task';
import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
import { ReaderResult as Result } from '../result';
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
import { Column } from '../../../mol-data/db';
// http://ambermd.org/prmtop.pdf
// https://ambermd.org/FileFormats.php#topology
const Pointers = {
'NATOM': '', 'NTYPES': '', 'NBONH': '', 'MBONA': '', 'NTHETH': '', 'MTHETA': '',
'NPHIH': '', 'MPHIA': '', 'NHPARM': '', 'NPARM': '', 'NNB': '', 'NRES': '',
'NBONA': '', 'NTHETA': '', 'NPHIA': '', 'NUMBND': '', 'NUMANG': '', 'NPTRA': '',
'NATYP': '', 'NPHB': '', 'IFPERT': '', 'NBPER': '', 'NGPER': '', 'NDPER': '',
'MBPER': '', 'MGPER': '', 'MDPER': '', 'IFBOX': '', 'NMXRS': '', 'IFCAP': '',
'NUMEXTRA': '', 'NCOPY': '',
};
const PointersNames = Object.keys(Pointers);
const { readLine, markLine, trim } = Tokenizer;
function State(tokenizer, runtimeCtx) {
return {
tokenizer,
runtimeCtx,
};
}
function handleTitle(state) {
const { tokenizer } = state;
const title = [];
while (tokenizer.tokenEnd < tokenizer.length) {
if (tokenizer.data.charAt(tokenizer.position) === '%')
break;
const line = readLine(tokenizer).trim();
if (line)
title.push(line);
}
return title;
}
function handlePointers(state) {
const { tokenizer } = state;
const pointers = Object.create(null);
PointersNames.forEach(name => { pointers[name] = 0; });
let curIdx = 0;
while (tokenizer.tokenEnd < tokenizer.length) {
if (tokenizer.data.charAt(tokenizer.position) === '%')
break;
const line = readLine(tokenizer);
const n = Math.min(curIdx + 10, 32);
for (let i = 0; curIdx < n; ++i, ++curIdx) {
pointers[PointersNames[curIdx]] = parseInt(line.substring(i * 8, i * 8 + 8).trim());
}
}
return pointers;
}
function handleTokens(state, count, countPerLine, itemSize) {
const { tokenizer } = state;
const tokens = TokenBuilder.create(tokenizer.data, count * 2);
let curIdx = 0;
while (tokenizer.tokenEnd < tokenizer.length) {
if (tokenizer.data.charAt(tokenizer.position) === '%')
break;
tokenizer.tokenStart = tokenizer.position;
const n = Math.min(curIdx + countPerLine, count);
for (let i = 0; curIdx < n; ++i, ++curIdx) {
const p = tokenizer.position;
trim(tokenizer, tokenizer.position, tokenizer.position + itemSize);
TokenBuilder.addUnchecked(tokens, tokenizer.tokenStart, tokenizer.tokenEnd);
tokenizer.position = p + itemSize;
}
markLine(tokenizer);
}
return tokens;
}
async function parseInternal(data, ctx) {
const t = Tokenizer(data);
const state = State(t, ctx);
const result = Object.create(null);
let prevPosition = 0;
while (t.tokenEnd < t.length) {
if (t.position - prevPosition > 100000 && ctx.shouldUpdate) {
prevPosition = t.position;
await ctx.update({ current: t.position, max: t.length });
}
const line = readLine(state.tokenizer).trim();
if (line.startsWith('%VERSION')) {
result.version = line.substring(8).trim();
}
else if (line.startsWith('%FLAG')) {
const flag = line.substring(5).trim();
let formatLine = readLine(state.tokenizer).trim();
while (formatLine.startsWith('%COMMENT')) {
formatLine = readLine(state.tokenizer).trim();
}
if (!formatLine.startsWith('%FORMAT'))
throw new Error(`expected %FORMAT got "${formatLine}"`);
if (flag === 'TITLE' || flag === 'CTITLE') {
result.title = handleTitle(state);
}
else if (flag === 'POINTERS') {
result.pointers = handlePointers(state);
}
else if (flag === 'ATOM_NAME') {
const tokens = handleTokens(state, result.pointers['NATOM'], 20, 4);
result.atomName = TokenColumn(tokens)(Column.Schema.str);
}
else if (flag === 'CHARGE') {
const tokens = handleTokens(state, result.pointers['NATOM'], 5, 16);
result.charge = TokenColumn(tokens)(Column.Schema.float);
}
else if (flag === 'MASS') {
const tokens = handleTokens(state, result.pointers['NATOM'], 5, 16);
result.mass = TokenColumn(tokens)(Column.Schema.float);
}
else if (flag === 'RESIDUE_LABEL') {
const tokens = handleTokens(state, result.pointers['NRES'], 20, 4);
result.residueLabel = TokenColumn(tokens)(Column.Schema.str);
}
else if (flag === 'RESIDUE_POINTER') {
const tokens = handleTokens(state, result.pointers['NRES'], 10, 8);
result.residuePointer = TokenColumn(tokens)(Column.Schema.int);
}
else if (flag === 'BONDS_INC_HYDROGEN') {
const tokens = handleTokens(state, result.pointers['NBONH'] * 3, 10, 8);
result.bondsIncHydrogen = TokenColumn(tokens)(Column.Schema.int);
}
else if (flag === 'BONDS_WITHOUT_HYDROGEN') {
const tokens = handleTokens(state, result.pointers['NBONA'] * 3, 10, 8);
result.bondsWithoutHydrogen = TokenColumn(tokens)(Column.Schema.int);
}
else if (flag === 'RADII') {
const tokens = handleTokens(state, result.pointers['NATOM'], 5, 16);
result.radii = TokenColumn(tokens)(Column.Schema.float);
}
else {
while (t.tokenEnd < t.length) {
if (t.data.charAt(t.position) === '%')
break;
markLine(t);
}
}
}
}
return Result.success(result);
}
export function parsePrmtop(data) {
return Task.create('Parse PRMTOP', async (ctx) => {
return await parseInternal(data, ctx);
});
}