Skip to content

Latest commit

 

History

History
318 lines (236 loc) · 11.4 KB

forth系统入门手册24-1(编译器主体).md

File metadata and controls

318 lines (236 loc) · 11.4 KB

##第24章 编译器主体

###第1节 输入模块

把一个复杂的大问题分解成一个个简单的小问题,这个复杂的大问题也就容易解决了。这是软件工程里常见的思想,但却不是每个软件工程都能做得到的。

在forth里有两种开发方式,一种是自顶向下,另一种是自底向上。当你对全局把握得很清晰时,你可以使用自顶向下的开发方式;当你对全局把握得不是那么清晰时,你可以选择自底向上的方式,把肯定会用到的词先写出来。通常我们是两者兼用,从两头往中间编写。

forth编译器的全局结构
----------------------------------------------←不是就报错
↓                     ↑       ↑
输入命令→冒号?→是则编译模式→定义新词名    |      |
.   ↓                     ↓      |      |
.    否则解释模式→ 分解单词→句尾?→是则解释或编译   |
.            ↑     ↓          |
.            |   否则搜索词典→没找到,是否数字?
.            |    ↓        ↓
.            ---找到就编译单词  是则编译数值
.            ↑             |
.            ------------------------

$colon 'word',word_label
dw	word1_label, word2_label, word3_label, exit

在汇编的源文件里是用上面这种方式写colon字,但是这样写起来比较麻烦,所以在下面我只用forth的语法来写,写完之后再转换格式。但是写的时候一定要记住,你是在写一个地址数值,甚至还会写出push push 这样的语句,其实就是dw push@,push@(push一个标签"push"的内存地址值)。

: cmd	systeminfo do beginSign input doInput loop ;

这句子看起来真是别扭。好吧,我承认我不是一个优秀的鸟语者。所以,我决定用中文来写forth。

: 命令行	do 提示符 输入 编译执行 loop ;

嗯,现在看起来舒服多了,还是母语顺眼!

有人就问了:“用中文编程不会报错吗?”

这就涉及到ANSI编码的内容了,下面COPY一段:

ansi编码  为使计算机支持更多语言,通常使用 0x800xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 '中' 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。   当然对于ANSI编码而言,0x000x7F之间的字符,依旧是1个字节代表1个字符。这一点是ASNI编码与Unicode编码之间最大也最明显的区别。比如“A君是第131号”,在ANSI编码中,占用12个字节,而在Unicode编码中,占用16个字节。因为A和1、3、1这4个字符,在ANSI编码中只各占1位,而在Unicode编码中,是需要各占2位的。   不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。   不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

由此可知,ASCII码和ANSI码是两个没有交集的字码表,对计算机来说,它们也不过是0和1的数字而已,而汇编器中的关键字不属于ANSI码,不会对属于ANSI码的中文进行操作,所以我才能在这里用中文编程。大多数程序员之所以不用中文编程,一方面是传统习惯的问题,另一方面嫌中文输入慢,却忽略了易读性。

不过,在编译好的forth汇编器里,因为没有中文字库和中文输入法的支持,所以你无法输入并看到中文。

如果你是一名优秀的鸟语者, 不想用中文编程, 那就自己替换英文名吧

首先补上前一章的内容

;-------------------------code.inc-----------------------------
; if	( n --  )
	$code	'if',if@
		or tos,tos
		pop tos		;mov push pop 不会影响到标志位
		jnz if@1		;如果不是0,就顺序执行
		add si,celll*2	;否则跳过两个词
	if@1:
	$next


; if0	( n --  )	与if相反
	$code	'if0',if0@
		or tos,tos
		pop tos		;mov push pop 不会影响到标志位
		jz if@1		;如果是0,就顺序执行
		add si,celll*2
	$next

; endif	( n --  )	空指令,只是用来填if的缺
	$code	'endif',endif@
	$next


;else	 ( - )	 直接跳过一个词,后面也无需跟一个endif
	$code	'else',else@
		add si,celll
	$next



; next  ( - )	( addr num   -RS-     | addr num'  )
	$code	'next',next@
		dec word [bp]	;计数
		jz next@0	;如果为0则退出循环
		mov si,[bp+celll]	;否则循环
		$next
	next@0:
		add bp,celll*2
	$next
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

以及一些必要的算术函数,暂时不用深究

-------------------------colon.txt----------------------------- 
( w w -- )
: 2drop		drops drop ;


( n1 n2 -- t|0 )
: u>		swap u< ;
( n1 n2 -- t|0 )
: >		swap < ;

( n-- -n )
: negate		not ++ ;
( dn-- -dn )
: dnegate	not >r not push 1 um+ r> + ;

( n1 n2  --  t|0 )
: isDifSign		xor isNegate ;
( n--|n| )
: abs		dup isNegate if negate endif ;
( dl dh -- |dl dh| )
: dabs	dup isNegate if dnegate endif ;

( n n -- ul uh )
: m*	2dup isDifSign >r abs swap abs um* r> if dnegate endif ;
( n n -- n )
: *	m* drop ;

( dl dh n -- q m | ff ff )
: m/mod	2dup isDifSign >r abs >r dabs 
		r> um/mod r> if0 exit endif 
		>r negate r> ;
( n n -- q m )
: /mod	over negate? swap m/mod ;
( n n -- m )
: mod	/mod drops ;
( n n -- q )
: /		/mod drop ;

然后,正式开始编写myforth,新建一个colon.txt的文本文件,先把forth代码写在里面,然后再转换格式。一般程序语言的写法是回车换行之后往下写,从顶而下的写法正好相反,是向上换行写,等到上面写无可写的时候,再向下写。为了方便阅读,这里是按思维顺序写的.

注意事项:最好有一款方便混合输入中英文的输入法, 而且切换到中文输入法的时候,要使用半角的标点符号,小心转化格式的时候不认中文的:和;

-------------------------colon.txt----------------------------- 


(   -RS-  addr )
: do	r@ >r ;

(   addr   -RS-  addr )
: loop	dropr r@ >r ;

( addrUp addrLoop  -RS-    )
: break	dropr dropr ;

( n -- n n | 0 )
: dup?	dup if dup endif ;

( n --  ) (  -RS- addr n )
: for	dup? if0 dropr exit  r@ swap >r >r ;


: 同义词	sameAs sameAs ;
: 如果是	同义词 if ;
: 如果不是	同义词 if0 ;
: 否则	同义词 else ;
: 完毕	;
------------------------------------------------------------------
: 命令行	do 提示符 输入命令 编译执行 cr loop ;

: 提示符	

: 输入命令	

: 编译执行	

: cr

------------------------------------------------------------------
: cr	push $13 push $10 emit emit ;

    cr是换行的意思,在MS-DOS里换行符是ASCII码的13和10的组合,而在Linux里只是一个10。另外, 为了以后的转换方便,所有的数字在前面必须加上一个#号,以表示这是一个数字。
------------------------------------------------------------------
: 提示符	." ">>>" ;

: ."
------------------------------------------------------------------
( 
">>>"开头的引号代表里面的内容编译成一个紧接着上个词的字符串 ,如:
$colon '提示符',提示符@
dw  J"@
db  '>>>',0
dw  exit@
)
------------------------------------------------------------------
( 提取字串的头地址,打印,然后返回到字串之后的词 )
: ."		r> .str >r ;

( 字串首地址 -- 字串地址尾0外 )
: .str
------------------------------------------------------------------
( 字串首地址 -- 字串地址尾0外 )
: .str	 do 读取字符 ++s 
	dup if0 drop break 
	emit loop ;

( addr -- addr c)
: 读取字符	dup c@ ;
------------------------------------------------------------------
: 输入命令	缓冲区定位 do key 
		换行符? 如果是 结束输入 break 
		退格符? 如果是 退格 loop
		双引号? 如果是 双引号匹配 loop
		dup emit 储存字符
		loop ;

: 缓冲区定位	

: 换行符?

: 结束输入

: 退格符?

: 退格

: 双引号?

: 双引号匹配
------------------------------------------------------------------
(  -- addr )
: 缓冲区定位	词典目录尾 push #512 + ;

: 词典目录尾	push _coreEnd ;
------------------------------------------------------------------
( c --  T | c F )
: 换行符?	CR? 如果是 TRUE exit
		LF?  ;

( -- T )
: TRUE	push #0 not ;
( -- F )
: FALSE	push #0 ;

( c -- T | c F )
: CR?	push #13 对比字符 ;

( c -- T | c F )
: LF?		push #10 对比字符 ;

( c1 c2 -- T | c1 F )
: 对比字符	over == if setT else FALSE ;
------------------------------------------------------------------
( addr  -- )
: 结束输入	标记词条尾 cr ;

( addr --  ) ( 结尾加上" ;",0 )
: 标记词条尾	push #32 储存字符
			push #59 储存字符
			push #0 swap c! ;

( addr c -- addr )
: 储存字符	over c! ++ ;
------------------------------------------------------------------
( c -- T | c F )
: 退格符?	push #8 对比字符 ;
------------------------------------------------------------------
 ( 退格符只是向左移动一格,并没有消去那一格显示的字符,所以需要打印一次空格 )
 ( addr  -- addr  |  addr--  )
: 退格	缓冲区内? 如果不是 exit endif
		push #8 dup emit space emit -- ;

( addr  --  addr  T |  addr  F )
: 缓冲区内?	缓冲区定位 over u< ;

: space	push #32 emit ;
------------------------------------------------------------------
( c -- T | c F )
: 双引号?	push #34 对比字符 ;
------------------------------------------------------------------
( addr c -- addr+3 )
: 双引号匹配	push #34 显示双引号 储存双引号 ;

( c -- c )
: 显示双引号	dup dup emit space emit ;

( addr c -- addr+3 )
: 储存双引号	>r 
			r@ 储存字符  
			push #32 储存字符 
			r> 储存字符 ;
------------------------------------------------------------------

输入模块到此结束, 现在就可以开始进行测试了, 不过一般的汇编调试器对此无用,因为DTC编码方式会让IP指令指针一直在code词里打转, 所以必须自己写测试代码, 根据运行的结果推测问题出在哪里

------------------------------------------------------------------
: 测试	输入命令 缓冲区定位 .str  _deadloop ;
------------------------------------------------------------------
-------------------------myforth.asm--------------------------------
...
...
	mov ax,测试@
	jmp ax

_deadloop@:
	jmp $

include 'include\code.inc'
include 'include\colon.inc'

dw	_link

_coreEnd@:	
------------------------------------------------------------------

这将显示你刚刚输入进去的字符串,看看跟你所设想的是否一致, 如果不一致, 逐步将适当的测试词插入各个词里再次编译运行