Scratch少儿编程第十九例:简易编程语言
·
Scratch少儿编程第十九例:简易编程语言
项目介绍
这是一个在Scratch中实现的简易编程语言解释器,让孩子理解编程语言的基本原理,包括词法分析、语法解析和执行引擎,培养计算思维和语言设计能力。
学习目标
- ✅ 理解编程语言的基本组成
- ✅ 学习词法分析和语法解析
- ✅ 掌握解释器的工作原理
- ✅ 培养抽象思维和系统设计能力
详细步骤
第一步:创建编程语言基础
定义语言规范:
当绿旗被点击
列表 [源代码 v] 删除全部项目
列表 [词法分析结果 v] 删除全部项目
列表 [语法树 v] 删除全部项目
列表 [变量表 v] 删除全部项目
列表 [符号表 v] 删除全部项目
变量 [语言名称 v] 设为 [MiniLang]
变量 [当前行号 v] 设为 [1]
// 定义关键字
列表 [关键字 v] 删除全部项目
添加 [变量] 到 [关键字 v]
添加 [打印] 到 [关键字 v]
添加 [如果] 到 [关键字 v]
添加 [循环] 到 [关键字 v]
添加 [结束] 到 [关键字 v]
添加 [否则] 到 [关键字 v]
说 [MiniLang编程语言已初始化] (2) 秒
广播 [创建编辑器界面 v]
第二步:创建代码编辑器
简易代码编辑器:
当接收到 [创建编辑器界面 v]
清空
移到 x: (-180) y: (150)
将笔的颜色设为 [#000000]
将笔的粗细设为 (2)
// 绘制代码编辑区域
绘制矩形,宽度: (360) 高度: (200)
移到 x: (-180) y: (150)
落笔
移动 (360) 步
右转 ↻ (90) 度
移动 (200) 步
右转 ↻ (90) 度
移动 (360) 步
右转 ↻ (90) 度
移动 (200) 步
抬笔
说 [请在下方输入MiniLang代码] (2) 秒
当角色被点击 // 运行按钮
广播 [开始编译 v]
第三步:词法分析器(Lexer)
词法分析实现:
定义 词法分析 (源代码)
列表 [词法分析结果 v] 删除全部项目
变量 [当前字符 v] 设为 [1]
变量 [当前词 v] 设为 []
变量 [词类型 v] 设为 []
重复执行直到 <(当前字符) > (字數 (源代码))>
变量 [字符 v] 设为 (字數 (源代码) 的第 (当前字符) 项)
// 跳过空格和换行
如果 <<(字符) = [ ]> 或 <(字符) = [
]> 或 <(字符) = [ ]>> 那么
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (词类型) 到 [符号表 v]
将 [当前词 v] 设为 []
将 [词类型 v] 设为 []
end
否则
// 识别数字
如果 <<(字符) > [0]> 与 <(字符) < [9]>> 那么
将 [当前词 v] 设为 (连接 (当前词) (字符))
将 [词类型 v] 设为 [数字]
否则
// 识别标识符和关键字
如果 <<(字符) > [a]> 与 <(字符) < [z]>> 那么
将 [当前词 v] 设为 (连接 (当前词) (字符))
将 [词类型 v] 设为 [标识符]
否则
// 识别运算符
如果 <<(字符) = [+]> 或 <<(字符) = [-]> 或 <<(字符) = [*]> 或 <<(字符) = [/]> 或 <<(字符) = [=]> 或 <<(字符) = [>]> 或 <<(字符) = [<]>>> 那么
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (词类型) 到 [符号表 v]
end
添加 (字符) 到 [词法分析结果 v]
添加 [运算符] 到 [符号表 v]
将 [当前词 v] 设为 []
将 [词类型 v] 设为 []
否则
// 识别字符串
如果 <(字符) = ["]> 那么
将 [词类型 v] 设为 [字符串]
将 [当前字符 v] 增加 (1)
重复执行直到 <<(字數 (源代码) 的第 (当前字符) 项) = ["]> 或 <(当前字符) > (字數 (源代码))>>
将 [当前词 v] 设为 (连接 (当前词) (字數 (源代码) 的第 (当前字符) 项))
将 [当前字符 v] 增加 (1)
end
添加 (当前词) 到 [词法分析结果 v]
添加 [字符串] 到 [符号表 v]
将 [当前词 v] 设为 []
将 [词类型 v] 设为 []
end
end
end
end
end
将 [当前字符 v] 增加 (1)
end
// 添加最后一个词
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (词类型) 到 [符号表 v]
end
说 [词法分析完成!] (1) 秒
第四步:语法解析器(Parser)
语法解析实现:
定义 语法解析
列表 [语法树 v] 删除全部项目
变量 [当前标记 v] 设为 [1]
变量 [抽象语法树 v] 设为 []
重复执行直到 <(当前标记) > (词法分析结果 v) 的长度>
变量 [当前词 v] 设为 ([词法分析结果 v] 的第 (当前标记) 项)
变量 [词类型 v] 设为 ([符号表 v] 的第 (当前标记) 项)
如果 <(当前词) = [变量]> 那么
广播 [解析变量声明 v]
否则
如果 <(当前词) = [打印]> 那么
广播 [解析打印语句 v]
否则
如果 <(当前词) = [如果]> 那么
广播 [解析条件语句 v]
否则
如果 <(当前词) = [循环]> 那么
广播 [解析循环语句 v]
否则
// 解析表达式
广播 [解析表达式 v]
end
end
end
end
将 [当前标记 v] 增加 (1)
end
说 [语法解析完成!] (1) 秒
第五步:变量声明解析
变量声明处理:
当接收到 [解析变量声明 v]
变量 [变量名 v] 设为 ([词法分析结果 v] 的第 ((当前标记) + (1)) 项)
变量 [赋值符号 v] 设为 ([词法分析结果 v] 的第 ((当前标记) + (2)) 项)
变量 [初始值 v] 设为 ([词法分析结果 v] 的第 ((当前标记) + (3)) 项)
如果 <(赋值符号) = [=]> 那么
变量 [语法节点 v] 设为 (连接 (连接 (连接 [变量声明:] (变量名)) [ = ]) (初始值))
添加 (语法节点) 到 [语法树 v]
// 添加到变量表
如果 <不 <[变量表 v] 包含 (变量名) ?>> 那么
添加 (变量名) 到 [变量表 v]
添加 (初始值) 到 [变量值表 v]
end
将 [当前标记 v] 增加 (3) // 跳过已解析的部分
否则
说 (连接 [语法错误:期望 =,得到] (赋值符号)) (2) 秒
end
第六步:表达式求值器
表达式求值:
定义 求值表达式 (表达式)
变量 [结果 v] 设为 [0]
变量 [操作数栈 v] 删除全部项目
变量 [运算符栈 v] 删除全部项目
变量 [i v] 设为 [1]
重复执行直到 <(i) > (字數 (表达式))>
变量 [字符 v] 设为 (字數 (表达式) 的第 (i) 项)
如果 <<(字符) > [0]> 与 <(字符) < [9]>> 那么
// 处理数字
变量 [数字 v] 设为 []
重复执行直到 <<不 <<(字符) > [0]> 与 <(字符) < [9]>>> 或 <(i) > (字數 (表达式))>>
将 [数字 v] 设为 (连接 (数字) (字符))
将 [i v] 增加 (1)
将 [字符 v] 设为 (字數 (表达式) 的第 (i) 项)
end
添加 (数字) 到 [操作数栈 v]
否则
// 处理运算符
如果 <<(字符) = [+]> 或 <<(字符) = [-]> 或 <<(字符) = [*]> 或 <<(字符) = [/]>>> 那么
添加 (字符) 到 [运算符栈 v]
将 [i v] 增加 (1)
否则
将 [i v] 增加 (1)
end
end
end
// 执行计算
重复执行直到 <(运算符栈 v) 的长度 = [0]>
变量 [操作符 v] 设为 ([运算符栈 v] 的第 (1) 项)
变量 [右操作数 v] 设为 ([操作数栈 v] 的第 (2) 项)
变量 [左操作数 v] 设为 ([操作数栈 v] 的第 (1) 项)
如果 <(操作符) = [+]> 那么
变量 [临时结果 v] 设为 ((左操作数) + (右操作数))
否则
如果 <(操作符) = [-]> 那么
变量 [临时结果 v] 设为 ((左操作数) - (右操作数))
否则
如果 <(操作符) = [*]> 那么
变量 [临时结果 v] 设为 ((左操作数) * (右操作数))
否则
变量 [临时结果 v] 设为 ((左操作数) / (右操作数))
end
end
end
删除 [操作数栈 v] 的第 (1) 项
删除 [操作数栈 v] 的第 (1) 项
删除 [运算符栈 v] 的第 (1) 项
添加 (临时结果) 到 [操作数栈 v]
end
将 [结果 v] 设为 ([操作数栈 v] 的第 (1) 项)
回复 (结果)
完整代码示例
解释器主控制器:
当绿旗被点击
变量 [语言名称 v] 设为 [MiniLang v1.0]
变量 [解释器状态 v] 设为 [就绪]
列表 [源代码 v] 删除全部项目
列表 [词法分析结果 v] 删除全部项目
列表 [语法树 v] 删除全部项目
列表 [变量表 v] 删除全部项目
列表 [变量值表 v] 删除全部项目
列表 [符号表 v] 删除全部项目
// 预置示例代码
添加 [变量 x = 10] 到 [源代码 v]
添加 [变量 y = 5] 到 [源代码 v]
添加 [打印 x + y] 到 [源代码 v]
添加 [如果 x > y 打印 "x更大"] 到 [源代码 v]
添加 [循环 3 打印 "Hello"] 到 [源代码 v]
说 [MiniLang解释器已启动] (2) 秒
广播 [显示代码编辑器 v]
当接收到 [开始编译 v]
将 [解释器状态 v] 设为 [编译中]
说 [开始编译...] (1) 秒
// 合并源代码
变量 [完整代码 v] 设为 []
变量 [i v] 设为 [1]
重复执行直到 <(i) > (源代码 v) 的长度>
将 [完整代码 v] 设为 (连接 (完整代码) (连接 ([源代码 v] 的第 (i) 项) [;]))
将 [i v] 增加 (1)
end
广播 [词法分析 v] // 传入完整代码
词法分析器完整实现:
当接收到 [词法分析 v]
列表 [词法分析结果 v] 删除全部项目
列表 [符号表 v] 删除全部项目
变量 [当前位置 v] 设为 [1]
变量 [当前词 v] 设为 []
变量 [当前类型 v] 设为 []
说 [正在进行词法分析...] (1) 秒
重复执行直到 <(当前位置) > (字數 (完整代码))>
变量 [当前字符 v] 设为 (字數 (完整代码) 的第 (当前位置) 项)
如果 <(当前字符) = [;]> 那么 // 语句结束符
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (当前类型) 到 [符号表 v]
end
添加 [;] 到 [词法分析结果 v]
添加 [分隔符] 到 [符号表 v]
将 [当前词 v] 设为 []
将 [当前类型 v] 设为 []
否则
如果 <<(当前字符) = [ ]> 或 <(当前字符) = [
]>> 那么 // 空格或换行
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (当前类型) 到 [符号表 v]
将 [当前词 v] 设为 []
将 [当前类型 v] 设为 []
end
否则
// 检查是否是数字
如果 <<(当前字符) > [0]> 与 <(当前字符) < [9]>> 那么
将 [当前词 v] 设为 (连接 (当前词) (当前字符))
将 [当前类型 v] 设为 [数字]
否则
// 检查是否是字母(标识符或关键字)
如果 <<(当前字符) > [a]> 与 <(当前字符) < [z]>> 那么
将 [当前词 v] 设为 (连接 (当前词) (当前字符))
将 [当前类型 v] 设为 [标识符]
否则
// 检查运算符
如果 <<(当前字符) = [+]> 或 <<(当前字符) = [-]> 或 <<(当前字符) = [*]> 或 <<(当前字符) = [/]> 或 <<(当前字符) = [=]> 或 <<(当前字符) = [>]> 或 <<(当前字符) = [<]>>> 那么
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (当前类型) 到 [符号表 v]
将 [当前词 v] 设为 []
将 [当前类型 v] 设为 []
end
添加 (当前字符) 到 [词法分析结果 v]
添加 [运算符] 到 [符号表 v]
否则
// 字符串字面量
如果 <(当前字符) = ["]> 那么
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (当前类型) 到 [符号表 v]
end
将 [当前位置 v] 增加 (1)
将 [当前字符 v] 设为 (字數 (完整代码) 的第 (当前位置) 项)
重复执行直到 <<(当前字符) = ["]> 或 <(当前位置) > (字數 (完整代码))>>
将 [当前词 v] 设为 (连接 (当前词) (当前字符))
将 [当前位置 v] 增加 (1)
将 [当前字符 v] 设为 (字數 (完整代码) 的第 (当前位置) 项)
end
添加 (当前词) 到 [词法分析结果 v]
添加 [字符串] 到 [符号表 v]
将 [当前词 v] 设为 []
将 [当前类型 v] 设为 []
end
end
end
end
end
end
将 [当前位置 v] 增加 (1)
end
// 处理最后一个词
如果 <(当前词) ≠ []> 那么
添加 (当前词) 到 [词法分析结果 v]
添加 (当前类型) 到 [符号表 v]
end
说 (连接 (连接 [词法分析完成,生成] (词法分析结果 v) 的长度) [个词法单元]) (2) 秒
广播 [语法解析 v]
执行引擎:
当接收到 [执行程序 v]
变量 [程序计数器 v] 设为 [1]
变量 [执行状态 v] 设为 [运行中]
说 [开始执行程序...] (1) 秒
重复执行直到 <<(程序计数器) > (语法树 v) 的长度> 或 <(执行状态) = [停止]>>
变量 [当前指令 v] 设为 ([语法树 v] 的第 (程序计数器) 项)
如果 <(当前指令) 包含 [变量声明]> 那么
广播 [执行变量声明 v]
否则
如果 <(当前指令) 包含 [打印]> 那么
广播 [执行打印语句 v]
否则
如果 <(当前指令) 包含 [如果]> 那么
广播 [执行条件语句 v]
否则
如果 <(当前指令) 包含 [循环]> 那么
广播 [执行循环语句 v]
end
end
end
end
将 [程序计数器 v] 增加 (1)
等待 (0.5) 秒 // 模拟执行延迟
end
说 [程序执行完成!] (1) 秒
创意扩展
版本一:调试器功能
当接收到 [单步执行 v]
如果 <(执行状态) = [暂停]> 那么
变量 [当前指令 v] 设为 ([语法树 v] 的第 (程序计数器) 项)
说 (连接 (连接 [执行:] (当前指令)) []) (2) 秒
广播 [执行单条指令 v]
将 [程序计数器 v] 增加 (1)
// 显示变量状态
说 [当前变量状态:] (1) 秒
变量 [i v] 设为 [1]
重复执行直到 <(i) > (变量表 v) 的长度>
说 (连接 (连接 (连接 ([变量表 v] 的第 (i) 项) [ = ]) ([变量值表 v] 的第 (i) 项))) (1) 秒
将 [i v] 增加 (1)
end
end
当接收到 [设置断点 v]
询问 [输入要设置断点的行号:] 并等待
变量 [断点行 v] 设为 (回答)
添加 (断点行) 到 [断点列表 v]
说 (连接 (连接 [在第] (断点行)) [行设置断点]) (1) 秒
版本二:函数定义支持
当接收到 [解析函数定义 v]
变量 [函数名 v] 设为 ([词法分析结果 v] 的第 ((当前标记) + (1)) 项)
变量 [参数列表 v] 设为 []
变量 [函数体 v] 设为 []
变量 [函数开始位置 v] 设为 (当前标记)
// 查找函数结束
重复执行直到 <([词法分析结果 v] 的第 (当前标记) 项) = [结束]>
将 [当前标记 v] 增加 (1)
end
变量 [函数结束位置 v] 设为 (当前标记)
// 保存函数定义
变量 [函数定义 v] 设为 (连接 (连接 (连接 [函数] (函数名)) [ 参数:]) (参数列表))
添加 (函数定义) 到 [函数表 v]
添加 (函数体) 到 [函数体表 v]
说 (连接 (连接 [定义函数:] (函数名)) []) (1) 秒
版本三:错误处理系统
定义 报告错误 (错误类型) 错误信息 (错误信息)
变量 [错误位置 v] 设为 (当前标记)
变量 [错误上下文 v] 设为 ([词法分析结果 v] 的第 (当前标记) 项)
说 (连接 (连接 (连接 [编译错误:] (错误类型)) [,位置:]) (错误位置)) (2) 秒
说 (连接 [错误信息:] (错误信息)) (2) 秒
说 (连接 [上下文:] (错误上下文)) (2) 秒
将 [解释器状态 v] 设为 [错误]
停止 [这个脚本 v]
当接收到 [语法解析 v]
变量 [括号深度 v] 设为 [0]
重复执行直到 <(当前标记) > (词法分析结果 v) 的长度>
变量 [当前词 v] 设为 ([词法分析结果 v] 的第 (当前标记) 项)
如果 <(当前词) = [(]> 那么
将 [括号深度 v] 增加 (1)
否则
如果 <(当前词) = [)]> 那么
将 [括号深度 v] 增加 (-1)
如果 <(括号深度) < [0]> 那么
报告错误 (括号不匹配) 错误信息 ([多余的右括号])
end
end
end
将 [当前标记 v] 增加 (1)
end
如果 <(括号深度) ≠ [0]> 那么
报告错误 (括号不匹配) 错误信息 ([缺少右括号])
end
高级功能
类型系统实现
当绿旗被点击
列表 [类型表 v] 删除全部项目
添加 [数字] 到 [类型表 v]
添加 [字符串] 到 [类型表 v]
添加 [布尔] 到 [类型表 v]
定义 类型检查 (表达式)
变量 [操作数类型 v] 设为 []
变量 [i v] 设为 [1]
重复执行直到 <(i) > (字數 (表达式))>
变量 [词 v] 设为 (字數 (表达式) 的第 (i) 项)
如果 <[变量表 v] 包含 (词) ?> 那么
变量 [变量索引 v] 设为 (词) 在 [变量表 v] 中的位置
变量 [变量类型 v] 设为 ([类型表 v] 的第 (变量索引) 项)
添加 (变量类型) 到 [操作数类型 v]
否则
如果 <<(词) > [0]> 与 <(词) < [9]>> 那么
添加 [数字] 到 [操作数类型 v]
否则
如果 <(词) = ["]> 那么
添加 [字符串] 到 [操作数类型 v]
否则
添加 [未知] 到 [操作数类型 v]
end
end
end
将 [i v] 增加 (1)
end
// 检查类型兼容性
如果 <[操作数类型 v] 包含 [字符串]> 那么
如果 <[操作数类型 v] 包含 [数字]> 那么
报告错误 (类型错误) 错误信息 ([字符串和数字不能混合运算])
end
end
回复 [类型检查通过]
代码优化器
定义 优化代码
变量 [优化前大小 v] 设为 (语法树 v) 的长度
变量 [优化次数 v] 设为 [0]
说 [开始代码优化...] (1) 秒
// 常量折叠优化
变量 [i v] 设为 [1]
重复执行直到 <(i) > (语法树 v) 的长度>
变量 [当前节点 v] 设为 ([语法树 v] 的第 (i) 项)
如果 <(当前节点) 包含 [数字] 与 [运算符] 与 [数字]> 那么
变量 [优化结果 v] 设为 (求值表达式 (当前节点))
替换 [语法树 v] 的第 (i) 项为 (优化结果)
将 [优化次数 v] 增加 (1)
说 (连接 (连接 [优化:] (当前节点)) (连接 [ -> ] (优化结果))) (1) 秒
end
将 [i v] 增加 (1)
end
变量 [优化后大小 v] 设为 (语法树 v) 的长度)
说 (连接 (连接 [优化完成,优化了] (优化次数)) [处]) (1) 秒
说 (连接 (连接 [代码大小从] (优化前大小)) (连接 [减少到] (优化后大小))) (1) 秒
可视化执行跟踪
当接收到 [可视化执行 v]
清空
将笔的颜色设为 [#0000ff]
将笔的粗细设为 (2)
变量 [执行轨迹 v] 删除全部项目
变量 [x位置 v] 设为 (-180)
变量 [y位置 v] 设为 (150)
重复执行直到 <(程序计数器) > (语法树 v) 的长度>
// 绘制执行路径
移到 x: (x位置) y: (y位置)
落笔
移动 (20) 步
抬笔
// 显示当前指令
说 ([语法树 v] 的第 (程序计数器) 项) (0.5) 秒
将 [x位置 v] 增加 (30)
如果 <(x位置) > [180]> 那么
将 [x位置 v] 设为 (-180)
将 [y位置 v] 增加 (-30)
end
将 [程序计数器 v] 增加 (1)
等待 (0.3) 秒
end
界面设计技巧
创建专业的开发环境:
-
代码编辑器设计:
- 语法高亮显示不同词法单元
- 行号显示和错误标记
- 代码折叠和展开功能
- 自动缩进和格式化
-
调试界面:
- 变量监视窗口实时显示变量值
- 调用栈显示执行路径
- 断点管理和单步执行控制
- 内存使用情况显示
-
输出窗口:
- 程序执行结果输出
- 错误信息和警告显示
- 执行时间统计
- 优化建议提示
交互设计优化:
当接收到 [显示代码编辑器 v]
// 创建代码行
变量 [行号 v] 设为 [1]
重复执行直到 <(行号) > (源代码 v) 的长度>
克隆 [自己 v] // 创建代码行角色
将 [行号 v] 增加 (1)
end
当作为克隆体启动时
变量 [我的行号 v] 设为 (行号)
显示
移到 x: (-170) y: ((130) - ((我的行号) * (20)))
说 (连接 (连接 (我的行号) [. ]) ([源代码 v] 的第 (我的行号) 项)) (999) 秒
当角色被点击 // 代码行点击
如果 <(鼠标y) > (y坐标)> 那么
询问 [编辑代码:] 并等待
替换 [源代码 v] 的第 (我的行号) 项为 (回答)
说 (连接 (连接 (我的行号) [. ]) (回答)) (999) 秒
end
教学提示
给家长的指导:
- 计算思维:帮助孩子理解编程语言背后的抽象概念
- 系统设计:引导孩子思考如何设计一个完整的系统
- 问题分解:教导将复杂问题分解为简单步骤的方法
学习要点:
- 语言设计:理解编程语言的基本要素和结构
- 编译原理:学习从源代码到执行的完整流程
- 算法思维:掌握词法分析和语法解析的算法
探索问题:
- “编程语言是如何理解我们写的代码的?”
- “为什么需要词法分析和语法解析?”
- “解释器和编译器有什么区别?”
实际应用场景
学习场景:
- 编程入门:通过实现简单语言理解编程概念
- 算法实践:学习经典的编译原理算法
- 系统设计:培养设计和实现复杂系统的能力
教育应用:
- 教学工具:作为编程语言原理的教学演示
- 实验平台:进行语言设计和算法实验
- 创意表达:创造自己的编程语言和领域特定语言
调试技巧
常见问题解决:
Q:词法分析错误
A:检查分隔符处理,确保正确识别词法单元边界
Q:语法解析失败
A:验证语法规则定义,检查括号匹配和语句结构
Q:执行结果不正确
A:调试表达式求值,检查运算符优先级和结合性
项目成果
完成这个项目后,孩子将拥有:
- 一个功能完整的简易编程语言解释器
- 对编程语言原理的深入理解
- 系统设计和算法实现的能力
- 抽象思维和问题分解的技能
扩展创意
鼓励孩子尝试:
- 添加新的语言特性(数组、对象等)
- 实现编译器而不仅是解释器
- 设计可视化编程界面
- 创建领域特定语言(如数学公式语言)
在编程语言的世界中探索,让每个语法规则都成为思维表达的桥梁! 💻🔤🌟
更多推荐
所有评论(0)