You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否创建类LISP的功能性自定义HTML元素?

实现类LISP特性的功能性HTML标签方案

你可以通过HTML预处理器(客户端或服务端)实现这类功能性标签,核心是先完成函数定义的收集,再执行标签逻辑,解决递归调用的问题。以下是具体实现思路:

核心设计原则

  1. 先定义后执行:优先解析所有<defun>标签,将函数注册到全局环境,再处理其他功能性标签,避免递归调用时函数未定义的问题。
  2. 环境隔离:为变量和函数维护独立的执行环境,支持局部/全局作用域。
  3. 标签替换逻辑:将功能性标签的执行结果直接替换原标签的HTML内容,最终输出纯静态HTML。

客户端实现(浏览器端)

利用原生JavaScript结合DOM解析,在页面渲染前完成标签处理:

1. 环境初始化

// 维护变量和函数的全局环境
const runtimeEnv = {
  variables: {},
  functions: {}
};

2. 解析函数定义()

先收集所有函数定义,不执行内部递归调用:

function parseDefun(element) {
  const funcName = element.querySelector('var-name').textContent.trim();
  // 提取参数列表
  const args = Array.from(element.querySelectorAll('list var-name'))
    .map(el => el.textContent.trim());
  // 提取函数体(跳过前两个子元素:var-name和list)
  const body = element.children[2].cloneNode(true);
  
  runtimeEnv.functions[funcName] = { args, body };
  element.remove(); // 移除定义标签,避免干扰后续渲染
}

3. 实现核心标签逻辑

变量赋值

function handleSetf(element) {
  const varName = element.querySelector('var-name').textContent.trim();
  const listItems = Array.from(element.querySelectorAll('list li'))
    .map(el => el.textContent.trim());
  
  runtimeEnv.variables[varName] = listItems;
  element.replaceWith(''); // 赋值标签无需保留
}

列表遍历

function handleForeach(element) {
  const itemVar = element.querySelector('var-name').textContent.trim();
  const listVar = element.querySelector('var-val').textContent.trim();
  const template = element.querySelector('div').innerHTML;
  
  const result = runtimeEnv.variables[listVar]
    .map(item => template.replace(/<var-val>(.*?)<\/var-val>/g, item))
    .join('');
  
  element.replaceWith(result);
}

递归函数执行(如)

function executeFunctionCall(element) {
  const funcName = element.tagName.toLowerCase();
  const func = runtimeEnv.functions[funcName];
  if (!func) return;

  // 解析函数参数(支持嵌套标签)
  const args = Array.from(element.children).map(child => {
    if (child.tagName.toLowerCase() === 'var-val') {
      return runtimeEnv.variables[child.textContent.trim()];
    }
    // 递归处理嵌套标签(如<cdr>、<car>)
    return executeTag(child);
  });

  // 创建局部作用域
  const localEnv = { ...runtimeEnv.variables };
  func.args.forEach((name, idx) => {
    localEnv[name] = args[idx];
  });

  // 执行函数体(以<if>为例)
  return executeBody(func.body, localEnv);
}

function executeBody(bodyElement, localEnv) {
  const tagName = bodyElement.tagName.toLowerCase();
  switch(tagName) {
    case 'if':
      const nullCheck = bodyElement.querySelector('null');
      const targetVal = executeTag(nullCheck.querySelector('var-val'), localEnv);
      if (targetVal.length === 0) {
        return executeTag(bodyElement.querySelector('then').firstElementChild, localEnv);
      } else {
        return executeTag(bodyElement.querySelector('else').firstElementChild, localEnv);
      }
    case 'cons':
      const carVal = executeTag(bodyElement.querySelector('car'), localEnv);
      const cdrVal = executeTag(bodyElement.querySelector('cdr'), localEnv);
      return [carVal, ...cdrVal];
    case 'cdr':
      const cdrTarget = executeTag(bodyElement.querySelector('var-val'), localEnv);
      return cdrTarget.slice(1);
    case 'car':
      const carTarget = executeTag(bodyElement.querySelector('var-val'), localEnv);
      return carTarget[0];
    case 'nil':
      return [];
    default:
      // 处理自定义函数调用
      if (runtimeEnv.functions[tagName]) {
        return executeFunctionCall(bodyElement);
      }
      return '';
  }
}

// 通用标签执行入口
function executeTag(element, env = runtimeEnv.variables) {
  const tagName = element.tagName.toLowerCase();
  switch(tagName) {
    case 'setf':
      handleSetf(element);
      break;
    case 'foreach':
      handleForeach(element);
      break;
    default:
      if (runtimeEnv.functions[tagName]) {
        return executeFunctionCall(element);
      }
      // 处理变量引用
      if (tagName === 'var-val') {
        return env[element.textContent.trim()];
      }
      break;
  }
}

4. 页面加载时执行处理

document.addEventListener('DOMContentLoaded', () => {
  // 第一步:收集所有函数定义
  document.querySelectorAll('defun').forEach(parseDefun);
  // 第二步:处理其他功能性标签
  document.querySelectorAll('setf, foreach, reverse, if, null, car, cdr, cons').forEach(executeTag);
});

服务端实现(Node.js为例)

利用cheerio解析HTML,在服务器端完成标签处理后返回静态HTML:

1. 依赖与环境初始化

const express = require('express');
const cheerio = require('cheerio');
const app = express();

const runtimeEnv = { variables: {}, functions: {} };

2. HTML处理核心逻辑

function processHtml(rawHtml) {
  const $ = cheerio.load(rawHtml);

  // 第一步:解析所有<defun>定义
  $('defun').each((_, el) => {
    const funcName = $(el).find('var-name').first().text().trim();
    const args = $(el).find('list var-name').map((_, argEl) => $(argEl).text().trim()).get();
    const body = $(el).children().eq(2).html(); // 提取函数体

    runtimeEnv.functions[funcName] = { args, body };
    $(el).remove();
  });

  // 第二步:处理<setf>变量赋值
  $('setf').each((_, el) => {
    const varName = $(el).find('var-name').first().text().trim();
    const values = $(el).find('list li').map((_, liEl) => $(liEl).text().trim()).get();
    runtimeEnv.variables[varName] = values;
    $(el).replaceWith('');
  });

  // 第三步:处理<foreach>遍历
  $('foreach').each((_, el) => {
    const itemVar = $(el).find('var-name').first().text().trim();
    const listVar = $(el).find('var-val').first().text().trim();
    const template = $(el).find('div').html();
    
    const result = runtimeEnv.variables[listVar]
      .map(item => template.replace(/<var-val>(.*?)<\/var-val>/g, item))
      .join('');
    
    $(el).replaceWith(result);
  });

  // 第四步:处理递归函数调用(如<reverse>)
  function resolveFunctions() {
    let hasUnresolved = false;
    Object.keys(runtimeEnv.functions).forEach(funcName => {
      $(funcName).each((_, el) => {
        hasUnresolved = true;
        const func = runtimeEnv.functions[funcName];
        // 解析参数并执行函数体(逻辑类似客户端实现)
        // ...省略具体执行逻辑,参考客户端的executeFunctionCall
        // 最终将元素替换为执行结果
        const result = executeServerFunction(func, $(el));
        $(el).replaceWith(result.join(', '));
      });
    });
    if (hasUnresolved) resolveFunctions(); // 递归处理直到所有函数调用完成
  }

  resolveFunctions();
  return $.html();
}

3. 服务器路由

app.get('/', (req, res) => {
  const rawHtml = `
    <setf>
      <var-name>X</var-name>
      <list>
        <li>a large red box</li>
        <li>an iron key</li>
        <li>a mangy cat</li>
      </list>
    </setf>

    <defun>
      <var-name>reverse</var-name>
      <list> <var-name>X</var-name> </list>
      <if> <null> <var-val>X</var-val> </null>
        <then> <nil /> </then>
        <else>
          <cons> <reverse> <cdr> <var-val>X</var-val> </cdr> </reverse> <car> <var-val>X</var-val> </car> </cons>
        </else>
      </if>
    </defun>

    You see <foreach> <var-name>EL</var-name> <var-val>X</var-val>
              <div><var-val>EL</var-val>,</div>
            </foreach>
  `;
  const processedHtml = processHtml(rawHtml);
  res.send(processedHtml);
});

app.listen(3000, () => console.log('Server running on port 3000'));

环境选择建议

  • 客户端:适合轻量计算、无需服务器的场景,直接在浏览器完成处理,避免服务器请求开销。
  • 服务端:适合复杂计算、需要访问服务器资源的场景,处理完成后返回纯静态HTML,兼容性更好。

内容的提问来源于stack exchange,提问作者Phil Goetz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:06:07