So I got bored and thought I would familiarize myself with some of the new features of NASM-X...
;;; project: turtle
;;; author: S. Bryant Keller <ga.geek@yahoo.com>
;;;
;;; purpose:
;;; The purpose of this project was mostly to familiarize myself
;;; with new features of NASM-X. Also, I thought it would be useful
;;; to have a slightly larger GNU/Linux demo. And more importantly,
;;; it was fun.
;;;
;;; This is basically a NASM-X translation (and refactoring) of
;;; the GNU foundation's Tutorial Introduction to Guile available
;;; in the references section below. You should check that site for
;;; example scheme/guile code to test on this. It should work well.
;;;
;;; disclaimer:
;;; The following code is not a demonstration of good coding practices,
;;; for your own sanity, don't use this as a style guide. I don't
;;; take any responsibility if you break something (or lose your
;;; sanity) while trying to read or use my code. You do both at your
;;; own risk.
;;;
;;; warnings?
;;; Throughout this source you'll see warnings about global state,
;;; this is a habit I've picked up from doing a lot of functional
;;; programming in recent years. Basically, anything that modifies
;;; global data gets marked with a warning so, in the event I ever
;;; decide to reuse the code, I know it's not thread-safe and
;;; bound to some global data.
;;;
;;; references:
;;; Tutorial Introduction to Guile by Daniel Kraft (d@domob.eu)
;;; https://www.gnu.org/software/guile/docs/guile-tut/tutorial.html
;;;
;;; build: nasm -f elf32 -o turtle.o turtle.asm
;;; gcc -o turtle turtle.o -lc -lguile-2.0 -lgc
BITS 32
%include 'nasmx.inc'
%include 'linux/libc.inc'
%ixdefine bool_t uint32_t
%ixdefine SCM_t ptrdiff_t
%define TRUE 1
%define FALSE 0
%define NULL 0
%define SCM_UNSPECIFIED 2052
%define WIDTH 10
%define HEIGHT 10
%define NULL 0
%define M_PI 3.141592653589793
%define M_ROT 180.0
ENTRY application
IMPORT pipe
IMPORT fork
IMPORT dup2
IMPORT execlp
IMPORT fdopen
IMPORT fprintf
IMPORT fflush
IMPORT scm_from_double
IMPORT scm_to_double
IMPORT scm_list_2
IMPORT scm_c_define_gsubr
IMPORT scm_with_guile
IMPORT scm_shell
SECTION .rodata
dblPiVal:
declare(double_t) M_PI
dblRotVal:
declare(double_t) M_ROT
dblZeroVal:
declare(double_t) 0.0
szGnuPlot:
declare(NASMX_TCHAR) NASMX_TEXT("gnuplot"), 0
szWriteMode:
declare(NASMX_TCHAR) NASMX_TEXT("w"), 0
szClear:
declare(NASMX_TCHAR) NASMX_TEXT(`clear\n`), 0
szInitString:
declare(NASMX_TCHAR) NASMX_TEXT(`set multiplot\n`)
declare(NASMX_TCHAR) NASMX_TEXT(`set parametric\n`)
declare(NASMX_TCHAR) NASMX_TEXT(`set xrange [-%d:%d]\n`)
declare(NASMX_TCHAR) NASMX_TEXT(`set yrange [-%d:%d]\n`)
declare(NASMX_TCHAR) NASMX_TEXT(`set size ratio -1\n`)
declare(NASMX_TCHAR) NASMX_TEXT(`unset xtics\n`)
declare(NASMX_TCHAR) NASMX_TEXT(`unset ytics\n`), 0
szDrawLine:
declare(NASMX_TCHAR) NASMX_TEXT(`plot [0:1] %f + %f * t, %f + %f * t notitle\n`), 0
szTurtleReset:
declare(NASMX_TCHAR) NASMX_TEXT("turtle-reset"), 0
szTurtlePenup:
declare(NASMX_TCHAR) NASMX_TEXT("turtle-penup"), 0
szTurtlePendown:
declare(NASMX_TCHAR) NASMX_TEXT("turtle-pendown"), 0
szTurtleTurn:
declare(NASMX_TCHAR) NASMX_TEXT("turtle-turn"), 0
szTurtleMove:
declare(NASMX_TCHAR) NASMX_TEXT("turtle-move"), 0
SECTION .data
bPenDown:
declare(bool_t) TRUE
dblXcoord:
declare(double_t) 0.0
dblYcoord:
declare(double_t) 0.0
dblDirection:
declare(double_t) 0.0
fdGnuPlot:
declare(ptrdiff_t) NULL
SECTION .text
;;; start_gnuplot : nothing -> ptrdiff_t.
;;; create an instance of the 'gnuplot' process and produce a
;;; file descriptor for use later on.
proc start_gnuplot
locals
local fdOutput, ptrdiff_t
local iaPipes, uint32_t, 3
endlocals
lea eax, [var(.iaPipes)]
invoke pipe, eax
invoke fork
cmp eax, 0
jne .parent
.child:
lea eax, [var(.iaPipes)]
invoke dup2, dword [eax], STDIN_FILENO
invoke execlp, szGnuPlot, NULL
return
.parent:
lea eax, [var(.iaPipes)]
invoke fdopen, dword [eax + 4], szWriteMode
mov dword [var(.fdOutput)], eax
invoke fprintf, dword [var(.fdOutput)], szInitString, \
dword WIDTH, dword WIDTH, dword HEIGHT, dword HEIGHT
invoke fflush, dword [var(.fdOutput)]
return dword [var(.fdOutput)]
endproc
;;; draw_line : ptrdiff_t double_t double_t double_t double_t -> nothing.
;;; draw a line on the 'gnuplot' instance.
proc draw_line, ptrdiff_t fdOutput, double_t dblX_1, double_t dblY_1, double_t dblX_2, double_t dblY_2
locals
local dblXrel, double_t
local dblYrel, double_t
endlocals
fld qword [argv(.dblX_2)]
fsub qword [argv(.dblX_1)]
fstp qword [var(.dblXrel)]
fld qword [argv(.dblY_2)]
fsub qword [argv(.dblY_1)]
fstp qword [var(.dblYrel)]
invoke fprintf, dword [argv(.fdOutput)], szDrawLine, \
dword [argv(.dblX_1)], dword [argv(.dblX_1) + 4], \
dword [var(.dblXrel)], dword [var(.dblXrel) + 4], \
dword [argv(.dblY_1)], dword [argv(.dblY_1) + 4], \
dword [var(.dblYrel)], dword [var(.dblYrel) + 4]
invoke fflush, dword [argv(.fdOutput)]
return
endproc
;;; degrees_to_radians : double_t -> double_t.
;;; convert from degrees to radians.
;;; using radians = PI / 180.0 * degrees.
;;; given degrees = 0.0000 expect 0.0000#i,
;;; degrees = 10.0000 expect 0.1745#i,
;;; degrees = 20.0000 expect 0.3491#i,
;;; degrees = 30.0000 expect 0.5236#i,
;;; degrees = 40.0000 expect 0.6981#i,
;;; degrees = 50.0000 expect 0.8727#i,
;;; degrees = 60.0000 expect 1.0472#i,
;;; degrees = 70.0000 expect 1.2217#i,
;;; degrees = 80.0000 expect 1.3963#i,
;;; degrees = 90.0000 expect 1.5708#i.
proc degrees_to_radians, double_t dblDegrees
locals none
;; RPN: PI 180.0 / degrees *
fld qword [dblPiVal]
fdiv qword [dblRotVal]
fmul qword [argv(.dblDegrees)]
return
endproc
;;; radians_to_degrees : double_t -> double_t.
;;; convert from degrees to radians.
;;; using degrees = radians * 180.0 / PI.
;;; given radians = 0.0000 expect 0.000000#i,
;;; radians = 10.0000 expect 572.957795#i,
;;; radians = 20.0000 expect 1145.915590#i,
;;; radians = 30.0000 expect 1718.873385#i,
;;; radians = 40.0000 expect 2291.831181#i,
;;; radians = 50.0000 expect 2864.788976#i,
;;; radians = 60.0000 expect 3437.746771#i,
;;; radians = 70.0000 expect 4010.704566#i,
;;; radians = 80.0000 expect 4583.662361#i,
;;; radians = 90.0000 expect 5156.620156#i.
proc radians_to_degrees, double_t dblRadians
locals none
;; RPN: radians 180.0 * PI /
fld qword [argv(.dblRadians)]
fmul qword [dblRotVal]
fdiv qword [dblPiVal]
return
endproc
;;; compute_rotation : double_t -> double_t.
;;; compute and update the current direction.
;;; warning: this procedure maintains global state!
;;; Computational process:
;;; dblDirection := dblDirection + degrees_to_radians (degrees)
;;; compute_rotation := radians_to_degrees (dblDirection)
;;; test case:
;;; S1 : direction = 0, input = 10, output = 10
;;; S2 : direction = 10, input = 10, output = 20
;;; S3 : direction = 20, input = 90, output = 110
;;; ...
proc compute_rotation, double_t dblDegrees
locals none
invoke degrees_to_radians, qword [argv(.dblDegrees)]
fadd qword [dblDirection]
fstp qword [dblDirection]
invoke radians_to_degrees, qword [dblDirection]
return
endproc
;;; translate_x : double_t -> double_t.
;;; translate the current x coordinate.
;;; Computational process:
;;; newX = x + length * cos (direction);
proc translate_x, double_t dblLength
locals none
;; RPN: direction COS length * x +
fld qword [dblDirection]
fcos
fld qword [argv(.dblLength)]
fmul
fadd qword [dblXcoord]
return
endproc
;;; translate_y : double_t -> double_t.
;;; translate the current y coordinate.
;;; Computational process:
;;; newY = y + length * sin (direction);
proc translate_y, double_t dblLength
locals none
;; RPN: direction SIN length * y +
fld qword [dblDirection]
fsin
fld qword [argv(.dblLength)]
fmul
fadd qword [dblYcoord]
return
endproc
;;; reset_position : nothing -> nothing.
;;; reset the x, y, and direction variables to zero.
;;; warning: this procedure maintains global state!
;;; Computational process:
;;; x := 0 ; y := 0 ; dir := 0
proc reset_position
locals none
finit
fld qword [dblZeroVal]
fst qword [dblXcoord]
fst qword [dblYcoord]
fstp qword [dblDirection]
return
endproc
;;; scm_from_bool : bool_t -> SCM_t.
;;; translate from bool_t to a guile boolean.
;;; this is implemented as a macro in C, so we
;;; need to do this ourself.
proc scm_from_bool, bool_t bExpr
locals none
mov eax, [argv(.bExpr)]
cmp eax, FALSE
jne .is_true
mov eax, 4 ; #f
jmp .done
.is_true:
cmp eax, TRUE
jne .done
mov eax, 1028 ; #t
.done:
return eax
endproc
;;; turtle_reset : nothing -> SCM_t.
;;; clear the screen and reset to starting position.
;;; warning: this procedure maintains global state!
proc turtle_reset
locals none
invoke reset_position
mov bool_t [bPenDown], TRUE
invoke fprintf, [fdGnuPlot], szClear
invoke fflush, [fdGnuPlot]
return SCM_UNSPECIFIED
endproc
;;; turtle_pendown : nothing -> SCM_t.
;;; set the state of our pen to "down".
;;; warning: this procedure maintains global state!
proc turtle_pendown
locals none
mov eax, bool_t [bPenDown]
mov bool_t [bPenDown], TRUE
invoke scm_from_bool, eax
return eax
endproc
;;; turtle_penup : nothing -> SCM_t.
;;; set the state of our pen to "up".
;;; warning: this procedure maintains global state!
proc turtle_penup
locals none
mov eax, bool_t [bPenDown]
mov bool_t [bPenDown], FALSE
invoke scm_from_bool, eax
return eax
endproc
;;; turtle_turn : SCM_t -> SCM_t.
;;; rotate our turtle's direction by some degrees.
;;; warning: this procedure maintains global state!
proc turtle_turn, SCM_t scmDegrees
locals
local dblTemp, double_t
endlocals
invoke scm_to_double, [argv(.scmDegrees)]
fstp qword [var(.dblTemp)]
invoke compute_rotation, qword [var(.dblTemp)]
fstp qword [var(.dblTemp)]
invoke scm_from_double, dword [var(.dblTemp)], \
dword [var(.dblTemp) + 4]
return eax
endproc
;;; turtle_move : SCM_t -> SCM_t.
;;; move our turtle a distance of length.
;;; warning: this procedure maintains global state!
proc turtle_move, SCM_t scmLength
locals
local dblLen, double_t
local dblXnew, double_t
local dblYnew, double_t
local scmXcoord, SCM_t
local scmYcoord, SCM_t
endlocals
invoke scm_to_double, [argv(.scmLength)]
fstp qword [var(.dblLen)]
invoke translate_x, qword [var(.dblLen)]
fstp qword [var(.dblXnew)]
invoke translate_y, qword [var(.dblLen)]
fstp qword [var(.dblYnew)]
cmp bool_t [bPenDown], TRUE
jne .endif
invoke draw_line, ptrdiff_t [fdGnuPlot], \
qword [dblXcoord], qword [dblYcoord], \
qword [var(.dblXnew)], qword [var(.dblYnew)]
.endif:
invoke scm_from_double, qword [var(.dblXnew)]
mov SCM_t [var(.scmXcoord)], eax
fld qword [var(.dblXnew)]
fstp qword [dblXcoord]
invoke scm_from_double, qword [var(.dblYnew)]
mov SCM_t [var(.scmYcoord)], eax
fld qword [var(.dblYnew)]
fstp qword [dblYcoord]
invoke scm_list_2, SCM_t [var(.scmXcoord)], SCM_t [var(.scmYcoord)]
return
endproc
;;; register_functions : nothing -> ptrdiff_t.
;;; register our functions with guile.
proc register_functions
locals none
invoke scm_c_define_gsubr, szTurtleReset, 0, 0, 0, turtle_reset
invoke scm_c_define_gsubr, szTurtlePenup, 0, 0, 0, turtle_penup
invoke scm_c_define_gsubr, szTurtlePendown, 0, 0, 0, turtle_pendown
invoke scm_c_define_gsubr, szTurtleTurn, 1, 0, 0, turtle_turn
invoke scm_c_define_gsubr, szTurtleMove, 1, 0, 0, turtle_move
xor eax, eax
return
endproc
;;; application : uint32_t (ptrdiff_t . ptrdiff_t) -> uint32_t
;;; the application entrypoint.
;;; warning: this procedure maintains global state!
proc application, uint32_t iArgCount, ptrdiff_t pszCmdLine
locals none
invoke start_gnuplot
mov dword [fdGnuPlot], eax
invoke turtle_reset
invoke scm_with_guile, register_functions, NULL
invoke scm_shell, [argv(.iArgCount)], [argv(.pszCmdLine)]
xor eax, eax
return
endproc
The original code was from
Tutorial Introduction to Guile.
Here's something you can use with it...
(define (sierpinski length depth sign)
(define (move-forward x) (turtle-move x))
(define (move-backward x) (turtle-move (* -1 x)))
(define (turn-left x) (turtle-turn x))
(define (turn-right x) (turtle-turn (* -1 x)))
(define (draw-triangle length)
(let iterate ((i 0))
(if (< i 3)
(begin
(turtle-move length)
(turtle-turn (* sign 120))
(iterate (1+ i))))))
(if (zero? depth)
(draw-triangle length)
(begin
(sierpinski (/ length 2) (1- depth) sign)
(move-forward (/ length 2))
(sierpinski (/ length 2) (1- depth) sign)
(move-backward (/ length 2))
(turn-left 60)
(move-forward (/ length 2))
(turn-right 60)
(sierpinski (/ length 2) (1- depth) sign)
(turn-left 60)
(move-backward (/ length 2))
(turn-right 60))))
Hope someone enjoys it.
Best Regards,
Bryant Keller
EDIT: I noticed my use of different editors has butched the white spacing so I fixed that.. also added a comment to explain the warning's I write in my code. I thought, since this is a demo of sorts, I would leave all the experimental comments I usually write while prototyping my ideas. This includes annotations for non-thread safe routines, test case comments (though I didn't bother to include my unit tests as those tend to be larger than the actual project), and the common habit I have of writing any FPU routines as RPN to make translating easier (the FPU is a stack machine, if you can solve your problem in RPN it makes translating to FPU code MUCH easier).