
ومرة أخرى المبالغة في تقدير حجم المقال! لقد خططت أن تكون هذه هي المقالة الأخيرة ، حيث سنقوم بعمل مترجم ونجري الاختبارات. لكن تبين أن حجم الصوت كبير ، وقررت تقسيم المقالة إلى قسمين.
في هذه المقالة ، سنفعل ما يقرب من جميع الوظائف الأساسية للمترجم. سوف يأتي إلى الحياة ، وسيكون من الممكن كتابة وتجميع وتنفيذ تعليمات برمجية خطيرة للغاية. وسنفعل الاختبار في الجزء التالي. (بالمناسبة ، الأجزاء السابقة:
واحد ،
اثنان ،
ثلاثة ).
أنا أكتب لأول مرة في حبري ، وربما لا يكون الأمر على ما يرام دائمًا. في رأيي ، تبين أن المادتين 2 و 3 كانت جافة إلى حد ما ، والكثير من الكود ، ووصف قليل. هذه المرة سأحاول القيام بشيء مختلف ، والتركيز على وصف الأفكار نفسها. حسنًا ، الكود ... الكود ، بالطبع! الذي يريد أن يفهم جيدا ، فإن هذه الفرصة ستكون. في كثير من الحالات ، سوف أضع الكود تحت المفسد. وبالطبع ، يمكنك دائمًا إلقاء نظرة على المصدر الكامل على جيثب.
سيستمر المترجم في الكتابة لبعض الوقت في المجمع ، ولكن بعد ذلك يذهب إلى الحصن ويستمر في كتابة المترجم على أنفسنا. سوف يشبه هذا البارون مونشاوسين ، الذي سحب نفسه من الشعر من المستنقع. لكن بالنسبة للمبتدئين ، سأشرح كيفية عمل المترجم على الحصن. مرحبا بكم في القط!
كيف يعمل المترجم؟
تتكون الذاكرة في الحصن من جزء مستمر يتم فيه ترتيب إدخالات القاموس بالتسلسل. بعد الانتهاء منها تتبعها مساحة خالية من الذاكرة. يشار إلى البايت الحر الأول بواسطة المتغير h. هناك أيضًا الكلمة المستخدمة غالبًا ، والتي تدفع عنوان البايتة المجانية الأولى على المكدس ، وهي مصممة بكل بساطة:
: here h @ ;

تجدر الإشارة إلى كلمة التخصيص ، والتي تحتفظ بعدد محدد من البايتات عن طريق تحريك المؤشر h. يمكن تعريف كلمة التخصيص على النحو التالي:
: allot h +! ;
في الواقع ، يستخدم المترجم وضع مترجم خاص بالإضافة إلى بعض الكلمات الخاصة. لذلك ، مع جملة واحدة ، يمكنك وصف مبدأ المترجم الكامل في الحصن. يتم تحديد وضع المترجم الذي يعمل فيه بواسطة متغير الحالة. إذا كان الصفر ، ثم يتم تعيين وضع التنفيذ ، وإلا - وضع الترجمة. نحن على دراية بالفعل بوضع التنفيذ ، حيث يتم ببساطة تنفيذ الكلمات من المخزن المؤقت الإدخال واحد تلو الآخر. ولكن في وضع التحويل البرمجي ، لا يتم تنفيذها ، ولكن يتم تجميعها في الذاكرة بواسطة المؤشر h. وفقًا لذلك ، يتحرك المؤشر للأمام.
في الحصن الكلاسيكي ، يتم استخدام كلمة "،" لتجميع قيمة عدد صحيح ، يتم استخدام كلمة "c" ، لتجميع بايت. يستخدم نظامنا قيمًا ذات أعماق بتات مختلفة (8 ، 16 ، 32 ، 64) ، وبالتالي ، سنقوم بإضافة الكلمات "w" و "i". نصنع أيضًا الكلمة "str" ، والتي ستقوم بترجمة السلسلة ، مع أخذ قيمتين من المجموعة - عنوان السلسلة وطولها.
يتم استخدام كلمات مترجم خاص لتشكيل هياكل التحكم. هذه هي الكلمات إذا ، ثم ، تفعل ، حلقة ، وغيرها. يتم تنفيذ هذه الكلمات حتى في وضع الترجمة. على سبيل المثال ، الكلمة إذا جمعت أمر بايت فرع الشرطي (؟ Nbranch) في التنفيذ. بحيث يعرف النظام الكلمات التي يجب تنفيذها في وضع الترجمة ، وليس المترجمة ، يتم استخدام العلامة الفورية (علامة). لدينا بالفعل في حقل العلم لإدخال القاموس. في شفرة مصدر المجمّع ، يطلق عليه f_immediate. لتعيين هذه العلامة ، استخدم الكلمة فورًا. لا يوجد لديه معلمات ، يتم تعيين العلم الفوري في الكلمة الأخيرة في القاموس.
الآن دعنا ننتقل من النظرية إلى الممارسة!
التحضير
في البداية ، نحتاج إلى القيام ببعض أوامر البايت البسيطة في لغة التجميع التي نحتاجها. فيما يلي: تحريك (نسخ منطقة الذاكرة) ، وملء (تعبئة مساحة الذاكرة) ، وعمليات البت (و ، أو ، xor ، عكس) ، أوامر إزاحة البت (rshift ، lshift). دعونا نفعل نفس rpick (هذا هو نفس الاختيار ، فهو يعمل فقط مع رصة العودة ، وليس رصة البيانات).
هذه الأوامر بسيطة للغاية ، وإليك رمزها b_move = 0x66 bcmd_move: pop rcx pop rdi pop rsi repz movsb jmp _next b_fill = 0x67 bcmd_fill: pop rax pop rcx pop rdi repz stosb jmp _next b_rpick = 0x63 bcmd_rpick: pop rcx push [rbp + rcx * 8] jmp _next b_and = 0x58 bcmd_and: pop rax and [rsp], rax jmp _next b_or = 0x59 bcmd_or: pop rax or [rsp], rax jmp _next b_xor = 0x5A bcmd_xor: pop rax xor [rsp], rax jmp _next b_invert = 0x5B bcmd_invert: notq [rsp] jmp _next b_rshift = 0x5C bcmd_rshift: pop rcx or rcx, rcx jz _next 1: shrq [rsp] dec rcx jnz 1b jmp _next b_lshift = 0x5D bcmd_lshift: pop rcx or rcx, rcx jz _next 1: shlq [rsp] dec rcx jnz 1b jmp _next
لا تزال بحاجة لجعل كلمة كلمة. هذا هو نفس اللوم ، ولكن محدد محدد محدد على المكدس. لا أقدم الرمز ، بل يمكن العثور عليه في المصدر. لقد قمت بنسخ / لصق الكلمات blworld واستبدالها بأوامر المقارنة.
في الختام ، نجعل الكلمة syscall. مع ذلك ، سيكون من الممكن القيام بعمليات النظام المفقودة ، على سبيل المثال ، العمل مع الملفات. لن يعمل مثل هذا الحل إذا كان استقلال النظام الأساسي مطلوبًا. لكن هذا النظام يستخدم الآن للاختبارات ، لذا فليكن كذلك في الوقت الحالي. إذا لزم الأمر ، يمكن تحويل جميع العمليات إلى أوامر بايت ، فليس من الصعب على الإطلاق. سيقبل الأمر syscall 6 معلمات لاستدعاء النظام ورقم الاتصال من المكدس. سيعود معلمة واحدة. يتم تحديد تعيينات المعلمة وقيمة الإرجاع بواسطة رقم استدعاء النظام.
b_syscall = 0xFF bcmd_syscall: sub rbp, 8 mov [rbp], r8 pop rax pop r9 pop r8 pop r10 pop rdx pop rsi pop rdi syscall push rax mov r8, [rbp] add rbp, 8 jmp _next
والآن دعنا ننتقل مباشرة إلى المترجم.
مترجم
لنقم بإنشاء المتغير h ، كل شيء بسيط هنا.
item h h: .byte b_var0 .quad 0
سنكتب التهيئة في خط البداية:
# forth last_item context @ ! h dup 8 + swap ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_call16 .word h - . - 2 .byte b_dup, b_num8, b_add, b_swap, b_set .byte b_quit
لنجعل الكلمة هنا:
item here .byte b_call8, h - . - 1 .byte b_get .byte b_exit
وكذلك كلمات لتجميع القيم: "تخصيص" و "c" ، "w" ، "i" ، "،" ، "str" # : allot h +! ; item allot allot: .byte b_call8, h - . - 1, b_setp, b_exit # : , here ! 8 allot ; item "," .byte b_call8, here - . - 1, b_set, b_num8, b_call8, allot - . - 1, b_exit # : i, here i! 4 allot ; item "i," .byte b_call8, here - . - 1, b_set32, b_num4, b_call8, allot - . - 1, b_exit # : w, here w! 2 allot ; item "w," .byte b_call8, here - . - 1, b_set16, b_num2, b_call8, allot - . - 1, b_exit # : c, here c! 1 allot ; item "c," .byte b_call8, here - . - 1, b_set8, b_num1, b_call8, allot - . - 1, b_exit # : str, dup -rot dup c, here swap move 1+ h +!; item "str," c_str: .byte b_dup, b_mrot, b_dup callb c_8 callb here .byte b_swap, b_move callb h .byte b_setp .byte b_exit
الآن لنجعل متغير الحالة وكلمتين للتحكم في قيمته: "[" و "]". عادة ما تستخدم هذه الكلمات لأداء شيء في وقت التجميع. لذلك ، تقوم الكلمة "[" بإيقاف تشغيل وضع الترجمة ، وتقوم الكلمة "]" بتشغيله. لكن لا شيء يمنعهم من الاستخدام في حالات أخرى عندما يكون من الضروري تشغيل وضع الترجمة أو إيقافه. ستكون الكلمة "[" هي أول كلمة لنا بها علامة فورية. وإلا ، فلن يكون قادرًا على إيقاف تشغيل وضع الترجمة ، حيث سيتم تجميعه ، وليس تنفيذه.
item state .byte b_var0 .quad 0 item "]" .byte b_num1 callb state .byte b_set, b_exit item "[", f_immediate .byte b_num0 callb state .byte b_set, b_exit
جاء الدور لكلمة $ compile. سوف يستغرق عنوان إدخال القاموس من المكدس وتجميع الكلمة المحددة. من أجل ترجمة كلمة في تطبيقات Fort العادية ، يكفي تطبيق الكلمة "،" على عنوان التنفيذ. كل شيء أكثر تعقيدًا هنا. أولاً ، هناك نوعان من الكلمات - كود الشفرة ورمز الجهاز. يتم تصنيف السابق بواسطة بايت ، والأخير بواسطة استدعاء بايت الأمر. وثانياً - لدينا ما يصل إلى أربعة أنواع مختلفة من أمر الاتصال: call8 ، call16 ، call32 و call64. أربعة؟ لا! عندما كتبت المترجم ، أضفت 16 أكثر إلى هؤلاء الأربعة! :)
كيف حدث هذا؟ علينا أن نجعل استطرادا صغيرا.
تحسين أمر الاتصال
عندما بدأ المترجم العمل ، وجدت أنه في كثير من الحالات (ولكن ليس كلها) يكون الأمر call8 كافياً. هذا هو عندما تكون الكلمة تسمى داخل 128 بايت. فكرت - وكيف أتأكد من حدوث ذلك في جميع الحالات تقريبًا؟ كيفية وضع أكثر من 256 القيم في بايت؟
النقطة الأولى التي لاحظتها هي أن الدعوة في الحصن تتجه دائمًا نحو العناوين السفلية. هذا يعني أنه يمكنك إعادة أمر الاستدعاء بطريقة يمكنها فقط الاتصال بالعناوين الأقل ، ولكن 256 بايت ، وليس 128. إنه أفضل.
ولكن إذا وضعت بضع بت في مكان ما ... اتضح أن هناك مكان! لدينا وحدتي بايت: بايت واحد هو الأمر ، والثاني هو الإزاحة. لكن لا شيء يمنع البتات الأدنى للأمر من وضع البتات العالية للمعلمة (الإزاحة). بالنسبة إلى آلة البايت ، يبدو أن هناك العديد من الأوامر بدلاً من أمر استدعاء واحد. نعم ، وبهذه الطريقة نحتل عدة خلايا في جدول كود البايت للأوامر بأمر واحد ، لكن في بعض الأحيان يجدر القيام بذلك. يعد أمر call أحد أكثر الأوامر استخدامًا ، لذلك قررت أن أضع 4 بتات إزاحة في الأمر. وبالتالي ، يمكنك إجراء مكالمة على مسافة تصل إلى 4095 بايت! هذا يعني أنه سيتم استخدام أمر استدعاء قصير دائمًا تقريبًا. لقد وضعت هذه الأوامر مع الكود 0xA0 وظهرت الأسطر التالية في جدول الأوامر:
.quad bcmd_call8b0, bcmd_call8b1, bcmd_call8b2, bcmd_call8b3, bcmd_call8b4, bcmd_call8b5, bcmd_call8b6, bcmd_call8b7 # 0xA0 .quad bcmd_call8b8, bcmd_call8b9, bcmd_call8b10, bcmd_call8b11, bcmd_call8b12, bcmd_call8b13, bcmd_call8b14, bcmd_call8b15
يقوم أول أوامر البايت هذه بإجراء مكالمة في اتجاه العناوين السفلية عند الإزاحة المحددة في المعلمة (حتى 255). الباقي إضافة الإزاحة المقابلة للمعلمة. يضيف bcmd_call8b1 256 ، ويضيف bcmd_call8b2 512 ، وهكذا. لقد تقدمت بأمر أول استدعاء على حدة ، والباقي مع ماكرو.
الأمر الأول:
b_call8b0 = 0xA0 bcmd_call8b0: movzx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 sub r8, rax jmp _next
ماكرو وإنشاء بقية أوامر الاتصال:
.macro call8b N b_call8b\N = 0xA\N bcmd_call8b\N: movzx rax, byte ptr [r8] sub rbp, 8 inc r8 add rax, \N * 256 mov [rbp], r8 sub r8, rax jmp _next .endm call8b 1 call8b 2 call8b 3 call8b 4 call8b 5 call8b 6 call8b 7 call8b 8 call8b 9 call8b 10 call8b 11 call8b 12 call8b 13 call8b 14 call8b 15
حسنًا ، قمت بإعادة استدعاء أمر call8 القديم للاتصال بالأمام ، نظرًا لأن لدينا بالفعل 16 فريقًا يقوم بإجراء مكالمة. مهما كان الالتباس ، قمت بإعادة تسميته b_call8f:
b_call8f = 0x0C bcmd_call8f: movzx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next
بالمناسبة ، من أجل الراحة ، قمت بإنشاء ماكرو يجمع المجمّع تلقائيًا المكالمة المقابلة مرة أخرى في غضون 4095. ثم لم أكن بحاجة إلى :)
.macro callb adr .if \adr > . .error "callb do not for forward!" .endif .byte b_call8b0 + (. - \adr + 1) >> 8 .byte (. - \adr + 1) & 255 .endm
والآن ...
تجميع الفريق
لذلك ، نحصل على خوارزمية تجميع أوامر معقدة إلى حد ما. إذا كان هذا أمر بايت ، فقم فقط بترجمة بايت (رمز أمر بايت). وإذا كانت هذه الكلمة مكتوبة بالفعل في bytecode ، فيجب عليك ترجمة مكالمتها باستخدام أمر call ، واختيار واحدة من عشرين. بتعبير أدق 19 ، لذلك ليس لدينا دعوة إلى الأمام ، ولن يتم استخدام call8f للقلعة.
وبالتالي فإن الخيار هو هذا. إذا كان الإزاحة يقع ضمن 0 ...- 4095 ، فحدد الأمر bcmd_call8b بالرمز 0xA0 ، مع وضع بتات الإزاحة الأربعة الأكثر أهمية في البتات الأقل أهمية من الأمر. في الوقت نفسه ، بالنسبة إلى آلة البايت ، يكون رمز أحد أوامر bcmd_call8b0 هو bcmd_call8b15.
إذا كان الإزاحة الخلفية أكبر من أو يساوي 4095 ، فإننا نحدد البعد الذي يتم وضع الإزاحة فيه ونستخدم الأمر المناسب من call16 / 32/64. يجب أن يؤخذ في الاعتبار أن يتم إزاحة هذه الفرق. يمكن أن يسبب كل من الأمام والخلف. على سبيل المثال ، يمكن call16 استدعاء مسافة 32767 في كلا الاتجاهين.
هنا هو التنفيذ نتيجة لذلك:
ترجمة $يجمع كلمة. كمعلمة ، يأخذ عنوان إدخال القاموس للكلمة المترجمة. في الواقع ، يتحقق من علامة f_code ، ويحسب عنوان الرمز (cfa) ، ويستدعي compile_b أو compile_c (إذا تم تعيين العلم).
compile_cيجمع أمر بايت. أبسط كلمة هنا موصوفة على الحصن مثل هذا:
: compile_c c@ c, ;
compile_bيستغرق عنوان بايت على المكدس ويجمع مكالمته.
test_bvيستغرق إزاحة من المكدس (مع إشارة) ويحدد عمق البت الذي يجب استخدامه (1 أو 2 أو 4 أو 8 بايت). تُرجع القيمة 0 أو 1 أو 2 أو 3. باستخدام هذه الكلمة ، يمكنك تحديد القيمة التي يجب استخدامها من أوامر call16 / 32/64. ستكون هذه الكلمة في متناول اليد عند تجميع الأرقام (اختيار من lit8 / 16/32/64).
بالمناسبة ، يمكنك بدء تشغيل النظام و "التجول" في وحدة التحكم بالحصن باستخدام أي من هذه الكلمات. على سبيل المثال:
$ ./forth ( 0 ): > 222 test_bv ( 2 ): 222 1 > drop drop ( 0 ): > 1000000 test_bv ( 2 ): 1000000 2 > drop drop ( 0 ): > -33 test_bv ( 2 ): -33 0 >
test_bvcيستغرق إزاحة (مع إشارة) من المكدس وتحديد أي أمر استدعاء لاستخدام. في الواقع ، فإنه يتحقق لمعرفة ما إذا كان الإزاحة يقع ضمن 0 ... -4095 ، وإرجاع 0. في هذه الحالة ، إذا لم يكن هناك ضرب في هذا الفاصل ، فإنه يستدعي test_bv.
هذا كل ما يتطلبه الأمر لتجميع الأمر. # : test_bvc dup 0 >= over FFF <= and if 0 exit else ... item test_bvc test_bvc: .byte b_dup, b_neg .byte b_num0 .byte b_gteq .byte b_over, b_neg .byte b_lit16 .word 0xFFF .byte b_lteq .byte b_and .byte b_qnbranch8, 1f - . .byte b_num0 .byte b_exit item test_bv test_bv: .byte b_dup, b_lit8, 0x80, b_gteq, b_over, b_lit8, 0x7f, b_lteq, b_and, b_qnbranch8, 1f - ., b_num0 .byte b_exit 1: .byte b_dup .byte b_lit16 .word 0x8001 .byte b_gteq .byte b_over .byte b_lit16 .word 0x7ffe .byte b_lteq, b_and, b_qnbranch8, 2f - ., b_num1, b_exit 2: .byte b_dup .byte b_lit32 .int 0x80000002 .byte b_gteq .byte b_over .byte b_lit32 .int 0x7ffffffd .byte b_lteq, b_and, b_qnbranch8, 3f - ., b_num2, b_exit 3: .byte b_num3 .byte b_exit # - item compile_c compile_c: .byte b_get8 callb c_8 .byte b_exit # - item compile_b compile_b: callb here .byte b_num2, b_add .byte b_sub callb test_bvc .byte b_dup .byte b_zeq .byte b_qnbranch8, 1f - . .byte b_drop .byte b_neg .byte b_dup .byte b_lit8, 8 .byte b_rshift .byte b_lit8, b_call8b0 .byte b_or callb c_8 callb c_8 .byte b_exit 1: .byte b_dup, b_num1, b_eq, b_qnbranch8, 2f - ., b_drop, b_lit8, b_call16 callb c_8 .byte b_wm callb c_16 .byte b_exit 2: .byte b_num2, b_eq, b_qnbranch8, 3f - ., b_lit8, b_call32 callb c_8 .byte b_num3, b_sub callb c_32 .byte b_exit 3: .byte b_lit8, b_call64 callb c_8 .byte b_lit8, 7, b_sub callb c_64 .byte b_exit #: $compile dup c@ 0x80 and if cfa compile_c else cfa compile_b then ; item "$compile" _compile: .byte b_dup, b_get8, b_lit8, 0x80, b_and, b_qnbranch8, 1f - ., b_cfa callb compile_c .byte b_exit 1: .byte b_cfa callb compile_b .byte b_exit
الآن نحن بحاجة إلى تجميع الرقم.
تجميع رقم (حرفي)
كتب عنوانًا فرعيًا كاملاً ، أعد لوصف التجميع الحرفي على وجه التحديد ، لكن اتضح أنه لا يوجد شيء خاص لوصف :)
لقد أنجزنا بالفعل نصف العمل في كلمة test_bv. يبقى فقط لاستدعاء test_bv ، ووفقًا للنتيجة ، قم بتجميع lit8 / 16/32/64 ، ثم القيمة المقابلة للحجم 1 أو 2 أو 4 أو 8 بايت.
ونحن نفعل ذلك من خلال تحديد كلمة compile_n # item compile_n compile_n: callb test_bv .byte b_dup .byte b_zeq .byte b_qnbranch8, 1f - . .byte b_drop, b_lit8, b_lit8 callb c_8 callb c_8 .byte b_exit 1: .byte b_dup, b_num1, b_eq, b_qnbranch8, 2f - ., b_drop, b_lit8, b_lit16 callb c_8 callb c_16 .byte b_exit 2: .byte b_num2, b_eq, b_qnbranch8, 3f - ., b_lit8, b_lit32 callb c_8 callb c_32 .byte b_exit 3: .byte b_lit8, b_lit64 callb c_8 callb c_64 .byte b_exit
تعديل المترجم
كل شيء جاهز لتجميع الأمر والحرفية. الآن يجب أن يتم دمجها في المترجم الفوري. هذا التعديل بسيط. حيث تم تنفيذ الأمر ، أضف التحقق من الحالة. إذا كانت الحالة غير فارغة والكلمة لا تحتوي على إشارة فورية ، فبدلاً من التنفيذ ، يلزمك استدعاء $ compile. وحول نفس الشيء يجب القيام به عند الحصول على الرقم من دفق الإدخال. إذا كانت الحالة صفرية ، فاترك الرقم في الحزمة ، وإذا لم يكن كذلك ، فاتصل بـ compile_n.
هنا هو المترجم item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop callb state .byte b_get .byte b_qnbranch8, irpt_execute - . # 0, .byte b_dup, b_get8, b_lit8, f_immediate, b_and # immediate .byte b_qbranch8, irpt_execute - . # - # ! callb _compile .byte b_branch8, 2f - . irpt_execute: .byte b_cfa # , (state = 0 immediate ) .byte b_execute .byte b_branch8, 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # , .byte b_qbranch8, 3f - . # 0, , 3 .byte b_type # .byte b_strp # .byte 19 # .ascii " : word not found!\n" .byte b_quit # 3: .byte b_nip, b_nip # , ( b_over, b_over) # - callb state # , .byte b_get .byte b_qnbranch8, 2f - . # - ; - # callb compile_n 2: # .byte b_depth # .byte b_zlt # , 0 ( 0<) .byte b_qnbranch8, interpret_ok - . # , , .byte b_strp # .byte 14 .ascii "\nstack fault!\n" .byte b_quit # interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
الآن نحن على بعد خطوة واحدة من المترجم ...
تعريف الكلمات الجديدة (الكلمة ":")
الآن ، إذا قمنا بتعيين متغير الحالة إلى قيمة غير صفرية ، فستبدأ عملية الترجمة. لكن النتيجة ستكون عديمة الفائدة ، لا يمكننا تحقيقها ، ولا حتى العثور عليها في الذاكرة. لجعل ذلك ممكنًا ، من الضروري تنسيق نتيجة الترجمة في شكل مقالة قاموس. للقيام بذلك ، قبل تشغيل وضع الترجمة ، تحتاج إلى إنشاء عنوان للكلمة.
يجب أن يحتوي الرأس على أعلام وحقل اتصال واسم. لدينا هنا قصة مألوفة - يمكن أن يكون مجال الاتصال 1 أو 2 أو 4 أو 8 بايت. لنجعل الكلمة compile_1248 ، والتي سوف تساعدنا في تشكيل حقل التواصل هذا. سوف يستغرق الأمر رقمين في الحزمة - الإزاحة والقيمة التي تم إنشاؤها بواسطة الأمر test_bv.
compile_1248 # , , # , test_dv item compile_1248 compile_1248: .byte b_dup .byte b_zeq .byte b_qnbranch8, 1f - . .byte b_drop callb c_8 .byte b_exit 1: .byte b_dup, b_num1, b_eq, b_qnbranch8, 2f - . .byte b_drop callb c_16 .byte b_exit 2: .byte b_num2, b_eq, b_qnbranch8, 3f - . callb c_32 .byte b_exit 3: callb c_64 .byte b_exit
الآن جعل الكلمة $ خلق. سيكون من المفيد لنا أكثر من مرة. يمكنك استخدامه كلما احتجت إلى إنشاء عنوان لإدخال القاموس. سوف يستغرق الأمر قيمتين من المكدس - عنوان اسم الكلمة التي تم إنشاؤها وطولها. بعد تنفيذ هذه الكلمة ، سيظهر عنوان إدخال القاموس الذي تم إنشاؤه في الحزمة.
$ خلق # : $create here current @ @ here - test_bv dup c, compile_1248 -rot str, current @ ! ' var0 here c!; item "$create" create: callb here callb current .byte b_get, b_get callb here .byte b_sub callb test_bv .byte b_dup callb c_8 callb compile_1248 .byte b_mrot callb c_str # callb current .byte b_get, b_set # - var0, here # , - , # , # 1 allot , .byte b_lit8, b_var0 callb here .byte b_set8 .byte b_exit
سوف تلتقط الكلمة التالية اسم الكلمة الجديدة من دفق الإدخال باستخدام الكلمة blword وتدعو $ create ، لإنشاء كلمة جديدة بالاسم المحدد.
create_in item "create_in" create_in: .byte b_blword .byte b_dup .byte b_qbranch8 .byte 1f - . .byte b_strp # ( ) .byte 3f - 2f # 2: .ascii "\ncreate_in - name not found!\n" 3: .byte b_quit 1: callb create .byte b_exit
وأخيرا ، اجعل الكلمة ":". ستقوم بإنشاء كلمة جديدة باستخدام create_in وتعيين وضع الترجمة ، لم يتم تثبيتها. وإذا كان مثبتا ، فإنه يعطي خطأ. كلمة ":" سيكون لها علامة فورية.
الكلمة: # : : create_in 1 state dup @ if ." : - no execute state!" then ! 110 ; immediate item ":", f_immediate colon: callb create_in .byte b_num1 callb state .byte b_dup .byte b_get .byte b_qnbranch8, 2f - . .byte b_strp # ( ) .byte 4f - 3f # 3: .ascii "\n: - no execute state!\n" 4: .byte b_quit 2: .byte b_set .byte b_lit8, 110 .byte b_exit
إذا نظر شخص ما إلى الكود ، فقد رأى أن هذه الكلمة تفعل شيئًا آخر :)
وهنا 110 ؟؟
نعم ، هذه الكلمة تدفع الرقم 110 أيضًا إلى المجموعة ، وهذا هو السبب. عندما يتم تجميعها ، يجب أن تكون البنيات المختلفة ككل واحد. على سبيل المثال ، بعد إذا كان يجب أن يكون ذلك الحين. والكلمة التي تم إنشاؤها باستخدام ":" يجب أن تنتهي بـ "؛". للتحقق من هذه الشروط ، وضعت كلمات خاصة من المحول البرمجي قيمًا معينة على المكدس وتحقق من وجودها. على سبيل المثال ، الكلمة ":" تضع القيمة 110 والكلمة "؛" يتحقق من أن 110 في الجزء العلوي من المكدس ، وإذا لم يكن الأمر كذلك ، فهذا خطأ. لذلك ، لم يتم إقران هياكل التحكم.
يتم تنفيذ مثل هذا الاختيار في كل هذه الكلمات من المترجم ، وبالتالي ، فإننا سوف تجعل كلمة خاصة لهذا - "أزواج". سيستغرق الأمر قيمتين من المكدس ، ورمي خطأ إذا لم تكن متساوية.
أيضًا ، في مثل هذه الكلمات ، يتعين عليك في كثير من الأحيان التحقق من ضبط وضع الترجمة. لنجعل كلمة "دولة" لهذا الغرض.
"أزواج" الدولة #: ?pairs = ifnot exit then ." \nerror: no pairs operators" quit then ; item "?pairs" .byte b_eq, b_qbranch8, 1f - . .byte b_strp .byte 3f - 2f 2: .ascii "\nerror: no pairs operators" 3: .byte b_quit 1: .byte b_exit #: ?state state @ 0= if abort" error: no compile state" then ; item "?state" callb state .byte b_get, b_zeq, b_qnbranch8, 1f - . .byte b_strp .byte 3f - 2f 2: .ascii "\nerror: no compile state" 3: .byte b_quit 1: .byte b_exit
هذا كل شئ! لن نجمع أي شيء آخر في المجمّع يدويًا :)
لكن حتى النهاية ، لم تتم كتابة المترجم بعد ، لذلك في البداية سيكون عليك استخدام بعض الأساليب غير المعتادة ...
دعونا نستعد لتجميع المترجم الذي تم إنشاؤه باستخدام المترجم الذي تم إنشاؤه
للبدء ، يمكنك التحقق من كيفية عمل الكلمة ":" من خلال تجميع شيء بسيط. لنجعل ، على سبيل المثال ، الكلمة:
: ^2 dup * ;
هذه الكلمة تربيع. ولكن ليس لدينا كلمة "؛" ماذا تفعل؟ exit, . "[" 110:
$ ./forth ( 0 ): > : ^2 dup * exit [ drop ( 0 ): > 4 ^2 ( 1 ): 16 >
!
…
, , , . . , . , here, . , , «» , . , , , , .
fcode: .ascii " 2 2 + . quit"
ولكن ، في بداية السطر ، يستحق وضع عشرات المساحات.لإنجاح هذا العمل ، نقوم بتغيير رمز البداية بحيث يشير tib ، #tib إلى هذا السطر. في النهاية ، هناك استقالة لإدخال سطر الأوامر العادي للنظام.بدء bytecode أصبح مثل هذا start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_call16 .word vhere - . - 2 .byte b_dup .byte b_call16 .word h - . - 2 .byte b_set .byte b_call16 .word definitions - . - 2 .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word fcode_end - fcode .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_quit
إطلاق! $ ./forth 4 ( 0 ): >
عظيم!
والآن ...ترجمة المحول البرمجي مع المحول البرمجي
بعد ذلك ، نكتب الرمز في سطر fcode. أول شيء فعله ، بالطبع ، هو كلمة "؛". : ; ?state 110 ?pairs lit8 [ blword exit find cfa c@ c, ] c, 0 state ! exit [ current @ @ dup c@ 96 or swap c! drop
سأقدم بعض التفسيرات. ?state 110 ?pairs
نحن هنا نتحقق من أن حالة الترجمة قد تم ضبطها بالفعل ، وأن 110 منها في وضع مكدس ، وإلا فسيحدث انقطاع عن طريق الخطأ. lit8 [ blword exit find cfa c@ c, ]
lit - exit. , exit, , . compile. , «compile exit» :)
c, 0 state !
exit ";", . "[" , immediate
,
";", .
exit [
. exit . , ";" . ?
current @ @ dup c@ 96 or swap c! drop
immediate . , drop. drop 110, ":" .
!
.
$ ./forth ( 0 ): > : ^3 dup dup * * ; ( 0 ): > 6 ^3 . 216 ( 0 ): >
هناك! , « ».
, , … , : immediate. immediate :
: immediate current @ @ dup c@ 96 or swap c! ;
تسلسل مألوف :) في الآونة الأخيرة ، تم كتابته يدويًا ، لن تكون هناك حاجة لهذا بعد الآن.الآن لنصنع بعض الكلمات الصغيرة ولكن المفيدة: : hex 16 base ! ; : decimal 10 base ! ; : bl 32 ; : tab 9 ; : lf 10 ;
عرافة وعشري تعيين نظام الرقم المقابل. الباقي ثوابت للحصول على رموز الأحرف المقابلة.نحن أيضًا نصنع كلمة لنسخ سطر مع عداد:: cmove over c @ 1+ move؛والآن سوف نشارك في الظروف. بشكل عام ، إذا كان هناك ترجمة كلمة ، فستبدو كما يلي: : if ?state compile ?nbranch8 here 0 c, 111 ; immediate : then ?state 111 ?pairs dup here swap - swap c! ; immediate
تتحقق كل هذه الكلمات في البداية من ضبط وضع الترجمة وإنشاء خطأ إذا لم يكن الأمر كذلك.الكلمة if تجمع فرعًا شرطيًا ، وتحجز بايتًا لمعلمة أمر الفرع الشرطي ، وتدفع عنوان هذه البايتة إلى المكدس. ثم يدفع قيمة التحكم 111 إلى المكدس ،ثم تتحقق الكلمة من وجود قيمة التحكم 111 ، ثم تكتب الإزاحة إلى العنوان الموجود في الحزمة.else. , else. , if, , , . , then: else. - , :) - , , :
: if compile ?nbranch8 here 0 c, ; immediate : then dup here swap - swap c! ; immediate
. compile, , ";":
: if ?state lit8 [ blword ?nbranch8 find cfa c@ c, ] c, here 0 c, 111 ; immediate : then ?state 111 ?pairs dup here swap - swap c! ; immediate : else ?state 111 ?pairs lit8 [ blword branch8 find cfa c@ c, ] c, here 0 c, swap dup here swap - swap c! 111 ; immediate
. , , , 1000, 5, 0 :
$ ./forth ( 0 ): > : test 5 = if 1000 . else 0 . then ; ( 0 ): > 22 test 0 ( 0 ): > 3 test 0 ( 0 ): > 5 test 1000 ( 0 ): >
فمن الواضح أن هذه النتيجة لم تنجح على الفور ، كانت هناك أخطاء ، كان هناك تصحيح. ولكن في النهاية ، عملت الظروف!استطرادا صغيرا عن طول أوامر الانتقال, , 127 . . , , . , , . 8 , 40 127 . , ?
. — 16 .
. 16 — . , , call, . , 11 ( 1023 ). 300 1000 . , . 3 , 8 . : (?nbranch), (?branch) (branch). — 24 .
لدينا شروط ، تصبح الحياة أسهل :)دعنا نصنع كلمة ". (Dot-quote). يعرض النص المحدد عند تنفيذه. يتم استخدامه بهذه الطريقة: ." "
يمكنك استخدام هذه الكلمة فقط في وضع الترجمة. سيصبح هذا واضحًا بعد تحليل جهاز هذه الكلمة: : ." ?state 34 word dup if lit8 [ blword (.") find cfa c@ c, ] c, str, else drop then ; immediate
. (34 word). , . , . , :) , , .
, - (."), . - (- ) , .
.
$ ./forth ( 0 ): > : test ." " ; ( 0 ): > test ( 0 ): >
, , compile.
, , . : -, , -. . : "(compile_b)" "(compile_c)".
(compile_b) call -. 64- — -.
(compile_c) -. , — .
compile (compile_b), (compile_c) .
(compile_c), :
: (compile_c) r> dup c@ swap 1+ >rc, ;
على الرغم من بساطته ، نكتب أولاً كلمة في الرمز الثنائي ، والتي في حد ذاتها بها معلمات. لذلك ، سأعلق. بعد الدخول (compile_c) ، يقع عنوان المرسل في مكدس المرسل ، حيث إنه ليس مبتذلاً. هذا هو عنوان البايت التالي بعد أمر الاستدعاء. يظهر الموقف في وقت المكالمة أدناه. A0 - رمز أمر الاستدعاء ، XX - معلمة أمر الاستدعاء - عنوان الاتصال (إزاحة) من شفرة البايت للكلمة (compile_c).
NN. . , NN — "(compile_c)", , - . , , -. «r> dup c@ swap 1+ >r». , , ( ) , . «c,» -.
(compile_b) :
: (compile_b) r> dup @ swap 8 + >r compile_b ;
كل شيء هو نفسه هنا ، تتم قراءة المعلمة 64 بت فقط ، ويتم استخدام الكلمة compile_b في ترجمة الكلمة ، التي أنشأناها بالفعل للمترجم.والآن الكلمة تجميع. كما تمت مناقشته بالفعل ، فإنه يقرأ اسم الكلمة ، ويجدها ويجمع أحد الأمرين السابقين. لن أعلق عليها ، لقد قمنا بالفعل بتفكيك جميع المنشآت المستخدمة وتفكيكها.ترجمة كلمة : compile blword over over find dup if dup c@ 128 and if cfa c@ (compile_b) [ blword (compile_c) find cfa , ] c, else cfa (compile_b) [ blword (compile_b) find cfa , ] , then drop drop else drop ." compile: " type ." - not found" then ; immediate
للتحقق من الكلمة التي تم إنشاؤها ، نحن نصنع ، بمساعدتها ، كلمة ifnot. : ifnot ?state compile ?branch8 here 0 c, 111 ; immediate
التحقق من ذلك! $ ./forth ( 0 ): > : test 5 = ifnot 1000 . else 0 . then ; ( 0 ): > 22 test 1000 ( 0 ): > 3 test 1000 ( 0 ): > 5 test 0 ( 0 ): >
كل شيء على ما يرام! وقد حان الوقت للقيام بالدورات ...في هذه المقالة ، سنجعل دورات مع شرط. القلعة لديها خياران لدورة مع شرط.الخيار الأول يبدأ ... حتى. الكلمة حتى تزيل القيمة من المكدس ، وإذا لم تكن مساوية للصفر ، تنتهي الدورة.الخيار الثاني يبدأ ... بينما ... كرر. في هذه الحالة ، يحدث التحقق عند تنفيذ الكلمة أثناء. يتم إنهاء الحلقة إذا كانت القيمة على المكدس تساوي صفرًا.يتم إجراء الدورات على الحصن بنفس الطريقة التي تتم بها الظروف - على التحولات المشروطة وغير المشروطة. أحمل الرمز ، والتعليقات ، كما أعتقد ، ليست ضرورية. : begin ?state here 112 ; immediate : until ?state 112 ?pairs compile ?nbranch8 here - c, ; immediate : while ?state 112 ?pairs compile ?nbranch8 here 0 c, 113 ; immediate : repeat ?state 113 ?pairs swap compile branch8 here - c, dup here swap - swap c! ; immediate
. . , — . leave. .
!
, words. , , .
, , link@. ( ). , : 1, 2, 4 8 . , : .
: link@ dup c@ 3 and swap 1+ swap dup 0= if drop dup 1+ swap c@ else dup 1 = if drop dup 2 + swap w@ else 2 = if drop dup 4 + swap i@ else drop dup 8 + swap @ then then then ;
words:
: words context @ @ 0 begin + dup link@ swap count type tab emit dup 0= until drop drop ;
…
$ ./forth ( 0 ): > words words link@ repeat while until begin ifnot compile (compile_b) (compile_c) ." else then if cmove tab bl decimal hex immediate ; bye ?state ?pairs : str, interpret $compile compile_b compile_n compile_1248 compile_c c, w, i, , allot here h test_bv test_bvc [ ] state .s >in #tib tib . #> #s 60 # hold span holdpoint holdbuf base quit execute cfa find word blword var16 var8 (.") (") count emit expect type lshift rshift invert xor or and >= <= > < = 0> 0< 0= bfind compare syscall fill move rpick r@ r> >r -! +! i! i@ w! w@ c! c@ ! @ depth roll pick over -rot rot swap drop dup abs /mod mod / * - + 1+ 1- exit ?nbranch16 ?nbranch8 ?branch16 ?branch8 branch16 branch8 call8b0 call64 call32 call16 call8f lit64 lit32 lit16 lit8 8 4 3 2 1 0 context definitions current forth ( 0 ): >
ها هي ثروتنا :)أردت أن أقول كل شيء ... لا ، فلنسمح مع ذلك بتحديد ملف مع برنامج حصن للتجميع والتنفيذ كمعلمة.نجعل أوامر syscall لفتح وإغلاق وقراءة الملف. نحدد الثوابت اللازمة لهم. : file_open 0 0 0 2 syscall ; : file_close 0 0 0 0 0 3 syscall ; : file_read 0 0 0 0 syscall ; : file_O_RDONLY 0 ; : file_O_WRONLY 1 ; : file_O_RDWR 3 ;
الآن يمكنك جعل الكلمة البدء _start: : _start 0 pick 1 > if 2 pick file_O_RDONLY 0 file_open dup 0< if .\" error: \" . quit then dup here 32 + 32768 file_read dup 0< if .\" error: \" . quit then swap file_close drop #tib ! here 32 + tib ! 0 >in ! interpret then ;
سيتم تحميل هذه الكلمة من الملف وتنفيذ أي برنامج حصن. بتعبير أدق ، سيقوم المترجم بتنفيذ كل ما سيكون في هذا الملف. وقد يكون هناك ، على سبيل المثال ، مجموعة من الكلمات الجديدة وتنفيذها. تتم الإشارة إلى اسم الملف بواسطة المعلمة الأولى عند بدء التشغيل. لن أخوض في التفاصيل ، لكن معلمات الإطلاق في Linux تنتقل عبر الحزمة. ستصل إليهم الكلمة _start بالأوامر 0 pick (عدد المعلمات) و 2 pick (المؤشر إلى المعلمة الأولى). بالنسبة لنظام الحصن ، تقع هذه القيم خارج الحزمة ، ولكن يمكنك الحصول عليها باستخدام أمر الالتقاط. يقتصر حجم الملف على 32 كيلو بايت ، بينما لا توجد إدارة للذاكرة.الآن يبقى أن نكتب في سطر fcode في النهاية: _start quit
قم بإنشاء ملف test.f واكتب شيئًا ما على الحصن. على سبيل المثال ، الخوارزمية الإقليدية لإيجاد العامل المشترك الأكبر: : NOD begin over over <> while over over > if swap over - swap else over - then repeat drop ; 23101 44425 NOD . bye
نبدأ.
$ ./forth test.f 1777 Bye! $
. , . , bye. , NOD . :-)
هذا كل شيء. , x86-64 :
https://github.com/hal9000cc/forth64GNU GPL v2 v1 — :-)