Tiny Lisp ล่ามจิ๋ว


33

เสียงกระเพื่อมโปรแกรมเมอร์โม้ว่าเสียงกระเพื่อมเป็นภาษาที่มีประสิทธิภาพซึ่งสามารถสร้างขึ้นจากชุดขนาดเล็กมากของการดำเนินงานดั้งเดิม tinylispขอนำความคิดที่ว่าในการปฏิบัติโดยการเล่นกอล์ฟล่ามภาษาที่เรียกว่า

ข้อกำหนดภาษา

ในสเปคนี้เงื่อนไขใด ๆ ที่ผลลัพธ์ถูกอธิบายว่า "ไม่ได้กำหนด" อาจทำอะไรก็ได้ในล่ามของคุณ: ล้มเหลวล้มเหลวอย่างเงียบ ๆ สร้าง gobbldegook แบบสุ่มหรือทำงานตามที่คาดไว้ การดำเนินการอ้างอิงในหลาม 3 สามารถใช้ได้ที่นี่

วากยสัมพันธ์

ราชสกุลใน tinylisp มี(, )หรือสตริงใด ๆ ของหนึ่งหรือพิมพ์ตัวอักษร ASCII ยกเว้นวงเล็บหรือพื้นที่ (เช่น regex ต่อไปนี้: [()]|[^() ]+.) โทเค็นใด ๆ ที่ประกอบด้วยตัวเลขทั้งหมดเป็นตัวอักษรจำนวนเต็ม (ศูนย์ชั้นนำจะถูก.) โทเค็นใด ๆ ที่มีตัวเลขที่ไม่ใช่เป็นสัญลักษณ์แม้ตัวอย่างตัวเลขที่ดูชอบ123abc, และ3.14 -10ช่องว่างทั้งหมด (รวมถึงอย่างน้อยที่สุดตัวอักษร ASCII 32 และ 10) จะถูกละเว้นยกเว้นตราบเท่าที่มันแยกโทเค็น

โปรแกรม tinylisp ประกอบด้วยชุดของการแสดงออก แต่ละนิพจน์เป็นจำนวนเต็มสัญลักษณ์หรือเอส - นิพจน์ (รายการ) รายการประกอบด้วยนิพจน์เป็นศูนย์หรือมากกว่าที่อยู่ในวงเล็บ ไม่มีการใช้ตัวคั่นระหว่างรายการ นี่คือตัวอย่างของการแสดงออก:

4
tinylisp!!
()
(c b a)
(q ((1 2)(3 4)))

นิพจน์ที่ไม่ได้อยู่ในรูปแบบที่ดี (โดยเฉพาะอย่างยิ่งที่มีวงเล็บที่ไม่ตรงกัน) ให้พฤติกรรมที่ไม่ได้กำหนด (การใช้งานการอ้างอิงอัตโนมัติปิด parens ที่เปิดอยู่และหยุดการแยกวิเคราะห์ parens ใกล้เคียงที่ไม่ตรงกัน)

ชนิดข้อมูล

ชนิดข้อมูลของ tinylisp เป็นจำนวนเต็มสัญลักษณ์และรายการ ฟังก์ชั่นและมาโครในตัวสามารถจัดเป็นประเภทได้แม้ว่ารูปแบบเอาต์พุตจะไม่ได้กำหนด รายการสามารถมีค่าจำนวนเท่าใดก็ได้ทุกประเภทและสามารถซ้อนกันได้ลึกโดยพลการ จำนวนเต็มต้องได้รับการสนับสนุนอย่างน้อยตั้งแต่ -2 ^ 31 ถึง 2 ^ 31-1

รายการว่าง - ()ยังเรียกว่าไม่มี - และจำนวนเต็ม0เป็นค่าเดียวที่ถือว่าเป็นเท็จตรรกะ; จำนวนเต็มอื่น ๆ ทั้งหมด, รายการที่ไม่มีข้อ จำกัด , บิลด์อินและสัญลักษณ์ทั้งหมดเป็นจริงตามหลักเหตุผล

การประเมินผล

นิพจน์ในโปรแกรมจะถูกประเมินตามลำดับและผลลัพธ์ของแต่ละรายการที่ส่งไปยัง stdout (เพิ่มเติมเกี่ยวกับการจัดรูปแบบเอาต์พุตในภายหลัง)

  • ตัวอักษรจำนวนเต็มประเมินตัวเอง
  • รายการที่ว่างเปล่า()ประเมินเป็นของตัวเอง
  • รายการหนึ่งรายการขึ้นไปจะประเมินรายการแรกและถือเป็นฟังก์ชั่นหรือมาโครเรียกใช้รายการที่เหลือเป็นอาร์กิวเมนต์ หากรายการนั้นไม่ใช่ฟังก์ชัน / มาโครพฤติกรรมนั้นจะไม่ได้กำหนดไว้
  • สัญลักษณ์ประเมินว่าเป็นชื่อโดยให้ค่ากับชื่อนั้นในฟังก์ชันปัจจุบัน หากไม่ได้กำหนดชื่อไว้ในฟังก์ชั่นปัจจุบันชื่อนั้นจะประเมินเป็นค่าที่เชื่อมโยงกับขอบเขตทั่วโลก หากไม่ได้กำหนดชื่อไว้ที่ขอบเขตปัจจุบันหรือระดับโลกผลลัพธ์จะไม่ได้กำหนดไว้ (การใช้การอ้างอิงจะให้ข้อความแสดงข้อผิดพลาดและส่งคืนค่าศูนย์)

ฟังก์ชั่นและมาโครในตัว

มีฟังก์ชันในตัวเจ็ดรายการใน tinylisp ฟังก์ชั่นประเมินแต่ละข้อโต้แย้งของมันก่อนที่จะใช้การดำเนินการบางอย่างกับพวกเขาและส่งกลับผลลัพธ์

  • c- cons [รายการ truct] รับอาร์กิวเมนต์สองค่าและรายการและส่งคืนรายการใหม่ที่ได้รับโดยการเพิ่มค่าที่ด้านหน้าของรายการ
  • h- หัว ( รถยนต์ในคำศัพท์ Lisp) นำรายการและส่งคืนไอเท็มแรกที่อยู่ในนั้นหรือไม่มีถ้ากำหนดเป็นศูนย์
  • t- tail ( cdrในคำศัพท์ Lisp) รับรายการและส่งคืนรายการใหม่ที่มีทั้งหมดยกเว้นรายการแรกหรือไม่มีถ้ากำหนดเป็นศูนย์
  • s- ลบ รับจำนวนเต็มสองจำนวนและส่งกลับค่าเป็นลบแรก
  • l- น้อยกว่า. ใช้จำนวนเต็มสองจำนวน ผลตอบแทนที่ 1 ถ้าแรกน้อยกว่าที่สองเป็น 0 มิฉะนั้น
  • e- เท่ากัน. รับค่าสองประเภทเดียวกัน (ทั้งจำนวนเต็มทั้งสองรายการหรือสัญลักษณ์ทั้งสอง) ส่งกลับ 1 ถ้าทั้งสองมีค่าเท่ากัน (หรือเหมือนกันในทุกองค์ประกอบ), 0 มิฉะนั้น การทดสอบบิลด์อินเพื่อความเท่าเทียมกันนั้นไม่ได้ถูกกำหนดไว้
  • v- eval ใช้เวลาหนึ่งรายการจำนวนเต็มหรือสัญลักษณ์แสดงถึงการแสดงออกและประเมินมัน เช่นการทำเช่น(v (q (c a b)))เดียวกับการทำ(c a b); จะช่วยให้(v 1)1

"ค่า" ที่นี่รวมถึงรายการจำนวนเต็มสัญลักษณ์หรือบิวด์อินเว้นแต่จะระบุไว้เป็นอย่างอื่น หากฟังก์ชั่นถูกระบุว่ารับชนิดที่เฉพาะเจาะจงการส่งผ่านประเภทที่แตกต่างนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดเช่นเดียวกับการส่งผ่านจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้อง

มีแมโครในตัวสามตัวใน tinylisp แมโครซึ่งแตกต่างจากฟังก์ชั่นไม่ได้ประเมินข้อโต้แย้งของมันก่อนที่จะใช้การดำเนินการกับพวกเขา

  • q- อ้าง รับหนึ่งนิพจน์และส่งคืนโดยไม่ประเมินค่า เช่นการประเมินผล(1 2 3)ให้ข้อผิดพลาดเพราะมันพยายามที่จะเรียก1ว่าเป็นฟังก์ชั่นหรือแมโคร แต่ผลตอบแทนที่รายการ(q (1 2 3)) (1 2 3)การประเมินaจะให้ค่ากับชื่อaแต่(q a)ให้ชื่อเอง
  • i- ถ้า ใช้สามนิพจน์: เงื่อนไขนิพจน์ iftrue และนิพจน์ iffalse ประเมินเงื่อนไขก่อน หากผลลัพธ์นั้นเป็นเท็จ ( 0หรือไม่มี) ให้ประเมินและส่งคืนนิพจน์ iffalse มิฉะนั้นประเมินและส่งคืนนิพจน์ iftrue โปรดทราบว่านิพจน์ที่ไม่ได้ส่งคืนจะไม่ถูกประเมิน
  • d- def ใช้สัญลักษณ์และการแสดงออก ประเมินค่านิพจน์และผูกกับสัญลักษณ์ที่กำหนดซึ่งถือว่าเป็นชื่อที่ขอบเขตทั่วโลกจากนั้นส่งคืนสัญลักษณ์ การพยายามกำหนดชื่อซ้ำควรล้มเหลว (เงียบ ๆ ด้วยข้อความหรือล้มเหลวการใช้การอ้างอิงจะแสดงข้อความแสดงข้อผิดพลาด) หมายเหตุ: มันไม่จำเป็นที่จะต้องพูดชื่อก่อนที่จะผ่านมันไปdแม้ว่ามันจะเป็นสิ่งที่จำเป็นที่จะพูดการแสดงออกว่าเป็นรายการหรือสัญลักษณ์ที่คุณไม่ต้องการประเมิน: (d x (q (1 2 3)))เช่น

การส่งจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้องไปยังแมโครนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ (การใช้งานการอ้างอิงล้มเหลว) การส่งสิ่งที่ไม่ใช่สัญลักษณ์เนื่องจากอาร์กิวเมนต์แรกของdคือพฤติกรรมที่ไม่ได้กำหนด (การใช้การอ้างอิงไม่ได้ให้ข้อผิดพลาด แต่ไม่สามารถอ้างอิงค่าได้ในภายหลัง)

ฟังก์ชันและมาโครที่ผู้ใช้กำหนด

เริ่มต้นจากบิวด์อินทั้งสิบภาษาสามารถขยายได้โดยการสร้างฟังก์ชันและมาโครใหม่ สิ่งเหล่านี้ไม่มีประเภทข้อมูลเฉพาะ มันเป็นเพียงรายการที่มีโครงสร้างที่แน่นอน:

  • ฟังก์ชั่นคือรายการของสองรายการ แรกคือรายการของชื่อพารามิเตอร์หนึ่งชื่อขึ้นไปหรือชื่อเดียวซึ่งจะได้รับรายการของอาร์กิวเมนต์ใด ๆ ที่ส่งผ่านไปยังฟังก์ชัน (ดังนั้นจึงอนุญาตให้ใช้สำหรับฟังก์ชัน ที่สองคือการแสดงออกซึ่งเป็นร่างกายฟังก์ชั่น
  • แมโครเหมือนกับฟังก์ชันยกเว้นว่ามี nil อยู่ข้างหน้าชื่อพารามิเตอร์จึงทำให้เป็นรายการสามรายการ (การพยายามเรียกรายการสามรายการที่ไม่ได้เริ่มต้นด้วยศูนย์คือพฤติกรรมที่ไม่ได้กำหนดการใช้การอ้างอิงจะข้ามอาร์กิวเมนต์แรกและถือเป็นแมโครเช่นกัน)

ตัวอย่างเช่นนิพจน์ต่อไปนี้เป็นฟังก์ชันที่เพิ่มจำนวนเต็มสองจำนวน:

(q               List must be quoted to prevent evaluation
 (
  (x y)          Parameter names
  (s x (s 0 y))  Expression (in infix, x - (0 - y))
 )   
)

และมาโครที่รับจำนวนอาร์กิวเมนต์ใด ๆ และประเมินและส่งกลับค่าแรก:

(q
 (
  ()
  args
  (v (h args))
 )
)

สามารถเรียกใช้ฟังก์ชันและแมโครโดยตรงผูกกับชื่อที่ใช้dและส่งผ่านไปยังฟังก์ชันหรือแมโครอื่น

เนื่องจากฟังก์ชันของฟังก์ชันไม่ได้ถูกเรียกใช้งานในเวลาที่กำหนดฟังก์ชันเรียกซ้ำจึงสามารถนิยามได้ง่าย:

(d len
 (q (
  (list)
  (i list                      If list is nonempty
   (s 1 (s 0 (len (t list))))  1 - (0 - len(tail(list)))
   0                           else 0
  )
 ))
)

อย่างไรก็ตามโปรดทราบว่าข้างต้นไม่ใช่วิธีที่ดีในการกำหนดฟังก์ชันความยาวเนื่องจากไม่ได้ใช้ ...

เรียกซ้ำแบบหางเรียก

การเรียกซ้ำแบบเรียกหางเป็นแนวคิดที่สำคัญใน Lisp ใช้การเรียกซ้ำบางชนิดเป็นลูปดังนั้นจึงทำให้ call stack เล็ก ล่าม tinylisp ของคุณจะต้องใช้การเรียกซ้ำแบบหางเรียกที่เหมาะสม!

  • หากนิพจน์ส่งคืนของฟังก์ชันหรือแมโครที่ผู้ใช้กำหนดเองเป็นการเรียกไปยังฟังก์ชันหรือแมโครที่ผู้ใช้กำหนดเองอื่นล่ามของคุณจะต้องไม่ใช้การสอบถามซ้ำเพื่อประเมินการโทรนั้น แต่จะต้องแทนที่ฟังก์ชั่นปัจจุบันและข้อโต้แย้งด้วยฟังก์ชั่นใหม่และข้อโต้แย้งและห่วงจนกว่าสายการโทรได้รับการแก้ไข
  • ถ้านิพจน์ส่งคืนของฟังก์ชันหรือแมโครที่ผู้ใช้กำหนดเป็นการเรียกใช้iอย่าประเมินสาขาที่เลือกทันที ตรวจสอบว่าเป็นการเรียกไปยังฟังก์ชันหรือแมโครที่ผู้ใช้กำหนดเองแทนหรือไม่ ถ้าเป็นเช่นนั้นให้สลับฟังก์ชั่นและอาร์กิวเมนต์ดังกล่าวข้างต้น iนี้นำไปใช้โดยพลการเกิดขึ้นซ้อนกันอย่างลึกซึ้งของ

การเรียกซ้ำแบบหางจะต้องใช้ทั้งสำหรับการเรียกซ้ำโดยตรง (ฟังก์ชั่นเรียกตัวเอง) และการเรียกซ้ำทางอ้อม (ฟังก์ชันaเรียกใช้ฟังก์ชันbที่เรียกใช้ [ฯลฯ ] ซึ่งเรียกใช้ฟังก์ชันa)

ฟังก์ชันความยาวหางแบบเรียกซ้ำ (พร้อมฟังก์ชันตัวช่วยlen*):

(d len*
 (q (
  (list accum)
  (i list
   (len*
    (t list)
    (s 1 (s 0 accum))
   )
   accum
  )
 ))
)
(d len
 (q (
  (list)
  (len* list 0)
 ))
)

การใช้งานนี้ใช้ได้กับรายการที่มีขนาดใหญ่ตามอำเภอใจโดย จำกัด ด้วยขนาดเต็มจำนวนสูงสุดเท่านั้น

ขอบเขต

พารามิเตอร์ฟังก์ชั่นเป็นตัวแปรท้องถิ่น (จริง ๆ แล้วยังคงอยู่เนื่องจากไม่สามารถแก้ไขได้) พวกเขาอยู่ในขอบเขตในขณะที่เนื้อความของการเรียกฟังก์ชั่นนั้นกำลังถูกดำเนินการและอยู่นอกขอบเขตในระหว่างการโทรที่ลึกและหลังจากที่ฟังก์ชันส่งคืน พวกเขาสามารถ "เงา" ชื่อที่กำหนดไว้ทั่วโลกจึงทำให้ชื่อส่วนกลางไม่สามารถใช้งานได้ชั่วคราว ตัวอย่างเช่นโค้ดต่อไปนี้ส่งคืน 5 ไม่ใช่ 41:

(d x 42)
(d f
 (q (
  (x)
  (s x 1)
 ))
)
(f 6)

อย่างไรก็ตามรหัสต่อไปนี้จะคืนค่า 41 เนื่องจากxที่ระดับการโทร 1 ไม่สามารถเข้าถึงได้จากระดับการโทร 2:

(d x 42)
(d f
 (q (
  (x)
  (g 15)
 ))
)
(d g
 (q (
  (y)
  (s x 1)
 ))
)
(f 6)

ชื่อที่อยู่ในขอบเขต ณ เวลาที่กำหนดคือ 1) ชื่อโลคัลของฟังก์ชันการดำเนินการปัจจุบันถ้ามีและ 2) ชื่อโกลบอล

ข้อกำหนดในการส่ง

อินพุตและเอาต์พุต

ล่ามของคุณอาจอ่านโปรแกรมจาก stdin หรือจากไฟล์ที่ระบุผ่าน stdin หรืออาร์กิวเมนต์บรรทัดคำสั่ง หลังจากประเมินแต่ละนิพจน์แล้วควรส่งผลลัพธ์ของนิพจน์นั้นไปยัง stdout ด้วยการขึ้นบรรทัดใหม่

  • จำนวนเต็มควรถูกส่งออกในการนำเสนอที่เป็นธรรมชาติที่สุดของภาษาที่ใช้งานของคุณ จำนวนเต็มลบสามารถส่งออกได้โดยมีเครื่องหมายลบนำหน้า
  • สัญลักษณ์ควรถูกส่งออกเป็นสตริงโดยไม่มีเครื่องหมายคำพูดหรือ Escape ล้อมรอบ
  • รายการควรจะถูกส่งออกด้วยรายการทั้งหมดที่คั่นด้วยช่องว่างและห่อในวงเล็บ ช่องว่างในวงเล็บเป็นตัวเลือก: (1 2 3)และ( 1 2 3 )เป็นรูปแบบที่ยอมรับได้
  • เอาท์พุทฟังก์ชั่นในตัวและมาโครเป็นพฤติกรรมที่ไม่ได้กำหนด (การตีความอ้างอิงแสดงเป็น<built-in function>.)

อื่น ๆ

ล่ามอ้างอิงรวมถึงสภาพแวดล้อม REPL และความสามารถในการโหลดโมดูล tinylisp จากไฟล์อื่น ๆ ; สิ่งเหล่านี้มีไว้เพื่อความสะดวกและไม่จำเป็นสำหรับความท้าทายนี้

กรณีทดสอบ

กรณีทดสอบจะถูกแบ่งออกเป็นหลายกลุ่มเพื่อให้คุณสามารถทดสอบกรณีที่ง่ายกว่าก่อนที่จะทำงานกับกลุ่มที่ซับซ้อนกว่า อย่างไรก็ตามพวกเขาจะทำงานได้ดีถ้าคุณถ่ายโอนทั้งหมดในไฟล์เดียวเข้าด้วยกัน อย่าลืมที่จะลบส่วนหัวและเอาท์พุทที่คาดไว้ก่อนที่จะเรียกใช้

หากคุณใช้การเรียกซ้ำแบบหางเรียกอย่างถูกต้องกรณีทดสอบสุดท้าย (หลายส่วน) จะกลับมาโดยไม่ทำให้เกิดการล้นสแต็ก การใช้งานอ้างอิงคำนวณในเวลาประมาณหกวินาทีบนแล็ปท็อปของฉัน


"โทเค็นใด ๆ ที่ประกอบด้วยตัวเลขทั้งหมดเป็นตัวอักษรจำนวนเต็ม (ศูนย์นำหน้าไม่เป็นไร) โทเค็นใด ๆ ที่มีตัวเลขไม่ใช่ตัวเลขเป็นสัญลักษณ์แม้แต่ตัวอย่างที่เป็นตัวเลขเช่น 123abc, 3.14 และ -10" ดูเหมือนจะขัดแย้งกัน "จำนวนเต็มต้องได้รับการสนับสนุนอย่างน้อยจาก -2 ^ 31 ถึง 2 ^ 31-1"
msh210

3
@ msh210 ไม่ได้จริงๆเพราะในอดีตมีการพูดคุยเกี่ยวกับโทเค็นในขณะที่หลังมีการพูดคุยเกี่ยวกับค่า แม้ว่าจะไม่มีทางตรงที่จะใส่-1ก็ยังสามารถสร้างมูลค่า -1 (s 0 1)โดยการทำ
DLosc

1
@coredump หลังจากอ่านบทความ Wikipedia ที่เกี่ยวข้องแล้วฉันได้ข้อสรุปว่าการติดตั้งนั้นใกล้เคียงกับไดนามิกมาก แต่ไม่มีขอบเขตซ้อนกัน ตัวแปรในฟังก์ชั่นFไม่สามารถใช้งานได้ในฟังก์ชั่นGถ้าการFโทรG(เช่นเดียวกับการกำหนดขอบเขตแบบไดนามิก) แต่ก็ยังไม่สามารถใช้ได้ในฟังก์ชั่นHหากฟังก์ชั่นHที่ซ้อนกันที่กำหนดไว้ภายในF(เช่นเดียวกับการกำหนดขอบเขตคำศัพท์) - ดูกรณีทดสอบ 5 "อาจทำให้เข้าใจผิด
DLosc

1
หากต้องการกล่าวอีกวิธีหนึ่ง: เนื่องจากไม่มีการซ้อนขอบเขตการใช้งานอาจใช้กลยุทธ์แบบไดนามิกหรือแบบกำหนดขอบเขตศัพท์และสร้างผลลัพธ์ที่เหมือนกัน ชื่อที่อยู่ในขอบเขต ณ เวลาที่กำหนดคือ 1) ชื่อโลคัลของฟังก์ชันการดำเนินการปัจจุบันถ้ามีและ 2) ชื่อโกลบอล ปิดไม่ได้รับการสนับสนุน (การดำเนินการอ้างอิงที่ช่วยให้สแต็คของการผูกชื่อที่สอดคล้องกับสาย stack -. วิธีการแบบไดนามิกสไตล์ซึ่งผมคิดว่าจะเป็นวิธีที่ง่ายที่สุดในการดำเนินการ)
DLosc

1
บังคับxkcd
mınxomaτ

คำตอบ:


11

Python 2 685 675 660 657 646 642 640 ไบต์

import sys,re
E=[]
G=zip("chtsle",[eval("lambda x,y=0:"+f)for f
in"[x]+y (x+[E])[0] x[1:] x-y +(x<y) +(x==y)".split()])
def V(e,L=E):
 while 1:
    try:return e and int("0%s"%e)
    except:A=e[1:]
    if""<e:return dict(G+L).get(e,e)
    f=V(e[0],L)
    if""<f:
     if f in"iv":t=V(A[0],L);e=(e[~bool(t)],t)[f>"u"];continue
     if"e">f:G[:]+=(A[0],V(A[1],L)),
     return A[0]
    if[]>f or f[0]:A=[V(a,L)for a in A]
    if[]>f:return f(*A)
    P,e=f[-2:];L=([(P,A)],zip(P,A))[P<""]
F=lambda x:V<x<""and"(%s)"%" ".join(map(F,x))or"%s"%x
for t in re.sub("([()])"," \\1 ",sys.stdin.read()).split():
 if")"==t:t=E.pop()
 if"("==t:E+=[],
 elif E:E[-1]+=t,
 else:print F(V(t))

อ่านอินพุตจาก STDIN และเขียนเอาต์พุตไปยัง STDOUT

แม้ว่าจะไม่ได้จำเป็นต้องใช้อย่างเคร่งครัดล่ามสนับสนุนฟังก์ชั่น nullary vและแมโครและเพิ่มประสิทธิภาพสายหางดำเนินการผ่าน

คำอธิบาย

วจีวิภาค

ในการแยกวิเคราะห์อินพุตเราจะล้อมรอบการเกิดขึ้นของ(และ)ด้วยช่องว่างก่อนแล้วจึงแยกสตริงผลลัพธ์เป็นคำ สิ่งนี้ทำให้เรามีรายการของโทเค็น เรารักษา stack stack Eซึ่งตอนแรกว่างเปล่า เราสแกนโทเค็นตามลำดับ:

  • หากเราพบ a (เราจะผลักรายการว่างที่ด้านบนของ stack stack;
  • หากเราพบ a )เราจะใส่ค่าที่ด้านบนสุดของสแต็กนิพจน์และผนวกเข้ากับรายการที่อยู่ด้านล่างของสแต็กก่อนหน้านี้
  • มิฉะนั้นเราจะต่อโทเค็นปัจจุบันเป็นสตริงไปยังรายการที่ด้านบนของสแต็กนิพจน์ (เราเก็บจำนวนเต็มเป็นสตริงในขั้นตอนนี้และแยกวิเคราะห์ระหว่างการประเมิน)

หากเมื่อประมวลผลโทเค็นธรรมดาหรือหลังจากที่ป๊อปนิพจน์จากสแต็คเนื่องจาก)นิพจน์สแต็กว่างเปล่าเราอยู่ที่นิพจน์ระดับบนสุดและเราประเมินค่าที่เราได้ผนวกใช้V()และ F()พิมพ์ผลของการจัดรูปแบบที่เหมาะสมโดยใช้

การประเมินผล

เรารักษาขอบเขตทั่วโลกGไว้เป็นรายการคู่คีย์ / ค่า เริ่มแรกมันมีเพียงฟังก์ชั่นบิวอิน (แต่ไม่ใช่มาโครและไม่ใช่vซึ่งเราถือว่าเป็นมาโคร) ซึ่งมีการใช้งานเป็น lambdas

การประเมินผลที่เกิดขึ้นภายในV()ซึ่งจะมีการแสดงออกในการประเมินผลeและขอบเขตในท้องถิ่นLซึ่งเป็นเกินไปรายการคีย์ / คู่ค่า (เมื่อมีการประเมินการแสดงออกระดับบนสุดขอบเขตท้องถิ่นเป็นที่ว่างเปล่า.) The guts ของV()สด ภายในวงวนไม่สิ้นสุดซึ่งเป็นวิธีที่เราดำเนินการปรับให้เหมาะสมแบบหางเรียก (TCO) ดังที่อธิบายไว้ในภายหลัง

เราดำเนินการeตามประเภท:

  • ถ้าเป็นรายการว่างเปล่าหรือสตริงที่สามารถแปลงเป็น int เราจะส่งคืนทันที (อาจเกิดจากการแปลงเป็น int) มิฉะนั้น,

  • หากเป็นสตริงเราจะค้นหาในพจนานุกรมที่สร้างจากการต่อข้อมูลของขอบเขตโกลบอลและโลคัล หากเราพบค่าที่เกี่ยวข้องเราจะส่งคืน มิฉะนั้นeจะต้องมีชื่อของแมโครในตัว (เช่นq, i, dหรือv) และเรากลับไม่เปลี่ยนแปลง มิฉะนั้นถ้าeไม่ใช่สตริง

  • eเป็นรายการ (ไม่ใช่ข้อยกเว้น) เช่นการเรียกใช้ฟังก์ชัน เราประเมินองค์ประกอบแรกของรายการคือการแสดงออกของฟังก์ชั่นโดยการเรียกV()ซ้ำ (ใช้ขอบเขตท้องถิ่นปัจจุบัน); fที่เราเรียกว่าผลที่ตามมา ส่วนที่เหลือของรายการAคือรายการของอาร์กิวเมนต์ fสามารถเป็นสตริงเท่านั้นในกรณีนี้มันเป็นแมโครในตัว (หรือฟังก์ชันv), แลมบ์ดาซึ่งในกรณีนี้มันเป็นฟังก์ชันในตัวหรือรายการในกรณีนี้มันเป็นฟังก์ชั่นที่ผู้ใช้กำหนดหรือมาโคร

    ถ้าfเป็นสตริง, เช่นมาโครในตัว, เราจัดการมันเอง ถ้ามันแมโครiหรือvเราประเมินตัวถูกดำเนินการครั้งแรกแล้วเลือกตัวถูกดำเนินการที่สองหรือสามดังนั้นในกรณีของiหรือใช้ผลของการถูกดำเนินการครั้งแรกในกรณีของv; แทนที่จะประเมินการแสดงออกที่เลือกซ้ำซึ่งจะเอาชนะ TCO เราเพียงแทนที่eด้วยการแสดงออกดังกล่าวและข้ามไปที่จุดเริ่มต้นของวง หากfเป็นมาโครdเราจะต่อท้ายคู่ที่มีองค์ประกอบแรกคือตัวถูกดำเนินการแรกและองค์ประกอบที่สองเป็นผลมาจากการประเมินตัวถูกดำเนินการที่สองไปยังขอบเขตทั่วโลกGและส่งกลับตัวถูกดำเนินการแรก มิฉะนั้นfเป็นแมโครqซึ่งในกรณีนี้เราเพียงคืนค่าตัวถูกดำเนินการโดยตรง

    Othrtwise ถ้าfเป็นแลมบ์ดาหรือรายการที่องค์ประกอบแรกไม่ใช่()มันก็เป็นฟังก์ชั่นที่ไม่เป็นโมฆะไม่ใช่แมโครซึ่งในกรณีนี้เราประเมินอาร์กิวเมนต์ของมันคือองค์ประกอบของAและแทนที่Aด้วยผลลัพธ์

    ถ้าfเป็นแลมบ์ดาเราเรียกมันว่าส่งผ่านข้อโต้แย้งที่ไม่ได้แพ็คออกAและส่งคืนผลลัพธ์

    มิฉะนั้นfเป็นรายการคือฟังก์ชั่นที่ผู้ใช้กำหนดหรือแมโคร; รายการพารามิเตอร์ของมันคือองค์ประกอบที่สองถึงครั้งสุดท้ายและร่างกายของมันเป็นองค์ประกอบสุดท้าย เช่นเดียวกับในกรณีของมาโครiและvเพื่อที่จะดำเนินการกับ TCO เราไม่ได้ประเมินร่างกายแบบวนซ้ำ แต่จะแทนที่eด้วยเนื้อความและทำซ้ำไปเรื่อย ๆ แตกต่างจากiและvอย่างไรก็ตามเรายังแทนที่ขอบเขตท้องถิ่นLด้วยขอบเขตท้องถิ่นใหม่ของฟังก์ชั่น หากรายการพารามิเตอร์P, คือ, ในความเป็นจริงรายการขอบเขตท้องถิ่นใหม่จะถูกสร้างขึ้นโดยการซิปรายการพารามิเตอร์P, พร้อมกับรายการอาร์กิวเมนต์, A; มิฉะนั้นเราจัดการกับฟังก์ชั่น variadic (P, A)ซึ่งในกรณีขอบเขตท้องถิ่นใหม่มีเพียงองค์ประกอบหนึ่งคู่

REPL

หากคุณต้องการเล่นกับมันนี่เป็นล่ามรุ่น REPL สนับสนุนการกำหนดสัญลักษณ์ใหม่และนำเข้าไฟล์ผ่านอาร์กิวเมนต์บรรทัดคำสั่งหรือ(import <filename>)แมโคร หากต้องการออกจากล่ามให้ยุติอินพุต (โดยปกติคือ Ctrl + D หรือ Ctrl + Z)

และนี่คือตัวอย่างเซสชันการใช้การเรียงลำดับการผสาน:


คุณสามารถทำให้บางสิ่งบางอย่างสั้นลงโดยใช้ zlib :) บีบอัดโค้ดของคุณเป็นไบต์แล้วแทนที่ด้วย:import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
Labo

คุณสามารถบันทึกสองไบต์โดยกำหนดให้A[0]กับตัวแปรแบบตัวเดียวหลังจากบล็อก
Hannes Karppila

@ HannesKarppila ถูกต้อง แต่นี่จะทำให้ฟังก์ชั่น nullary (เนื่องจากAไม่มีข้อมูลในกรณีนี้) และฉันไม่ต้องการ "ถดถอย"
Ell

4

C (GNU) 1,095 ไบต์

การกระทำส่วนใหญ่เกิดขึ้นในvฟังก์ชั่นยักษ์ แทนที่จะใช้การเรียกซ้ำแบบหางอย่างชัดเจนvมีการจัดโครงสร้างเพื่อให้การเรียกจำนวนมากจากvถึงvจะได้รับการจัดการโดยการเพิ่มประสิทธิภาพการเรียกซ้ำหางของ gcc ไม่มีการรวบรวมขยะ

สิ่งนี้ทำให้มีการใช้งานส่วนขยาย GCC อย่างหนักดังนั้นจึงสามารถรวบรวมได้ด้วย gcc เท่านั้น (ใช้คำสั่งgcc -w -Os tl.c) นอกจากนี้ยังใช้ส่วนscanfขยายบางอย่างที่ไม่มีใน Windows ซึ่งโดยปกติฉันจะใช้ ความคาดหวังของการเขียนโปรแกรมวิเคราะห์คำด้วยมาตรฐานscanfนั้นแย่มากจนฉันใช้ Linux VM เพื่อทดสอบโปรแกรมแทน การแยกวิเคราะห์โดยไม่มีscanfคลาสของตัวอักษรน่าจะเพิ่ม 100+ ไบต์

#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})
#define P printf
#define F(I,B)({for(I;x->c;x=x->l)B;})
#define Z return
typedef struct o{struct o*n,*l,*c;int i,t;}o;E(o a,o b){Z
a.n?!strcmp(a.n,b.n):a.c?b.c&&E(*a.c,*b.c)&E(*a.l,*b.l):!b.c&a.i==b.i;}p(o*x){x->t?P("%d ",x->i):x->n?P("%s ",x->n):F(P("("),p(x->c);P(")"));}o*x,G,N;*C(o*h,o*t){Z
O(c:h,l:t);}o*v(o*x,o*e){o*W(o*l,o*e){Z
l->c?C(v(l->c,e),W(l->l,e)):&N;}o*y,*a,*f;int t;Z
x->c?y=v(x->c,e),x=x->l,t=y->i,t?9/t?a=v(x->c,e),t>7?(t>8?a->c:a->l)?:a:t>6?v(a,e):t<6?x=v(x->l->c,e),t>4?C(a,x):O(t:1,i:t>3?E(*a,*x):t>2?a->i<x->i:a->i-x->i):v((a-&N&&!a->t|a->i?x:x->l)->l->c,e):(t&1&&d(x->c->n,v(x->l->c,e)),x->c):(y->l->l->l?y=y->l:(x=W(x,e)),a=y->c,v(y->l->c,a->n?O(n:a->n,c:x,l:&G):F(f=&G,(f=O(n:a->c->n,c:x->c,l:f),a=a->l);f))):x->n?e->n?strcmp(x->n,e->n)?v(x,e->l):e->c:e:x;}d(o*n,o*x){*v(O(n:""),&G)=(o){n:n,c:x,l:O()};}*R(h){char*z,*q;Z
scanf(" %m[^ \n()]",&q)>0?h=strtoul(q,&z,10),C(*z?O(n:q):O(t:1,i:h),R()):~getchar()&1?q=R(),C(q,R()):&N;}main(i){for(;++i<12;)d(strndup("slecivthqd"+i-2,1),O(i:i));F(x=R(),p(v(x->c,&G)));}

กึ่ง ungolfed

typedef struct o o;
struct o {
    char* n;
    o* l, //next in this list
     * c; 
    int i,
        t;
} ;



#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})

E(o a, o b) { //tests equality 
    return
        a.n ? !strcmp(a.n,b.n) :
        a.t ? a.i==b.i :
        a.c ? b.c && E(*a.c,*b.c)&E(*a.l,*b.l) :
        !b.c
    ;
}

#define P printf


p(o*x){
    x->t?P("%d ",x->i):x->n?P("%s ",x->n):({for(P("(");x->c;x=x->l)p(x->c);P(")");});
}


o*_,G,N; //N = nil



o*C(o*h,o*t){return O(c:h,l:t);}


/*
        2 3 4 5 6 7 8 9 10 11
        s l e c i v t h d  q
    */


o* v(o* x, o* e) { //takes list, int, or name
    o*W(o* l, o* e) { //eval each item in list
        return l->c ? C(v(l->c ,e), W(l->l, e)) : &N;
    }

    o*y,*a,*f;int t;
    return x->c ? //nonempty list = function/macro call
        y = v(x->c,e), //evals to function/macro
        x = x->l,   //list position of first arg (if it exists)
        (t=y->t)?   //builtin no., if any
             t>9 ?
              t&1 ? x->c // 11 = q
                : //10 = d
                (d(x->c,v(x->l->c,e)),x->c)
           : (a = v(x->c,e), //eval'd first arg
             t)>7 ? // t/h
                (t > 8 ? a->c : a->l) ?: a
           : t>6 ? //v
                v(a,e)
           : (x = x->l, //position of 2nd arg in list
             t)>5 ? //i
                v( (a->n||a->l||a->i|a->t>1 ? x : x->l)->c, e)
           : (x = v(x->c,e), //evaluated 2nd arg
             t)>4 ? // c
                C(a,x)
           : O(t:1,i:
                t>3 ? E(*a,*x) :  //e
                t>2 ? a->i<x->i : //l
                      a->i-x->i   //s
              )
        :
        (
            y->l->l->l ? //whether this is macro
                y = y->l :
                (x = W(x,e)),  //eval args
            a = y->c,  //a = arg list
            //a = a->n ? x=C(x, &N), C(a, &N) : a, //transform variadic style to normal
            v(y->l->c,
               a->n ? //variadic
                O(n:a->n,c:x,l:&G)
              : ({
                   for(f=&G; a->c; a=a->l,x=x->l)
                      f=O(n:a->c->n, c: x->c, l:f);
                   f;
                })
            )
        )
    :
    x->n ? // name
        e->n ?
            strcmp(x->n,e->n) ?
                v(x,e->l)
            : e->c
        : e
     : x; //int or nil
}

d(o*n,o*x){
    * v(O(n:""),&G) =
        (o){n:n->n,c:x,l:O()};
}


;
o*R(){
    char*z,*q;int h;
return scanf(" %m[^ \n()]",&q)>0?
    h=strtoul(q,&z,10),
    C(*z ? O(n:q) : O(t:1,i:h), R())
: getchar()&1?&N:(q=R(),C(q,R()));
}
main(i) {

    for(;++i<12;) d(O(n:strndup("slecivthdq"+i-2,1)),O(t:i));

    o *q;
    for(q=R(); q->c; q=q->l) p(v(q->c,&G));

}

การใช้งานคอมไพล์เอ็กซีคิ้วท์คืออะไร? มันคือ REPL? ใช้ชื่อไฟล์เป็นอินพุตหรือไม่
ckjbgames

@ckjbgames มันอ่านโปรแกรมจาก stdin
feersum

ถูก ฉันคิดว่าคุณควรแก้ไขคำตอบของคุณและทราบว่า
ckjbgames

1

Ceylon, 2422 ไบต์

(ฉันคิดว่านี่เป็นโปรแกรมกอล์ฟที่ยาวที่สุดของฉัน)

import ceylon.language{sh=shared,va=variable,fo=formal,O=Object}import ceylon.language.meta.model{F=Function}interface X{sh fo V v(S t);sh fo G g;}class G(va Map<S,V>m)satisfies X{v(S t)=>m[t]else nV;g=>this;sh S d(S s,V v){assert(!s in m);m=map{s->v,*m};return s;}}V nV=>nothing;class LC(G c,Map<S,V>m)satisfies X{g=>c;v(S t)=>m[t]else g.v(t);}alias C=>V|Co;interface Co{sh fo C st();}interface V{sh fo C l(X c,V[]a);sh default Boolean b=>0<1;sh fo C vO(X c);sh default V vF(X c){va C v=vO(c);while(is Co n=v){v=n.st();}assert(is V r=v);return r;}}class L(sh V*i)satisfies V{vO(X c)=>if(nonempty i)then i[0].vF(c).l(c,i.rest)else this;equals(O o)=>if(is L o)then i==o.i else 1<0;b=>!i.empty;string=>"(``" ".join(i)``)";hash=>i.hash;sh actual C l(X c,V[]p){value[h,ns,x]=i.size<3then[f,i[0],i[1]]else[m,i[1],i[2]];value n=if(is L ns)then[*ns.i.narrow<S>()]else ns;assert(is S|S[]n,is V x);V[]a=h(c,p);LC lC=if(is S n)then LC(c.g,map{n->L(*a)})else LC(c.g,map(zipEntries(n,a)));return object satisfies Co{st()=>x.vO(lC);};}}class S(String n)satisfies V{vO(X c)=>c.v(this);l(X c,V[]a)=>nV;equals(O o)=>if(is S o)then n==o.n else 1<0;hash=>n.hash;string=>n;}class I(sh Integer i)satisfies V{vO(X c)=>this;l(X c,V[]a)=>nV;equals(O o)=>if(is I o)then i==o.i else 1<0;hash=>i;b=>!i.zero;string=>i.string;}V[]f(X c,V[]a)=>[for(v in a)v.vF(c)];V[]m(X c,V[]a)=>a;L c(X c,V h,L t)=>L(h,*t.i);V h(X c,L l)=>l.i[0]else L();V t(X c,L l)=>L(*l.i.rest);I s(X c,I f,I s)=>I(f.i-s.i);I l(X c,I f,I s)=>I(f.i<s.i then 1else 0);I e(X c,V v1,V v2)=>I(v1==v2then 1else 0);C v(X c,V v)=>v.vO(c);V q(X c,V a)=>a;C i(X c,V d,V t,V f)=>d.vF(c).b then t.vO(c)else f.vO(c);S d(X c,S s,V x)=>c.g.d(s,x.vF(c));class B<A>(F<C,A>nat,V[](X,V[])h=f)satisfies V given A satisfies[X,V+]{vO(X c)=>nV;string=>nat.declaration.name;l(X c,V[]a)=>nat.apply(c,*h(c,a));}{<S->V>*}b=>{S("c")->B(`c`),S("h")->B(`h`),S("t")->B(`t`),S("s")->B(`s`),S("l")->B(`l`),S("e")->B(`e`),S("v")->B(`v`),S("q")->B(`q`,m),S("i")->B(`i`,m),S("d")->B(`d`,m)};[V*]p(String inp){value ts=inp.split(" \n()".contains,1<0,1<0);va[[V*]*]s=[];va[V*]l=[];for(t in ts){if(t in" \n"){}else if(t=="("){s=[l,*s];l=[];}else if(t==")"){l=[L(*l.reversed),*(s[0]else[])];s=s.rest;}else if(exists i=parseInteger(t),i>=0){l=[I(i),*l];}else{l=[S(t),*l];}}return l.reversed;}sh void run(){va value u="";while(exists l=process.readLine()){u=u+l+"\n";}V[]e=p(u);X c=G(map(b));for(v in e){print(v.vF(c));}}

ฉันสามารถเล่นกอล์ฟได้มากกว่าหนึ่งไบต์เนื่องจากฉันใช้ตัวระบุสองตัวอักษรในบางที่ แต่ฉันใช้ตัวอักษรเดี่ยวที่มีความหมายค่อนข้างมาก แม้ว่าจะเป็นแบบนี้มันก็ดูไม่เหมือนศรีลังกามาก ...

นี่คือการใช้งานเชิงวัตถุ

เรามีอินเทอร์เฟซที่Vมีค่าพร้อมคลาสการใช้งานL(รายการ - เพียงแค่ wrapper รอบ ๆ ลำดับของ Ceylon V), S(สัญลักษณ์ - wrapper รอบสตริง), I(จำนวนเต็ม - wrapper รอบจำนวนเต็ม Ceylon) และB(builtin function หรือ macro, wrapper รอบ a ฟังก์ชันของศรีลังกา)

ฉันใช้สัญลักษณ์ความเท่าเทียมกัน Ceylon มาตรฐานโดยใช้equalsวิธีการ (และhashคุณลักษณะซึ่งจำเป็นสำหรับสัญลักษณ์) และstringคุณลักษณะมาตรฐานสำหรับผลลัพธ์

เรามีคุณสมบัติบูลีนb(ซึ่งเป็นจริงโดยค่าเริ่มต้นแทนที่ในIและLเพื่อกลับเท็จสำหรับรายการที่ว่างเปล่า) และสองวิธีl(การโทรคือใช้วัตถุนี้เป็นฟังก์ชั่น) และvO(ประเมินหนึ่งขั้นตอน) ทั้งสองส่งกลับค่าหรือวัตถุต่อเนื่องซึ่งช่วยให้การประเมินอีกขั้นตอนหนึ่งและvF(ประเมินเต็มที่) ลูปจนกว่าผลลัพธ์จะไม่ต่อเนื่องอีกต่อไป

อินเตอร์เฟสบริบทอนุญาตให้เข้าถึงตัวแปร มีการนำไปใช้งานสองแบบGสำหรับบริบทโลก (ซึ่งอนุญาตให้เพิ่มตัวแปรโดยใช้dบิวด์อิน) และLCสำหรับบริบทโลคัลซึ่งแอ็คทีฟเมื่อประเมินผลการแสดงออกของฟังก์ชันผู้ใช้

การประเมินผลสัญลักษณ์เข้าถึงบริบทรายการ (ถ้าไม่ว่าง) ประเมินโดยการประเมินองค์ประกอบแรกของพวกเขาก่อนแล้วเรียกวิธีการโทร การเรียกใช้เพียงแค่รายการและบิวอิน - มันจะประเมินอาร์กิวเมนต์ก่อน (ถ้าฟังก์ชั่นไม่ใช่มาโคร) จากนั้นทำสิ่งที่น่าสนใจจริง ๆ - สำหรับบิลด์ในตัวที่ฮาร์ดโค้ดเพียงอย่างเดียวสำหรับรายการที่สร้างบริบทท้องถิ่นใหม่ ต่อเนื่องกับที่

สำหรับบิวด์อินฉันใช้กลอุบายที่คล้ายกับที่ฉันใช้ในShift Interpreterซึ่งช่วยให้ฉันกำหนดประเภทอาร์กิวเมนต์ที่ต้องการ แต่เรียกพวกเขาด้วยลำดับทั่วไปโดยใช้การสะท้อน (ชนิดจะถูกตรวจสอบในเวลาโทร) วิธีนี้ช่วยหลีกเลี่ยงการแปลง / ยืนยันความยุ่งยากภายในฟังก์ชั่น / มาโคร แต่ต้องการฟังก์ชั่นระดับสูงสุดเพื่อให้ได้Functionวัตถุเมตาแบบจำลอง

ฟังก์ชันp(แยกวิเคราะห์) แยกสตริงที่ช่องว่างบรรทัดใหม่และวงเล็บจากนั้นวนซ้ำโทเค็นและสร้างรายการโดยใช้สแต็กและรายการที่กำลังทำงาน

ล่าม (ในrunเมธอดซึ่งเป็นจุดเข้าใช้งาน) จากนั้นใช้รายการของนิพจน์ (ซึ่งเป็นเพียงค่า) ประเมินค่าแต่ละรายการและพิมพ์ผลลัพธ์


ด้านล่างเป็นรุ่นที่มีความคิดเห็นและเรียกใช้ผ่านฟอร์แมตเตอร์

รุ่นก่อนหน้าก่อนที่ฉันจะเริ่มเล่นกอล์ฟ (และยังคงมีความเข้าใจผิดเกี่ยวกับการประเมินรายการ) อยู่ในที่เก็บ Github ของฉันฉันจะใส่อันนี้ไว้ในไม่ช้า (ดังนั้นอย่าลืมดูที่เวอร์ชันแรกหากคุณต้องการต้นฉบับ)

//  Tiny Lisp, tiny interpreter
//
// An interpreter for a tiny subset of Lisp, from which most of the
// rest of the language can be bootstrapped.
//
// Question:   https://codegolf.stackexchange.com/q/62886/2338
// My answer:  https://codegolf.stackexchange.com/a/63352/2338
//
import ceylon.language {
    sh=shared,
    va=variable,
    fo=formal,
    O=Object
}
import ceylon.language.meta.model {
    F=Function
}

// context

interface X {
    sh fo V v(S t);
    sh fo G g;
}
// global (mutable) context, with the buildins 
class G(va Map<S,V> m) satisfies X {
    // get entry throws error on undefined variables. 
    v(S t) => m[t] else nV;
    g => this;
    sh S d(S s, V v) {
        // error when already defined
        assert (!s in m);
        // building a new map is cheaper (code-golf wise) than having a mutable one.
        m = map { s->v, *m };
        return s;
    }
}

// This is simply a shorter way of writing "this is not an allowed operation".
// It will throw an exception when trying to access it.
// nV stands for "no value".
V nV => nothing;

// local context
class LC(G c, Map<S,V> m) satisfies X {
    g => c;
    v(S t) => m[t] else g.v(t);
    // sh actual String string => "[local: ``m``, global: ``g``]";
}

// continuation or value
alias C => V|Co;

// continuation
interface Co {
    sh fo C st();
}

// value
interface V {
    // use this as a function and call with arguments.
    // will not work for all types of stuff.
    sh fo C l(X c, V[] a);
    // check the truthiness. Defaults to true, as
    // only lists and integers can be falsy.
    sh default Boolean b => 0 < 1;
    // evaluate once (return either a value or a continuation).
    // will not work for all kinds of expression.
    sh fo C vO(X c);
    /// evaluate fully
    sh default V vF(X c) {
        va C v = vO(c);
        while (is Co n = v) {
            v = n.st();
        }
        assert (is V r = v);
        return r;
    }
}
class L(sh V* i) satisfies V {

    vO(X c) => if (nonempty i) then i[0].vF(c).l(c, i.rest) else this;
    equals(O o) => if (is L o) then i == o.i else 1 < 0;
    b => !i.empty;
    string => "(``" ".join(i)``)";
    hash => i.hash;

    sh actual C l(X c, V[] p) {
        value [h, ns, x] =
                i.size < 3
                then [f, i[0], i[1]]
                else [m, i[1], i[2]];
        // parameter names – either a single symbol, or a list of symbols.
        // If it is a list, we filter it to throw out any non-symbols.
        // Should throw an error if there are any, but this is harder.
        value n = if (is L ns) then [*ns.i.narrow<S>()] else ns;
        assert (is S|S[] n, is V x);
        V[] a = h(c, p);

        // local context
        LC lC = if (is S n) then
            LC(c.g, map { n -> L(*a) })
        else
            LC(c.g, map(zipEntries(n, a)));
        // return a continuation instead of actually
        // calling it here, to allow stack unwinding.
        return object satisfies Co {
            st() => x.vO(lC);
        };
    }
}

// symbol
class S(String n) satisfies V {
    // evaluate: resolve
    vO(X c) => c.v(this);
    // call is not allowed
    l(X c, V[] a) => nV;
    // equal if name is equal
    equals(O o) => if (is S o) then n == o.n else 1 < 0;
    hash => n.hash;
    string => n;
}

// integer
class I(sh Integer i) satisfies V {

    vO(X c) => this;
    l(X c, V[] a) => nV;
    equals(O o) => if (is I o) then i == o.i else 1 < 0;
    hash => i;
    b => !i.zero;
    string => i.string;
}

// argument handlers for functions or macros
V[] f(X c, V[] a) => [for (v in a) v.vF(c)];
V[] m(X c, V[] a) => a;

// build-in functions
// construct
L c(X c, V h, L t) => L(h, *t.i);
// head
V h(X c, L l) => l.i[0] else L();
// tail
V t(X c, L l) => L(*l.i.rest);
// subtract
I s(X c, I f, I s) => I(f.i - s.i);
// lessThan
I l(X c, I f, I s) => I(f.i < s.i then 1 else 0);
// equal
I e(X c, V v1, V v2) => I(v1 == v2 then 1 else 0);
// eval (returns potentially a continuation)
C v(X c, V v) => v.vO(c);

// build-in macros
// quote
V q(X c, V a) => a;
// if (also returns potentially a continuation)
C i(X c, V d, V t, V f) => d.vF(c).b then t.vO(c) else f.vO(c);
// define symbol in global context
S d(X c, S s, V x) => c.g.d(s, x.vF(c));

// buildin function or macro, made from a native function and an argument handler
class B<A>(F<C,A> nat, V[](X, V[]) h = f)
        satisfies V
        given A satisfies [X, V+] {
    vO(X c) => nV;
    string => nat.declaration.name;
    // this "apply" is a hack which breaks type safety ...
    // but it will be checked at runtime.
    l(X c, V[] a) => nat.apply(c, *h(c, a));
}

// define buildins
{<S->V>*} b => {
    S("c") -> B(`c`),
    S("h") -> B(`h`),
    S("t") -> B(`t`),
    S("s") -> B(`s`),
    S("l") -> B(`l`),
    S("e") -> B(`e`),
    S("v") -> B(`v`),
    S("q") -> B(`q`, m),
    S("i") -> B(`i`, m),
    S("d") -> B(`d`, m)
};

// parses a string into a list of expressions.
[V*] p(String inp) {
    // split string into tokens (retain separators, don't group them –
    // whitespace and empty strings will be sorted out later in the loop)
    value ts = inp.split(" \n()".contains, 1 < 0, 1 < 0);
    // stack of not yet finished nested lists, outer most at bottom
    va [[V*]*] s = [];
    // current list, in reverse order (because appending at the start is shorter)
    va [V*] l = [];
    for (t in ts) {
        if (t in " \n") {
            // do nothing for empty tokens
        } else if (t == "(") {
            // push the current list onto the stack, open a new list.
            s = [l, *s];
            l = [];
        } else if (t == ")") {
            // build a lisp list from the current list,
            // pop the latest list from the stack, append the created lisp list. 
            l = [L(*l.reversed), *(s[0] else [])];
            s = s.rest;
        } else if (exists i = parseInteger(t), i >= 0) {
            // append an integer to the current list.
            l = [I(i), *l];
        } else {
            // append a symbol to the current list.
            l = [S(t), *l];
        }
    }
    return l.reversed;
}

// Runs the interpreter.
// This handles input and output, calls the parser and evaluates the expressions.
sh void run() {
    va value u = "";
    while (exists l = process.readLine()) {
        u = u + l + "\n";
    }
    V[] e = p(u);
    // create global context
    X c = G(map(b));
    // iterate over the expressions, ...
    for (v in e) {
        // print("  '``v``' → ...");
        // ... evaluate each (fully) and print the result.
        print(v.vF(c));
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.