Files
sanbuphy f44c842fe7 feat(docs): update computer fundamentals content and demos
- Refactor frontend framework demo descriptions for clarity
- Remove interactive features from triad and field map demos
- Add new computer organization and DSL documentation links
- Split type systems and compilers into separate pages
- Enhance power-on-to-web article with relay race analogy
- Add new interactive demos for type systems and compilation
- Improve visual presentation of boot process and hardware flow
- Introduce new Vibe Coding flow demo component
2026-02-25 01:38:27 +08:00

193 lines
5.8 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="ast-visualizer-demo">
<h4>🌳 AST 可视化看见代码的"骨架"</h4>
<p class="desc">选择一个表达式观察它的抽象语法树结构</p>
<div class="expr-selector">
<button
v-for="(ex, i) in expressions"
:key="i"
:class="['expr-btn', { active: selected === i }]"
@click="selected = i"
>
<code>{{ ex.code }}</code>
</button>
</div>
<div class="ast-container">
<div class="tree-view">
<div class="tree-title">语法树</div>
<div class="tree-nodes">
<ASTNode
:node="expressions[selected].tree"
:depth="0"
/>
</div>
</div>
<div class="explain-view">
<div class="explain-title">解析说明</div>
<div class="explain-list">
<div v-for="(step, j) in expressions[selected].explains" :key="j" class="explain-item">
<span class="explain-num">{{ j + 1 }}</span>
<span>{{ step }}</span>
</div>
</div>
<div class="tool-tip">
💡 试试 <a href="https://astexplorer.net/" target="_blank">AST Explorer</a> 在线查看任意代码的 AST
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, h, defineComponent } from 'vue'
const selected = ref(0)
const ASTNode = defineComponent({
name: 'ASTNode',
props: { node: Object, depth: Number },
setup(props) {
return () => {
const n = props.node
const children = []
children.push(
h('div', { class: 'node-box', style: { marginLeft: props.depth * 24 + 'px' } }, [
h('span', { class: 'node-type' }, n.type),
n.value ? h('span', { class: 'node-value' }, n.value) : null
])
)
if (n.children) {
for (const child of n.children) {
children.push(h(ASTNode, { node: child, depth: props.depth + 1 }))
}
}
return h('div', { class: 'node-wrapper' }, children)
}
}
})
const expressions = [
{
code: '1 + 2 * 3',
tree: {
type: 'BinaryExpression', value: '+',
children: [
{ type: 'NumericLiteral', value: '1' },
{
type: 'BinaryExpression', value: '*',
children: [
{ type: 'NumericLiteral', value: '2' },
{ type: 'NumericLiteral', value: '3' }
]
}
]
},
explains: [
'* 优先级高于 +,所以 2 * 3 先结合',
'2 * 3 形成一个 BinaryExpression 子树',
'1 和这个子树作为 + 的左右操作数',
'最终 + 是根节点,体现了运算顺序'
]
},
{
code: 'let x = 10',
tree: {
type: 'VariableDeclaration', value: 'let',
children: [
{
type: 'VariableDeclarator', value: '',
children: [
{ type: 'Identifier', value: 'x' },
{ type: 'NumericLiteral', value: '10' }
]
}
]
},
explains: [
'let 声明创建 VariableDeclaration 节点',
'内部包含一个 VariableDeclarator(声明器)',
'声明器左侧是标识符 x,右侧是初始值 10',
'树结构清晰表达了"把 10 赋给 x"的语义'
]
},
{
code: 'add(a, b)',
tree: {
type: 'CallExpression', value: '',
children: [
{ type: 'Identifier', value: 'add' },
{
type: 'Arguments', value: '',
children: [
{ type: 'Identifier', value: 'a' },
{ type: 'Identifier', value: 'b' }
]
}
]
},
explains: [
'函数调用创建 CallExpression 节点',
'被调用的函数名 add 是 Identifier',
'参数列表 (a, b) 形成 Arguments 节点',
'每个参数都是独立的 Identifier 子节点'
]
}
]
</script>
<style scoped>
.ast-visualizer-demo {
padding: 20px; border: 1px solid var(--vp-c-divider);
border-radius: 12px; margin: 16px 0; background: var(--vp-c-bg-soft);
}
h4 { margin: 0 0 4px; }
.desc { color: var(--vp-c-text-2); font-size: 14px; margin: 0 0 16px; }
.expr-selector { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.expr-btn {
padding: 6px 14px; border-radius: 6px; border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg); cursor: pointer; font-size: 13px; transition: all 0.2s;
}
.expr-btn.active { background: var(--vp-c-brand-1); color: #fff; border-color: var(--vp-c-brand-1); }
.expr-btn code { font-size: 13px; }
.ast-container { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.tree-view, .explain-view {
border: 1px solid var(--vp-c-divider); border-radius: 8px;
background: var(--vp-c-bg); overflow: hidden;
}
.tree-title, .explain-title {
padding: 8px 12px; font-weight: 600; font-size: 13px;
background: var(--vp-c-bg-soft); border-bottom: 1px solid var(--vp-c-divider);
}
.tree-nodes { padding: 12px; }
:deep(.node-box) {
display: flex; align-items: center; gap: 6px; padding: 3px 0;
}
:deep(.node-type) {
padding: 2px 8px; background: #dbeafe; color: #1e40af;
border-radius: 4px; font-size: 12px; font-weight: 500;
}
:deep(.node-value) {
padding: 2px 6px; background: #fef3c7; color: #92400e;
border-radius: 4px; font-size: 12px; font-family: 'Fira Code', monospace;
}
.explain-list { padding: 10px 12px; }
.explain-item { display: flex; align-items: flex-start; gap: 8px; padding: 4px 0; font-size: 13px; }
.explain-num {
width: 20px; height: 20px; border-radius: 50%; background: var(--vp-c-brand-1);
color: #fff; display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 600; flex-shrink: 0;
}
.tool-tip {
padding: 8px 12px; font-size: 12px; color: var(--vp-c-text-2);
border-top: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft);
}
.tool-tip a { color: var(--vp-c-brand-1); }
@media (max-width: 640px) { .ast-container { grid-template-columns: 1fr; } }
</style>