diff --git a/8Bitsy.dsk b/8Bitsy.dsk new file mode 100644 index 0000000..cca970e Binary files /dev/null and b/8Bitsy.dsk differ diff --git a/NeutTower.dsk b/NeutTower.dsk index fbd8c37..d87c1a5 100644 Binary files a/NeutTower.dsk and b/NeutTower.dsk differ diff --git a/asm/asm.fnl b/asm/asm.fnl index 0eb2f6f..46cced1 100644 --- a/asm/asm.fnl +++ b/asm/asm.fnl @@ -115,12 +115,13 @@ ; takes the form [:op args] ; pdat - a parsed dat; takes the form {:type type :addr addr ...} (local dat-parser {}) - (fn new-block [] {:type :block :pdats [] :symbols {} :globals {}}) + (fn new-block [last-symbol] {:type :block :pdats [] :preserved {} :symbols {} :globals {} : last-symbol}) (fn parse-dats [block dats] (each [_ dat (ipairs dats)] (if (= (type dat) "string") - (do (tset block.symbols dat (+ (length block.pdats) 1)) + (do (set block.last-symbol dat) + (tset block.symbols dat (+ (length block.pdats) 1)) (when (= (dat:sub 1 2) "G-") (tset block.globals dat true))) @@ -133,10 +134,15 @@ (if parser (parser dat block) (. opcodes opcode) (dat-parser.op dat) (error (.. "Unrecognized opcode " (fv opcode))))] - (table.insert block.pdats pdat) - (when (and pdat pdat.globals) - (each [name _ (pairs pdat.globals)] - (tset block.globals name (length block.pdats))))))) + (when pdat + (set pdat.nearest-symbol block.last-symbol) + (table.insert block.pdats pdat) + (when pdat.globals + (each [name _ (pairs pdat.globals)] + (tset block.globals name (length block.pdats)))) + (when pdat.preserved + (each [name pdat-preserved (pairs pdat.preserved)] + (tset block.preserved name pdat-preserved))))))) block) (fn dat-parser.op [op] @@ -146,7 +152,7 @@ (fn dat-parser.block [block] (let [dats (lume.clone block)] (table.remove dats 1) - (parse-dats (new-block) dats))) + (parse-dats (new-block block.last-symbol) dats))) (fn dat-parser.db [db] {:type :var :init (. db 2) :size 1}) (fn dat-parser.dw [dw] {:type :var :init (. dw 2) :size 2}) @@ -163,6 +169,13 @@ nil) (fn dat-parser.align [pad] {:type :pad :align (. pad 2)}) + (fn dat-parser.hot-preserve [[_ label & dats] block] + (let [preserve-block (new-block)] + (tset block.preserved label preserve-block) + (tset preserve-block.globals label true) + (parse-dats preserve-block [label]) + (parse-dats preserve-block dats) + preserve-block)) (local pdat-processor { :op {} @@ -174,9 +187,11 @@ }) (fn process-pdat [pdat process default ...] -; (pp pdat) + (fn complain [ok ...] + (if ok (values ...) + (do (error (.. process " failed in " pdat.type " near " (or pdat.nearest-symbol "") " @" (or pdat.addr "") " - " ...))))) (local processor (. pdat-processor pdat.type process)) - (if processor (processor pdat ...) default)) + (if processor (complain (pcall #(processor pdat $...) ...)) default)) (fn pdat-processor.op.patch [op env] (when (and op.mode (= (op.mode:sub 1 4) :addr)) @@ -311,6 +326,7 @@ (or (self:env-lookup name :lookup-addr) (self:parse-addr name))) :pass (fn [self passname] + (print passname) (each [org block (pairs self.org-to-block)] (: self passname org block (if self.prg-base (. self.prg-base.org-to-block org) nil)))) :gather-symbols @@ -332,6 +348,21 @@ (self.dbgfile:close) (set self.dbgfile nil)) self) + :read-hotswap + (fn [self machine] + (let [addr-to-label {} + addr-to-size {}] + (each [_ block (pairs self.org-to-block)] + (each [label pdat (pairs block.preserved)] + (tset addr-to-label pdat.addr label) + (tset addr-to-size pdat.addr pdat.size))) + (collect [addr bytes (pairs (machine:read-batch addr-to-size))] + (values (. addr-to-label addr) bytes)))) + :write-hotswap + (fn [self machine hotswap] + (machine:write-batch + (collect [label bytes (pairs hotswap)] + (values (self:lookup-addr label) bytes)))) :upload (fn [self machine] (if machine.upload (machine:upload self) diff --git a/asm/tape.fnl b/asm/tape.fnl index 2940f25..7a1b3c8 100644 --- a/asm/tape.fnl +++ b/asm/tape.fnl @@ -25,7 +25,7 @@ (table.insert block :nextline) block) (fn prg.dat-parser.basic [lines] - (local block (prg:new-block)) + (local block (prg.new-block)) (each [_ line (ipairs (lume.slice lines 2))] (prg:parse-dats block [(parse-line line)])) (prg:parse-dats block [[:dw 0]]) diff --git a/asm/vm.fnl b/asm/vm.fnl index 12da712..a8ea02f 100644 --- a/asm/vm.fnl +++ b/asm/vm.fnl @@ -38,7 +38,7 @@ (fn install-vm-parser [prg] (fn prg.dat-parser.vm [bytecodes] - (local block (prg:new-block)) + (local block (prg.new-block)) (each [_ bytecode (ipairs (lume.slice bytecodes 2))] (if (= (type bytecode) :number) @@ -385,14 +385,15 @@ (fn vm.var [self name init] (self.code:append name [:jsr :$dovar] - (if (= (type init) :table) init - [:dw init]))) + [:hot-preserve (.. :G-HOT-PRESERVE- name) + (if (= (type init) :table) init + [:dw init])])) (vm:def :$doconst ; usage: [jsr :$doconst] followed by two bytes (vm:reserve) [:pla] [:sta vm.W] [:pla] [:sta vm.WH] - [:ldy 1] [:lda [vm.W] :y] [:sta vm.TOP] - [:iny] [:lda [vm.W] :y] [:sta vm.TOPH]) + [:ldy 1] [:lda [vm.W] :y] [:sta vm.TOP :x] + [:iny] [:lda [vm.W] :y] [:sta vm.TOPH :x]) (fn vm.const [self name val] (self.code:append name [:jsr :$doconst] diff --git a/bitsy/boop.fnl b/bitsy/boop.fnl new file mode 100644 index 0000000..5adb6f5 --- /dev/null +++ b/bitsy/boop.fnl @@ -0,0 +1,73 @@ +(local {: vm} (require :bitsy.defs)) + +(local speaker :0xc030) +(vm:def :blipmem ; count p -- + [:block + [:lda [vm.ST1 :x]] + [:tay] + :sample + [:lda speaker] + [:lda [vm.TOP :x]] + [:inc vm.TOP :x] + [:bne :wait] + [:inc vm.TOPH :x] + :wait + [:clc] [:adc 1] + [:bne :wait] + [:dey] + [:bne :sample]] + (vm:drop) (vm:drop)) + +(vm:def :bliptone ; duration-f1 f2 -- + [:block + [:lda vm.ST1H :x] + [:sta vm.W] + :top + [:lda speaker] + [:ldy vm.ST1 :x] + :wave1 [:dey] [:bne :wave1] + [:lda speaker] + [:lda vm.TOPH :x] + [:ldy vm.TOP :x] [:iny] + :wave2 [:dey] [:bne :wave2] + [:ldy 0xff] + [:sec] [:sbc 1] [:bcs :wave2] + [:dec vm.W] + [:bne :top] + (vm:drop) (vm:drop)]) + +; 0x39a "samples" = 440hz +(local notes {}) +(each [i note (ipairs [:a :a# :b :c :c# :d :d# :e :f :f# :g :g#])] + (tset notes note (- i 1))) +(fn wavelength [note] + (-> 0x39a + (/ (math.pow 1.05946 (. notes note))) + (math.floor))) +(fn octave [wvl oct] + (-> wvl + (/ (math.pow 2 (- oct 3))) + (math.floor))) +(fn parse-note [n] + (values (n:sub 1 -2) (tonumber (n:sub -1)))) +(fn note-wavelength [n] + (local (note oct) (parse-note n)) + (-> (wavelength note) + (octave oct))) +(fn note [n ?duration ?timbre] + (local timbre (or ?timbre 0x20)) + (local duration (or ?duration 0x10)) + (local wvl (note-wavelength n)) + [:vm (bit.bor (bit.lshift duration 8) timbre) (- wvl timbre) :bliptone]) +(fn notes [ns ?duration ?timbre] + (local result [:block]) + (each [_ n (ipairs ns)] + (table.insert result (note n ?duration ?timbre))) + result) + +(vm:word :snd-explode 0x40 :lit :randombytes :blipmem) +(vm:word :snd-dooropen (notes [:c1 :e1] 3)) +(vm:word :snd-doorclose (notes [:e1 :c1] 3)) +(vm:word :snd-teleport (notes [:e4 :d#4 :d4 :g#4] 0x1a 0x50)) + +{: note : notes} diff --git a/bitsy/defs.fnl b/bitsy/defs.fnl new file mode 100644 index 0000000..ef039f3 --- /dev/null +++ b/bitsy/defs.fnl @@ -0,0 +1,182 @@ +(local util (require :lib.util)) +(local {: lo : hi : readjson} util) +(local lume (require :lib.lume)) +(local asm (require :asm.asm)) +(local VM (require :asm.vm)) +(local tiles (require :game.tiles)) +(local files (require :game.files)) +(local Prodos (require :asm.prodos)) +(local actions (require :editor.actions)) + +(local prg (asm.new)) +(local vm (VM.new prg {:org 0xc00})) +(Prodos.install-words vm) + +(local org { + :boot vm.code + :code (prg:org 0x4000) +}) + +(local mapw 20) +(local maph 12) + +(local mon { + :hexout :0xfdda + :putchar :0xfded + :bell :0xff3a +}) + +(local style { + :normal 0x80 + :inverse 0x00 + :flashing 0x40 +}) +(fn str-with-style [s stylebits] + (-> [(string.byte s 1 -1)] + (lume.map #(bit.bor (bit.band $1 0x3f) stylebits)) + (-> (table.unpack) (string.char)))) +(fn achar [c] (bit.bor (string.byte c) style.normal)) +(fn astr [s ?style] (str-with-style s (or ?style style.normal))) + +(fn rot8l [n] ; clears carry + (local block [:block [:clc]]) + (for [_ 1 n] (table.insert block [:block [:asl :a] [:adc 0]])) + block) + +; core graphics words needed for booting +(vm:def :hires + [:sta :0xc050] + [:sta :0xc057] + [:sta :0xc052] + [:sta :0xc054]) + +(vm:def :cleargfx + (vm:push 0x4000) + [:block :page + [:dec vm.TOPH :x] + [:lda 0] + [:block :start + [:sta [vm.TOP :x]] + [:inc vm.TOP :x] + [:bne :start]] + [:lda vm.TOPH :x] + [:cmp 0x20] + [:bne :page]] + (vm:drop)) + +; a handful of debugging words +(vm:def :. + [:lda vm.TOPH :x] + [:jsr mon.hexout] + [:lda vm.TOP :x] + [:jsr mon.hexout] + [:lda (achar " ")] + [:jsr mon.putchar] + (vm:drop)) + +(vm:def :stacklen + (vm:reserve) + [:txa] [:lsr :a] [:sta vm.TOP :x] + [:lda 0] [:sta vm.TOPH :x]) + +(vm:word :.s + :stacklen (prg:parse-addr vm.TOP) :swap + (vm:for :dup :get :. :inc :inc) :drop) + +; input words +(vm:def :last-key ; -- key + (vm:reserve) + [:lda :0xc000] + [:and 0x7f] + [:sta vm.TOP :x] + [:lda 0] + [:sta vm.TOPH :x]) + +(vm:def :read-key ; -- key|0 + [:block + (vm:reserve) + [:lda :0xc000] + [:bmi :key-pressed] + [:lda 0] + [:sta vm.TOP :x] + [:sta vm.TOPH :x] + (vm:ret) + :key-pressed + [:and 0x7f] + [:sta vm.TOP :x] + [:lda 0] + [:sta vm.TOPH :x] + [:sta :0xc010]]) + +; "random" numbers +; this is used only for cosmetic purposes and short noise generation, so we can get away +; with just including a short table of random digits rather than implementing our own +; pseudorandom number generator +(var randombytes "") +(for [i 0 0x40] (set randombytes (.. randombytes (string.char (math.random 0 255))))) +(vm.code:append :randombytes [:bytes randombytes]) +(vm:var :irandom [:db 0]) +(vm:word :rnd + :irandom :bget + :dup 1 :+ 0x3f :& :irandom :bset + :lit :randombytes :+ :bget) + +; 20x12 means full map is 240 bytes - we have an extra 16 bytes at the end for metadata +(fn append-map [map org label] + (org:append + [:align 0x100] label + [:bytes map.map] + [:db (length map.objects)] + [:dw (tiles.encode-yx map.player)] + [:jmp (if (= (or map.tickword "") "") :next map.tickword)] + [:jmp (if (= (or map.moveword "") "") :move-noop map.moveword)] + [:jmp (if (= (or map.loadword "") "") :next map.loadword)])) + +(vm.code:append :map-ptr [:db 0] [:hot-preserve :map-page [:db 0]]) +(vm:word :map :lit :map-ptr :get) +(vm:word :entity-count :map 240 :+ :bget) +(vm:word :map-player-yx-ptr 241 :+) +(vm:word :map-player-yx :map :map-player-yx-ptr :get) +(vm:word :map-specific-tick :map 243 :+ :execute) +(vm:word :map-specific-move :map 246 :+ :execute) +(vm:word :map-specific-load :map 249 :+ :execute) + +(fn generate-entity-code [level vm prefix] + (each [ientity entity (ipairs level.objects)] + (when (not entity.advanced) + (let [code []] + (each [iaction action (ipairs (or entity.steps []))] + (if action.condition (lume.push code (.. :cond- action.condition) (vm:when (actions.generate action vm iaction))) + (lume.push code (actions.generate action vm iaction)))) + (vm:word (.. prefix ientity) :drop (table.unpack code)))))) + +(fn deflevel [ilevel label] + (local level prg) ; todo: (asm.new prg) - if we want to load levels as an overlay + (local org level.vm.code) ; (level:org org.level.org) - if we want to give level data a stable loxation + (local map (. files.game.levels ilevel)) + (local entity (require :bitsy.entity)) + (append-map map org label) + (entity.append-from-map map org label) + (set level.vm.code org) + (generate-entity-code map level.vm (.. label "-entity-word-")) + level) + +(fn say-runon [portrait ...] + (local result [:vm (.. :draw-portrait- portrait)]) + (local lines [...]) + (local ilineOffset (if (< (length lines) 4) 1 0)) + (each [iline line (ipairs lines)] + (table.insert result [:vm (vm:str line) (.. :draw-text (+ iline ilineOffset))])) + result) + +(fn say [portrait ...] + (local result (say-runon portrait ...)) + (table.insert result :dismiss-dialog) + result) + +(fn itile [label] (tiles.find-itile files.game.tiles label)) + +(set vm.code org.code) + +{: vm : prg : mapw : maph : mon : org : achar : astr : style : rot8l : deflevel : say : say-runon : itile} + diff --git a/bitsy/disk.fnl b/bitsy/disk.fnl new file mode 100644 index 0000000..9d0a96f --- /dev/null +++ b/bitsy/disk.fnl @@ -0,0 +1,88 @@ +(local asm (require :asm.asm)) +(local VM (require :asm.vm)) +(local Prodos (require :asm.prodos)) +(local util (require :lib.util)) +(local {: lo : hi} util) +(local {: org} (require :bitsy.defs)) + +(fn append-boot-loader [prg] + (local vm prg.vm) + (set vm.code org.boot) + (set prg.files []) + + (vm:word :loadfile ; length addr filename -- + 0xbb00 :open :read :drop :close) + (vm:word :loadscreen :cleargfx 0x2000 0x2000 :r 0 :entity-count + (vm:while [:dup] :dec ; entity|0 i + :dup :lookup-entity :get :rtop := + (vm:when :lookup-entity :swap) + ) :drop :rdrop) +(vm:var :responder 0) +(vm:word :get-responder :responder :get) +(vm:word :entity-itile :get :itile-at) +(vm:word :responder-itile :get-responder :entity-itile) +(vm:word :entity>do ; entity ev -- + :over :responder :dup :get :>r :set + :swap 2 :+ :get :execute + :r> :responder :set) +(vm:word :link-arg ; e -- a + 6 :+ :get) +(vm:word :linked-entity :get-responder :dup 4 :+ :get :dup (vm:if [:execute] [:drop :link-arg])) +(vm:word :entity-at>do ; yx ev -- f + :>r :entity-at :dup (vm:if [:r> :entity>do vm.true] [:rdrop])) +(vm:word :touch-entity ; yx -- f + ev.touch :entity-at>do) +(vm:word :untouch-entity ; yx -- + ev.untouch :entity-at>do :drop) + +(vm:word :entity-around>do ; yx ev -- + :over 0x0100 :yx+ :over :entity-at>do :drop + :over 0x0001 :yx+ :over :entity-at>do :drop + :over 0xff00 :yx+ :over :entity-at>do :drop + :swap 0x00ff :yx+ :swap :entity-at>do :drop) + +(vm:word :set-entitytile ; e itile -- + :swap :get :swap :update-itile) + +(vm:word :set-respondertile ; itile -- + :get-responder :get :swap :update-itile) + +; run only when processing an ev.touch event +(vm:word :transparent-entity-move ; -- f + :get-responder :get :dup :handle-general-move + :swap :over :not (vm:if [:move-player-to] [:drop])) + +(vm:var :pre-handled-tile 0) +(vm:var :pre-handled-ev 0) +(vm:word :handle-onoff ; ev off on -- + :responder-itile :pre-handled-tile :set :do + ] [:drop])) + +(vm:word :move-to-responder :get-responder :get :move-player-to) +(vm:word :disappear :get-responder 0 :set-entitytile 0xffff :get-responder :set) + +(fn append-from-map [map entity-org prefix] + (entity-org:append [:align 0x100]) + (each [ientity entity (ipairs map.objects)] + (when entity.name + (entity-org:append entity.name)) + (entity-org:append + (.. prefix "-entity-" ientity) + [:db (- entity.x 1)] [:db (- entity.y 1)] + [:ref (if entity.advanced entity.func (.. prefix "-entity-word-" ientity))] + (if (and entity.advanced entity.linkword (> (length entity.linkword) 0)) [:ref entity.linkword] [:dw 0]) + (if entity.link [:ref (.. prefix "-entity-" entity.link)] + (and entity.advanced entity.linkentity) [:ref entity.linkentity] + [:dw 0])))) + +{: ev : append-from-map} + diff --git a/game/footer.fnl b/bitsy/footer.fnl similarity index 97% rename from game/footer.fnl rename to bitsy/footer.fnl index 8dbae7d..8477e2f 100644 --- a/game/footer.fnl +++ b/bitsy/footer.fnl @@ -1,4 +1,4 @@ -(local {: vm : org} (require :game.defs)) +(local {: vm : org} (require :bitsy.defs)) (local {: hi : lo} (require :lib.util)) (vm:def :draw-pchar ; pscreen pchar -- diff --git a/bitsy/game.json b/bitsy/game.json new file mode 100644 index 0000000..295a90d --- /dev/null +++ b/bitsy/game.json @@ -0,0 +1 @@ +{"tiles":[{"gfx":"8080808080808080808080808080808080808080808080808080808080808080","word":"","label":"","flags":{"walkable":true}},{"gfx":"8080C0C0C0C0E0F0F8FCE6E6E0B0B0B0808183838383878F9FBFE7E7868C8C8C","word":"","label":"player-frame1","flags":[]},{"gfx":"8080808084CCFCFCFCFCFCF8F0B0B0B080808080E1E1E1F1B99F9F8F8F8C8C8C","word":"","label":"","flags":[]},{"gfx":"D5D55555D5D55555D5D55555D5D555552A2AAAAA2A2AAAAA2A2AAAAA2A2AAAAA","word":"","label":"","flags":[]},{"gfx":"8080D4D0D0D0D4D45454D4D4D4D0808080808A8282828A8A8A0A0A8A8A828080","word":"pot","label":"","flags":[]},{"gfx":"8080808080D490948484A48494D080808080808080AA88A8A0A5A5A0A88A8080","word":"","label":"","flags":[]},{"gfx":"808080F8F89898989898989E9F9E80808080809F9F9898989898989E9F9E8080","word":"","label":"","flags":[]},{"gfx":"000000A0908884827E0A0A0A0000000000000000201008040785858500000000","word":"","label":"","flags":[]},{"gfx":"000014040414500000000000A888A8800000282020280A010101010195919580","word":"","label":"","flags":[]},{"gfx":"00008C92921C60105010781C0E070300000098A4A41C030504050F1C38706000","word":"","label":"","flags":[]},{"gfx":"000000004040000000D4D4ECECECD480000000000202010101AAAAB6B6B6AA80","word":"","label":"","flags":[]},{"gfx":"0000000000004828282828482800000000000000000004050505050405000000","word":"","label":"","flags":[]},{"flags":{"walkable":true},"word":"","label":"","gfx":"000000002020202020000A080808000000000000000000415111111111110100"},{"flags":[],"word":"","label":"","gfx":"00002028282A2A2A2820A0A0A0A0A08000000515155555151505858585858580"},{"flags":[],"word":"","label":"","gfx":"8080808080F8FCFC9C9CBCFEE6E6E68080808287878780818784848484848480"},{"flags":[],"word":"","label":"","gfx":"C0C0D0D0D4D4D4D5D5D080C0D0D4D4D08282828080828AAAAA8A8A8A82808282"},{"flags":{},"word":"","label":"stickbob","gfx":"E090B090A0C0A8D0D0A8C0E0B0988080838486848281958A8A958183868C8080"},{"flags":[],"word":"","label":"","gfx":"8080808080A0A8A8A0A0A0A8A8A8A08081818181818594948585859594948580"},{"flags":[],"word":"","label":"","gfx":"008AA88A000020282A0A0A0A2A28000000151115140505010000101014150500"},{"flags":[],"word":"","label":"","gfx":"000000002028381828282020202028000000000005151D191515040404041400"}],"levels":[{"loadword":"","map":"000000000000000000000000000000000000000000606060606060606060606060606060606060000060000000000000000000000000000000006000006000000000000000000000000000000000600000600000A00000000000000000004000000060600060000000000000000000000000000000000000006000000000000000000000000000000000606000600000000000000000000000000000000060000060000000000000000080808080800000006000006000000000000000000000000000000000600000606060606060606060606060606060606060000000000000000000000000000000000000000000","player":{"y":4,"x":5},"tickword":"","moveword":"","objects":[{"x":15,"func":"cat","linkentity":"","y":8,"linkword":"","name":"","steps":[{"action":"say","character":"cat","lines":["I'm a cat.","","",""]},{"lines":["","Nice kitty!","",""],"action":"say","character":"player"}]},{"x":5,"y":8,"linkentity":"","func":"fish","name":"","linkword":"","steps":[{"lines":["","","Grr! Go away!",""],"action":"say","character":"angryfish"}]},{"x":13,"y":4,"linkentity":"","func":"pot","linkword":"","name":"","steps":[{"character":"player","lines":["","Just another empty pot.","",""],"action":"say"}]},{"x":12,"y":4,"linkentity":"","func":"suspiciouspot","linkword":"","name":"","steps":[{"lines":["","","",""],"action":"disappear","character":"player"},{"lines":["","Whoops, I broke it!","",""],"action":"say","character":"player"}]},{"x":11,"y":4,"linkentity":"","func":"pot","name":"","linkword":"","steps":[{"character":"player","lines":["","It's a plain old empty pot.","",""],"action":"say"}]},{"x":14,"y":4,"linkentity":"","func":"pot","name":"","linkword":"","steps":[{"character":"player","lines":["","Nothing special about these pots.","",""],"action":"say"}]},{"x":15,"y":4,"linkentity":"","func":"pot","name":"","linkword":"","steps":[{"character":"player","lines":["","This one has a million dollars","inside!!",""],"action":"say"},{"character":"player","lines":["","Oh, no, wait. That was just","a dust bunny.",""],"action":"say"},{"character":"player","lines":["","There's really nothing special","about this pot.",""],"action":"say"},{"character":"player","lines":["","Probably.","",""],"action":"say"},{"action":"say","character":"snake","lines":["","There's a snake though.","",""],"condition":"has glasses"},{"action":"say","character":"glasses","lines":["","aaack!","",""],"condition":"has glasses"}]},{"func":"","x":20,"y":7,"steps":[{"position":1537,"character":"player","lines":["","","",""],"action":"warp","map":"map2"}]}]},{"loadword":"","objects":[{"func":"","x":1,"y":7,"steps":[{"position":1554,"character":"player","lines":["","","",""],"action":"warp","map":"map1"}]},{"func":"","x":18,"y":2,"steps":[{"lines":["","Neat, it's a pair of 3D glasses!","I wonder what I can see with","these..."],"action":"say","character":"player"},{"lines":["","","",""],"action":"disappear","character":"player"},{"flag":"has glasses","action":"set-flag","lines":["","","",""],"rhs":{"label":"","value":65535},"character":"player"}]},{"func":"","x":10,"y":7,"steps":[{"character":"old man","lines":["","I am but a weary old man,","living alone in these woods.",""],"action":"say"},{"action":"say","character":"player","lines":["","Cool story. Any words of wisdom","for me?",""]},{"action":"say","character":"old man","lines":["","Not really.","",""]},{"condition":"has glasses","character":"glasses","lines":["Holy cow!!","","This \"old man\" is actually","an alien!!"],"action":"say"}]},{"func":"","x":20,"y":7,"steps":[{"position":1537,"character":"player","lines":["","","",""],"action":"warp","map":"map3"}]},{"func":"","x":10,"y":1,"steps":[{"position":2057,"character":"player","action":"warp","lines":["","","",""],"map":"map4"}]},{"func":"","x":16,"y":10,"steps":[{"lines":["","Would you be my friend?","",""],"action":"say","character":"charismatic frog"},{"lines":["","OF COURSE!","",""],"action":"say","character":"player"}]}],"tickword":"","moveword":"","map":"A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1000000A1A1A1A10000818181818181A1A1A10000000000A1A1A1000000818181628181A1A1A1000000000000A1A100000000818181818100A160600000000000000000000000000081000000A1000000000000000000C10000000000000000000060600000000000000000000000000000000000A1A1000081818100000000000000A1A100000000A1A10081818181810000000000A1A1A1A1000000A1A100818181818100000000A1A1A1A1A1A10000A1A10000008181000000000000A1A1A1A1A1E0A1A1A1A1A1A1A1A1A1A1A100A1A1A1A1A1A1A1A1A1A1"},{"loadword":"","map":"A1A1A1A1A1818181818181E1E181818181818181A1A1A1A18181818181818181E181818181818181A1A181818142810000008181E1E1818181000000A1000081818100000000818181E1E18181000000A100000000000000000000818181E181818100000000000000000000000000008181E18181810000A181810000000000000000000000E10062000000A181818181000000A10000818181E18181000000A1818181810000000000008181E1E18181000000A1A181818100000000818181E1E1818181818181A1A1A1818181000081818181E181818181818181A1A1A1A18181818181818181E181818181818181","tickword":"","moveword":"","objects":[{"func":"","x":1,"y":7,"steps":[{"position":1554,"character":"player","lines":["","","",""],"action":"warp","map":"map2"}]},{"func":"","x":9,"y":5,"steps":[{"action":"say","character":"player","lines":["","This wood would be perfect to","build a bridge with!",""]},{"action":"say","character":"player","lines":["","Haha, \"wood would\". Wood would","wood would.",""]},{"action":"say","character":"player","lines":["","Hey, I bet I could cut down this","tree with my cool time scissors!",""],"condition":"has scissors"},{"action":"disappear","character":"player","lines":["","","",""],"condition":"has scissors"},{"action":"say","character":"player","lines":["","Haha, awesome.","",""],"condition":"has scissors"},{"condition":"has scissors","flag":"has wood","character":"player","lines":["","","",""],"rhs":{"label":"","value":65535},"action":"set-flag"}]},{"func":"","x":15,"y":6,"steps":[{"action":"say","character":"player","lines":["","If only there was some convenient","way to cross this ankle-deep","stream!"]},{"condition":"has wood","character":"player","lines":["","Guess I'll just have to build a","raft and sail it across.",""],"action":"say"},{"condition":"has wood","character":"player","lines":["","","",""],"action":"disappear"}]},{"func":"","x":6,"y":10,"steps":[{"character":"snake","lines":["","Badger badger badger badger","badger badger badger badger","badger badger badger badger"],"action":"say"},{"character":"player","lines":["","A snake! A snake!","",""],"action":"say"}]},{"func":"","x":17,"y":6,"steps":[{"lines":["","Ribbit.","",""],"action":"say","character":"charismatic frog"},{"lines":["","I thought you'd be more","charismatic.",""],"action":"say","character":"player"}]}]},{"loadword":"","map":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000A1A1A1A1A1A1600060A1A1A1A1A1A10000000000606060606060600060606060606060000000000060C00000000000000000000000C060000000000060000000000000000000000000006000000000006000000000000002000000000000600000000000600000000000000000000000000060000000000060C00000210000000000220000C060000000000060606060606060606060606060606000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","tickword":"","moveword":"","objects":[{"func":"","x":10,"y":6,"steps":[{"lines":["","Would you like to hear a jig","and a reel?",""],"action":"say","character":"stick bob"},{"lines":["","Do you play the theremin?","",""],"action":"say","character":"player"},{"lines":["","I can play anything!","",""],"action":"say","character":"stick bob"}]},{"func":"","x":10,"y":10,"steps":[{"position":265,"character":"player","action":"warp","lines":["","","",""],"map":"map2"}]},{"func":"","x":13,"y":4,"steps":[{"lines":["","It's a fiddle. I promise.","I worked hard to draw it.",""],"action":"say","character":"player"}]},{"func":"","x":7,"y":4,"steps":[{"lines":["","Scissors with a clock on them?","They're super sharp!",""],"action":"say","character":"player"},{"lines":["","I'm sure they'll come in handy!","",""],"action":"say","character":"player"},{"flag":"has scissors","character":"player","lines":["","","",""],"rhs":{"label":"","value":65535},"action":"set-flag"},{"lines":["","","",""],"action":"disappear","character":"player"}]}]}],"flags":["has wood","has scissors","","has glasses"],"portraits":[{"gfx":"000000000000000000000000000000000000007C7C7E7E7E7E7E7E7E7E7E7E7E00000000707F7F7F7F7F7F7F7F7F7F7F7E7E7E7E7F7F7F7F7F7F7F7F7F7F7F7F0000000F0F1F1F1F1F1F1F1F1F1F1F1F000000000000000000000000000000001F1F1F1F7F7F7F7F7F7F7F7F7F7F7F7F00000000077F7F7F7F7F7F7F7F7F7F7F","label":"player","flags":[]},{"gfx":"00000000004040606060707070707070000000000000010103037F7F7F7F7F7F707060604000000000000000000000007F7F7F7F7F7F7800004040406060606000000000001018383C3C7F7F7F7F7F7F000000000000000000000000000000007F7F7F3F0F67717C7F7F7F7F7F7F7F7F00007C7E7F7F7F7F7F7F7F7F7F7F7F7F","label":"cat","flags":[]},{"gfx":"80808080808080808080D5818181858480808080808080808080AA80808080808484848484A4848484A48484848494D0809090D0D4F5D5D5D5D5D480808080AA80808080808080808080D5808080808080808080808080808080AAA0A0A0A888808080808AA8AAAAAA828080808080D588888888888889898888888888888A82","label":"angryfish","flags":[]},{"flags":[],"label":"old man","gfx":"0000000000000000000000000000000000000000000000707C7872737373736300000000303F3F3F7F7F7F7F7F7F7F7F0F7F7F7F7E7E7E7E7C7C79797979797300000000000000073F3F7F7F7F7F7F60000000000000000000000000000000000E7F3F3F3F3F7F7F7F7F7F7F7F3F1F1F00000000000000000004040C0C1C1E1F"},{"flags":[],"label":"stick bob","gfx":"808080808080808080A0A8E8E8F0F0F08080808080C0D4D5FDFFFFF3F1F1FFFFF0F0E0E0C08080808080808080808080FFFFF983FFBFBEFCF4D4D4D4D4D4D4D480808080808AAAABAFBFFFE7C7C7FFFF80808080808080808185858583838383FFFFCFE0FFFEBE8F8B8A8A8A8A8A8A8A83838383818080808080808080808080"},{"flags":[],"label":"snake","gfx":"000000000000808080808AA88A8080000000000000000014557155455054545000000020202020202020000000000000505455150505050505155554545040000000000000000000000202020202020200000000000000000000000000000000002020202000000000000002022A2A2A00000001010505051514141414150505"},{"flags":[],"label":"glasses","gfx":"000000000000000000000000000000000000007C7C7E7E7E7E555555757E7E7E00000000707F7F7F7F7F7F7F7F7F7F7F7E7E7E7E7F7F7F7F7F7F7F7F7F7F7F7F0000000F0F1F1F1F9FAAAAAAAB1F1F1F000000000000000000000000000000001F1F1F1F7F7F7F7F7F7F7F7F7F7F7F7F00000000077F7F7F7F7F7F7F7F7F7F7F"},{"flags":[],"label":"charismatic frog","gfx":"00000000000020202020202020202000000000005455555F47475555050055550000000000000000002820282028000054040404040404040405050505010000000000002A2A2A7A62622A2A2A002A2A000000000001050505050505050405012A20202020202020202020202000000000000000000000000015051505150000"}],"font":[{"flags":[],"gfx":"0000000000000000"},{"flags":[],"gfx":"081C1C1C08000800"},{"flags":[],"gfx":"3636241200000000"},{"flags":[],"gfx":"123F1212123F1200"},{"flags":[],"gfx":"083C0A1C281E0800"},{"flags":[],"gfx":"0026160834320000"},{"flags":[],"gfx":"0E1B1B062F1B3600"},{"flags":[],"gfx":"0C0C080400000000"},{"flags":[],"gfx":"180C0606060C1800"},{"flags":[],"gfx":"0C18303030180C00"},{"flags":[],"gfx":"082A1C081C2A0800"},{"flags":[],"gfx":"000C0C3F3F0C0C00"},{"flags":[],"gfx":"000000000C0C0804"},{"flags":[],"gfx":"0000001E1E000000"},{"flags":[],"gfx":"00000000000C0C00"},{"flags":[],"gfx":"002030180C060200"},{"flags":[],"gfx":"1C26263E26261C00"},{"flags":[],"gfx":"181C181818183C00"},{"flags":[],"gfx":"1C2620180C063E00"},{"flags":[],"gfx":"1C26201820261C00"},{"flags":[],"gfx":"2626263C30303000"},{"flags":[],"gfx":"3E061E2020201E00"},{"flags":[],"gfx":"1C26061E26261C00"},{"flags":[],"gfx":"3E2630180C0C0C00"},{"flags":[],"gfx":"1C26261C26261C00"},{"flags":[],"gfx":"1C26263C20201C00"},{"flags":[],"gfx":"000C0C000C0C0000"},{"flags":[],"gfx":"000C0C000C0C0804"},{"flags":[],"gfx":"30180C060C183000"},{"flags":[],"gfx":"00003E003E000000"},{"flags":[],"gfx":"060C1830180C0600"},{"flags":[],"gfx":"1C3630180C000C00"},{"flags":[],"gfx":"1E33212D3D011E00"},{"flags":[],"gfx":"1C3E26263E262600"},{"flags":[],"gfx":"1E26261E26261E00"},{"flags":[],"gfx":"1C26060606261C00"},{"flags":[],"gfx":"1E26262626261E00"},{"flags":[],"gfx":"3E06061E06063E00"},{"flags":[],"gfx":"3E06061E06060600"},{"flags":[],"gfx":"1C26063626263C00"},{"flags":[],"gfx":"2626263E26262600"},{"flags":[],"gfx":"3C18181818183C00"},{"flags":[],"gfx":"3C30303036361C00"},{"flags":[],"gfx":"2626261E26262600"},{"flags":[],"gfx":"0606060606063E00"},{"flags":[],"gfx":"373F2B2B23232300"},{"flags":[],"gfx":"26262E3626262600"},{"flags":[],"gfx":"1C26262626261C00"},{"flags":[],"gfx":"1E26261E06060600"},{"flags":[],"gfx":"1C26262626363C00"},{"flags":[],"gfx":"1E26261E26262600"},{"flags":[],"gfx":"3C26061C20221E00"},{"flags":[],"gfx":"3F0C0C0C0C0C0C00"},{"flags":[],"gfx":"2626262626261C00"},{"flags":[],"gfx":"2626261C1C1C0800"},{"flags":[],"gfx":"2323232B2B2A3600"},{"flags":[],"gfx":"2626261C26262600"},{"flags":[],"gfx":"2626261C18181800"},{"flags":[],"gfx":"3E3E30180C063E00"},{"flags":[],"gfx":"1C0C0C0C0C0C1C00"},{"flags":[],"gfx":"0002060C18302000"},{"flags":[],"gfx":"1C18181818181C00"},{"flags":[],"gfx":"081C260000000000"},{"flags":[],"gfx":"0000000000003E00"},{"flags":[],"gfx":"0C0C100000000000"}]} \ No newline at end of file diff --git a/bitsy/gfx.fnl b/bitsy/gfx.fnl new file mode 100644 index 0000000..00b2411 --- /dev/null +++ b/bitsy/gfx.fnl @@ -0,0 +1,127 @@ +(local {: lo : hi} (require :lib.util)) +(local {: vm : mapw : maph : org} (require :bitsy.defs)) + +; Graphics routines +(vm:def :mixed [:sta :0xc053]) +(vm:def :textmode [:sta :0xc051]) +(vm:def :page1 [:sta :0xc054]) +(vm:def :page2 [:sta :0xc055]) + +; starting address: +; 0x2000 + (x*2) + (y%4 * 0x100) + ((y/4) * 0x28) +; x between 0-19 +; y between 0-12 +; yx - 16-bit value, low byte x, high byte y +(vm.code:append :screeny-lookup [:bytes "\0\040\080"]) +(vm:def :yx>screen ; yx -- p + [:lda vm.TOPH :x] ; a=y + [:lsr :a] [:lsr :a] ; a=y/4 + [:tay] ; y=y/4 + [:lda 0x03] + [:and vm.TOPH :x] ; a=y%4 + [:ora 0x20] ; a=0x20 + y%4 + [:sta vm.TOPH :x] ; high byte is set (and y is wiped) + [:lda vm.TOP :x] ; a=x + [:asl :a] ; a = x*2 + [:clc] + [:adc :screeny-lookup :y] ; a=x*2 + (y/4)*0x28 + [:sta vm.TOP :x] ; low byte is set +) + +; note: the graphical tile data must not cross a page boundary +; (this happens automatically because each tile is 32 bytes and we +; start them on a page; this lets lookup-tile be fast) +(fn draw-block [] + [:block + [:clc] + [:ldy 8] + :loop + [:lda [vm.TOP :x]] + [:sta [vm.ST1 :x]] + [:inc vm.TOP :x] + [:lda vm.ST1H :x] + [:adc 4] + [:sta vm.ST1H :x] + [:dey] + [:bne :loop]]) + +(fn draw-vertical-block [] + [:block + (draw-block) + [:lda vm.ST1H :x] + [:sbc 31] ; with carry clear this is 32 + [:sta vm.ST1H :x] + [:lda vm.ST1 :x] + [:ora 0x80] + [:sta vm.ST1 :x] + (draw-block)]) + +(vm:def :drawtile ; p gfx -- + (draw-vertical-block) + [:lda vm.ST1H :x] + [:sbc 31] + [:sta vm.ST1H :x] + [:lda vm.ST1 :x] + [:sbc 0x7f] + [:sta vm.ST1 :x] + (draw-vertical-block) + (vm:drop) (vm:drop)) + +(vm:def :clearline ; pscreen -- + [:lda vm.TOP :x] [:sta vm.W] + [:lda vm.TOPH :x] [:sta vm.WH] + (vm:drop) + [:block + :row + [:ldy 0x27] [:lda 0] + :start + [:sta [vm.W] :y] + [:dey] + [:bpl :start] + + [:lda vm.WH] + [:cmp 0x3c] + [:bcs :done] + ; cmp has cleared carry for us here + [:lda 4] [:adc vm.WH] [:sta vm.WH] + [:bcc :row] + :done]) + +(vm:word :drawfooter + 0x39d0 :clearline + 0x2250 :clearline 0x22d0 :clearline 0x2350 :clearline 0x23d0 :clearline) + +(vm:word :drawmaprow ; pscreen pmap -- pmap + mapw (vm:for + :2dup :bget :lookup-tile :drawtile + :inc :swap :inc :inc :swap) :swap :drop) + +(vm:word :drawmap + :map 0x0c00 (vm:until 0x100 :- + :dup :yx>screen ; pmap yx pscreen + :screen + :rot :over := :>rot := :|) + +(vm:word :movement-dir ; key -- dyx + (vm:ifchain [:dup (string.byte "I") 0x0b :either=] [:drop 0xff00] + [:dup (string.byte "J") 0x08 :either=] [:drop 0x00ff] + [:dup (string.byte "K") 0x15 :either=] [:drop 0x0001] + [:dup (string.byte "M") 0x0a :either=] [:drop 0x0100] + [:drop 0x0000])) + +(vm:def :yx+ ; yx yx -- yx + [:lda vm.TOP :x] + [:clc] [:adc vm.ST1 :x] + [:sta vm.ST1 :x] + [:lda vm.TOPH :x] + [:clc] [:adc vm.ST1H :x] + [:sta vm.ST1H :x] + (vm:drop)) + +(vm:var :player-yx 0x0a0a) + +(vm:word :draw-player ; -- + :player-yx :dup (vm:if [:get :dup 0xffff := (vm:if [:drop] [:yx>screen :player-tile :drawtile])] [:drop])) + +(vm:var :noclip) +(vm:word :move-if-clear ; yx -- f + :noclip :get (vm:if [:drop vm.false] [:movable-player-flag :flag-at? :not])) + +(vm:const :movable-player-flag ; -- flag + walkable) + +(vm:word :move-player-to ; yx -- + :player-yx :dup :get :dup 0xffff := (vm:if [:drop] [:drawtile-at]) + :set :draw-player) + +(vm:word :move-noop :drop vm.false) +(vm:word :handle-general-move ; yx -- f + (vm:if-or [[:dup :map-specific-move] [:dup :move-if-clear]] + [:drop vm.true] [:move-noop])) + +(vm:def :yxclip? ; yx -- f + [:block + [:lda vm.TOP :x] + [:cmp mapw] + [:bcs :clipped] + [:lda vm.TOPH :x] + [:cmp maph] + [:bcs :clipped] + [:lda 0] [:sta vm.TOP :x] [:sta vm.TOPH :x] (vm:ret) + :clipped + [:lda 0xff] [:sta vm.TOP :x] [:sta vm.TOPH :x]]) + +(vm:word :try-move-player ; dir -- + :player-yx :get :yx+ ; yxnew + (vm:if-or [[:dup :yxclip?] [:dup :touch-entity] [:dup :handle-general-move]] + [:drop :player-yx :get]) + ; always "move" so that player can visibly change direction + ; touch-entity can modify player-yx so we have to refetch + :move-player-to) + +(vm:word :two-frame :tick-count :get 0x1f :& 0x10 :<) +(vm:word :player-tile ; -- ptile + (itile :player-frame1) :lookup-tile) + +(vm:word :flag-at? ; yx flag -- f + :swap :itile-at :lookup-flags :&) + +(vm:word :player-key ; key -- + (vm:ifchain [:movement-dir :dup] [:try-move-player :load-next-level] + [:drop])) + +(vm:word :full-redraw :drawmap :player-redraw) + +(vm:word :player-redraw :draw-player) + diff --git a/editor/8bitsy.fnl b/editor/8bitsy.fnl new file mode 100644 index 0000000..f1256c6 --- /dev/null +++ b/editor/8bitsy.fnl @@ -0,0 +1,63 @@ +(local util (require :lib.util)) +(local actions (require :editor.actions)) +(local {: textbox : dropdown : textfield} (util.require :editor.imstate)) +(local files (require :game.files)) +(local lume (require :lib.lume)) +(local style (require :core.style)) + +(actions.register :say + (fn [action view x y w i] + (let [characters (lume.map files.game.portraits #$1.label) + character (or action.character (. characters 1)) + lines (or action.lines []) + (character y) (dropdown view [:say :char i] character characters x (+ y style.padding.y) w) + (line1 y) (textbox view [:say :line1 i] (or (. lines 1) "") x (+ y style.padding.y) w) + (line2 y) (textbox view [:say :line2 i] (or (. lines 2) "") x y w) + (line3 y) (textbox view [:say :line3 i] (or (. lines 3) "") x y w) + (line4 y) (textbox view [:say :line4 i] (or (. lines 4) "") x y w)] + (set action.character character) + (util.nested-tset action [:lines 1] (line1:sub 1 33)) + (util.nested-tset action [:lines 2] (line2:sub 1 33)) + (util.nested-tset action [:lines 3] (line3:sub 1 33)) + (util.nested-tset action [:lines 4] (line4:sub 1 33)) + y)) + (fn [action vm] + (local {: say} (require :bitsy.defs)) + (say action.character (table.unpack (lume.map action.lines #($1:upper)))))) + +(actions.register :warp + (fn [action view x y w i] + (let [maps (icollect [imap _ (ipairs files.game.levels)] (.. :map imap)) + map (or action.map (. maps 1)) + y (+ y style.padding.y) + map (dropdown view [:warp :map i] map maps x y 100) + (position-string y) (textbox view [:warp :loc i] (string.format "%x" (or action.position 0)) (+ x 150) y 150) + position (or (tonumber position-string 16) action.position)] + (set action.map map) + (set action.position position) + y)) + (fn [action vm] + (values :move-to-responder action.position :lit action.map :map-player-yx-ptr :set :lit action.map :next-level :set))) + +(actions.register-const :move-here :move-to-responder) +(actions.register-const :disappear :disappear) + +(actions.register :set-flag + (fn [action view x y w i] + (let [y (+ y style.padding.y) + x (renderer.draw_text style.font "Set " x y style.text) + flag (or action.flag (. files.game.flags 1)) + flag (dropdown view [:set-flag :flag i] flag files.game.flags x y 100) + x (renderer.draw_text style.font " to " (+ x 100) y style.text) + options (lume.concat + [{:label "" :value 0xffff} {:label "" :value 0}] + (icollect [_ flag (ipairs files.game.flags)] {:label flag :value (.. :cond- flag)})) + rhs (or action.rhs (. options 1)) + (rhs y) (dropdown view [:set-flag :rhs i] rhs options x y 100)] + (set action.flag flag) + (set action.rhs rhs) + y)) + (fn [action vm] + (values action.rhs.value (.. :cond-var- action.flag) :set))) + +{} diff --git a/editor/actions.fnl b/editor/actions.fnl new file mode 100644 index 0000000..5604731 --- /dev/null +++ b/editor/actions.fnl @@ -0,0 +1,23 @@ +(local util (require :lib.util)) +(local {: defmulti : defmethod} (util.require :lib.multimethod)) +(local {: textfield} (util.require :editor.imstate)) + +(local actions (util.hot-table ...)) + +(set actions.edit (defmulti #$1.action :edit ...)) +(set actions.generate (defmulti #$1.action :generate ...)) + +(defmethod actions.edit :default (fn [action view x y w i] y)) + +(fn actions.register [key edit generate] + (when (= actions.actionlist nil) + (set actions.actionlist [])) + (lume.remove actions.actionlist key) + (table.insert actions.actionlist key) + (defmethod actions.edit key edit) + (defmethod actions.generate key generate)) + +(fn actions.register-const [key generated-value] + (actions.register key (fn [action view x y w i] y) #generated-value)) + +actions.hot diff --git a/editor/fontedit.fnl b/editor/fontedit.fnl index ccaa5a4..a1d2f18 100644 --- a/editor/fontedit.fnl +++ b/editor/fontedit.fnl @@ -5,7 +5,6 @@ (local FontEditView (TileView:extend)) -(fn FontEditView.spritegen [self] tiledraw.char-to-sprite) (fn FontEditView.tilesize [self] (values 8 8)) (fn FontEditView.tilekeys [self] [:gfx]) (fn FontEditView.map-bitxy [self x y] (values y x)) @@ -14,7 +13,7 @@ (local char (string.char (+ self.itile 0x20 -1))) (renderer.draw_text style.big_font char x y style.text)) (love.graphics.setColor 1 1 1 1)) -(fn FontEditView.filename [self] tiles.fn-font) +(fn FontEditView.resource-key [self] :font) (fn FontEditView.get_name [self] "Font Editor") FontEditView diff --git a/editor/gfxedit.fnl b/editor/gfxedit.fnl index 3febe48..0df5395 100644 --- a/editor/gfxedit.fnl +++ b/editor/gfxedit.fnl @@ -2,6 +2,7 @@ (local tiles (require :game.tiles)) (local tiledraw (require :editor.tiledraw)) (local util (require :lib.util)) +(local files (require :game.files)) (local {: attach-imstate : mouse-inside : activate : active? : button} (util.require :editor.imstate)) (local GraphicsEditView (View:extend)) @@ -11,15 +12,17 @@ (fn GraphicsEditView.new [self] (GraphicsEditView.super.new self) - (set self.tilecache (tiledraw.TileCache (tiles.loadgfx (self:filename)) (self:spritegen))) + (set self.tilecache (files.cache (self:resource-key))) (set self.itile 1) + (set self.scrollheight math.huge) + (set self.scrollable true) (attach-imstate self)) -(fn GraphicsEditView.spritegen [self] tiledraw.tile-to-sprite) +(fn GraphicsEditView.get_scrollable_size [self] self.scrollheight) +(fn GraphicsEditView.resource-key [self] :tiles) (fn GraphicsEditView.tilesize [self] (values 16 16)) (fn GraphicsEditView.tilebytelen [self] (let [(w h) (self:tilesize)] (/ (* w h) 8))) -(fn GraphicsEditView.filename [self] tiles.fn-tiles) -(fn GraphicsEditView.reload [self] - (self.tilecache:load (tiles.loadgfx (self:filename)))) +(fn GraphicsEditView.reload [self] (files.reload)) +(fn GraphicsEditView.save [self] (files.save)) (fn GraphicsEditView.select-rel [self ditile] (when self.itile diff --git a/editor/imstate.fnl b/editor/imstate.fnl index bbbcafa..e8ae68f 100644 --- a/editor/imstate.fnl +++ b/editor/imstate.fnl @@ -23,6 +23,10 @@ (fn view.draw [self] (set self.cursor nil) (self.__index.draw self) + (when self.imstate.postponed + (each [_ action (ipairs self.imstate.postponed)] + (action)) + (set self.imstate.postponed nil)) (when (= self.cursor nil) (set self.cursor :arrow)) (set self.imstate.keys nil) (set self.imstate.text nil) @@ -57,6 +61,11 @@ (fn [] (when (= (-?> core.active_view.imstate (. :focus)) nil) (p-fn)))) +(fn postpone [view f] + (when (= view.imstate.postponed nil) + (set view.imstate.postponed [])) + (table.insert view.imstate.postponed f)) + (fn make-tag [tag] (match (type tag) :string tag @@ -67,20 +76,29 @@ (local (mx my) (values (love.mouse.getX) (love.mouse.getY))) (and (>= mx x) (<= mx (+ x w)) (>= my y) (<= my (+ y h)))) +(fn consume-pressed [view button] + (when (= (. view.imstate button) :pressed) + (tset view.imstate button :down) + true)) + (fn activate [view tag x y w h] - (when (and (= view.imstate.left :pressed) (mouse-inside x y w h)) + (when (and (mouse-inside x y w h) (consume-pressed view :left)) (set view.imstate.active (make-tag tag)) true)) + +(fn set-cursor [view cursor] + (when (= view.cursor nil) (set view.cursor cursor))) + (fn active? [view tag] (= view.imstate.active (make-tag tag))) (fn button [view tag x y w h] - (when (mouse-inside x y w h) (set view.cursor :hand)) + (when (mouse-inside x y w h) (set-cursor view :hand)) (activate view tag x y w h) - (and (active? view tag) (= view.imstate.left :released) (mouse-inside x y w h))) + (values (and (active? view tag) (= view.imstate.left :released) (mouse-inside x y w h)) (+ y h style.padding.y))) (fn textbutton [view label x y] - (local (w h) (values (+ (style.font:get_width label) 8) 24)) + (local (w h) (values (+ (style.font:get_width label) style.padding.x) (+ (style.font:get_height) style.padding.y))) (renderer.draw_rect x y w h style.selection) - (renderer.draw_text style.font label (+ x 4) (+ y 4) style.text) + (renderer.draw_text style.font label (+ x (/ style.padding.x 2)) (+ y (/ style.padding.y 2)) style.text) (values (button view label x y w h) (+ y h))) (fn checkbox [view name isset x y ?tag] @@ -89,14 +107,14 @@ (love.graphics.setColor 1 1 1 1) (button view (or ?tag name) x y (- xEnd x) 12)) -(fn focused? [view tag] (= tag (-?> view.imstate.focus (. :tag)))) +(fn focused? [view tag] (= (make-tag tag) (-?> view.imstate.focus (. :tag)))) (fn focus [view tag x y w h opts] (if (activate view tag x y w h) (set view.imstate.focus (doto (lume.clone (or opts {})) - (tset :tag tag))) + (tset :tag (make-tag tag)))) - (and (= view.imstate.left :released) (focused? view tag) (not (active? view tag))) + (and (= view.imstate.left :released) (focused? view tag) (not (mouse-inside x y w h))) (set view.imstate.focus nil)) (focused? view tag)) @@ -155,11 +173,14 @@ (fn textbox [view tag text x y w] (var textNew (or text "")) - (local (h hText xText yText) (values 20 16 (+ x 2) (+ y 2))) + (local (h hText xText yText) (values (+ (style.font:get_height) 4) (style.font:get_height) (+ x 2) (+ y 2))) + (local initial-press (= view.imstate.left :pressed)) ; handle key events (when (focus view tag x y w h {:i 1 :iAnchor 1 :blink (love.timer.getTime)}) (local f view.imstate.focus) + (when (> f.i (+ (length text) 1)) (set f.i (+ (length text) 1))) + (when (> f.iAnchor (+ (length text) 1)) (set f.iAnchor (+ (length text) 1))) (when view.imstate.text (set textNew (replace-selection view textNew view.imstate.text))) (each [_ key (ipairs (or view.imstate.keys []))] @@ -179,10 +200,10 @@ (set textNew (replace-selection view textNew "" iStartDel iLimDel))))))) ; handle mouse events - (when (mouse-inside x y w h) (set view.cursor :ibeam)) + (when (mouse-inside x y w h) (set-cursor view :ibeam)) (when (and (focused? view tag) (active? view tag) (mouse-inside x y w h)) (local mouse-i (i-from-x textNew (love.mouse.getX) x style.font)) - (when (= view.imstate.left :pressed) + (when initial-press (set view.imstate.focus.iAnchor mouse-i)) (set view.imstate.focus.i mouse-i)) @@ -212,4 +233,34 @@ (renderer.draw_text style.font label x y style.text) (textbox view label text (+ x wLabel) y wText)) -{: attach-imstate : cmd-predicate : mouse-inside : activate : active? : button : checkbox : textbox : textfield : textbutton} +(fn option-text [option] + (match (type option) + :string option + :table (or option.label (tostring option)) + _ (tostring option))) + +(fn dropdown [view tag selection options x y w] + (local row-h (+ (style.font:get_height) style.padding.y)) + (var new-selection nil) + + (renderer.draw_rect x y w row-h style.selection) + (renderer.draw_text style.font (option-text selection) (+ x style.padding.x) (+ y (/ style.padding.y 2)) style.text) + (renderer.draw_text style.icon_font "-" (+ x w (- style.padding.x)) (+ y (/ style.padding.y 2)) style.text) + + (when (focused? view tag) + (var row-y (+ y row-h)) + (each [i option (ipairs options)] + (when (button view [(make-tag tag) i] x row-y w row-h) + (set new-selection option)) + (set row-y (+ row-y row-h))) + (postpone view (fn [] + (var row-y (+ y row-h)) + (each [i option (ipairs options)] + (renderer.draw_rect x row-y w row-h style.selection) + (renderer.draw_text style.font (option-text option) (+ x style.padding.x) (+ row-y (/ style.padding.y 2)) style.text) + (set row-y (+ row-y row-h)))))) + (focus view tag x y w row-h) + (values (or new-selection selection) (+ y row-h))) + +{: attach-imstate : cmd-predicate : postpone : mouse-inside : activate : active? + : button : checkbox : textbox : textfield : textbutton : dropdown} diff --git a/editor/init.fnl b/editor/init.fnl index e56be3d..91edad7 100644 --- a/editor/init.fnl +++ b/editor/init.fnl @@ -9,8 +9,11 @@ (local keymap (require :core.keymap)) (local common (require :core.common)) +(require :editor.8bitsy) +(require :presentation.commands) + (let [commands {}] - (each [_ name (ipairs [:tile :portrait :font :brush])] + (each [_ name (ipairs [:tile :portrait :font :brush :map])] (local cls (require (.. "editor." name "edit"))) (tset commands (.. "honeylisp:" name "-editor") (fn [] (local node (core.root_view:get_active_node)) @@ -18,8 +21,7 @@ (command.add nil commands)) (local fileeditors - {:map {:view MapEditView :filefilter "^game/map%d+%.json"} - :screen {:view ScreenEditView :filefilter "^game/.*%.screen"}}) + {:screen {:view ScreenEditView :filefilter ".*%.screen"}}) (each [type {: view : filefilter} (pairs fileeditors)] (command.add nil @@ -57,6 +59,22 @@ "ctrl+v" "tileedit:paste" }) +(command.add :editor.replview { + "repl:submit" #(core.active_view:submit) +}) + +(local ReplView (require :editor.replview)) +(local repl (require :editor.repl)) +(command.add nil { + "repl:create" (fn [] + (local node (core.root_view:get_active_node)) + (node:add_view (ReplView (repl.new))) + ) +}) +(keymap.add { + :return "repl:submit" +}) + (fn inline-eval [eval] (let [ldoc core.active_view.doc (aline acol bline bcol) (ldoc:get_selection) diff --git a/editor/mapedit.fnl b/editor/mapedit.fnl index aa2f64e..25226bb 100644 --- a/editor/mapedit.fnl +++ b/editor/mapedit.fnl @@ -2,9 +2,11 @@ (local style (require :core.style)) (local util (require :lib.util)) (local lume (require :lib.lume)) -(local {: mouse-inside : activate : active? : checkbox : textfield : textbutton} (util.require :editor.imstate)) +(local files (require :game.files)) +(local {: mouse-inside : activate : active? : checkbox : textfield : textbutton : textbox : dropdown} (util.require :editor.imstate)) (local {: tilestrip-to-sprite} (util.require :editor.tiledraw)) (local {: encode-yx : encode-itile : decode-itile} (util.require :game.tiles)) +(local actions (require :editor.actions)) (local MapEditView (GraphicsEditView:extend)) (local sprite-scale 3) @@ -13,11 +15,11 @@ (local tilew (* sprite-scale 14)) (local tileh (* sprite-scale 16)) -(fn MapEditView.new [self filename] +(fn MapEditView.new [self] (MapEditView.super.new self) (set self.sprite-scale sprite-scale) (set self.stripcache {}) - (set self.mapfilename filename) + (set self.ilevel 1) (self:reload)) ; map is stored bottom-to-top @@ -58,6 +60,18 @@ (when (. objects (+ iobjectsrc 1)) (move-object objects (+ iobjectsrc 1) iobjectsrc))) +(fn MapEditView.draw-map-selector [self x y] + (renderer.draw_text style.font "Map" x (+ y (/ style.padding.y 2)) style.text) + (let [options {} + level-count (length files.game.levels) + _ (do (for [i 1 level-count] (tset options i i)) + (table.insert options :New)) + (ilevel yNext) (dropdown self :map-selector self.ilevel options (+ x 50) y 100)] + (when (not= ilevel self.ilevel) + (set self.ilevel (if (= ilevel :New) (+ level-count 1) ilevel)) + (self:load-level)) + (- yNext y))) + (fn MapEditView.linking-obj [self] (. self.level.objects self.iobject-linking)) (fn MapEditView.draw-link-line [self x y iobjectSrc color toMouse?] (local objectSrc (. self.level.objects iobjectSrc)) @@ -79,7 +93,7 @@ (var stripid "") (for [mx 1 mapw] (local itile (self:itile-from-xy mx my)) - (local tile (. self.tilecache.tiles itile :gfx)) + (local tile (?. self.tilecache.tiles itile :gfx)) (table.insert tilestrip tile) (set stripid (.. stripid (string.char itile)))) (var sprite (. self.stripcache stripid)) @@ -89,6 +103,8 @@ (love.graphics.draw sprite x y 0 self.sprite-scale self.sprite-scale)) (fn MapEditView.draw-map-editor [self x y] + (love.graphics.setColor 1 1 1 1) + (local button-state self.imstate.left) (activate self :map x y (* tilew mapw) (* tileh maph)) (var iobject-over nil) (for [my 1 maph] @@ -99,7 +115,7 @@ (local itile (self:itile-from-xy mx my)) (local iobject (self:iobject-from-xy mx my)) (when (= self.itile nil) - (each [_ player (ipairs [:jaye :neut])] + (each [_ player (ipairs (or files.game.players [:player]))] (match (. self.level player) {:x mx :y my} (renderer.draw_text style.font player tilex tiley style.text))) (love.graphics.setColor 1 1 1)) @@ -115,7 +131,7 @@ (when (and self.itile (active? self :map) (mouse-inside tilex tiley tilew tileh) (not= itile self.itile)) (self:set-tile mx my self.itile)) (when (and (= self.itile nil) (active? self :map) (mouse-inside tilex tiley tilew tileh)) - (match self.imstate.left + (match button-state :pressed (set self.iobject-linking iobject) :released (if (and (not= iobject nil) (= self.iobject-linking iobject)) @@ -140,64 +156,116 @@ (if (= self.imstate.left :released) (set self.iobject-linking nil) (self:draw-link-line x y self.iobject-linking [0 1 0] true))))) -(fn MapEditView.draw-object-editor [self x y] +(fn condition-label [flag] + (if flag {:label flag : flag} {:label ""})) + +(fn condition-options [] + (let [options [(condition-label nil)]] + (each [_ flag (ipairs (or files.game.flags []))] + (table.insert options (condition-label flag))) + options)) + +(fn MapEditView.draw-object-code-editor [self object x y] (var y y) - (local object (self:object)) - (set (object.func y) (textfield self "Word" object.func x y 100 200)) - (set (object.name y) (textfield self "Name" object.name x (+ y 5) 100 200)) - (set (object.linkword y) (textfield self "Link word" object.linkword x (+ y 5) 100 200)) - (if object.link - (match (textbutton self "Unlink" x (+ y 5)) - (unlink yNext) (do (when unlink (set object.link nil)) - (set y yNext))) - (set (object.linkentity y) (textfield self "Link entity" object.linkentity x (+ y 5) 100 200))) - (when (textbutton self "Delete" x (+ y 40)) - (move-object self.level.objects (+ self.iobject 1) self.iobject) - (set self.iobject nil))) + (var istep-to-delete nil) + (when (not object.steps) (set object.steps [])) + (each [istep step (ipairs object.steps)] + (when (textbutton self "X" (+ x 280) y) + (set istep-to-delete istep)) + (set step.condition (. (dropdown self [:code-condition istep] (condition-label step.condition) (condition-options) + (+ x 100 style.padding.x) y 100) + :flag)) + (set (step.action y) (dropdown self [:code-action istep] (or step.action (. actions.actionlist 1)) actions.actionlist x y 100)) + (set y (actions.edit step self x y 300 istep)) + (set y (+ y style.padding.y))) + (when istep-to-delete (table.remove object.steps istep-to-delete)) + (let [(do-new y) (textbutton self "+ New Step" x (+ y style.padding.y))] + (when do-new (table.insert object.steps {})) + y)) + +(fn advanced? [object] + (or object.advanced + (and (= object.advanced nil) + object.func))) + +(fn MapEditView.draw-object-advanced-editor [self object x y] + (let [(func y) (textfield self "Word" object.func x y 100 200) + (name y) (textfield self "Name" object.name x (+ y style.padding.y) 100 200) + (linkword y) (textfield self "Link word" object.linkword x (+ y style.padding.y) 100 200) + (do-unlink y) (if object.link (textbutton self "Unlink" x (+ y style.padding.y)) (values false y)) + (linkentity y) (if object.link (values object.linkentity y) (textfield self "Link entity" object.linkentity x (+ y style.padding.y) 100 200))] + (lume.extend object {: func : name : linkword : linkentity}) + (when do-unlink (set object.link nil)) + y)) + +(fn MapEditView.draw-object-editor [self x y] + (let [object (self:object) + y (if (advanced? object) + (self:draw-object-advanced-editor object x y) + (self:draw-object-code-editor object x y)) + new-flag-name (textbox self :new-flag-name self.new-flag-name x (+ y style.padding.y) 200) + (mk-new-flag y) (textbutton self "+ New Flag" (+ x 200 style.padding.x) (+ y style.padding.y)) + do-delete (textbutton self "Delete" x (+ y 40)) + (do-advanced y) (textbutton self (if (advanced? object) "Simple" "Advanced") (+ x 150) (+ y 40))] + (set self.new-flag-name new-flag-name) + (when mk-new-flag + (when (= files.game.flags nil) + (set files.game.flags [])) + (table.insert files.game.flags new-flag-name) + (set self.new-flag-name "")) + (when do-delete + (move-object self.level.objects (+ self.iobject 1) self.iobject) + (set self.iobject nil)) + (when do-advanced (set object.advanced (not (advanced? object)))) + y)) + +(fn MapEditView.load-level [self] + (set self.stripcache {}) + (when (= (. files.game.levels self.ilevel) nil) + (tset files.game.levels self.ilevel {:map (string.rep "\0" (* mapw maph)) :objects []})) + (set self.level (. files.game.levels self.ilevel)) + (set self.iobject nil)) (fn MapEditView.reload [self] (MapEditView.super.reload self) - (local (loaded level) (pcall #(util.readjson self.mapfilename))) - (set self.level - (match (and loaded (type level)) - false {:map (string.rep "\0" (* mapw maph)) :objects []} - :string {:map (level:fromhex) :objects []} - :table (doto level (tset :map (level.map:fromhex)))))) - -(fn MapEditView.save [self] - (util.writejson self.mapfilename - (doto (lume.clone self.level) - (tset :map (self.level.map:tohex))))) + (self:load-level)) (fn MapEditView.draw [self] - (var x (+ self.position.x 10)) - (var y (+ self.position.y 10)) + (var x (+ self.position.x style.padding.x (- self.scroll.x))) + (var y (+ self.position.y style.padding.y (- self.scroll.y))) (self:draw_background style.background) - (love.graphics.setColor 1 1 1 1) + (self:draw_scrollbar) + (local ytop y) + (set y (+ y (self:draw-map-selector x y) style.padding.y)) (self:draw-map-editor x y) - (when self.iobject - (self:draw-object-editor (+ x (* tilew mapw) 10) y)) - (set y (+ y (* tileh maph) 10)) - (set self.level.tickword (textfield self "Tick word" self.level.tickword x y 100 200)) - (set y (+ y 30)) - (set self.level.moveword (textfield self "Move word" self.level.moveword x y 100 200)) - (set y (+ y 30)) - (set self.level.loadword (textfield self "Load word" self.level.loadword x y 100 200)) - (set y (+ y 30)) - (when (checkbox self "Edit objects" (= self.itile nil) x y) - (set self.itile nil) - (set self.playerpos nil)) - (set y (+ y 30)) - (each [_ player (ipairs [:jaye :neut])] - (when (checkbox self (.. "Position " player) (and (= self.itile nil) (= self.playerpos player)) x y) - (set self.itile nil) - (set self.playerpos player)) - (set y (+ y 30))) - (when (checkbox self "Start with Gord" self.level.gord-following x y) - (set self.level.gord-following (not self.level.gord-following))) - (set y (+ y 30)) - (self:draw-tile-selector x y (- self.size.x 20))) + (set y (+ y (* tileh maph) style.padding.y)) + (set y (+ y (self:draw-tile-selector x y (- self.size.x (* style.padding.x 2))))) -(fn MapEditView.get_name [self] (.. "Map: " self.mapfilename)) + (set (self.level.tickword y) (textfield self "Tick word" self.level.tickword x (+ y style.padding.y) 100 200)) + (set (self.level.moveword y) (textfield self "Move word" self.level.moveword x (+ y style.padding.y) 100 200)) + (set (self.level.loadword y) (textfield self "Load word" self.level.loadword x (+ y style.padding.y) 100 200)) + (let [(checked y-new) (checkbox self "Edit objects" (= self.itile nil) x (+ y style.padding.y)) + _ (when checked + (set self.itile nil) + (set self.playerpos nil))] + (set y y-new) + (each [_ player (ipairs (or files.game.players [:player]))] + (let [(checked y-new) (checkbox self (.. "Position " player) (and (= self.itile nil) (= self.playerpos player)) x (+ y style.padding.y))] + (when checked + (set self.itile nil) + (set self.playerpos player)) + (set y y-new)))) + (each [_ levelflag (ipairs (or files.game.levelflags []))] + (let [(checked y-new) (checkbox self levelflag (. self.level levelflag) x (+ y style.padding.y))] + (when checked (tset self.level levelflag (not (. self.level levelflag)))) + (set y y-new))) + (when self.iobject + (set y (math.max y (if (> self.size.x (+ (* tilew mapw) 300)) + (self:draw-object-editor (+ x (* tilew mapw) style.padding.x) ytop) + (self:draw-object-editor x (+ y style.padding.y)))))) + + (set self.scrollheight (- y (+ self.position.y style.padding.y (- self.scroll.y))))) + +(fn MapEditView.get_name [self] (.. "Map " self.ilevel)) MapEditView diff --git a/editor/portraitedit.fnl b/editor/portraitedit.fnl index 726d747..c6cd688 100644 --- a/editor/portraitedit.fnl +++ b/editor/portraitedit.fnl @@ -6,10 +6,9 @@ (local PortraitView (TileView:extend)) -(fn PortraitView.spritegen [self] tiledraw.portrait-to-sprite) (fn PortraitView.tilesize [self] (values 32 32)) (fn PortraitView.tilekeys [self] [:gfx]) -(fn PortraitView.filename [self] tiles.fn-portraits) +(fn PortraitView.resource-key [self] :portraits) (fn PortraitView.map-bitxy [self x y] (local quadrant (+ (if (>= x 16) 2 0) (if (>= y 16) 1 0))) (local tilex diff --git a/editor/repl.fnl b/editor/repl.fnl new file mode 100644 index 0000000..90cd3f1 --- /dev/null +++ b/editor/repl.fnl @@ -0,0 +1,49 @@ +(local util (require :lib.util)) +(local fennel (require :lib.fennel)) +(local style (require :core.style)) +(local lume (require :lib.lume)) +(local {: textbutton} (util.require :editor.imstate)) +(local {: inspect} (util.require :inspector)) +(local repl (util.hot-table ...)) + +(fn repl.inspector [{: vals : states} view x y] + (var h 0) + (each [i v (ipairs vals)] + (set h (+ h (inspect (. states i) v view x (+ y h) view.size.x)))) + (+ h style.padding.y)) + +(fn repl.notify [listeners line] + (each [_ listener (ipairs listeners)] + (listener:append line))) + +(fn repl.mk-result [vals] + (local inspector #(repl.inspector $...)) + {:draw inspector : vals :states (icollect [_ (ipairs vals)] {})}) + +(fn repl.run [{: listeners}] + (fennel.repl {:readChunk coroutine.yield + :onValues #(repl.notify listeners (repl.mk-result $1)) + :onError (fn [errType err luaSource] (repl.notify listeners (repl.mk-result [err]))) + :pp #$1 + :env (lume.clone _G)})) + +(fn repl.listen [{: listeners} listener] + (table.insert listeners listener)) + +(fn repl.unlisten [{: listeners} listener] + (lume.remove listeners listener)) + +(fn repl.submit [{: coro} chunk] + (coroutine.resume coro (.. chunk "\n"))) + +(fn repl.new [] + (local result + {:listeners [] + :listen #(repl.listen $...) + :unlisten #(repl.unlisten $...) + :submit #(repl.submit $...) + :coro (coroutine.create repl.run)}) + (coroutine.resume result.coro result) + result) + +repl.hot diff --git a/editor/replview.fnl b/editor/replview.fnl new file mode 100644 index 0000000..f7a2e6c --- /dev/null +++ b/editor/replview.fnl @@ -0,0 +1,61 @@ +(local util (require :lib.util)) +(local {: attach-imstate : textbox} (util.require :editor.imstate)) +(local View (require :core.view)) +(local style (require :core.style)) + +(local ReplView (View:extend)) + +(fn ReplView.new [self conn] + (ReplView.super.new self) + (attach-imstate self) + (set self.conn conn) + (set self.log []) + (set self.cmd "") + (set self.scrollheight math.huge) + (set self.scrollable true) + (self.conn:listen self)) + +(fn ReplView.try_close [self do_close] + (self.conn:unlisten self) + (ReplView.super.try_close self do_close)) + +(fn ReplView.get_scrollable_size [self] self.scrollheight) + +(fn ReplView.append [self line] + (table.insert self.log line)) + +(fn ReplView.draw-cmd [{: cmd} view x y] + (renderer.draw_text style.font cmd x y style.text) + (+ (style.font:get_height) style.padding.y)) + +(fn ReplView.submit [self ?cmd] + (local cmd (or ?cmd self.cmd)) + (when (= ?cmd nil) + (set self.cmd "")) + (self:append {:draw #(self.draw-cmd $...) : cmd}) + (self.conn:submit cmd)) + +(fn ReplView.draw [self] + (self:draw_background style.background) + (self:draw_scrollbar) + (var x (- self.position.x self.scroll.x)) + (var y (- self.position.y self.scroll.y)) + (var rendered-h 0) + + ; todo: cache sizes and avoid drawing if offscreen? + ; note: then offscreen items can't be focussed without further effort + ; todo: draw line numbers + (each [i line (ipairs self.log)] + (let [h (line:draw self x y)] + (set y (+ y h)) + (set rendered-h (+ rendered-h h)))) + + (set self.cmd (textbox self :command self.cmd x y self.size.x)) + + (local pin-to-bottom (>= self.scroll.to.y (- self.scrollheight self.size.y))) + (set self.scrollheight (+ rendered-h (style.font:get_height) 4)) + (when pin-to-bottom + (set self.scroll.to.y (- self.scrollheight self.size.y)))) + +ReplView + diff --git a/editor/tileedit.fnl b/editor/tileedit.fnl index 0aed1ff..049d5bc 100644 --- a/editor/tileedit.fnl +++ b/editor/tileedit.fnl @@ -1,6 +1,7 @@ (local GraphicsEditView (require :editor.gfxedit)) (local style (require :core.style)) (local tiles (require :game.tiles)) +(local files (require :game.files)) (local tiledraw (require :editor.tiledraw)) (local util (require :lib.util)) (local {: mouse-inside : activate : active? : checkbox : textfield} (util.require :editor.imstate)) @@ -20,7 +21,9 @@ (values ibyte ibit))) (fn TileView.tilesize [self] (values 16 16)) -(fn TileView.tilekeys [self] [:gfx :neut]) +(fn TileView.tilekeys [self] + (if files.game.tilesets (icollect [_ key (pairs files.game.tilesets)] key) + [:gfx])) (fn get-byte [tile ibyte] (: (tile:sub (+ ibyte 1) (+ ibyte 1)) :byte)) @@ -79,24 +82,26 @@ (when tile (set tile.word (textfield self "Default word" tile.word x y 100 200)) (set tile.label (textfield self "Label" tile.label x (+ y pixel-size 4) 100 200))) - (each [iflag flagname (ipairs tiles.flags)] + (each [iflag flagname (ipairs (tiles.flags))] (self:draw-tile-flag flagname x (+ y (* (+ iflag 1) (+ pixel-size 4)))))) (fn TileView.update-tile [self newtile] (self.tilecache:update-tile self.itile newtile self.tilekey)) -(fn TileView.save [self] (tiles.savegfx (self:filename) self.tilecache.tiles)) - (fn TileView.draw [self] (self:draw_background style.background) - (local (x y) (values (+ self.position.x 10) (+ self.position.y 10))) + (self:draw_scrollbar) + (local (x y) (values (+ self.position.x style.padding.x (- self.scroll.x)) + (+ self.position.y style.padding.y (- self.scroll.y)))) (local (editor-w editor-h) (self:draw-tile-editor (self:tile) x y)) (self:draw-tile-flags (+ x editor-w pixel-size) y) (var selector-y (+ y editor-h pixel-size)) (each [_ key (ipairs (self:tilekeys))] (local selector-h (self:draw-tile-selector x selector-y (- self.size.x 20) key)) - (set selector-y (+ selector-y selector-h pixel-size)))) + (set selector-y (+ selector-y selector-h pixel-size))) + (set self.scrollheight (- selector-y y))) +(fn TileView.resource-key [self] :tiles) (fn TileView.get_name [self] "Tile Editor") TileView diff --git a/game/files.fnl b/game/files.fnl new file mode 100644 index 0000000..d1c6569 --- /dev/null +++ b/game/files.fnl @@ -0,0 +1,91 @@ +(local util (require :lib.util)) +(local lume (require :lib.lume)) +(local tiledraw (require :editor.tiledraw)) + +(local files (util.hot-table ...)) + +(local default-filename "bitsy/game.json") + +(local encoded-tile-fields [:gfx :mask]) +(fn convert [tile field method] + (local oldval (. tile field)) + (when oldval + (tset tile field (: oldval method))) + tile) +(fn convert-all [tile method root] + (let [encoded-tile-fields [:mask]] + (each [_ key (pairs (or root.tilesets {:tileset :gfx}))] + (table.insert encoded-tile-fields key)) + (each [_ field (ipairs encoded-tile-fields)] + (convert tile field method)) + tile)) + +(fn tile-deserialize [tile root] + (match (type tile) + :string {:gfx (tile:fromhex) :flags {}} + :table (convert-all tile :fromhex root))) + +(fn tile-serialize [tile root] (convert-all (lume.clone tile) :tohex root)) + +(fn deserialize [key value root] + (match key + (where (or :tiles :portraits :font)) (tile-deserialize value root) + :levels (do (set value.map (value.map:fromhex)) value) + _ value)) + +(fn serialize [key value root] + (match key + (where (or :tiles :portraits :font)) (tile-serialize value root) + :levels (do (set value.map (value.map:tohex)) value) + _ value)) + +(fn clone [v] + (match (type v) + :table (lume.clone v) + _ v)) + +(fn filename [] (or files.filename default-filename)) +(fn files.load [?filename] + (when ?filename (set files.filename ?filename)) + (set files.game + (if (util.file-exists (filename)) + (let [game (util.readjson (filename))] + (each [k v (pairs game)] + (tset game k (lume.map v #(deserialize k (clone $1) game)))) + game) + {:tiles [] :portraits [] :font [] :levels []})) + files.game) + +(fn files.save [?filename] + (when ?filename (set files.filename ?filename)) + (let [game {}] + (each [k v (pairs files.game)] + (tset game k (lume.map v #(serialize k (clone $1) files.game)))) + (util.writejson (filename) game))) + +(fn new-cache [game key] + (let [spritegen (match key + :font tiledraw.char-to-sprite + :portraits tiledraw.portrait-to-sprite + _ tiledraw.tile-to-sprite) + gfx (. game key)] + (tiledraw.TileCache gfx spritegen))) + +(fn files.cache [key] + (when (= (?. files :tilecaches key) nil) + (util.nested-tset files [:tilecaches key] (new-cache files.game key))) + (. files.tilecaches key)) + +(fn files.reload [?filename] + (files.load ?filename) + (when files.tilecaches + (each [key cache (pairs files.tilecaches)] + (cache:load (. files.game key))))) + +(fn files.module [] + (or files.game.module (: (filename) :match "^[^/]+"))) + +(when (= files.game nil) + (files.load)) + +files.hot diff --git a/game/font.json b/game/font.json deleted file mode 100644 index 408fe68..0000000 --- a/game/font.json +++ /dev/null @@ -1 +0,0 @@ -[{"flags":[],"gfx":"8080808080808080"},{"flags":[],"gfx":"8C8C8C8C88808C80"},{"flags":[],"gfx":"B3B3928080808080"},{"flags":[],"gfx":"B6FFB6B6B6FFB680"},{"flags":[],"gfx":"8CBE839EB09F8C80"},{"flags":[],"gfx":"80A3938884B2B180"},{"flags":[],"gfx":"8E9B9BCEBBB3EE80"},{"flags":[],"gfx":"8C8C888080808080"},{"flags":[],"gfx":"988C8C8C8C8C9880"},{"flags":[],"gfx":"8C98989898988C80"},{"flags":[],"gfx":"8CAD9E8C9EAD8C80"},{"flags":[],"gfx":"808C8CBF8C8C8080"},{"flags":[],"gfx":"808080808C8C8880"},{"flags":[],"gfx":"8080809C80808080"},{"flags":[],"gfx":"80808080808C8C80"},{"flags":[],"gfx":"80A0B0988C868280"},{"flags":[],"gfx":"9CB6B6BEB6B69C80"},{"flags":[],"gfx":"989C989898989880"},{"flags":[],"gfx":"9CB6B0988C86BE80"},{"flags":[],"gfx":"9CB6B098B0B69C80"},{"flags":[],"gfx":"9C9E9B9BBF989880"},{"flags":[],"gfx":"BE86869EB0B09E80"},{"flags":[],"gfx":"9C86869EB6B69C80"},{"flags":[],"gfx":"BEB0B0988C8C8C80"},{"flags":[],"gfx":"9CB6B69CB6B69C80"},{"flags":[],"gfx":"9CB6B6BCB0B09C80"},{"flags":[],"gfx":"808C8C808C8C8080"},{"flags":[],"gfx":"808C8C808C8C8880"},{"flags":[],"gfx":"B0988C868C98B080"},{"flags":[],"gfx":"8080BE80BE808080"},{"flags":[],"gfx":"868C98B0988C8680"},{"flags":[],"gfx":"9CB6B0988C808C80"},{"flags":[],"gfx":"9EB3B3BBBB839E80"},{"flags":[],"gfx":"9CB6B6B6BEB6B680"},{"flags":[],"gfx":"9EB6B69EB6B69E80"},{"flags":[],"gfx":"9CB6868686B69C80"},{"flags":[],"gfx":"9EB6B6B6B6B69E80"},{"flags":[],"gfx":"BE86869E8686BE80"},{"flags":[],"gfx":"BE86869E86868680"},{"flags":[],"gfx":"9EB383BBB3B39E80"},{"flags":[],"gfx":"B6B6B6BEB6B6B680"},{"flags":[],"gfx":"8C8C8C8C8C8C8C80"},{"flags":[],"gfx":"B0B0B0B0B6BE9C80"},{"flags":[],"gfx":"B6B69E9EB6B6B680"},{"flags":[],"gfx":"868686868686BE80"},{"flags":[],"gfx":"92BFBFBFB3B3B380"},{"flags":[],"gfx":"9EB6B6B6B6B6B680"},{"flags":[],"gfx":"9CB6B6B6B6B69C80"},{"flags":[],"gfx":"9EB6B69E86868680"},{"flags":[],"gfx":"9CB6B6B6BEB6BC80"},{"flags":[],"gfx":"9EB6B69EB6B6B680"},{"flags":[],"gfx":"9CB6869CB0B69C80"},{"flags":[],"gfx":"BFBF8C8C8C8C8C80"},{"flags":[],"gfx":"B6B6B6B6B6B69C80"},{"flags":[],"gfx":"B6B6B69C9C9C8880"},{"flags":[],"gfx":"B3B3B3BFBFBF9280"},{"flags":[],"gfx":"B6B6B69CB6B6B680"},{"flags":[],"gfx":"B6B6B6BCB0B09C80"},{"flags":[],"gfx":"BEBEB0988CBEBE80"},{"flags":[],"gfx":"BC8C8C8C8C8CBC80"},{"flags":[],"gfx":"8082868C98B0A080"},{"flags":[],"gfx":"9E98989898989E80"},{"flags":[],"gfx":"8894808080808080"},{"flags":[],"gfx":"808080808080BE80"}] \ No newline at end of file diff --git a/game/init.fnl b/game/init.fnl index b8d45a8..424161a 100644 --- a/game/init.fnl +++ b/game/init.fnl @@ -1,64 +1,5 @@ +(local files (require :game.files)) (local util (require :lib.util)) -(local {: lo : hi : readjson} util) -(local tile (util.reload :game.tiles)) -(local {: prg : vm : org} (util.reload :game.defs)) -(local disk (util.reload :game.disk)) +(util.reload (files.module)) -(util.reload :game.gfx) -(util.reload :game.footer) -(util.reload :game.map) -(util.reload :game.entity) -(util.reload :game.player) -(util.reload :game.boop) -(util.reload :game.cheat) - -(tile.appendtiles org.code) -(org.code:append [:align 0x100] :font) -(tile.appendgfx org.code (tile.loadgfx tile.fn-font)) -(tile.append-portraitwords vm {:pneut #[:vm :chuck-mode :get (vm:if [:lit :pchuck] [:lit :pneut])]}) - -(util.reload :game.level1) -(util.reload :game.level2) -(util.reload :game.level3) -(util.reload :game.level4) -(util.reload :game.level5) -(util.reload :game.level6) - -(util.reload :game.bosskey) - -(vm:var :tick-count) -(vm:word :handle-key :tick :read-key :dup :cheat-key :player-key :hide-footer) -(vm:word :tick :map-specific-tick :tick-count :get 1 :+ :tick-count :set :player-redraw :rnd :drop) - -(vm:var :next-level 0) -(vm:word :load-next-level :next-level :get :dup (vm:if [:load-level 0 :next-level :set] [:drop])) -(vm:word :load-level ; level-ptr -- - :lit :map-ptr :set :reload-level) - -(vm:word :reload-level - :map-jaye-yx :jaye-yx :set - :map-neut-yx :neut-yx :set - :map-gord-yx :gord-yx :set - 0 :gord-dir :set - 0xffff :rexx-yx :set - :map-specific-load - :full-redraw) - -(vm.code:append :main - [:jsr :reset] - [:jsr :interpret] - [:vm :hires - :lit :level1 :load-level - (vm:forever - (vm:hotswap-sync :lit :level6 :load-level) - :interactive-eval-checkpoint - :handle-key - ) - :quit]) - -(disk.append-boot-loader prg) -(prg:assemble) -(disk.write prg) - -prg diff --git a/game/level2.fnl b/game/level2.fnl deleted file mode 100644 index 18b58ec..0000000 --- a/game/level2.fnl +++ /dev/null @@ -1,6 +0,0 @@ -(local {: deflevel : say : itile} (require :game.defs)) -(local {: ev} (require :game.entity)) -(local level (deflevel "game/map2.json" :level2)) -(local vm level.vm) - -level diff --git a/game/map1.json b/game/map1.json deleted file mode 100644 index 6fd524e..0000000 --- a/game/map1.json +++ /dev/null @@ -1 +0,0 @@ -{"loadword":"earthquake","map":"212121214121212121212121212141212121212161026161610261616102616161616102616161216143C0C0C2C0C0C0C0C0C0C081C0C0C0C0C0612161C0C08282C0C0C082C0C0C061C0C0C0C0C2024161C0C0C0C0C0C0C0C0C2C082C182C0C0E082612161C2C08282C0C0C0C082C0C061616161616161216161616161C16181616161616143C0C0C282612161C0C0C0C0C0C0C0C0C0C0C061C0C0C0C0C0022161E0828282C0C0C0C0C2C0C081C0C0C0C003612161C2C2C2C0C0C0C0C0C0C0C061C0C0C0C0C06141610303C043C2C0C0C0C0C0C061C0C0C003C061216161616161616161228161616161616161610221","jaye":{"y":9,"x":15},"tickword":"","moveword":"","objects":[{"x":8,"func":"door","linkword":"","name":"","y":6},{"x":2,"func":"firstterm","y":4,"name":"","linkword":"","link":3},{"x":17,"func":"neutterm","y":8,"name":"","linkword":"","link":2},{"x":13,"func":"switch","y":8,"name":"","linkword":"","link":8},{"link":6,"x":9,"y":1,"linkword":"","name":"","func":"exitscanner"},{"x":10,"linkentity":"level2","func":"exitdoor","y":1,"name":"","linkword":"exitlevel"},{"link":1,"x":6,"y":6,"linkword":"","name":"","func":"switch"},{"x":13,"func":"firstdoor","name":"","linkword":"","y":10}]} \ No newline at end of file diff --git a/game/map2.json b/game/map2.json deleted file mode 100644 index 3b77c64..0000000 --- a/game/map2.json +++ /dev/null @@ -1 +0,0 @@ -{"neut":{"y":12,"x":10},"map":"616161616161626161618161616161616161612161C0C0C06361C0C0E0C0C0C0C0C06103C0C0022161E0C0C0C081C0C0C0C0C0C0C0C081C0C0E0614161C0C0C0C06143C0C0C0C0C0C04322C0C0C0222161C0C0C0C061618161616161816161C0C0C00221616181616161C0C0C06143C0C0C061618161612161C0C0C06361E0C0C061C0C0C0C0C1E0C003612122C0C0C0C061C0C0C061C0E0C0C061C0C0C0022161C0C0C0C061616161616261616161C0C0C061416101C0C0C081C0C0E061C0C0E0C081C0C0C0022162C0C0C02361E0C0C06143C0C0C061E2A2E061216161C1616261612281616122226162C1C1616121","loadword":"","jaye":{"y":11,"x":11},"tickword":"","moveword":"","objects":[{"x":9,"link":2,"func":"term","linkword":"","name":"","y":11},{"x":2,"link":6,"func":"term","linkword":"","name":"","y":3},{"x":6,"func":"door","linkword":"","name":"","y":10},{"x":1,"link":5,"func":"scan","linkword":"","name":"","y":5},{"x":8,"func":"door","linkword":"","name":"","y":8},{"x":7,"link":2,"func":"term","linkword":"","name":"","y":6},{"x":12,"link":8,"func":"scan","linkword":"","name":"","y":1},{"x":17,"func":"door","linkword":"","name":"","y":7},{"x":13,"link":10,"func":"scan","linkword":"","name":"","y":1},{"x":13,"func":"door","linkword":"","name":"","y":8},{"x":15,"link":12,"func":"switch","linkword":"","name":"","y":6},{"x":2,"link":13,"func":"term","linkword":"","name":"","y":10},{"x":12,"link":12,"func":"term","linkword":"","name":"","y":5},{"x":15,"link":15,"func":"scan","linkword":"","name":"","y":9},{"x":15,"func":"door","linkword":"","name":"","y":10},{"x":16,"link":17,"func":"term","linkword":"","name":"","y":6},{"x":18,"link":16,"func":"term","linkword":"","name":"","y":10},{"x":15,"func":"door","linkword":"","name":"","y":3},{"x":19,"link":18,"func":"scan","linkword":"","name":"","y":9},{"x":13,"link":21,"func":"term","linkword":"","name":"","y":3},{"x":18,"link":20,"func":"term","linkword":"","name":"","y":2},{"x":8,"link":23,"func":"scan","linkword":"","name":"","y":1},{"x":9,"linkentity":"level3","func":"exitdoor","linkword":"exitlevel","name":"","y":1},{"x":16,"link":3,"func":"switch","linkword":"","name":"","y":1},{"x":3,"link":26,"func":"switch","linkword":"","name":"","y":1},{"x":6,"func":"door","linkword":"","name":"","y":3},{"x":9,"link":28,"func":"term","linkword":"","name":"","y":3},{"x":7,"link":27,"func":"term","linkword":"","name":"","y":2},{"x":3,"func":"door","linkword":"","name":"","y":7},{"x":17,"link":29,"func":"switch","linkword":"","name":"","y":1}]} \ No newline at end of file diff --git a/game/map3.json b/game/map3.json deleted file mode 100644 index 617ef27..0000000 --- a/game/map3.json +++ /dev/null @@ -1 +0,0 @@ -{"neut":{"y":12,"x":8},"map":"616161616161616181616161616161616161612161C063C0C0C0C0C0C0C06143C0E0C2C0C043022161C0C0C0C0C0C0C0C0C022C0C0C0C0C0C0C0614161C0C0C2C0C0C0C0C0C081C0C0C0C0C0C0C0612161C08282A2C0C0C0C0436123C0C0C0C0C0030221616161616161616261616161618161616161612161C063C0C02301C3C163C0C0C0C0C0822363022122C0C0C0C0C0C0A2A1C0C0C0C0C0C0C2C0C0614181C0C0C0C0E2C0C061C0C0C0C0C0C0C0C2C061216123C0C0C0C083C061E0C2C0C0C0C0438203022161610261610261616161026161026161026161212121212141212121212121212121412121212121","loadword":"level3-load","jaye":{"y":11,"x":9},"tickword":"","moveword":"","objects":[{"link":2,"x":1,"y":5,"linkword":"","name":"","func":"scan"},{"x":1,"linkentity":"level4","func":"exitdoor","name":"","linkword":"exitlevel","y":4},{"link":4,"x":7,"y":6,"linkword":"","name":"","func":"gordterm"},{"link":7,"x":14,"y":11,"linkword":"","name":"","func":"term"},{"link":6,"x":9,"y":6,"linkword":"","name":"gordswitch","func":"switch"},{"x":11,"func":"door","name":"","linkword":"","y":9},{"link":3,"x":10,"y":3,"linkword":"","name":"","func":"term"},{"link":9,"x":11,"y":10,"linkword":"","name":"","func":"scan"},{"x":14,"func":"door","name":"","linkword":"","y":7},{"x":7,"func":"meetrexx","y":3,"name":"","linkword":""},{"x":8,"func":"meetgord","y":6,"name":"","linkword":""},{"x":8,"func":"gordtable","y":5,"linkword":"","name":""}]} \ No newline at end of file diff --git a/game/map4.json b/game/map4.json deleted file mode 100644 index 1a4cb68..0000000 --- a/game/map4.json +++ /dev/null @@ -1 +0,0 @@ -{"neut":{"y":5,"x":20},"map":"61616161616161616261616161616161616161616143C0C0C082E082C0C0636163C0C083C0C0436161C0C0C0C0C0C2C0C0C0C061C0C0C0C0C0C0C06161C0C0C0C0C0C0C0C0C0C061C0C0C0C0C0C0C2616103C0C0A3C0C0C003C0C06163C0C0C0C082A2616161616161C1C1C16161812261618161616161616143C0C0C0C0C0C04361C0C0C0C0C0C0C0C0636181C0C0C0C0C0C0C0C081C0C0C0C0C0C0C0C0C06122C0C0C0C0C0E2C0C0C1A2E2C0C0C0C0C0C0C08161E082828282E0C02361A2C0C0C0C0C0C003236161026161610222616102616161610261616102612121214121212121212121212121412121212121","loadword":"","gord-following":true,"jaye":{"y":4,"x":19},"tickword":"","moveword":"","objects":[{"x":7,"func":"term","y":11,"linkword":"term-dual-link","name":""},{"link":1,"y":3,"func":"term","name":"term-exit","linkword":"","x":2},{"link":1,"y":3,"func":"term","name":"term-scan","linkword":"","x":7},{"x":11,"func":"door","y":7,"linkword":"","name":""},{"link":4,"y":7,"func":"scan","name":"","linkword":"","x":12},{"x":15,"func":"door","y":7,"linkword":"","name":""},{"link":3,"y":7,"func":"switch","name":"","linkword":"","x":7},{"x":10,"func":"door","y":5,"linkword":"","name":""},{"link":4,"y":4,"func":"switch","name":"","linkword":"","x":10},{"x":16,"func":"rexx","y":11,"linkword":"","name":""},{"link":8,"y":2,"func":"scan","name":"","linkword":"","x":7},{"link":13,"y":4,"func":"scan","name":"","linkword":"","x":1},{"x":1,"linkentity":"level5","func":"exitdoor","y":5,"linkword":"exitlevel","name":""},{"x":5,"func":"rexx","y":8,"linkword":"","name":""},{"x":7,"func":"tutorial-chair","linkword":"","name":"","y":10},{"link":6,"x":8,"y":7,"linkword":"","name":"","func":"switch"},{"link":2,"x":6,"y":7,"linkword":"","name":"","func":"switch"}]} \ No newline at end of file diff --git a/game/map5.json b/game/map5.json deleted file mode 100644 index 01c244a..0000000 --- a/game/map5.json +++ /dev/null @@ -1 +0,0 @@ -{"neut":{"y":6,"x":20},"map":"21616161228161616161616161616161616162612102E0C0C0C0C0E0C04361C0C0C0C0C0A3C0C0612161C0C0C0C0C0C0C0C022C0C0C08282C0828261416103C0C0C0C0C0C0C081C0C0C0C0C0C0C0C2C1210203C0C0C0C0C0C04361C0C0C08282838282622161616161816161616161618161616161616161216163C0C0C0C0E0C0C06143C0C0C0C0C0C063612102C0C0C0C0C0C0C0C061C0C0C0C0C0C0C0C0814161C0C0C0C0E2C0E2836101C0C0C0C0C0C0C0C1216143C0C0C082A2A2A281C0C0C043034382E26121610261610261C16102616161026161610261612121212121214121212121212121214121212121","loadword":"","tickword":"doortimer-tick","jaye":{"y":5,"x":19},"gord-following":true,"moveword":"move-garbagerexx","objects":[{"x":17,"func":"garbagerexx","name":"south-rexx","linkword":"","y":11},{"x":17,"func":"garbagerexx","name":"","linkword":"","y":8},{"link":13,"x":20,"y":9,"linkword":"","name":"timedswitch","func":"do-timedswitch"},{"link":16,"x":20,"y":4,"linkword":"","name":"","func":"switch"},{"link":9,"x":12,"y":4,"linkword":"","name":"","func":"term"},{"x":10,"func":"healthyrexx","name":"","linkword":"","y":4},{"link":17,"x":8,"y":2,"linkword":"","name":"","func":"switch"},{"link":10,"x":8,"y":6,"linkword":"","name":"","func":"term"},{"link":5,"x":8,"y":11,"linkword":"","name":"","func":"term"},{"link":8,"x":3,"y":11,"linkword":"","name":"","func":"term"},{"link":12,"x":5,"y":12,"linkword":"","name":"","func":"scan"},{"x":6,"linkentity":"level6","func":"exitdoor","name":"","linkword":"exitlevel","y":12},{"x":6,"func":"door","name":"","linkword":"","y":7},{"x":11,"func":"explodingdoor","name":"","linkword":"","y":9},{"link":14,"x":11,"y":10,"linkword":"","name":"","func":"scan"},{"x":13,"func":"door","name":"","linkword":"","y":7},{"x":11,"func":"door","name":"","linkword":"","y":3}]} \ No newline at end of file diff --git a/game/map6.json b/game/map6.json deleted file mode 100644 index 1493c9c..0000000 --- a/game/map6.json +++ /dev/null @@ -1 +0,0 @@ -{"neut":{"y":1,"x":5},"map":"210261616161616161616161812261616161E1614161C2C081C0C0C0C0C0C0C0C0C0E0C081C0C0622161E04384C0C0C043C0C0C043C0C0C0848201612102616161C0C061610281026161C0C0616161612161C2C0C0C0C061E2C0C0C0A261C0C0C0C0E2612161E003C0C0C061C0C0C0C08261C0C0C003E06121026161C0C0C061E08282E2E061C0C0C06161612161C2C0C0C0C06103C0C0C02361C0C0C0C0C2614161E003C0C0C0616102C1026161C0C0C003E06121026184C0C0C0C043C043C063C0C0C0846161612161E081C0C0C0C0C0C0C0C0C0C0C0C081C083612161612261816161616161616161616161616161","loadword":"","tickword":"","jaye":{"y":2,"x":6},"gord-following":true,"moveword":"","objects":[{"x":3,"func":"c4","y":10,"name":"","linkword":"linkloop","link":6},{"x":5,"func":"keypad2","y":10,"name":"","linkword":"","link":3},{"x":5,"func":"door","y":11,"name":"","linkword":""},{"x":11,"func":"door","y":9,"name":"","linkword":""},{"x":13,"func":"c9","y":6,"name":"","linkword":"linkloop","link":17},{"x":9,"func":"c8","y":6,"name":"","linkword":"linkloop","link":5},{"link":23,"y":4,"func":"switch","linkword":"","name":"","x":11},{"x":3,"func":"c3","y":7,"name":"","linkword":"linkloop","link":1},{"x":3,"func":"c2","y":4,"name":"","linkword":"linkloop","link":8},{"x":3,"func":"c1","y":2,"name":"","linkword":"linkloop","link":23},{"x":4,"func":"door","y":2,"name":"","linkword":""},{"x":4,"func":"keypad1","y":3,"name":"","linkword":"","link":11},{"x":4,"func":"scan","y":1,"name":"","linkword":"","link":4},{"x":17,"func":"door","y":2,"name":"","linkword":""},{"x":17,"func":"keypad3","y":3,"name":"","linkword":"","link":14},{"x":19,"func":"rexx","y":2,"name":"","linkword":""},{"x":19,"func":"c5","y":4,"name":"","linkword":"linkloop","link":18},{"x":19,"func":"c6","y":7,"name":"","linkword":"linkloop","link":19},{"x":19,"func":"c7","y":10,"name":"firewall","linkword":"linkloop","link":10},{"x":19,"func":"switch","y":12,"name":"","linkword":"","link":19},{"x":17,"func":"keypad4","y":10,"name":"","linkword":"","link":22},{"x":17,"func":"door","y":11,"name":"","linkword":""},{"x":15,"func":"cx","y":11,"name":"","linkword":"linkloop","link":9},{"x":14,"func":"scan","y":12,"name":"","linkword":"","link":25},{"x":13,"linkentity":"","func":"exitdoor","y":12,"name":"","linkword":"endgame"}]} \ No newline at end of file diff --git a/game/portraits.json b/game/portraits.json deleted file mode 100644 index fa2f3ab..0000000 --- a/game/portraits.json +++ /dev/null @@ -1 +0,0 @@ -[{"gfx":"8080808080E0E0F0F8FC2CBCACACACAC80809CFEFFFFFFD7D5D5555D4F5DD5D5BCB8B8B8F8F8F8F8FCFCFEFE86D0D0D495D5D5E5D5D5D5D7C797D7D0AAAAAAAA808086BFFFFFFFFAEAAA2A2E3C2EAAAA80808080808183878787058F8D8D8D8FAAAAAAA7AAAAEAEAE2E8EA8AD5D5D5D58F8F8787878F8F8F9F9FBFBE808A8AAA","label":"pjaye","flags":[]},{"gfx":"00002020000000004808080800202028004040011404450144010805445420352020000808084800000000202000000020544405080144014504140140400000000202012921220122011021222A052D00000405000001011211101100040415052A222110012201222129010202000004040011101112010100000504000000","label":"pneut","flags":[]},{"gfx":"80C090808484848484848484A8A8A8A8AA8080808080D4D4D5D5F59DFDD5D5D5A8A08080808080808080808080E0F8FC95D5D5D5F595D5D4D084D4D4D4D7FFFFD58080808080AAAAAAAABAE2FAAAAAA280828880A0A0A0A1A1A1A1A195959595A0AAAAAAAFA8AAAA8AA0AAAAAAEAFFFF95858181818181808080808080879FBF","label":"pgord","flags":[]},{"gfx":"808080808080C0C0C0C0C0C0C0C0C0C0808080808080AAAAFAFAFAAAAAAAAAAA8080808080C0D0D0D0D0D0D0D0D0D0D0A0A0A0A0A0AAAAAAA8A8A8A8A8A8A8A8808080808080D5D5DFDFDFD5D5D5D5D5808080808080828282828282828282828585858585D5D5D595959595959595958080808080828A8A8A8A8A8A8A8A8A8A","label":"prexx","flags":[]},{"gfx":"808080F8F8F8F8F8F8B8B8F8F8B8B8F8808080FFFFFFFFDFDFFDFDDFDFFDFDDFF8B8B8F8F8B8B8F8F8F8F8F8F8D0D080DFFDFDDFDFFDFDDFDFFFFFFFFFAAAA80808080FFFFFFFFFEFEAFAFFEFEAFAFFE8080808FAFAFAFAFAFAFAFAFAFAFAFAFFEAFAFFEFEAFAFFEFEFFFFFFFFD5D580AFAFAFAFAFAFAFAFAFAFAFAFAFAAAA80","label":"ppady","flags":[]},{"gfx":"0080C07070303030303030303030303000AAAA7F0045001400150051005400003030707000703018187C4C7E000000000000007F007F0000007F017F0000000000D5D57F400A0028000A002000080000008A8A8B8B8B8B8B8B8B8B8B8B8B8B8B0000407F007F6030301F187F000000008B8B8B03000F0C060603030100000000","label":"pterm","flags":[]},{"gfx":"80808080808080A0C0A0C0A0808088AA8080808080808185AA858285C1D0A0D0D4AA94AA94AA888080A0C0A0C0A08080AAD2A2D2A2D2A2D2C185AA858285818080A0A8D0A8A09090D581A1C1A0C2A1C280808180818080808081858285828582A1828182959291929090D5808080808085818080808084958A958A958A958480","label":"plibb","flags":[]},{"gfx":"808080808080808088A082A8A0AAA8AA849494D4D4D4D0D5D4D1D5D58595D5D5AAAA8AAA8AAA82AA828A808280808080D4D5D4D5D4D5D4D4D4D4D0C0C0C080808080808080A288A2A08AA2AAAAAAAAAA84949495958585808181848184848581AAAAAA8AAA8AAA8AAAAAA8A2A2AAAA8080818181818181818585818185818080","label":"pchuck","flags":[]}] \ No newline at end of file diff --git a/game/tiles.fnl b/game/tiles.fnl index 6c80f1b..ec0593b 100644 --- a/game/tiles.fnl +++ b/game/tiles.fnl @@ -1,62 +1,37 @@ (local util (require :lib.util)) (local lume (require :lib.lume)) +(local files (require :game.files)) -(local flags [:walkable :neutable :debris :sittable]) -(local flag-to-bit {}) -(each [iflag flag (ipairs flags)] - (tset flag-to-bit flag (bit.lshift 1 (- iflag 1)))) +(fn flags [] (or files.game.tileflags [:walkable])) +(fn flag-to-bit [] + (collect [iflag flag (ipairs (flags))] (values flag (bit.lshift 1 (- iflag 1))))) -(local encoded-tile-fields [:gfx :neut :mask]) -(fn convert [tile field method] - (local oldval (. tile field)) - (when oldval - (tset tile field (: oldval method))) - tile) -(fn convert-all [tile method] - (each [_ field (ipairs encoded-tile-fields)] - (convert tile field method)) - tile) - -(fn deserialize [tile] - (match (type tile) - :string {:gfx (tile:fromhex) :flags {}} - :table (convert-all tile :fromhex))) - -(fn serialize [tile] (convert-all (lume.clone tile) :tohex)) - -(local fn-tiles "game/tiles.json") -(local fn-portraits "game/portraits.json") -(local fn-font "game/font.json") - -(fn loadgfx [filename] (lume.map (util.readjson filename) deserialize)) -(fn savegfx [filename gfx] (util.writejson filename (lume.map gfx serialize))) - -(fn appendgfx [org gfx ?key ?ignore-labels] +(fn appendgfx [org gfx ?key ?label-prefix] (each [_ g (ipairs gfx)] - (when (and g.label (not ?ignore-labels)) (org:append g.label)) + (when g.label (org:append (.. (or ?label-prefix "") g.label))) (org:append [:bytes (. g (or ?key :gfx))]))) (fn appendtiles [org] - (local tiles (loadgfx fn-tiles)) - (org:append [:align 0x100] :jaye-tileset) - (appendgfx org tiles) - (org:append [:align 0x100] :neut-tileset) - (appendgfx org tiles :neut true) - (appendgfx org (loadgfx fn-portraits)) + (local tiles files.game.tiles) + (local flag-lookup (flag-to-bit)) + (each [tileset key (pairs (or files.game.tilesets {:tileset :gfx}))] + (org:append [:align 0x100] tileset) + (appendgfx org tiles key (if (= key :gfx) nil (.. key :-)))) + (appendgfx org files.game.portraits nil :portrait-) (org:append :tileflags) (each [_ tile (ipairs tiles)] (var flags 0) (each [flag _ (pairs tile.flags)] - (set flags (bit.bor flags (. flag-to-bit flag)))) + (set flags (bit.bor flags (. flag-lookup flag)))) (org:append [:db flags]))) (fn append-portraitwords [vm ?overrides] (local overrides (or ?overrides {})) - (each [_ p (ipairs (loadgfx fn-portraits))] - (let [wordname (.. :draw- p.label) + (each [_ p (ipairs files.game.portraits)] + (let [wordname (.. :draw-portrait- p.label) override (. overrides p.label)] - (vm:word (.. :draw- p.label) :show-footer - (if override (override p.label) [:vm :lit p.label]) + (vm:word wordname :show-footer + (if override (override p.label) [:vm :lit (.. :portrait- p.label)]) :draw-portrait)))) (fn encode-yx [xy] @@ -79,6 +54,6 @@ (if (= tile.label label) (encode-itile itile) (find-itile tiles label (+ itile 1)))) -{: loadgfx : savegfx : appendtiles : appendgfx : append-portraitwords : flags : flag-to-bit : find-itile - : fn-tiles : fn-portraits : fn-font : encode-yx : encode-itile : decode-itile} +{: appendtiles : appendgfx : append-portraitwords : flags : flag-to-bit : find-itile + : encode-yx : encode-itile : decode-itile} diff --git a/game/tiles.json b/game/tiles.json deleted file mode 100644 index ce81f65..0000000 --- a/game/tiles.json +++ /dev/null @@ -1 +0,0 @@ -[{"neut":"5F5F1F03090923436943230909031F5F7A7A784111104542174245101141787A","label":"neut1","flags":[],"word":"","gfx":"7F7F1F03090923436943230909031F7F7F7F784111104542174245101141787F"},{"neut":"5F1F03090923436943230909031F5F5F7A784111104542174245101141787A7A","label":"neut2","flags":[],"word":"","gfx":"7F1F03090923436943230909031F7F7F7F784111104542174245101141787F7F"},{"neut":"808080C0C0C0E0C0D0C8C04040404080808083058585828A9282820A08081980","label":"jaye-e","flags":[],"word":"","gfx":"808080C0C0C0E0C0D0C8C04040404080808083058585828A9282820A08081980"},{"neut":"8080C020A0A0C0C0D0C8C0501010188080808183838782828A8A920202020380","label":"jaye-w","flags":[],"word":"","gfx":"8080C020A0A0C0C0D0C8C0501010188080808183838782828A8A920202020380"},{"neut":"8080E030B0B098C0D0D0C840404060808080870D8D8D99828A8A920202020780","label":"jaye-s","flags":[],"word":"","gfx":"8080E030B0B098C0D0D0C840404060808080870D8D8D99828A8A920202020780"},{"neut":"8080C0E0E0E0B0C0D0C8C040404060808080838787878D828A92820202020780","label":"jaye-n","flags":[],"word":"","gfx":"8080C0E0E0E0B0C0D0C8C040404060808080838787878D828A92820202020780"},{"neut":"8080808080808080808080808080808080808080808080808080808080808080","label":"t-floor","flags":{"walkable":true},"word":"","gfx":"80808C8080808080B08080808C808080808C80808083B0808080808080868080"},{"neut":"80FC8C8C8C8CFC80FCFEFE8080808080809F989898989F809F8F878080808080","label":"termoff","flags":[],"word":"term","gfx":"007C0C0C0C0C7C007C7E7EAA88888800001F181818181F001F0F979584848400"},{"neut":"507C2C2C2C2C7C557D7D7E005F5F5F5F0A3F353535353F2A3F2F67707A7A7A7A","label":"termon","flags":{"neutable":true},"word":"term","gfx":"007C2C0C0C2C7C007C7E7EAA88888800001F18191C191F001F0F979584848400"},{"neut":"D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","label":"","flags":[],"word":"","gfx":"D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},{"neut":"D5D5D5D581F9F9F9F9C195D5D5D5D5D5AAAAAAAAAA809F9F9F9F80AAAAAAAAAA","label":"","flags":[],"word":"","gfx":"D5D5D5D5D5F5F5FDDDD5D5D5D5D5D5D5AAAAAAAAAEAEBFBFBFABAAAAAAAAAAAA"},{"neut":"5F5F5F5F5F5F57555555575F5F5F5F5F7A7A7A7A7A7A6A2A2A2A6A7A7A7A7A7A","label":"","flags":{"neutable":true},"word":"","gfx":"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"},{"neut":"8080F8F8F8F8F8F8F8F8F8F8F8F8808080808F8F8F8F8F8F8F8F8F8F8F8F8080","label":"doorclosed","flags":[],"word":"door","gfx":"FF8FA7B3B3B3B3B3B3B3B3B3B3B383FFFFF8F2E6E6E6E6E6E6E6E6E6E6E6E0FF"},{"neut":"8080FC8C8C8C8C8C8C8C8C8C8CFC808080809F989898989898989898989F8080","label":"dooropen","flags":{"walkable":true},"word":"door","gfx":"FF8F87838383838383838383838383FFFFF8F0E0E0E0E0E0E0E0E0E0E0E0E0FF"},{"neut":"5F5F5F5F0F0705050505070F5F5F5F5F7A7A7A7A70602020202060707A7A7A7A","label":"switchoff","flags":{"neutable":true},"word":"switch","gfx":"FFFFCFCFCFCF898183838787878FFFFFFFFCE4E4E4E4E0E0E0E0E0E0F0F8FFFF"},{"neut":"5F5F5F5F0F0725252525070F5F5F5F5F7A7A7A7A70602525252560707A7A7A7A","label":"switchon","flags":{"neutable":true},"word":"switch","gfx":"FFFFCFCFCFCF898123232727878FFFFFFFFCE4E4E4E0E0616565656571F8FFFF"},{"neut":"808080D0D0D0D0D0D0D0D0D0D08080808080808A8A8A8A8A8A8A8A8A8A808080","label":"","flags":[],"word":"","gfx":"FFFF83D3D3D3D3D3D3D3D3D3D383FFFFFFFFC0CACACECACBCACACACACAC0FFFF"},{"neut":"5F5F5F5F7F0F0D0D0D0D0F0F7F5F5F5F7A7A7A7A7F703030303070707F7A7A7A","label":"scanoff","flags":{"neutable":true},"word":"scan","gfx":"FFFFAFEBFBFBFBBBBBFBFBFBEBAFFFFFFFFFF5D7DFDFDFDDDDDFDFDFD7F5FFFF"},{"neut":"5F5F5F5F7F2F2D2D2D2D2F2F7F5F5F5F7A7A7A7A7F753535353575757F7A7A7A","label":"scanon","flags":{"neutable":true},"word":"scan","gfx":"FFFF2F2B2B2B6B6B6B6B2B2B2B2FFFFFFFFF755555555757575755555575FFFF"},{"neut":"9EF3C68CB3E7F88086BEE0E0B09898C6F8CFE1B18C8C988E868C99B0E6CE8F99","label":"","flags":[],"word":"","gfx":"FFF3C78FBFFCF98187BFFFFFBF9F9FC7FFCFE1F1FCFCF8FEFEFCF9F0E6CE8F9F"},{"neut":"8080808080D4848484D48484848480808080808080AAA0A1A0AAA0A0A0A08080","label":"","flags":[],"word":"","gfx":"80808C80808080A8AAAAAAA888888880808C8080808380859595958584848480"},{"neut":"8080808080D4808480D48084808480808085848581AA80A180AA80A080A08080","label":"broken-table","flags":{"debris":true},"word":"","gfx":"80808C8080A0A0A8AAAA8AA0A8808080808C8081919090848594959585858080"},{"neut":"808080D09090D0C0C0D48484D490B8808080808A88888A8282AAA0A0AA889C80","label":"t-chair","flags":{"sittable":true},"word":"","gfx":"00005054545450404054545010383800000C0A2A2A2A0A03032A2A0A081C1C00"},{"neut":"808080809080D080C0808480D480B8808085848589808A818280A080AA809C80","label":"","flags":{"debris":true},"word":"","gfx":"0000001C1C10545040606010545454000030070702020A0A0100020A080A0200"},{"neut":"8080808080D48484D48484D48484D48080808080808A88888A88888A88888A80","label":"","flags":[],"word":"","gfx":"80A0A8AA92D2D2AAC2C2AA92D2AA808080959595949494959494959494858080"},{"neut":"8080808080808480D48084808480D48080858485818089808A80888088808A80","label":"","flags":{"debris":true},"word":"","gfx":"80808C808080A8C292AAAAAAAA8AC0D0808C80808083959290959194948580A8"},{"neut":"8080D4C4D4C0D4C4D4C0C0D48484D48080808A888A808A888A80808A88888A80","label":"","flags":[],"word":"","gfx":"80806008282A0800202880A8A8A8A08080980000141501051511819595958580"},{"neut":"8080D4809480D480D480C0808480D4808085848589808A818A80808088808A80","label":"","flags":{"debris":true},"word":"","gfx":"80808C808080A0A8AAAA8AA2AAAAAA80808C0000000330010105051511010514"},{"neut":"80D0D0D0C0D4C4C4CECAC0E0A0808080808A8B8A82AAA2A2F2D2828785808080","label":"t-rexx","flags":[],"word":"rexx","gfx":"80D0D0D0C0D4C4C4CECAC0E0A0808080808A8B8A82AAA2A2F2D2828785808080"},{"neut":"F8989898BE8686868686BEB098F880809F989898FCE0E0E0E0E0FC8C989F8080","label":"t-rexxstop","flags":[],"word":"rexx","gfx":"F8989898BE8686868686BEB098F880809F989898FCE0E0E0E0E0FC8C989F8080"},{"neut":"8080808088A8A8E0E0F0C8C0C0C08080808080808080808183838282829A8080","label":"gord-ground","flags":[],"word":"","gfx":"80808C8088A8A8E0E0F0C8C0CCC08080808C80808080B08183838282829A8080"},{"neut":"808080A0A080F0F0F0E8D0D09090988080808081818083838385828282828680","label":"gord-s","flags":[],"word":"","gfx":"808080A0A080F0F0F0E8D0D09090988080808081818083838385828282828680"},{"neut":"80C09090A080F0F0F0F0D0D09090988080808282818083838383828282828680","label":"gord-n","flags":[],"word":"","gfx":"80C09090A080F0F0F0F0D0D09090988080808282818083838383828282828680"},{"neut":"808080A0A080F0F0F0F0D0D09090B08080808181818081838581808282828680","label":"gord-e","flags":[],"word":"","gfx":"808080A0A080F0F0F0F0D0D09090B08080808181818081838581808282828680"},{"neut":"8080A0A0A080E0F0E8E0D0D09090988080808081818083838383828282828380","label":"gord-w","flags":[],"word":"","gfx":"8080A0A0A080E0F0E8E0D0D09090988080808081818083838383828282828380"},{"neut":"8088A8A880F0F0F8B8D4D4C4C4E486808080808A88888A8181AAA0A0AA889C80","label":"gord-sit","flags":[],"word":"","gfx":"0088A8A8047470F8B8D4D4C4C4E40600000C0A2A2A2A0A81812A2A0A081C1C00"},{"neut":"FF81F9B9E9B9E9B9E9B9E9B9F9F981FFFFE0E7E7E5E7E5E7E5E7E5E7E7E7E0FF","label":"t-keyoff","flags":[],"word":"keypad","gfx":"FF81F9B9E9B9E9B9E9B9E9B9F9F981FFFFE0E7E7E5E7E5E7E5E7E5E7E7E7E0FF"},{"neut":"7F01793969396939693969397979017F7F60676765676567656765676767607F","label":"t-keyon","flags":[],"word":"keypad","gfx":"7F01793969396939693969397979017F7F60676765676567656765676767607F"},{"neut":"5F8FA7C7A389D189838FA7C7A70F5F5F7AF0C4928A91C0F1F2C1928A90427A7A","label":"libb1","flags":[],"word":"","gfx":"FF8FA7C7A389D189838FA7C7A78FFFFFFFF0C4928A91C0F1F2C1928A90C7FFFF"},{"neut":"5F5F8FA7C7A389D189838FA7C7A70F5F7A7AF0C4928A91C0F1F2C1928A90427A","label":"libb2","flags":[],"word":"","gfx":"FFFF8FA7C7A389D189838FA7C7A78FFFFFFFF0C4928A91C0F1F2C1928A90C7FF"},{"neut":"5F5F5F8FA3A9A9A3A7A7A7A7A70F5F5F7A7A7A7A7AE0C5959595909292427A7A","label":"t-chuck","flags":[],"word":"","gfx":"FFFFFF8FA3A9A9A3A7A7A7A7A78FFFFFFFFFFFFFFCE0C5959595909292C7FFFF"},{"neut":"5F5F8FA3A9A9A3A7A7A7A7A78F5F5F5F7A7A7A7860C5959595909292C27A7A7A","label":"t-chuck2","flags":[],"word":"","gfx":"FFFF8FA3A9A9A3A7A7A7A7A78FFFFFFFFFFFFFFCE0C5959595909292C7FFFFFF"}] \ No newline at end of file diff --git a/inspector/init.fnl b/inspector/init.fnl new file mode 100644 index 0000000..9d83900 --- /dev/null +++ b/inspector/init.fnl @@ -0,0 +1,68 @@ +(local util (require :lib.util)) +(local style (require :core.style)) +(local {: defmulti : defmethod} (util.require :lib.multimethod)) +(local {: textbutton} (util.require :editor.imstate)) + +(local inspector (util.hot-table ...)) + +(fn inspector.best-inspector [v] + (var best-inspector nil) + (var best-priority -1) + (each [inspector {: priority : predicate} (pairs inspector.inspectors)] + (when (and (> priority best-priority) (predicate v)) + (set best-inspector inspector) + (set best-priority priority))) + best-inspector) + +(set inspector.inspect + (defmulti (fn [state value view x y w] + (when (= state.inspector nil) + (set state.inspector (inspector.best-inspector value))) + state.inspector) :inspect ...)) + +(fn inspector.register [name priority predicate inspect-func] + (when (= inspector.inspectors nil) + (set inspector.inspectors {})) + (tset inspector.inspectors name {: predicate : priority :inspector inspect-func}) + (defmethod inspector.inspect name inspect-func)) + +(fn inspector.text-height [text ?font] + (let [font (or ?font style.code_font) + (_ newlines) (text:gsub "\n" "\n")] + (* (font:get_height) (+ newlines 1)))) + +(fn inspector.draw-text [font text x y color] + (renderer.draw_text font text x y color) + (inspector.text-height text)) + +(inspector.register :default 0 #true (fn [state value view x y w] + (inspector.draw-text style.code_font (fv value) x y style.text))) + +(inspector.register :table 10 + #(and (= (type $1) :table) (not= (next $1) nil)) + (fn [state tbl view x y w] + (local font style.code_font) + (var h 0) + ; todo: state assumes an .inspector key + ; todo: inspector swapping + ; todo: edit in place? + (fn get-kstate [tbl k state] + (when (= nil state.keys) (set state.keys {})) + (when (= nil (?. state.keys k)) + (util.nested-tset state [:keys k] {:collapsed (= (type (. tbl k)) :table) :children {}})) + (. state.keys k)) + (each [k v (pairs tbl)] + (let [kstate (get-kstate tbl k state) + kstr (fv k) + wk (font:get_width kstr) + xoffset (+ wk style.padding.x) + toggle-collapse (textbutton view kstr x (+ y h)) + hv (if kstate.collapsed + (inspector.draw-text font "..." (+ x xoffset) (+ y h) style.syntax.comment) + (inspector.inspect kstate.children v view (+ x xoffset) (+ y h) (- w xoffset)))] + (when toggle-collapse (set kstate.collapsed (not kstate.collapsed))) + (set h (+ h hv style.padding.y)))) + h)) + +inspector.hot + diff --git a/kfest2021.md b/kfest2021.md new file mode 100644 index 0000000..8692fdf --- /dev/null +++ b/kfest2021.md @@ -0,0 +1,66 @@ +# Honeylisp + +## Introduction +* 286 project +* Honeylisp vision + +## Assembler +### What is an assembler? +* I _thought_ the hard part was going to be converting mnemonics to bytes +* Turns out the hard part is actually converting labels to bytes +* zero-page instructions are a different size! +### How it works +* Represent each opcode as a Fennel data literal +* nest blocks arbitrarily - "lexical scope" +* multi-pass + +## VM +* Forth-ish stack machine +* "direct threaded" inner interpreter +* extend assembler with :vm directive +* "immediate words" are just Fennel functions + +## Lite +* Minimal extensible text editor built in Lua +* love2d port + +## Custom Editors +* imgui style +* show tile editor with map editor +* font + portrait editors based on tile editor +* generate bytes / code with fennel functions! (maps, gfx, etc) + +## MAME Upload +* Nod to Dagen Brock's 2016 KFest talk on GSPlus https://www.youtube.com/watch?v=1LzCmpAanpE +* Integrated Jeejah networked REPL into MAME +* Can send arbitrary Fennel code to MAME to control it +* Poke blocks of memory over the network (nREPL uses bencode from bittorrent, which allows this to be fairly low overhead) + +## Live Code Injection +* The assembled program is an object in memory, which we can extend interactively +* We can write new code and poke it into memory while the old code is running! +* Game code is a loop - we can have a "sync point" at the top of the loop where the state of the game is well-known +* (demo switching video modes, printing debug output, making sounds) + +## Hot Reload +* Because the assembled program is an object in memory + +## Tape generation +* Benefit of building tools in a game engine - I can just output audio +* Extended assembler to accept BASIC tokens and generate linked list of BASIC lines, so the whole thing could be bootstrapped + +## Disk generation +* Take a ProDOS disk image, parse it, and add files to it +* Generate loader program, rest of game can be loaded as an overlay +* New disk image is generated on every build because why not? It's fast + +## Neu] [ower +* Fun tricks: Random number generator (never used for gameplay purposes) = just dump a couple dozen random bytes + +## 8-Bitsy +* Full "code-optional" environment +* Kind of awkward to actually use, but it works! +* Son drew some art +* Improvisational game design! + + diff --git a/lib/fennel.lua b/lib/fennel.lua index 7384b1e..1185044 100644 --- a/lib/fennel.lua +++ b/lib/fennel.lua @@ -333,12 +333,14 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) end local function table_kv_pairs(t) local assoc_3f = false + local i = 1 local kv = {} local insert = table.insert for k, v in pairs(t) do - if (type(k) ~= "number") then + if ((type(k) ~= "number") or (k ~= i)) then assoc_3f = true end + i = (i + 1) insert(kv, {k, v}) end table.sort(kv, sort_keys) @@ -378,21 +380,19 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) end return seen0 end - local function detect_cycle(t, seen) - local seen0 = (seen or {}) - seen0[t] = true - for k, v in pairs(t) do - if ((type(k) == "table") and (seen0[k] or detect_cycle(k, seen0))) then - return true - end - if ((type(v) == "table") and (seen0[v] or detect_cycle(v, seen0))) then - return true + local function detect_cycle(t, seen, _3fk) + if ("table" == type(t)) then + seen[t] = true + local _2_0, _3_0 = next(t, _3fk) + if ((nil ~= _2_0) and (nil ~= _3_0)) then + local k = _2_0 + local v = _3_0 + return (seen[k] or detect_cycle(k, seen) or seen[v] or detect_cycle(v, seen) or detect_cycle(t, seen, k)) end end - return nil end local function visible_cycle_3f(t, options) - return (options["detect-cycles?"] and detect_cycle(t) and save_table(t, options.seen) and (1 < (options.appearances[t] or 0))) + return (options["detect-cycles?"] and detect_cycle(t, {}) and save_table(t, options.seen) and (1 < (options.appearances[t] or 0))) end local function table_indent(t, indent, id) local opener_length = nil @@ -403,7 +403,7 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) end return (indent + opener_length) end - local pp = {} + local pp = nil local function concat_table_lines(elements, options, multiline_3f, indent, table_type, prefix) local indent_str = ("\n" .. string.rep(" ", indent)) local open = nil @@ -422,13 +422,7 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) close = "}" end local oneline = (open .. table.concat(elements, " ") .. close) - local _4_ - if (table_type == "seq") then - _4_ = options["sequential-length"] - else - _4_ = options["associative-length"] - end - if (not options["one-line?"] and (multiline_3f or (#elements > _4_) or ((indent + #oneline) > options["line-length"]))) then + if (not options["one-line?"] and (multiline_3f or ((indent + #oneline) > options["line-length"]))) then return (open .. table.concat(elements, indent_str) .. close) else return oneline @@ -464,7 +458,7 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) else prefix = "" end - local elements = nil + local items = nil do local tbl_0_ = {} for _, _6_0 in pairs(kv) do @@ -473,16 +467,16 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) local v = _7_[2] local _8_ do - local k0 = pp.pp(k, options, (indent0 + 1), true) - local v0 = pp.pp(v, options, (indent0 + slength(k0) + 1)) + local k0 = pp(k, options, (indent0 + 1), true) + local v0 = pp(v, options, (indent0 + slength(k0) + 1)) multiline_3f = (multiline_3f or k0:find("\n") or v0:find("\n")) _8_ = (k0 .. " " .. v0) end tbl_0_[(#tbl_0_ + 1)] = _8_ end - elements = tbl_0_ + items = tbl_0_ end - return concat_table_lines(elements, options, multiline_3f, indent0, "table", prefix) + return concat_table_lines(items, options, multiline_3f, indent0, "table", prefix) end end local function pp_sequence(t, kv, options, indent) @@ -502,7 +496,7 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) else prefix = "" end - local elements = nil + local items = nil do local tbl_0_ = {} for _, _3_0 in pairs(kv) do @@ -511,15 +505,15 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) local v = _4_[2] local _5_ do - local v0 = pp.pp(v, options, indent0) + local v0 = pp(v, options, indent0) multiline_3f = (multiline_3f or v0:find("\n")) _5_ = v0 end tbl_0_[(#tbl_0_ + 1)] = _5_ end - elements = tbl_0_ + items = tbl_0_ end - return concat_table_lines(elements, options, multiline_3f, indent0, "seq", prefix) + return concat_table_lines(items, options, multiline_3f, indent0, "seq", prefix) end end local function concat_lines(lines, options, indent, force_multi_line_3f) @@ -561,7 +555,7 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) end options["visible-cycle?"] = _2_ _ = nil - local lines, force_multi_line_3f = metamethod(t, pp.pp, options, indent) + local lines, force_multi_line_3f = metamethod(t, pp, options, indent) options["visible-cycle?"] = nil local _3_0 = type(lines) if (_3_0 == "string") then @@ -570,7 +564,7 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) return concat_lines(lines, options, indent, force_multi_line_3f) else local _0 = _3_0 - return error("Error: __fennelview metamethod must return a table of lines") + return error("__fennelview metamethod must return a table of lines") end end end @@ -622,31 +616,28 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) return x0 end local function number__3estring(n) - local _2_0, _3_0, _4_0 = math.modf(n) - if ((nil ~= _2_0) and (_3_0 == 0)) then - local int = _2_0 - return tostring(int) - else - local _5_ - do - local frac = _3_0 - _5_ = (((_2_0 == 0) and (nil ~= _3_0)) and (frac < 0)) - end - if _5_ then - local frac = _3_0 - return ("-0." .. tostring(frac):gsub("^-?0.", "")) - elseif ((nil ~= _2_0) and (nil ~= _3_0)) then - local int = _2_0 - local frac = _3_0 - return (int .. "." .. tostring(frac):gsub("^-?0.", "")) - end - end + local _2_0 = string.gsub(tostring(n), ",", ".") + return _2_0 end local function colon_string_3f(s) - return s:find("^[-%w?\\^_!$%&*+./@:|<=>]+$") + return s:find("^[-%w?^_!$%&*+./@|<=>]+$") + end + local function pp_string(str, options, indent) + local escs = nil + local _2_ + if (options["escape-newlines?"] and (#str < (options["line-length"] - indent))) then + _2_ = "\\n" + else + _2_ = "\n" + end + local function _4_(_241, _242) + return ("\\%03d"):format(_242:byte()) + end + escs = setmetatable({["\""] = "\\\"", ["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\\"] = "\\\\", ["\n"] = _2_}, {__index = _4_}) + return ("\"" .. str:gsub("[%c\\\"]", escs) .. "\"") end local function make_options(t, options) - local defaults = {["associative-length"] = 4, ["detect-cycles?"] = true, ["empty-as-sequence?"] = false, ["line-length"] = 80, ["metamethod?"] = true, ["one-line?"] = false, ["sequential-length"] = 10, ["utf8?"] = true, depth = 128} + local defaults = {["detect-cycles?"] = true, ["empty-as-sequence?"] = false, ["escape-newlines?"] = false, ["line-length"] = 80, ["metamethod?"] = true, ["one-line?"] = false, ["prefer-colon?"] = false, ["utf8?"] = true, depth = 128} local overrides = {appearances = count_table_appearances(t, {}), level = 0, seen = {len = 0}} for k, v in pairs((options or {})) do defaults[k] = v @@ -656,34 +647,46 @@ package.preload["fennel.view"] = package.preload["fennel.view"] or function(...) end return defaults end - pp.pp = function(x, options, indent, key_3f) + local function _2_(x, options, indent, colon_3f) local indent0 = (indent or 0) local options0 = (options or make_options(x)) local tv = type(x) - local function _3_() - local _2_0 = getmetatable(x) - if _2_0 then - return _2_0.__fennelview + local function _4_() + local _3_0 = getmetatable(x) + if _3_0 then + return _3_0.__fennelview else - return _2_0 + return _3_0 end end - if ((tv == "table") or ((tv == "userdata") and _3_())) then + if ((tv == "table") or ((tv == "userdata") and _4_())) then return pp_table(x, options0, indent0) elseif (tv == "number") then return number__3estring(x) - elseif ((tv == "string") and key_3f and colon_string_3f(x)) then - return (":" .. x) - elseif (tv == "string") then - return string.format("%q", x) - elseif ((tv == "boolean") or (tv == "nil")) then - return tostring(x) else - return ("#<" .. tostring(x) .. ">") + local function _5_() + if (colon_3f ~= nil) then + return colon_3f + elseif ("function" == type(options0["prefer-colon?"])) then + return options0["prefer-colon?"](x) + else + return options0["prefer-colon?"] + end + end + if ((tv == "string") and colon_string_3f(x) and _5_()) then + return (":" .. x) + elseif (tv == "string") then + return pp_string(x, options0, indent0) + elseif ((tv == "boolean") or (tv == "nil")) then + return tostring(x) + else + return ("#<" .. tostring(x) .. ">") + end end end + pp = _2_ local function view(x, options) - return pp.pp(x, make_options(x, options), 0) + return pp(x, make_options(x, options), 0) end return view end @@ -870,8 +873,8 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct local function set_fn_metadata(arg_list, docstring, parent, fn_name) if utils.root.options.useMetadata then local args = nil - local function _0_(v) - return ("\"%s\""):format(deep_tostring(v)) + local function _0_(_241) + return ("\"%s\""):format(deep_tostring(_241)) end args = utils.map(arg_list, _0_) local meta_fields = {"\"fnl/arglist\"", ("{" .. table.concat(args, ", ") .. "}")} @@ -893,9 +896,30 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end return _0_, not multi, 3 else - return compiler.gensym(scope), true, 2 + return nil, true, 2 end end + local function compile_named_fn(ast, f_scope, f_chunk, parent, index, fn_name, local_3f, arg_name_list, arg_list, docstring) + for i = (index + 1), #ast do + compiler.compile1(ast[i], f_scope, f_chunk, {nval = (((i ~= #ast) and 0) or nil), tail = (i == #ast)}) + end + local _0_ + if local_3f then + _0_ = "local function %s(%s)" + else + _0_ = "%s = function(%s)" + end + compiler.emit(parent, string.format(_0_, fn_name, table.concat(arg_name_list, ", ")), ast) + compiler.emit(parent, f_chunk, ast) + compiler.emit(parent, "end", ast) + set_fn_metadata(arg_list, docstring, parent, fn_name) + utils.hook("fn", ast, f_scope) + return utils.expr(fn_name, "sym") + end + local function compile_anonymous_fn(ast, f_scope, f_chunk, parent, index, arg_name_list, arg_list, docstring, scope) + local fn_name = compiler.gensym(scope) + return compile_named_fn(ast, f_scope, f_chunk, parent, index, fn_name, true, arg_name_list, arg_list, docstring) + end SPECIALS.fn = function(ast, scope, parent) local f_scope = nil do @@ -906,7 +930,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct local f_chunk = {} local fn_sym = utils["sym?"](ast[2]) local multi = (fn_sym and utils["multi-sym?"](fn_sym[1])) - local fn_name, local_fn_3f, index = get_fn_name(ast, scope, fn_sym, multi) + local fn_name, local_3f, index = get_fn_name(ast, scope, fn_sym, multi) local arg_list = compiler.assert(utils["table?"](ast[index]), "expected parameters table", ast) compiler.assert((not multi or not multi["multi-sym-method-call"]), ("unexpected multi symbol " .. tostring(fn_name)), fn_sym) local function get_arg_name(arg) @@ -925,30 +949,18 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct return compiler.assert(false, ("expected symbol for function parameter: %s"):format(tostring(arg)), ast[2]) end end - do - local arg_name_list = utils.map(arg_list, get_arg_name) - local index0, docstring = nil, nil - if ((type(ast[(index + 1)]) == "string") and ((index + 1) < #ast)) then - index0, docstring = (index + 1), ast[(index + 1)] - else - index0, docstring = index, nil - end - for i = (index0 + 1), #ast do - compiler.compile1(ast[i], f_scope, f_chunk, {nval = (((i ~= #ast) and 0) or nil), tail = (i == #ast)}) - end - local _2_ - if local_fn_3f then - _2_ = "local function %s(%s)" - else - _2_ = "%s = function(%s)" - end - compiler.emit(parent, string.format(_2_, fn_name, table.concat(arg_name_list, ", ")), ast) - compiler.emit(parent, f_chunk, ast) - compiler.emit(parent, "end", ast) - set_fn_metadata(arg_list, docstring, parent, fn_name) + local arg_name_list = utils.map(arg_list, get_arg_name) + local index0, docstring = nil, nil + if ((type(ast[(index + 1)]) == "string") and ((index + 1) < #ast)) then + index0, docstring = (index + 1), ast[(index + 1)] + else + index0, docstring = index, nil + end + if fn_name then + return compile_named_fn(ast, f_scope, f_chunk, parent, index0, fn_name, local_3f, arg_name_list, arg_list, docstring) + else + return compile_anonymous_fn(ast, f_scope, f_chunk, parent, index0, arg_name_list, arg_list, docstring, scope) end - utils.hook("fn", ast, f_scope) - return utils.expr(fn_name, "sym") end doc_special("fn", {"name?", "args", "docstring?", "..."}, "Function syntax. May optionally include a name and docstring.\nIf a name is provided, the function will be bound in the current scope.\nWhen called with the wrong number of args, excess args will be discarded\nand lacking args will be nil, use lambda for arity-checked functions.") SPECIALS.lua = function(ast, _, parent) @@ -968,8 +980,9 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct if special_or_macro then return ("print(%q)"):format(doc_2a(special_or_macro, target)) else - local value = tostring(compiler.compile1(ast[2], scope, parent, {nval = 1})[1]) - return ("print(require('%s').doc(%s, '%s'))"):format((utils.root.options.moduleName or "fennel"), value, tostring(ast[2])) + local _0_ = compiler.compile1(ast[2], scope, parent, {nval = 1}) + local value = _0_[1] + return ("print(require('%s').doc(%s, '%s'))"):format((utils.root.options.moduleName or "fennel"), tostring(value), tostring(ast[2])) end end doc_special("doc", {"x"}, "Print the docstring and arglist for a function, macro, or special form.") @@ -1121,7 +1134,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end local cond = tostring(branch.cond) local cond_line = nil - if ((cond == "true") and branch.nested and (i == #branches)) then + if ((cond == "true") and branch.nested and (i == #branches) and not has_else_3f) then cond_line = "else" else cond_line = fstr:format(cond) @@ -1173,9 +1186,23 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end SPECIALS["if"] = if_2a doc_special("if", {"cond1", "body1", "...", "condN", "bodyN"}, "Conditional form.\nTakes any number of condition/body pairs and evaluates the first body where\nthe condition evaluates to truthy. Similar to cond in other lisps.") + local function remove_until_condition(bindings) + if ("until" == bindings[(#bindings - 1)]) then + table.remove(bindings, (#bindings - 1)) + return table.remove(bindings) + end + end + local function compile_until(condition, scope, chunk) + if condition then + local _0_ = compiler.compile1(condition, scope, chunk, {nval = 1}) + local condition_lua = _0_[1] + return compiler.emit(chunk, ("if %s then break end"):format(tostring(condition_lua)), condition) + end + end SPECIALS.each = function(ast, scope, parent) compiler.assert((#ast >= 3), "expected body expression", ast[1]) local binding = compiler.assert(utils["table?"](ast[2]), "expected binding table", ast) + local until_condition = remove_until_condition(binding) local iter = table.remove(binding, #binding) local destructures = {} local new_manglings = {} @@ -1198,6 +1225,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct compiler.destructure(args, raw, ast, sub_scope, chunk, {declaration = true, nomulti = true, symtype = "each"}) end compiler["apply-manglings"](sub_scope, new_manglings, ast) + compile_until(until_condition, sub_scope, chunk) compile_do(ast, sub_scope, chunk, 3) compiler.emit(parent, chunk, ast) return compiler.emit(parent, "end", ast) @@ -1226,6 +1254,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct doc_special("while", {"condition", "..."}, "The classic while loop. Evaluates body until a condition is non-truthy.") local function for_2a(ast, scope, parent) local ranges = compiler.assert(utils["table?"](ast[2]), "expected binding table", ast) + local until_condition = remove_until_condition(ast[2]) local binding_sym = table.remove(ast[2], 1) local sub_scope = compiler["make-scope"](scope) local range_args = {} @@ -1236,6 +1265,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct range_args[i] = tostring(compiler.compile1(ranges[i], sub_scope, parent, {nval = 1})[1]) end compiler.emit(parent, ("for %s = %s do"):format(compiler["declare-local"](binding_sym, {}, sub_scope, ast), table.concat(range_args, ", ")), ast) + compile_until(until_condition, sub_scope, chunk) compile_do(ast, sub_scope, chunk, 3) compiler.emit(parent, chunk, ast) return compiler.emit(parent, "end", ast) @@ -1361,40 +1391,41 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct return utils.expr(name, "sym") end doc_special("hashfn", {"..."}, "Function literal shorthand; args are either $... OR $1, $2, etc.") - local function define_arithmetic_special(name, zero_arity, unary_prefix, lua_name) - do - local padded_op = (" " .. (lua_name or name) .. " ") - local function _0_(ast, scope, parent) - local len = #ast - if (len == 1) then - compiler.assert((zero_arity ~= nil), "Expected more than 0 arguments", ast) - return utils.expr(zero_arity, "literal") + local function arithmetic_special(name, zero_arity, unary_prefix, ast, scope, parent) + local len = #ast + if (len == 1) then + compiler.assert(zero_arity, "Expected more than 0 arguments", ast) + return utils.expr(zero_arity, "literal") + else + local operands = {} + local padded_op = (" " .. name .. " ") + for i = 2, len do + local subexprs = nil + local _0_ + if (i ~= len) then + _0_ = 1 else - local operands = {} - for i = 2, len do - local subexprs = nil - local _1_ - if (i ~= len) then - _1_ = 1 - else - _1_ = nil - end - subexprs = compiler.compile1(ast[i], scope, parent, {nval = _1_}) - utils.map(subexprs, tostring, operands) - end - if (#operands == 1) then - if unary_prefix then - return ("(" .. unary_prefix .. padded_op .. operands[1] .. ")") - else - return operands[1] - end - else - return ("(" .. table.concat(operands, padded_op) .. ")") - end + _0_ = nil end + subexprs = compiler.compile1(ast[i], scope, parent, {nval = _0_}) + utils.map(subexprs, tostring, operands) + end + if (#operands == 1) then + if unary_prefix then + return ("(" .. unary_prefix .. padded_op .. operands[1] .. ")") + else + return operands[1] + end + else + return ("(" .. table.concat(operands, padded_op) .. ")") end - SPECIALS[name] = _0_ end + end + local function define_arithmetic_special(name, zero_arity, unary_prefix, lua_name) + local function _0_(...) + return arithmetic_special((lua_name or name), zero_arity, unary_prefix, ...) + end + SPECIALS[name] = _0_ return doc_special(name, {"a", "b", "..."}, "Arithmetic operator; works the same as Lua but accepts more arguments.") end define_arithmetic_special("+", "0") @@ -1405,16 +1436,57 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct define_arithmetic_special("%") define_arithmetic_special("/", nil, "1") define_arithmetic_special("//", nil, "1") - define_arithmetic_special("lshift", nil, "1", "<<") - define_arithmetic_special("rshift", nil, "1", ">>") - define_arithmetic_special("band", "0", "0", "&") - define_arithmetic_special("bor", "0", "0", "|") - define_arithmetic_special("bxor", "0", "0", "~") - doc_special("lshift", {"x", "n"}, "Bitwise logical left shift of x by n bits; only works in Lua 5.3+.") - doc_special("rshift", {"x", "n"}, "Bitwise logical right shift of x by n bits; only works in Lua 5.3+.") - doc_special("band", {"x1", "x2"}, "Bitwise AND of arguments; only works in Lua 5.3+.") - doc_special("bor", {"x1", "x2"}, "Bitwise OR of arguments; only works in Lua 5.3+.") - doc_special("bxor", {"x1", "x2"}, "Bitwise XOR of arguments; only works in Lua 5.3+.") + local function bitop_special(native_name, lib_name, zero_arity, unary_prefix, ast, scope, parent) + if (#ast == 1) then + return compiler.assert(zero_arity, "Expected more than 0 arguments.", ast) + else + local len = #ast + local operands = {} + local padded_native_name = (" " .. native_name .. " ") + local prefixed_lib_name = ("bit." .. lib_name) + for i = 2, len do + local subexprs = nil + local _0_ + if (i ~= len) then + _0_ = 1 + else + _0_ = nil + end + subexprs = compiler.compile1(ast[i], scope, parent, {nval = _0_}) + utils.map(subexprs, tostring, operands) + end + if (#operands == 1) then + if utils.root.options.useBitLib then + return (prefixed_lib_name .. "(" .. unary_prefix .. ", " .. operands[1] .. ")") + else + return ("(" .. unary_prefix .. padded_native_name .. operands[1] .. ")") + end + else + if utils.root.options.useBitLib then + return (prefixed_lib_name .. "(" .. table.concat(operands, ", ") .. ")") + else + return ("(" .. table.concat(operands, padded_native_name) .. ")") + end + end + end + end + local function define_bitop_special(name, zero_arity, unary_prefix, native) + local function _0_(...) + return bitop_special(native, name, zero_arity, unary_prefix, ...) + end + SPECIALS[name] = _0_ + return nil + end + define_bitop_special("lshift", nil, "1", "<<") + define_bitop_special("rshift", nil, "1", ">>") + define_bitop_special("band", "0", "0", "&") + define_bitop_special("bor", "0", "0", "|") + define_bitop_special("bxor", "0", "0", "~") + doc_special("lshift", {"x", "n"}, "Bitwise logical left shift of x by n bits.\nOnly works in Lua 5.3+ or LuaJIT with the --use-bit-lib flag.") + doc_special("rshift", {"x", "n"}, "Bitwise logical right shift of x by n bits.\nOnly works in Lua 5.3+ or LuaJIT with the --use-bit-lib flag.") + doc_special("band", {"x1", "x2", "..."}, "Bitwise AND of any number of arguments.\nOnly works in Lua 5.3+ or LuaJIT with the --use-bit-lib flag.") + doc_special("bor", {"x1", "x2", "..."}, "Bitwise OR of any number of arguments.\nOnly works in Lua 5.3+ or LuaJIT with the --use-bit-lib flag.") + doc_special("bxor", {"x1", "x2", "..."}, "Bitwise XOR of any number of arguments.\nOnly works in Lua 5.3+ or LuaJIT with the --use-bit-lib flag.") define_arithmetic_special("or", "false") define_arithmetic_special("and", "true") doc_special("and", {"a", "b", "..."}, "Boolean operator; works the same as Lua but accepts more arguments.") @@ -1466,7 +1538,6 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct define_comparator_special("<=") define_comparator_special("=", "==") define_comparator_special("not=", "~=", "or") - SPECIALS["~="] = SPECIALS["not="] local function define_unary_special(op, realop) local function opfn(ast, scope, parent) compiler.assert((#ast == 2), "expected one argument", ast) @@ -1479,9 +1550,10 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct define_unary_special("not", "not ") doc_special("not", {"x"}, "Logical operator; works the same as Lua.") define_unary_special("bnot", "~") - doc_special("bnot", {"x"}, "Bitwise negation; only works in Lua 5.3+.") + doc_special("bnot", {"x"}, "Bitwise negation; only works in Lua 5.3+ or LuaJIT with the --use-bit-lib flag.") define_unary_special("length", "#") doc_special("length", {"x"}, "Returns the length of a table or string.") + SPECIALS["~="] = SPECIALS["not="] SPECIALS["#"] = SPECIALS.length SPECIALS.quote = function(ast, scope, parent) compiler.assert((#ast == 2), "expected one argument") @@ -1496,7 +1568,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end doc_special("quote", {"x"}, "Quasiquote the following form. Only works in macro/compiler scope.") local already_warned_3f = {} - local compile_env_warning = ("WARNING: Attempting to %s %s in compile" .. " scope.\nIn future versions of Fennel this will not" .. " be allowed without the\n--no-compiler-sandbox flag" .. " or passing a :compilerEnv globals table in options.\n") + local compile_env_warning = table.concat({"WARNING: Attempting to %s %s in compile scope.", "In future versions of Fennel this will not be allowed without the", "--no-compiler-sandbox flag or passing a :compilerEnv globals table", "in the options.\n"}, "\n") local function compiler_env_warn(_, key) local v = _G[key] if (v and io and io.stderr and not already_warned_3f[key]) then @@ -1505,8 +1577,22 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end return v end - local safe_compiler_env = setmetatable({assert = assert, bit = rawget(_G, "bit"), error = error, getmetatable = getmetatable, ipairs = ipairs, math = math, next = next, pairs = pairs, pcall = pcall, print = print, rawequal = rawequal, rawget = rawget, rawlen = rawget(_G, "rawlen"), rawset = rawset, select = select, setmetatable = setmetatable, string = string, table = table, tonumber = tonumber, tostring = tostring, type = type, xpcall = xpcall}, {__index = compiler_env_warn}) - local function make_compiler_env(ast, scope, parent) + local function safe_getmetatable(tbl) + local mt = getmetatable(tbl) + assert((mt ~= getmetatable("")), "Illegal metatable access!") + return mt + end + local safe_require = nil + local function safe_compiler_env(strict_3f) + local _1_ + if strict_3f then + _1_ = nil + else + _1_ = compiler_env_warn + end + return setmetatable({assert = assert, bit = rawget(_G, "bit"), error = error, getmetatable = safe_getmetatable, ipairs = ipairs, math = utils.copy(math), next = next, pairs = pairs, pcall = pcall, print = print, rawequal = rawequal, rawget = rawget, rawlen = rawget(_G, "rawlen"), rawset = rawset, require = safe_require, select = select, setmetatable = setmetatable, string = utils.copy(string), table = utils.copy(table), tonumber = tonumber, tostring = tostring, type = type, xpcall = xpcall}, {__index = _1_}) + end + local function make_compiler_env(ast, scope, parent, strict_3f) local function _1_() return compiler.scopes.macro end @@ -1524,7 +1610,9 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct local _6_ do local _5_0 = utils.root.options - if ((type(_5_0) == "table") and (nil ~= _5_0.compilerEnv)) then + if ((type(_5_0) == "table") and (_5_0["compiler-env"] == "strict")) then + _6_ = safe_compiler_env(true) + elseif ((type(_5_0) == "table") and (nil ~= _5_0.compilerEnv)) then local compilerEnv = _5_0.compilerEnv _6_ = compilerEnv elseif ((type(_5_0) == "table") and (nil ~= _5_0["compiler-env"])) then @@ -1532,7 +1620,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct _6_ = compiler_env else local _ = _5_0 - _6_ = safe_compiler_env + _6_ = safe_compiler_env(false) end end return setmetatable({["assert-compile"] = compiler.assert, ["get-scope"] = _1_, ["in-scope?"] = _2_, ["list?"] = utils["list?"], ["multi-sym?"] = utils["multi-sym?"], ["sequence?"] = utils["sequence?"], ["sym?"] = utils["sym?"], ["table?"] = utils["table?"], ["varg?"] = utils["varg?"], _AST = ast, _CHUNK = parent, _IS_COMPILER = true, _SCOPE = scope, _SPECIALS = compiler.scopes.global.specials, _VARARG = utils.varg(), gensym = _3_, list = utils.list, macroexpand = _4_, sequence = utils.sequence, sym = utils.sym, unpack = unpack, view = view}, {__index = _6_}) @@ -1592,10 +1680,31 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end return allowed end - local function compiler_env_domodule(modname, env, _3fast) - local filename = compiler.assert(search_module(modname), (modname .. " module not found."), _3fast) - local globals = macro_globals(env, current_global_names()) - return utils["fennel-module"].dofile(filename, {allowedGlobals = globals, env = env, scope = compiler.scopes.compiler, useMetadata = utils.root.options.useMetadata}, modname, filename) + local function default_macro_searcher(module_name) + local _1_0 = search_module(module_name) + if (nil ~= _1_0) then + local filename = _1_0 + local function _2_(...) + return utils["fennel-module"].dofile(filename, {env = "_COMPILER"}, ...) + end + return _2_, filename + end + end + local macro_searchers = {default_macro_searcher} + local function search_macro_module(modname, n) + local _1_0 = macro_searchers[n] + if (nil ~= _1_0) then + local f = _1_0 + local _2_0, _3_0 = f(modname) + if ((nil ~= _2_0) and true) then + local loader = _2_0 + local _3ffilename = _3_0 + return loader, _3ffilename + else + local _ = _2_0 + return search_macro_module(modname, (n + 1)) + end + end end local macro_loaded = {} local function metadata_only_fennel(modname) @@ -1603,14 +1712,16 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct return {metadata = compiler.metadata} end end - safe_compiler_env.require = function(modname) - local function _1_() - local mod = compiler_env_domodule(modname, safe_compiler_env) - macro_loaded[modname] = mod - return mod + local function _1_(modname) + local function _2_() + local loader, filename = search_macro_module(modname, 1) + compiler.assert(loader, (modname .. " module not found.")) + macro_loaded[modname] = loader(modname, filename) + return macro_loaded[modname] end - return (macro_loaded[modname] or metadata_only_fennel(modname) or _1_()) + return (macro_loaded[modname] or metadata_only_fennel(modname) or _2_()) end + safe_require = _1_ local function add_macros(macros_2a, ast, scope) compiler.assert(utils["table?"](macros_2a), "expected macros to be table", ast) for k, v in pairs(macros_2a) do @@ -1622,12 +1733,14 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct SPECIALS["require-macros"] = function(ast, scope, parent, real_ast) compiler.assert((#ast == 2), "Expected one module name argument", (real_ast or ast)) local filename = (ast[2].filename or ast.filename) - local modname_code = compiler.compile(ast[2]) - local modname = load_code(modname_code, nil, filename)(utils.root.options["module-name"], filename) + local modname_chunk = load_code(compiler.compile(ast[2]), nil, filename) + local modname = modname_chunk(utils.root.options["module-name"], filename) compiler.assert((type(modname) == "string"), "module name must compile to string", (real_ast or ast)) if not macro_loaded[modname] then local env = make_compiler_env(ast, scope, parent) - macro_loaded[modname] = compiler_env_domodule(modname, env, ast) + local loader, filename0 = search_macro_module(modname, 1) + compiler.assert(loader, (modname .. " module not found."), ast) + macro_loaded[modname] = loader(modname, filename0) end return add_macros(macro_loaded[modname], ast, scope, parent) end @@ -1666,10 +1779,10 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct return error(..., 0) end end - local function _1_() + local function _2_() return f:read("*all"):gsub("[\13\n]*$", "") end - src = close_handlers_0_(xpcall(_1_, (package.loaded.fennel or debug).traceback)) + src = close_handlers_0_(xpcall(_2_, (package.loaded.fennel or debug).traceback)) end local ret = utils.expr(("require(\"" .. mod .. "\")"), "statement") local target = ("package.preload[%q]"):format(mod) @@ -1706,13 +1819,13 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end else local mod = load_code(("return " .. modexpr[1]))() - local function _2_() - local _1_0 = search_module(mod) - if (nil ~= _1_0) then - local fennel_path = _1_0 + local function _3_() + local _2_0 = search_module(mod) + if (nil ~= _2_0) then + local fennel_path = _2_0 return include_path(ast, opts, fennel_path, mod, true) else - local _ = _1_0 + local _ = _2_0 local lua_path = search_module(mod, package.path) if lua_path then return include_path(ast, opts, lua_path, mod, false) @@ -1723,7 +1836,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct end end end - return (include_circular_fallback(mod, modexpr, opts.fallback, ast) or utils.root.scope.includes[mod] or _2_()) + return (include_circular_fallback(mod, modexpr, opts.fallback, ast) or utils.root.scope.includes[mod] or _3_()) end end doc_special("include", {"module-name-literal"}, "Like require but load the target module during compilation and embed it in the\nLua output. The module must be a string literal and resolvable at compile time.") @@ -1747,7 +1860,7 @@ package.preload["fennel.specials"] = package.preload["fennel.specials"] or funct return val end doc_special("eval-compiler", {"..."}, "Evaluate the body at compile-time. Use the macro system instead if possible.") - return {["current-global-names"] = current_global_names, ["load-code"] = load_code, ["macro-loaded"] = macro_loaded, ["make-compiler-env"] = make_compiler_env, ["make-searcher"] = make_searcher, ["search-module"] = search_module, ["wrap-env"] = wrap_env, doc = doc_2a} + return {["current-global-names"] = current_global_names, ["load-code"] = load_code, ["macro-loaded"] = macro_loaded, ["macro-searchers"] = macro_searchers, ["make-compiler-env"] = make_compiler_env, ["make-searcher"] = make_searcher, ["search-module"] = search_module, ["wrap-env"] = wrap_env, doc = doc_2a} end package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or function(...) local utils = require("fennel.utils") @@ -1920,7 +2033,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local name = utils.deref(symbol) if (io and io.stderr and name:find("&") and not already_warned[symbol]) then already_warned[symbol] = true - do end (io.stderr):write(("-- Warning: & will not be allowed in identifier names in " .. "future versions: " .. symbol.filename .. ":" .. symbol.line .. "\n")) + do end (io.stderr):write(("-- Warning: & will not be allowed in identifier names in " .. "future versions: " .. (symbol.filename or "unknown") .. ":" .. (symbol.line or "?") .. "\n")) end assert_compile(not (scope.specials[name] or scope.macros[name]), ("local %s was overshadowed by a special form or macro"):format(name), ast) return assert_compile(not utils["quoted?"](symbol), string.format("macro tried to bind %s without gensym", name), symbol) @@ -1955,7 +2068,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct if (local_3f and scope.symmeta[parts[1]]) then scope.symmeta[parts[1]]["used"] = true end - assert_compile((not reference_3f or local_3f or global_allowed(parts[1])), ("unknown global in strict mode: " .. parts[1]), symbol) + assert_compile((not reference_3f or local_3f or global_allowed(parts[1])), ("unknown global in strict mode: " .. tostring(parts[1])), symbol) if (allowed_globals and not local_3f) then utils.root.scope.refedglobals[parts[1]] = true end @@ -1985,6 +2098,10 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct return utils.map(chunk, peephole) end end + local function ast_source(ast) + local m = getmetatable(ast) + return ((m and m.line and m) or (("table" == type(ast)) and ast) or {}) + end local function flatten_chunk_correlated(main_chunk) local function flatten(chunk, out, last_line, file) local last_line0 = last_line @@ -1993,8 +2110,9 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct else for _, subchunk in ipairs(chunk) do if (subchunk.leaf or (#subchunk > 0)) then - if (subchunk.ast and (file == subchunk.ast.file)) then - last_line0 = math.max(last_line0, (subchunk.ast.line or 0)) + local source = ast_source(subchunk.ast) + if (file == source.file) then + last_line0 = math.max(last_line0, (source.line or 0)) end last_line0 = flatten(subchunk, out, last_line0, file) end @@ -2016,7 +2134,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local code = chunk.leaf local info = chunk.ast if sm then - table.insert(sm, ((info and info.line) or ( - 1))) + table.insert(sm, {(info and info.filename), (info and info.line)}) end return code else @@ -2065,7 +2183,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local sm = {} local ret = flatten_chunk(sm, chunk0, options.indent, 0) if sm then - sm.short_src = make_short_src((options.filename or options.source or ret)) + sm.short_src = (options.filename or make_short_src((options.source or ret))) if options.filename then sm.key = ("@" .. options.filename) else @@ -2104,15 +2222,20 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local function exprs1(exprs) return table.concat(utils.map(exprs, 1), ", ") end + local function disambiguate_parens(code, chunk) + if (code:byte() == 40) then + return ("do end " .. code) + else + return code + end + end local function keep_side_effects(exprs, chunk, start, ast) - local start0 = (start or 1) - for j = start0, #exprs do + for j = (start or 1), #exprs do local se = exprs[j] if ((se.type == "expression") and (se[1] ~= "nil")) then emit(chunk, string.format("do local _ = %s end", tostring(se)), ast) elseif (se.type == "statement") then - local code = tostring(se) - emit(chunk, (((code:byte() == 40) and ("do end " .. code)) or code), ast) + emit(chunk, disambiguate_parens(tostring(se), chunk), ast) end end return nil @@ -2174,26 +2297,35 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct end end local function macroexpand_2a(ast, scope, once) - if not utils["list?"](ast) then - return ast + local _0_0 = nil + if utils["list?"](ast) then + _0_0 = find_macro(ast, scope, utils["multi-sym?"](ast[1])) else - local macro_2a = find_macro(ast, scope, utils["multi-sym?"](ast[1])) - if not macro_2a then - return ast - else - local old_scope = scopes.macro - local _ = nil - scopes.macro = scope - _ = nil - local ok, transformed = pcall(macro_2a, unpack(ast, 2)) - scopes.macro = old_scope - assert_compile(ok, transformed, ast) - if (once or not transformed) then - return transformed - else - return macroexpand_2a(transformed, scope) - end + _0_0 = nil + end + if (_0_0 == false) then + return ast + elseif (nil ~= _0_0) then + local macro_2a = _0_0 + local old_scope = scopes.macro + local _ = nil + scopes.macro = scope + _ = nil + local ok, transformed = nil, nil + local function _2_() + return macro_2a(unpack(ast, 2)) end + ok, transformed = xpcall(_2_, debug.traceback) + scopes.macro = old_scope + assert_compile(ok, transformed, ast) + if (once or not transformed) then + return transformed + else + return macroexpand_2a(transformed, scope) + end + else + local _ = _0_0 + return ast end end local function compile_special(ast, scope, parent, opts, special) @@ -2255,7 +2387,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct elseif (multi_sym_parts and multi_sym_parts["multi-sym-method-call"]) then local table_with_method = table.concat({unpack(multi_sym_parts, 1, (#multi_sym_parts - 1))}, ".") local method_to_call = multi_sym_parts[#multi_sym_parts] - local new_ast = utils.list(utils.sym(":", scope), utils.sym(table_with_method, scope), method_to_call, select(2, unpack(ast))) + local new_ast = utils.list(utils.sym(":", nil, scope), utils.sym(table_with_method, nil, scope), method_to_call, select(2, unpack(ast))) return compile1(new_ast, scope, parent, opts) else return compile_function_call(ast, scope, parent, opts, compile1, len) @@ -2277,25 +2409,8 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct return handle_compile_opts({e}, parent, opts, ast) end local function serialize_number(n) - local _0_0, _1_0, _2_0 = math.modf(n) - if ((nil ~= _0_0) and (_1_0 == 0)) then - local int = _0_0 - return tostring(int) - else - local _3_ - do - local frac = _1_0 - _3_ = (((_0_0 == 0) and (nil ~= _1_0)) and (frac < 0)) - end - if _3_ then - local frac = _1_0 - return ("-0." .. tostring(frac):gsub("^-?0.", "")) - elseif ((nil ~= _0_0) and (nil ~= _1_0)) then - local int = _0_0 - local frac = _1_0 - return (int .. "." .. tostring(frac):gsub("^-?0.", "")) - end - end + local _0_0 = string.gsub(tostring(n), ",", ".") + return _0_0 end local function compile_scalar(ast, _scope, parent, opts) local serialize = nil @@ -2343,9 +2458,13 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct table.sort(_0_0, _1_) keys = _0_0 end - local function _1_(k) - local v = tostring(compile1(ast[k[2]], scope, parent, {nval = 1})[1]) - return string.format("%s = %s", k[1], v) + local function _1_(_2_0) + local _3_ = _2_0 + local k1 = _3_[1] + local k2 = _3_[2] + local _4_ = compile1(ast[k2], scope, parent, {nval = 1}) + local v = _4_[1] + return string.format("%s = %s", k1, tostring(v)) end utils.map(keys, _1_, buffer) end @@ -2375,8 +2494,6 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local forceglobal = _0_["forceglobal"] local forceset = _0_["forceset"] local isvar = _0_["isvar"] - local nomulti = _0_["nomulti"] - local noundef = _0_["noundef"] local symtype = _0_["symtype"] local symtype0 = ("_" .. (symtype or "dst")) local setter = nil @@ -2388,7 +2505,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local new_manglings = {} local function getname(symbol, up1) local raw = symbol[1] - assert_compile(not (nomulti and utils["multi-sym?"](raw)), ("unexpected multi symbol " .. raw), up1) + assert_compile(not (opts0.nomulti and utils["multi-sym?"](raw)), ("unexpected multi symbol " .. raw), up1) if declaration then return declare_local(symbol, nil, scope, symbol, new_manglings) else @@ -2397,7 +2514,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct if ((#parts == 1) and not forceset) then assert_compile(not (forceglobal and meta), string.format("global %s conflicts with local", tostring(symbol)), symbol) assert_compile(not (meta and not meta.var), ("expected var " .. raw), symbol) - assert_compile((meta or not noundef), ("expected local " .. parts[1]), symbol) + assert_compile((meta or not opts0.noundef), ("expected local " .. parts[1]), symbol) end if forceglobal then assert_compile(not scope.symmeta[scope.unmanglings[raw]], ("global " .. raw .. " conflicts with local"), symbol) @@ -2432,6 +2549,8 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct end if ((#parent == (plen + 1)) and parent[#parent].leaf) then parent[#parent]["leaf"] = ("local " .. parent[#parent].leaf) + elseif (init == "nil") then + table.insert(parent, (plen + 1), {ast = ast, leaf = ("local " .. lvalue)}) else table.insert(parent, (plen + 1), {ast = ast, leaf = ("local " .. lvalue .. " = " .. init)}) end @@ -2606,8 +2725,12 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct else local remap = fennel_sourcemap[info.source] if (remap and remap[info.currentline]) then - info["short-src"] = remap["short-src"] - info.currentline = remap[info.currentline] + if remap[info.currentline][1] then + info.short_src = fennel_sourcemap[("@" .. remap[info.currentline][1])].short_src + else + info.short_src = remap.short_src + end + info.currentline = (remap[info.currentline][2] or -1) end if (info.what == "Lua") then local function _1_() @@ -2618,7 +2741,7 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct end end return string.format(" %s:%d: in function %s", info.short_src, info.currentline, _1_()) - elseif (info["short-src"] == "(tail call)") then + elseif (info.short_src == "(tail call)") then return " (tail call)" else return string.format(" %s:%d: in main chunk", info.short_src, info.currentline) @@ -2664,9 +2787,6 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct end return _0_ end - local function no() - return nil - end local function mixed_concat(t, joiner) local seen = {} local ret, s = "", "" @@ -2700,16 +2820,20 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct local symstr = utils.deref(form) assert_compile(not runtime_3f, "symbols may only be used at compile time", form) if (symstr:find("#$") or symstr:find("#[:.]")) then - return string.format("sym('%s', nil, {filename=%s, line=%s})", autogensym(symstr, scope), filename, (form.line or "nil")) + return string.format("sym('%s', {filename=%s, line=%s})", autogensym(symstr, scope), filename, (form.line or "nil")) else - return string.format("sym('%s', nil, {quoted=true, filename=%s, line=%s})", symstr, filename, (form.line or "nil")) + return string.format("sym('%s', {quoted=true, filename=%s, line=%s})", symstr, filename, (form.line or "nil")) end elseif (utils["list?"](form) and utils["sym?"](form[1]) and (utils.deref(form[1]) == "unquote")) then local payload = form[2] local res = unpack(compile1(payload, scope, parent)) return res[1] elseif utils["list?"](form) then - local mapped = utils.kvmap(form, entry_transform(no, q)) + local mapped = nil + local function _0_() + return nil + end + mapped = utils.kvmap(form, entry_transform(_0_, q)) local filename = nil if form.filename then filename = string.format("%q", form.filename) @@ -2718,6 +2842,22 @@ package.preload["fennel.compiler"] = package.preload["fennel.compiler"] or funct end assert_compile(not runtime_3f, "lists may only be used at compile time", form) return string.format(("setmetatable({filename=%s, line=%s, bytestart=%s, %s}" .. ", getmetatable(list()))"), filename, (form.line or "nil"), (form.bytestart or "nil"), mixed_concat(mapped, ", ")) + elseif utils["sequence?"](form) then + local mapped = utils.kvmap(form, entry_transform(q, q)) + local source = getmetatable(form) + local filename = nil + if source.filename then + filename = string.format("%q", source.filename) + else + filename = "nil" + end + local _1_ + if source then + _1_ = source.line + else + _1_ = "nil" + end + return string.format("setmetatable({%s}, {filename=%s, line=%s, sequence=%s})", mixed_concat(mapped, ", "), filename, _1_, "(getmetatable(sequence()))['sequence']") elseif (type(form) == "table") then local mapped = utils.kvmap(form, entry_transform(q, q)) local source = getmetatable(form) @@ -2748,7 +2888,7 @@ package.preload["fennel.friend"] = package.preload["fennel.friend"] or function( local m = getmetatable(ast) return ((m and m.line and m) or (("table" == type(ast)) and ast) or {}) end - local suggestions = {["$ and $... in hashfn are mutually exclusive"] = {"modifying the hashfn so it only contains $... or $, $1, $2, $3, etc"}, ["can't start multisym segment with a digit"] = {"removing the digit", "adding a non-digit before the digit"}, ["cannot call literal value"] = {"checking for typos", "checking for a missing function name"}, ["could not compile value of type "] = {"debugging the macro you're calling not to return a coroutine or userdata"}, ["could not read number (.*)"] = {"removing the non-digit character", "beginning the identifier with a non-digit if it is not meant to be a number"}, ["expected a function.* to call"] = {"removing the empty parentheses", "using square brackets if you want an empty table"}, ["expected binding table"] = {"placing a table here in square brackets containing identifiers to bind"}, ["expected body expression"] = {"putting some code in the body of this form after the bindings"}, ["expected each macro to be function"] = {"ensuring that the value for each key in your macros table contains a function", "avoid defining nested macro tables"}, ["expected even number of name/value bindings"] = {"finding where the identifier or value is missing"}, ["expected even number of values in table literal"] = {"removing a key", "adding a value"}, ["expected local"] = {"looking for a typo", "looking for a local which is used out of its scope"}, ["expected macros to be table"] = {"ensuring your macro definitions return a table"}, ["expected parameters"] = {"adding function parameters as a list of identifiers in brackets"}, ["expected rest argument before last parameter"] = {"moving & to right before the final identifier when destructuring"}, ["expected symbol for function parameter: (.*)"] = {"changing %s to an identifier instead of a literal value"}, ["expected var (.*)"] = {"declaring %s using var instead of let/local", "introducing a new local instead of changing the value of %s"}, ["expected vararg as last parameter"] = {"moving the \"...\" to the end of the parameter list"}, ["expected whitespace before opening delimiter"] = {"adding whitespace"}, ["global (.*) conflicts with local"] = {"renaming local %s"}, ["illegal character: (.)"] = {"deleting or replacing %s", "avoiding reserved characters like \", \\, ', ~, ;, @, `, and comma"}, ["local (.*) was overshadowed by a special form or macro"] = {"renaming local %s"}, ["macro not found in macro module"] = {"checking the keys of the imported macro module's returned table"}, ["macro tried to bind (.*) without gensym"] = {"changing to %s# when introducing identifiers inside macros"}, ["malformed multisym"] = {"ensuring each period or colon is not followed by another period or colon"}, ["may only be used at compile time"] = {"moving this to inside a macro if you need to manipulate symbols/lists", "using square brackets instead of parens to construct a table"}, ["method must be last component"] = {"using a period instead of a colon for field access", "removing segments after the colon", "making the method call, then looking up the field on the result"}, ["mismatched closing delimiter (.), expected (.)"] = {"replacing %s with %s", "deleting %s", "adding matching opening delimiter earlier"}, ["multisym method calls may only be in call position"] = {"using a period instead of a colon to reference a table's fields", "putting parens around this"}, ["unable to bind (.*)"] = {"replacing the %s with an identifier"}, ["unexpected closing delimiter (.)"] = {"deleting %s", "adding matching opening delimiter earlier"}, ["unexpected multi symbol (.*)"] = {"removing periods or colons from %s"}, ["unexpected vararg"] = {"putting \"...\" at the end of the fn parameters if the vararg was intended"}, ["unknown global in strict mode: (.*)"] = {"looking to see if there's a typo", "using the _G table instead, eg. _G.%s if you really want a global", "moving this code to somewhere that %s is in scope", "binding %s as a local in the scope of this code"}, ["unused local (.*)"] = {"fixing a typo so %s is used", "renaming the local to _%s"}, ["use of global (.*) is aliased by a local"] = {"renaming local %s", "refer to the global using _G.%s instead of directly"}} + local suggestions = {["$ and $... in hashfn are mutually exclusive"] = {"modifying the hashfn so it only contains $... or $, $1, $2, $3, etc"}, ["can't start multisym segment with a digit"] = {"removing the digit", "adding a non-digit before the digit"}, ["cannot call literal value"] = {"checking for typos", "checking for a missing function name"}, ["could not compile value of type "] = {"debugging the macro you're calling to return a list or table"}, ["could not read number (.*)"] = {"removing the non-digit character", "beginning the identifier with a non-digit if it is not meant to be a number"}, ["expected a function.* to call"] = {"removing the empty parentheses", "using square brackets if you want an empty table"}, ["expected binding table"] = {"placing a table here in square brackets containing identifiers to bind"}, ["expected body expression"] = {"putting some code in the body of this form after the bindings"}, ["expected each macro to be function"] = {"ensuring that the value for each key in your macros table contains a function", "avoid defining nested macro tables"}, ["expected even number of name/value bindings"] = {"finding where the identifier or value is missing"}, ["expected even number of values in table literal"] = {"removing a key", "adding a value"}, ["expected local"] = {"looking for a typo", "looking for a local which is used out of its scope"}, ["expected macros to be table"] = {"ensuring your macro definitions return a table"}, ["expected parameters"] = {"adding function parameters as a list of identifiers in brackets"}, ["expected rest argument before last parameter"] = {"moving & to right before the final identifier when destructuring"}, ["expected symbol for function parameter: (.*)"] = {"changing %s to an identifier instead of a literal value"}, ["expected var (.*)"] = {"declaring %s using var instead of let/local", "introducing a new local instead of changing the value of %s"}, ["expected vararg as last parameter"] = {"moving the \"...\" to the end of the parameter list"}, ["expected whitespace before opening delimiter"] = {"adding whitespace"}, ["global (.*) conflicts with local"] = {"renaming local %s"}, ["illegal character: (.)"] = {"deleting or replacing %s", "avoiding reserved characters like \", \\, ', ~, ;, @, `, and comma"}, ["local (.*) was overshadowed by a special form or macro"] = {"renaming local %s"}, ["macro not found in macro module"] = {"checking the keys of the imported macro module's returned table"}, ["macro tried to bind (.*) without gensym"] = {"changing to %s# when introducing identifiers inside macros"}, ["malformed multisym"] = {"ensuring each period or colon is not followed by another period or colon"}, ["may only be used at compile time"] = {"moving this to inside a macro if you need to manipulate symbols/lists", "using square brackets instead of parens to construct a table"}, ["method must be last component"] = {"using a period instead of a colon for field access", "removing segments after the colon", "making the method call, then looking up the field on the result"}, ["mismatched closing delimiter (.), expected (.)"] = {"replacing %s with %s", "deleting %s", "adding matching opening delimiter earlier"}, ["multisym method calls may only be in call position"] = {"using a period instead of a colon to reference a table's fields", "putting parens around this"}, ["unable to bind (.*)"] = {"replacing the %s with an identifier"}, ["unexpected closing delimiter (.)"] = {"deleting %s", "adding matching opening delimiter earlier"}, ["unexpected multi symbol (.*)"] = {"removing periods or colons from %s"}, ["unexpected vararg"] = {"putting \"...\" at the end of the fn parameters if the vararg was intended"}, ["unknown global in strict mode: (.*)"] = {"looking to see if there's a typo", "using the _G table instead, eg. _G.%s if you really want a global", "moving this code to somewhere that %s is in scope", "binding %s as a local in the scope of this code"}, ["unused local (.*)"] = {"fixing a typo so %s is used", "renaming the local to _%s"}, ["use of global (.*) is aliased by a local"] = {"renaming local %s", "refer to the global using _G.%s instead of directly"}} local unpack = (table.unpack or _G.unpack) local function suggest(msg) local suggestion = nil @@ -2780,21 +2920,19 @@ package.preload["fennel.friend"] = package.preload["fennel.friend"] or function( f:close() return codeline, bytes end - local function read_line_from_source(source, line) - local lines, bytes, codeline = 0, 0 - for this_line, newline in string.gmatch((source .. "\n"), "(.-)(\13?\n)") do - lines = (lines + 1) - if (lines == line) then - codeline = this_line - break - end - bytes = (bytes + #newline + #this_line) + local function read_line_from_string(matcher, target_line, _3fcurrent_line, _3fbytes) + local this_line, newline = matcher() + local current_line = (_3fcurrent_line or 1) + local bytes = ((_3fbytes or 0) + #this_line + #newline) + if (target_line == current_line) then + return this_line, bytes + elseif this_line then + return read_line_from_string(matcher, target_line, (current_line + 1), bytes) end - return codeline, bytes end local function read_line(filename, line, source) if source then - return read_line_from_source(source, line) + return read_line_from_string(string.gmatch((source .. "\n"), "(.-)(\13?\n)"), line) else return read_line_from_file(filename, line) end @@ -2927,6 +3065,7 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( end return r end + assert(((nil == filename) or ("string" == type(filename))), "expected filename as second argument to parser") local function parse_error(msg, byteindex_override) local _0_ = (options or utils.root.options or {}) local source = _0_["source"] @@ -2947,8 +3086,17 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( return nil elseif ((type(_0_0) == "table") and (nil ~= _0_0.prefix)) then local prefix = _0_0.prefix - table.remove(stack) - return dispatch(utils.list(utils.sym(prefix), v)) + local source = nil + do + local _1_0 = table.remove(stack) + _1_0["byteend"] = byteindex + source = _1_0 + end + local list = utils.list(utils.sym(prefix, source), v) + for k, v0 in pairs(source) do + list[k] = v0 + end + return dispatch(list) elseif (nil ~= _0_0) then local top = _0_0 whitespace_since_dispatch = false @@ -2984,7 +3132,7 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( end return parse_comment(getb(), _1_()) elseif (options and options.comments) then - return dispatch(utils.comment(table.concat(contents))) + return dispatch(utils.comment(table.concat(contents), {filename = filename, line = (line - 1)})) else return b end @@ -3005,7 +3153,49 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( end return dispatch(val) end + local function add_comment_at(comments, index, node) + local _0_0 = comments[index] + if (nil ~= _0_0) then + local existing = _0_0 + return table.insert(existing, node) + else + local _ = _0_0 + comments[index] = {node} + return nil + end + end + local function next_noncomment(tbl, i) + if utils["comment?"](tbl[i]) then + return next_noncomment(tbl, (i + 1)) + else + return tbl[i] + end + end + local function extract_comments(tbl) + local comments = {keys = {}, last = {}, values = {}} + while utils["comment?"](tbl[#tbl]) do + table.insert(comments.last, 1, table.remove(tbl)) + end + local last_key_3f = false + for i, node in ipairs(tbl) do + if not utils["comment?"](node) then + last_key_3f = not last_key_3f + elseif last_key_3f then + add_comment_at(comments.values, next_noncomment(tbl, i), node) + else + add_comment_at(comments.keys, next_noncomment(tbl, i), node) + end + end + for i = #tbl, 1, -1 do + if utils["comment?"](tbl[i]) then + table.remove(tbl, i) + end + end + return comments + end local function close_curly_table(tbl) + local comments = extract_comments(tbl) + local keys = {} local val = {} if ((#tbl % 2) ~= 0) then byteindex = (byteindex - 1) @@ -3017,7 +3207,10 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( tbl[i] = tostring(tbl[(i + 1)]) end val[tbl[i]] = tbl[(i + 1)] + table.insert(keys, tbl[i]) end + tbl.comments = comments + tbl.keys = keys return dispatch(val) end local function close_table(b) @@ -3025,7 +3218,7 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( if (top == nil) then parse_error(("unexpected closing delimiter " .. string.char(b))) end - if (top.closer ~= b) then + if (top.closer and (top.closer ~= b)) then parse_error(("mismatched closing delimiter " .. string.char(b) .. ", expected " .. string.char(top.closer))) end top.byteend = byteindex @@ -3073,9 +3266,9 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( return dispatch(load_fn()) end local function parse_prefix(b) - table.insert(stack, {prefix = prefixes[b]}) + table.insert(stack, {bytestart = byteindex, filename = filename, line = line, prefix = prefixes[b]}) local nextb = getb() - if whitespace_3f(nextb) then + if (whitespace_3f(nextb) or (true == delims[nextb])) then if (b ~= 35) then parse_error("invalid whitespace after quoting prefix") end @@ -3116,11 +3309,13 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( if (rawstr:match("^~") and (rawstr ~= "~=")) then return parse_error("illegal character: ~") elseif rawstr:match("%.[0-9]") then - return parse_error(("can't start multisym segment " .. "with a digit: " .. rawstr), (((byteindex - #rawstr) + rawstr:find("%.[0-9]")) + 1)) + return parse_error(("can't start multisym segment with a digit: " .. rawstr), (((byteindex - #rawstr) + rawstr:find("%.[0-9]")) + 1)) elseif (rawstr:match("[%.:][%.:]") and (rawstr ~= "..") and (rawstr ~= "$...")) then return parse_error(("malformed multisym: " .. rawstr), ((byteindex - #rawstr) + 1 + rawstr:find("[%.:][%.:]"))) elseif rawstr:match(":.+[%.:]") then - return parse_error(("method must be last component " .. "of multisym: " .. rawstr), ((byteindex - #rawstr) + rawstr:find(":.+[%.:]"))) + return parse_error(("method must be last component of multisym: " .. rawstr), ((byteindex - #rawstr) + rawstr:find(":.+[%.:]"))) + else + return rawstr end end local function parse_sym(b) @@ -3134,12 +3329,8 @@ package.preload["fennel.parser"] = package.preload["fennel.parser"] or function( return dispatch(utils.varg()) elseif rawstr:match("^:.+$") then return dispatch(rawstr:sub(2)) - elseif parse_number(rawstr) then - return nil - elseif check_malformed_sym(rawstr) then - return nil - else - return dispatch(utils.sym(rawstr, nil, {byteend = byteindex, bytestart = bytestart, filename = filename, line = line})) + elseif not parse_number(rawstr) then + return dispatch(utils.sym(check_malformed_sym(rawstr), {byteend = byteindex, bytestart = bytestart, filename = filename, line = line})) end end local function parse_loop(b) @@ -3185,19 +3376,27 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. for k in pairs(t) do table.insert(keys, k) end - local function _0_(a, b) - return (tostring(a) < tostring(b)) + local function _0_(_241, _242) + return (tostring(_241) < tostring(_242)) end table.sort(keys, _0_) for i, k in ipairs(keys) do succ[k] = keys[(i + 1)] end local function stablenext(tbl, idx) + local key = nil if (idx == nil) then - return keys[1], tbl[keys[1]] + key = keys[1] else - return succ[idx], succ[idx] and tbl[succ[idx]] + key = succ[idx] end + local value = nil + if (key == nil) then + value = nil + else + value = tbl[key] + end + return key, value end return stablenext, t, nil end @@ -3207,9 +3406,8 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. if (type(f) == "function") then f0 = f else - local s = f - local function _0_(x) - return x[s] + local function _0_(_241) + return _241[f] end f0 = _0_ end @@ -3228,9 +3426,8 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. if (type(f) == "function") then f0 = f else - local s = f - local function _0_(x) - return x[s] + local function _0_(_241) + return _241[f] end f0 = _0_ end @@ -3302,10 +3499,19 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. end return ("(" .. table.concat(map(safe, (tostring2 or tostring)), " ", 1, max) .. ")") end - local symbol_mt = {"SYMBOL", __fennelview = deref, __tostring = deref} + local function comment_view(c) + return c, true + end + local function sym_3d(a, b) + return ((deref(a) == deref(b)) and (getmetatable(a) == getmetatable(b))) + end + local function sym_3c(a, b) + return (a[1] < tostring(b)) + end + local symbol_mt = {"SYMBOL", __eq = sym_3d, __fennelview = deref, __lt = sym_3c, __tostring = deref} local expr_mt = {"EXPR", __tostring = deref} local list_mt = {"LIST", __fennelview = list__3estring, __tostring = list__3estring} - local comment_mt = {"COMMENT", __fennelview = deref, __tostring = deref} + local comment_mt = {"COMMENT", __eq = sym_3d, __fennelview = comment_view, __lt = sym_3c, __tostring = deref} local sequence_marker = {"SEQUENCE"} local vararg = setmetatable({"..."}, {"VARARG", __fennelview = deref, __tostring = deref}) local getenv = nil @@ -3320,9 +3526,9 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. local function list(...) return setmetatable({...}, list_mt) end - local function sym(str, scope, source) - local s = {str, scope = scope} - for k, v in pairs((source or {})) do + local function sym(str, _3fsource, _3fscope) + local s = {str, ["?scope"] = _3fscope} + for k, v in pairs((_3fsource or {})) do if (type(k) == "string") then s[k] = v end @@ -3336,8 +3542,11 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. local function expr(strcode, etype) return setmetatable({strcode, type = etype}, expr_mt) end - local function comment_2a(contents) - return setmetatable({contents}, comment_mt) + local function comment_2a(contents, _3fsource) + local _1_ = (_3fsource or {}) + local filename = _1_["filename"] + local line = _1_["line"] + return setmetatable({contents, filename = filename, line = line}, comment_mt) end local function varg() return vararg @@ -3354,9 +3563,6 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. local function sym_3f(x) return ((type(x) == "table") and (getmetatable(x) == symbol_mt) and x) end - local function table_3f(x) - return ((type(x) == "table") and (x ~= vararg) and (getmetatable(x) ~= list_mt) and (getmetatable(x) ~= symbol_mt) and x) - end local function sequence_3f(x) local mt = ((type(x) == "table") and getmetatable(x)) return (mt and (mt.sequence == sequence_marker) and x) @@ -3364,6 +3570,9 @@ package.preload["fennel.utils"] = package.preload["fennel.utils"] or function(.. local function comment_3f(x) return ((type(x) == "table") and (getmetatable(x) == comment_mt) and x) end + local function table_3f(x) + return ((type(x) == "table") and (x ~= vararg) and (getmetatable(x) ~= list_mt) and (getmetatable(x) ~= symbol_mt) and not comment_3f(x) and x) + end local function multi_sym_3f(str) if sym_3f(str) then return multi_sym_3f(tostring(str)) @@ -3450,7 +3659,7 @@ local compiler = require("fennel.compiler") local specials = require("fennel.specials") local repl = require("fennel.repl") local view = require("fennel.view") -local function get_env(env) +local function eval_env(env) if (env == "_COMPILER") then local env0 = specials["make-compiler-env"](nil, compiler.scopes.compiler, {}) local mt = getmetatable(env0) @@ -3460,26 +3669,32 @@ local function get_env(env) return (env and specials["wrap-env"](env)) end end -local function eval(str, options, ...) +local function eval_opts(options, str) local opts = utils.copy(options) - local _ = nil if ((opts.allowedGlobals == nil) and not getmetatable(opts.env)) then opts.allowedGlobals = specials["current-global-names"](opts.env) - _ = nil - else - _ = nil end - local env = get_env(opts.env) + if (not opts.filename and not opts.source) then + opts.source = str + end + if (opts.env == "_COMPILER") then + opts.scope = compiler["make-scope"](compiler.scopes.compiler) + end + return opts +end +local function eval(str, options, ...) + local opts = eval_opts(options, str) + local env = eval_env(opts.env) local lua_source = compiler["compile-string"](str, opts) local loader = nil - local function _1_(...) + local function _0_(...) if opts.filename then return ("@" .. opts.filename) else return str end end - loader = specials["load-code"](lua_source, env, _1_(...)) + loader = specials["load-code"](lua_source, env, _0_(...)) opts.filename = nil return loader(...) end @@ -3491,20 +3706,20 @@ local function dofile_2a(filename, options, ...) opts.filename = filename return eval(source, opts, ...) end -local mod = {["compile-stream"] = compiler["compile-stream"], ["compile-string"] = compiler["compile-string"], ["list?"] = utils["list?"], ["load-code"] = specials["load-code"], ["macro-loaded"] = specials["macro-loaded"], ["make-searcher"] = specials["make-searcher"], ["search-module"] = specials["search-module"], ["sequence?"] = utils["sequence?"], ["string-stream"] = parser["string-stream"], ["sym-char?"] = parser["sym-char?"], ["sym?"] = utils["sym?"], compile = compiler.compile, compile1 = compiler.compile1, compileStream = compiler["compile-stream"], compileString = compiler["compile-string"], doc = specials.doc, dofile = dofile_2a, eval = eval, gensym = compiler.gensym, granulate = parser.granulate, list = utils.list, loadCode = specials["load-code"], macroLoaded = specials["macro-loaded"], makeSearcher = specials["make-searcher"], make_searcher = specials["make-searcher"], mangle = compiler["global-mangling"], metadata = compiler.metadata, parser = parser.parser, path = utils.path, repl = repl, scope = compiler["make-scope"], searchModule = specials["search-module"], searcher = specials["make-searcher"](), sequence = utils.sequence, stringStream = parser["string-stream"], sym = utils.sym, traceback = compiler.traceback, unmangle = compiler["global-unmangling"], varg = utils.varg, version = "0.8.0", view = view} +local mod = {["comment?"] = utils["comment?"], ["compile-stream"] = compiler["compile-stream"], ["compile-string"] = compiler["compile-string"], ["list?"] = utils["list?"], ["load-code"] = specials["load-code"], ["macro-loaded"] = specials["macro-loaded"], ["macro-searchers"] = specials["macro-searchers"], ["make-searcher"] = specials["make-searcher"], ["search-module"] = specials["search-module"], ["sequence?"] = utils["sequence?"], ["string-stream"] = parser["string-stream"], ["sym-char?"] = parser["sym-char?"], ["sym?"] = utils["sym?"], comment = utils.comment, compile = compiler.compile, compile1 = compiler.compile1, compileStream = compiler["compile-stream"], compileString = compiler["compile-string"], doc = specials.doc, dofile = dofile_2a, eval = eval, gensym = compiler.gensym, granulate = parser.granulate, list = utils.list, loadCode = specials["load-code"], macroLoaded = specials["macro-loaded"], makeSearcher = specials["make-searcher"], make_searcher = specials["make-searcher"], mangle = compiler["global-mangling"], metadata = compiler.metadata, parser = parser.parser, path = utils.path, repl = repl, scope = compiler["make-scope"], searchModule = specials["search-module"], searcher = specials["make-searcher"](), sequence = utils.sequence, stringStream = parser["string-stream"], sym = utils.sym, traceback = compiler.traceback, unmangle = compiler["global-unmangling"], varg = utils.varg, version = "0.9.1-dev", view = view} utils["fennel-module"] = mod do local builtin_macros = [===[;; This module contains all the built-in Fennel macros. Unlike all the other ;; modules that are loaded by the old bootstrap compiler, this runs in the ;; compiler scope of the version of the compiler being defined. - + ;; The code for these macros is somewhat idiosyncratic because it cannot use any ;; macros which have not yet been defined. - + ;; TODO: some of these macros modify their arguments; we should stop doing that, ;; but in a way that preserves file/line metadata. - - (fn -> [val ...] + + (fn ->* [val ...] "Thread-first macro. Take the first value and splice it into the second form as its first argument. The value of the second form is spliced into the first arg of the third, etc." @@ -3514,8 +3729,8 @@ do (table.insert elt 2 x) (set x elt))) x) - - (fn ->> [val ...] + + (fn ->>* [val ...] "Thread-last macro. Same as ->, except splices the value into the last position of each form rather than the first." @@ -3525,8 +3740,8 @@ do (table.insert elt x) (set x elt))) x) - - (fn -?> [val ...] + + (fn -?>* [val ...] "Nil-safe thread-first macro. Same as -> except will short-circuit with nil when it encounters a nil value." (if (= 0 (select "#" ...)) @@ -3540,8 +3755,8 @@ do (if ,tmp (-?> ,el ,(unpack els)) ,tmp))))) - - (fn -?>> [val ...] + + (fn -?>>* [val ...] "Nil-safe thread-last macro. Same as ->> except will short-circuit with nil when it encounters a nil value." (if (= 0 (select "#" ...)) @@ -3555,8 +3770,15 @@ do (if ,tmp (-?>> ,el ,(unpack els)) ,tmp))))) - - (fn doto [val ...] + + (fn ?dot [tbl k ...] + "Nil-safe table look up. + Same as . (dot), except will short-circuit with nil when it encounters + a nil value in any of subsequent keys." + (if (= nil k) tbl `(let [res# (. ,tbl ,k)] + (and res# (?. res# ,...))))) + + (fn doto* [val ...] "Evaluates val and splices it into the first argument of subsequent forms." (let [name (gensym) form `(let [,name ,val])] @@ -3565,34 +3787,38 @@ do (table.insert form elt)) (table.insert form name) form)) - - (fn when [condition body1 ...] + + (fn when* [condition body1 ...] "Evaluate body for side-effects only when condition is truthy." (assert body1 "expected body") `(if ,condition - (do ,body1 ,...))) - - (fn with-open [closable-bindings ...] + (do + ,body1 + ,...))) + + (fn with-open* [closable-bindings ...] "Like `let`, but invokes (v:close) on each binding after evaluating the body. The body is evaluated inside `xpcall` so that bound values will be closed upon encountering an error before propagating it." - (let [bodyfn `(fn [] ,...) - closer `(fn close-handlers# [ok# ...] (if ok# ... - (error ... 0))) + (let [bodyfn `(fn [] + ,...) + closer `(fn close-handlers# [ok# ...] + (if ok# ... (error ... 0))) traceback `(. (or package.loaded.fennel debug) :traceback)] - (for [i 1 (# closable-bindings) 2] + (for [i 1 (length closable-bindings) 2] (assert (sym? (. closable-bindings i)) "with-open only allows symbols in bindings") (table.insert closer 4 `(: ,(. closable-bindings i) :close))) - `(let ,closable-bindings ,closer - (close-handlers# (xpcall ,bodyfn ,traceback))))) - - (fn collect [iter-tbl key-value-expr ...] + `(let ,closable-bindings + ,closer + (close-handlers# (xpcall ,bodyfn ,traceback))))) + + (fn collect* [iter-tbl key-value-expr ...] "Returns a table made by running an iterator and evaluating an expression that returns key-value pairs to be inserted sequentially into the table. This can be thought of as a \"table comprehension\". The provided key-value expression must return either 2 values, or nil. - + For example, (collect [k v (pairs {:apple \"red\" :orange \"orange\"})] (values v k)) @@ -3600,8 +3826,7 @@ do {:red \"apple\" :orange \"orange\"}" (assert (and (sequence? iter-tbl) (>= (length iter-tbl) 2)) "expected iterator binding table") - (assert (not= nil key-value-expr) - "expected key-value expression") + (assert (not= nil key-value-expr) "expected key-value expression") (assert (= nil ...) "expected exactly one body expression. Wrap multiple expressions with do") `(let [tbl# {}] @@ -3609,63 +3834,68 @@ do (match ,key-value-expr (k# v#) (tset tbl# k# v#))) tbl#)) - - (fn icollect [iter-tbl value-expr ...] + + (fn icollect* [iter-tbl value-expr ...] "Returns a sequential table made by running an iterator and evaluating an expression that returns values to be inserted sequentially into the table. This can be thought of as a \"list comprehension\". - + For example, (icollect [_ v (ipairs [1 2 3 4 5])] (when (> v 2) (* v v))) returns [9 16 25]" (assert (and (sequence? iter-tbl) (>= (length iter-tbl) 2)) "expected iterator binding table") - (assert (not= nil value-expr) - "expected table value expression") + (assert (not= nil value-expr) "expected table value expression") (assert (= nil ...) "expected exactly one body expression. Wrap multiple expressions with do") `(let [tbl# []] (each ,iter-tbl (tset tbl# (+ (length tbl#) 1) ,value-expr)) tbl#)) - - (fn partial [f ...] + + (fn partial* [f ...] "Returns a function with all arguments partially applied to f." + (assert f "expected a function to partially apply") (let [body (list f ...)] (table.insert body _VARARG) - `(fn [,_VARARG] ,body))) - - (fn pick-args [n f] + `(fn [,_VARARG] + ,body))) + + (fn pick-args* [n f] "Creates a function of arity n that applies its arguments to f. - + For example, (pick-args 2 func) expands to (fn [_0_ _1_] (func _0_ _1_))" (assert (and (= (type n) :number) (= n (math.floor n)) (>= n 0)) - "Expected n to be an integer literal >= 0.") + (.. "Expected n to be an integer literal >= 0, got " (tostring n))) (let [bindings []] - (for [i 1 n] (tset bindings i (gensym))) - `(fn ,bindings (,f ,(unpack bindings))))) - - (fn pick-values [n ...] + (for [i 1 n] + (tset bindings i (gensym))) + `(fn ,bindings + (,f ,(unpack bindings))))) + + (fn pick-values* [n ...] "Like the `values` special, but emits exactly n values. - + For example, (pick-values 2 ...) expands to (let [(_0_ _1_) ...] (values _0_ _1_))" (assert (and (= :number (type n)) (>= n 0) (= n (math.floor n))) - "Expected n to be an integer >= 0") - (let [let-syms (list) - let-values (if (= 1 (select :# ...)) ... `(values ,...))] - (for [i 1 n] (table.insert let-syms (gensym))) + (.. "Expected n to be an integer >= 0, got " (tostring n))) + (let [let-syms (list) + let-values (if (= 1 (select "#" ...)) ... `(values ,...))] + (for [i 1 n] + (table.insert let-syms (gensym))) (if (= n 0) `(values) - `(let [,let-syms ,let-values] (values ,(unpack let-syms)))))) - - (fn lambda [...] + `(let [,let-syms ,let-values] + (values ,(unpack let-syms)))))) + + (fn lambda* [...] "Function literal with arity checking. Will throw an exception if a declared argument is passed in as nil, unless that argument name begins with ?." @@ -3673,51 +3903,53 @@ do has-internal-name? (sym? (. args 1)) arglist (if has-internal-name? (. args 2) (. args 1)) docstring-position (if has-internal-name? 3 2) - has-docstring? (and (> (# args) docstring-position) + has-docstring? (and (> (length args) docstring-position) (= :string (type (. args docstring-position)))) arity-check-position (- 4 (if has-internal-name? 0 1) (if has-docstring? 0 1)) - empty-body? (< (# args) arity-check-position)] + empty-body? (< (length args) arity-check-position)] (fn check! [a] (if (table? a) (each [_ a (pairs a)] (check! a)) (let [as (tostring a)] - (and (not (as:match "^?")) (not= as "&") (not= as "_") (not= as "..."))) + (and (not (as:match "^?")) (not= as "&") (not= as "_") + (not= as "..."))) (table.insert args arity-check-position `(assert (not= nil ,a) (string.format "Missing argument %s on %s:%s" ,(tostring a) - ,(or a.filename "unknown") + ,(or a.filename :unknown) ,(or a.line "?")))))) + (assert (= :table (type arglist)) "expected arg list") (each [_ a (ipairs arglist)] (check! a)) (if empty-body? (table.insert args (sym :nil))) `(fn ,(unpack args)))) - - (fn macro [name ...] + + (fn macro* [name ...] "Define a single macro." (assert (sym? name) "expected symbol for macro name") (local args [...]) - `(macros { ,(tostring name) (fn ,(unpack args))})) - - (fn macrodebug [form return?] + `(macros {,(tostring name) (fn ,(unpack args))})) + + (fn macrodebug* [form return?] "Print the resulting form after performing macroexpansion. With a second argument, returns expanded form as a string instead of printing." (let [handle (if return? `do `print)] `(,handle ,(view (macroexpand form _SCOPE))))) - - (fn import-macros [binding1 module-name1 ...] + + (fn import-macros* [binding1 module-name1 ...] "Binds a table of macros from each macro module according to a binding form. Each binding form can be either a symbol or a k/v destructuring table. Example: (import-macros mymacros :my-macros ; bind to symbol {:macro1 alias : macro2} :proj.macros) ; import by name" - (assert (and binding1 module-name1 (= 0 (% (select :# ...) 2))) + (assert (and binding1 module-name1 (= 0 (% (select "#" ...) 2))) "expected even number of binding/modulename pairs") - (for [i 1 (select :# binding1 module-name1 ...) 2] + (for [i 1 (select "#" binding1 module-name1 ...) 2] (let [(binding modname) (select i binding1 module-name1 ...) ;; generate a subscope of current scope, use require-macros ;; to bring in macro module. after that, we just copy the @@ -3727,10 +3959,10 @@ do (_SPECIALS.require-macros `(require-macros ,modname) subscope {} ast) (if (sym? binding) ;; bind whole table of macros to table bound to symbol - (do (tset scope.macros (. binding 1) {}) - (each [k v (pairs subscope.macros)] - (tset (. scope.macros (. binding 1)) k v))) - + (do + (tset scope.macros (. binding 1) {}) + (each [k v (pairs subscope.macros)] + (tset (. scope.macros (. binding 1)) k v))) ;; 1-level table destructuring for importing individual macros (table? binding) (each [macro-name [import-key] (pairs binding)] @@ -3739,9 +3971,9 @@ do (tostring modname))) (tset scope.macros import-key (. subscope.macros macro-name)))))) nil) - + ;;; Pattern matching - + (fn match-values [vals pattern unifications match-pattern] (let [condition `(and) bindings []] @@ -3752,21 +3984,31 @@ do (each [_ b (ipairs subbindings)] (table.insert bindings b)))) (values condition bindings))) - + (fn match-table [val pattern unifications match-pattern] (let [condition `(and (= (type ,val) :table)) bindings []] (each [k pat (pairs pattern)] - (if (and (sym? pat) (= "&" (tostring pat))) - (do (assert (not (. pattern (+ k 2))) - "expected rest argument before last parameter") - (table.insert bindings (. pattern (+ k 1))) - (table.insert bindings [`(select ,k ((or table.unpack - _G.unpack) - ,val))])) - (and (= :number (type k)) - (= "&" (tostring (. pattern (- k 1))))) - nil ; don't process the pattern right after &; already got it + (if (= pat `&) + (do + (assert (= nil (. pattern (+ k 2))) + "expected & rest argument before last parameter") + (table.insert bindings (. pattern (+ k 1))) + (table.insert bindings + [`(select ,k ((or table.unpack _G.unpack) ,val))])) + (= k `&as) + (do + (table.insert bindings pat) + (table.insert bindings val)) + (and (= :number (type k)) (= `&as pat)) + (do + (assert (= nil (. pattern (+ k 2))) + "expected &as argument before last parameter") + (table.insert bindings (. pattern (+ k 1))) + (table.insert bindings val)) + ;; don't process the pattern right after &/&as; already got it + (or (not= :number (type k)) (and (not= `&as (. pattern (- k 1))) + (not= `& (. pattern (- k 1))))) (let [subval `(. ,val ,k) (subcondition subbindings) (match-pattern [subval] pat unifications)] @@ -3774,7 +4016,7 @@ do (each [_ b (ipairs subbindings)] (table.insert bindings b))))) (values condition bindings))) - + (fn match-pattern [vals pattern unifications] "Takes the AST of values and a single pattern and returns a condition to determine if it matches as well as a list of bindings to @@ -3784,11 +4026,9 @@ do ;; of vals) or we're not, in which case we only care about the first one. (let [[val] vals] (if (or (and (sym? pattern) ; unification with outer locals (or nil) - (not= :_ (tostring pattern)) ; never unify _ - (or (in-scope? pattern) - (= :nil (tostring pattern)))) - (and (multi-sym? pattern) - (in-scope? (. (multi-sym? pattern) 1)))) + (not= "_" (tostring pattern)) ; never unify _ + (or (in-scope? pattern) (= :nil (tostring pattern)))) + (and (multi-sym? pattern) (in-scope? (. (multi-sym? pattern) 1)))) (values `(= ,val ,pattern) []) ;; unify a local we've seen already (and (sym? pattern) (. unifications (tostring pattern))) @@ -3797,18 +4037,17 @@ do (sym? pattern) (let [wildcard? (: (tostring pattern) :find "^_")] (if (not wildcard?) (tset unifications (tostring pattern) val)) - (values (if (or wildcard? (string.find (tostring pattern) "^?")) - true `(not= ,(sym :nil) ,val)) - [pattern val])) + (values (if (or wildcard? (string.find (tostring pattern) "^?")) true + `(not= ,(sym :nil) ,val)) [pattern val])) ;; guard clause - (and (list? pattern) (sym? (. pattern 2)) (= :? (tostring (. pattern 2)))) + (and (list? pattern) (= (. pattern 2) `?)) (let [(pcondition bindings) (match-pattern vals (. pattern 1) unifications) condition `(and ,pcondition)] - (for [i 3 (# pattern)] ; splice in guard clauses + (for [i 3 (length pattern)] ; splice in guard clauses (table.insert condition (. pattern i))) - (values `(let ,bindings ,condition) bindings)) - + (values `(let ,bindings + ,condition) bindings)) ;; multi-valued patterns (represented as lists) (list? pattern) (match-values vals pattern unifications match-pattern) @@ -3817,20 +4056,21 @@ do (match-table val pattern unifications match-pattern) ;; literal value (values `(= ,val ,pattern) [])))) - + (fn match-condition [vals clauses] "Construct the actual `if` AST for the given match values and clauses." (if (not= 0 (% (length clauses) 2)) ; treat odd final clause as default - (table.insert clauses (length clauses) (sym :_))) + (table.insert clauses (length clauses) (sym "_"))) (let [out `(if)] (for [i 1 (length clauses) 2] (let [pattern (. clauses i) body (. clauses (+ i 1)) (condition bindings) (match-pattern vals pattern {})] (table.insert out condition) - (table.insert out `(let ,bindings ,body)))) + (table.insert out `(let ,bindings + ,body)))) out)) - + (fn match-val-syms [clauses] "How many multi-valued clauses are there? return a list of that many gensyms." (let [syms (list (gensym))] @@ -3840,23 +4080,102 @@ do (if (not (. syms valnum)) (tset syms valnum (gensym)))))) syms)) - - (fn match [val ...] - "Perform pattern matching on val. See reference for details." + + (fn match* [val ...] + ;; Old implementation of match macro, which doesn't directly support + ;; `where' and `or'. New syntax is implemented in `match-where', + ;; which simply generates old syntax and feeds it to `match*'. (let [clauses [...] vals (match-val-syms clauses)] ;; protect against multiple evaluation of the value, bind against as ;; many values as we ever match against in the clauses. - (list `let [vals val] - (match-condition vals clauses)))) - - {: -> : ->> : -?> : -?>> - : doto : when : with-open - : collect : icollect - : partial : lambda - : pick-args : pick-values - : macro : macrodebug : import-macros - : match} + (list `let [vals val] (match-condition vals clauses)))) + + ;; Construction of old match syntax from new syntax + + (fn partition-2 [seq] + ;; Partition `seq` by 2. + ;; If `seq` has odd amount of elements, the last one is dropped. + ;; + ;; Input: [1 2 3 4 5] + ;; Output: [[1 2] [3 4]] + (let [firsts [] + seconds [] + res []] + (for [i 1 (length seq) 2] + (let [first (. seq i) + second (. seq (+ i 1))] + (table.insert firsts (if (not= nil first) first `nil)) + (table.insert seconds (if (not= nil second) second `nil)))) + (each [i v1 (ipairs firsts)] + (let [v2 (. seconds i)] + (if (not= nil v2) + (table.insert res [v1 v2])))) + res)) + + (fn transform-or [[_ & pats] guards] + ;; Transforms `(or pat pats*)` lists into match `guard` patterns. + ;; + ;; (or pat1 pat2), guard => [(pat1 ? guard) (pat2 ? guard)] + (let [res []] + (each [_ pat (ipairs pats)] + (table.insert res (list pat `? (unpack guards)))) + res)) + + (fn transform-cond [cond] + ;; Transforms `where` cond into sequence of `match` guards. + ;; + ;; pat => [pat] + ;; (where pat guard) => [(pat ? guard)] + ;; (where (or pat1 pat2) guard) => [(pat1 ? guard) (pat2 ? guard)] + (if (and (list? cond) (= (. cond 1) `where)) + (let [second (. cond 2)] + (if (and (list? second) (= (. second 1) `or)) + (transform-or second [(unpack cond 3)]) + :else + [(list second `? (unpack cond 3))])) + :else + [cond])) + + (fn match-where [val ...] + "Perform pattern matching on val. See reference for details. + + Syntax: + + (match data-expression + pattern body + (where pattern guard guards*) body + (where (or pattern patterns*) guard guards*) body)" + (let [conds-bodies (partition-2 [...]) + else-branch (if (not= 0 (% (select "#" ...) 2)) + (select (select "#" ...) ...)) + match-body []] + (each [_ [cond body] (ipairs conds-bodies)] + (each [_ cond (ipairs (transform-cond cond))] + (table.insert match-body cond) + (table.insert match-body body))) + (if else-branch + (table.insert match-body else-branch)) + (match* val (unpack match-body)))) + + {:-> ->* + :->> ->>* + :-?> -?>* + :-?>> -?>>* + :?. ?dot + :doto doto* + :when when* + :with-open with-open* + :collect collect* + :icollect icollect* + :partial partial* + :lambda lambda* + :pick-args pick-args* + :pick-values pick-values* + :macro macro* + :macrodebug macrodebug* + :import-macros import-macros* + :match match-where} ]===] local module_name = "fennel.macros" local _ = nil diff --git a/lib/lume.lua b/lib/lume.lua index 2157891..851a23d 100644 --- a/lib/lume.lua +++ b/lib/lume.lua @@ -624,7 +624,7 @@ function lume.wordwrap(str, limit) check = limit end local rtn = {} - local line = "" + local line = str:match("^(%s*)") for word, spaces in str:gmatch("(%S+)(%s*)") do local s = line .. word if check(s) then @@ -694,7 +694,7 @@ function lume.hotswap(modname) local oldmt, newmt = getmetatable(old), getmetatable(new) if oldmt and newmt then update(oldmt, newmt) end for k, v in pairs(new) do - if type(v) == "table" then update(old[k], v) else old[k] = v end + if type(v) == "table" and type(old[k]) == "table" then update(old[k], v) else old[k] = v end end end local err = nil diff --git a/lib/multimethod.fnl b/lib/multimethod.fnl new file mode 100644 index 0000000..4ca7aac --- /dev/null +++ b/lib/multimethod.fnl @@ -0,0 +1,18 @@ +(local util (require :lib.util)) + +(local mm {}) + +(fn mm.__call [{: module : name} ...] + (let [dispatcher (. mm.dispatchers module name) + key (dispatcher ...) + method (or (. mm.methods module name key) (. mm.methods module name :default))] + (method ...))) + +(fn mm.defmulti [dispatcher name module] + (util.nested-tset mm [:dispatchers module name] dispatcher) + (setmetatable {: module : name} mm)) + +(fn mm.defmethod [{: module : name} key method] + (util.nested-tset mm [:methods module name key] method)) + +mm diff --git a/lib/util.fnl b/lib/util.fnl index dc071ad..db73734 100644 --- a/lib/util.fnl +++ b/lib/util.fnl @@ -53,6 +53,16 @@ (fn swappable-require [modname] (swappable (require modname))) +(fn hot-table [modname] + (local tbl {}) + (fn find-table [] + (let [loaded-pkg (. package.loaded modname)] + (if (= (type loaded-pkg) :table) loaded-pkg tbl))) + (setmetatable {:hot tbl} { + :__index (fn [_ key] (. (find-table) key)) + :__newindex (fn [_ key val] (tset (find-table) key val)) + })) + (fn readjson [filename] (local f (io.open filename :r)) (local text (f:read "*a")) @@ -76,8 +86,33 @@ (fn in-coro [f ...] (-> (coroutine.create f) (coroutine.resume ...))) -{: int8-to-bytes : int16-to-bytes : int24-to-bytes : bytes-to-uint8 : bytes-to-uint16 : bytes-to-uint24 - : splice : lo : hi - : reload : hotswap : swappable :require swappable-require - : readjson : writejson : waitfor : in-coro} +(fn multival-next [multival i] + (when (< i multival.n) + (values (+ i 1) (. multival (+ i 1))))) + +(fn multival-ipairs [multival] + (values multival-next multival 0)) + +(fn multival [...] + (local multival {:n (select :# ...) :ipairs multival-ipairs}) + (for [i 1 multival.n] + (tset multival i (select i ...))) + multival) + +(fn nested-tset [t keys value] + (let [next-key (. keys 1)] + (if (= (length keys) 1) (tset t next-key value) + (do (when (= (. t next-key) nil) + (tset t next-key {})) + (nested-tset (. t next-key) (lume.slice keys 2) value))))) + +(fn file-exists [name] + (let [f (io.open name :r)] + (when (not= f nil) (io.close f)) + (not= f nil))) + +{: int8-to-bytes : int16-to-bytes : int24-to-bytes : bytes-to-uint8 : bytes-to-uint16 : bytes-to-uint24 + : splice : lo : hi + : reload : hotswap : swappable :require swappable-require : hot-table : nested-tset + : readjson : writejson : file-exists : waitfor : in-coro : multival} diff --git a/link/mame.fnl b/link/mame.fnl index 01e126b..adb55c0 100644 --- a/link/mame.fnl +++ b/link/mame.fnl @@ -23,7 +23,7 @@ (set self.breakpoints {})) (fn Machine.boot [self] (when (not self.pid) - (set self.pid (start-mame :apple2p)))) + (set self.pid (start-mame :apple2e)))) (fn Machine.run [self] (self:boot) (self:connect)) @@ -87,6 +87,37 @@ (when self.monitor (self.monitor:shutdown-session)) (when (nrepl:connected?) (nrepl:disconnect)) (set self.breakpoints {})) +(fn Machine.read [self addr len] + (var bytes nil) + (self:coro-eval + "(let [bencode (require :bencode) + {: addr : len} (bencode.decode (io.read)) + mem (. manager.machine.devices ::maincpu :spaces :program)] + (var bytes \"\") + (for [i 1 len] + (set bytes (.. bytes (string.char (mem:read_u8 (+ addr i -1)))))) + (io.write bytes))" + (lume.merge + (self:input-handler (bencode.encode {: addr : len})) + {:out #(set bytes $2)})) + bytes) +(fn Machine.read-batch [self addr-to-len] + (var addr-to-bytes nil) + (self:coro-eval + "(let [bencode (require :bencode) + addr-to-len (bencode.decode (io.read)) + mem (. manager.machine.devices ::maincpu :spaces :program) + addr-to-bytes {}] + (each [addr len (pairs addr-to-len)] + (var bytes \"\") + (for [i 1 len] + (set bytes (.. bytes (string.char (mem:read_u8 (+ addr i -1)))))) + (tset addr-to-bytes addr bytes)) + (io.write (bencode.encode addr-to-bytes)))" + (lume.merge + (self:input-handler (bencode.encode addr-to-len)) + {:out #(set addr-to-bytes (bencode.decode $2))})) + addr-to-bytes) (fn Machine.write [self addr bytes] (if (> (bytes:len) 0x1000) (do (self:write addr (bytes:sub 1 0x1000)) @@ -99,15 +130,26 @@ (for [i 1 (bytes:len)] (mem:write_u8 (+ addr i -1) (string.byte (bytes:sub i i)))))" (bencode.encode {: addr : bytes})))) +(fn Machine.write-batch [self addr-to-bytes] + (self:eval-input + "(let [bencode (require :bencode) + addr-to-bytes (bencode.decode (io.read)) + mem (. manager.machine.devices ::maincpu :spaces :program)] + (each [addr bytes (pairs addr-to-bytes)] + (for [i 1 (bytes:len)] + (mem:write_u8 (+ addr i -1) (string.byte (bytes:sub i i))))))" + (bencode.encode addr-to-bytes))) (fn Machine.launch [self prg] (self:eval "(manager.machine:soft_reset)") (self:eval (string.format "(emu.keypost \"CALL-151\\n %xG\\n\")" (prg:lookup-addr prg.start-symbol)))) (fn Machine.reboot [self] (self:eval "(manager.machine:hard_reset)")) -(fn Machine.coro-eval [self code] +(fn Machine.coro-eval [self code ?handlers] (var result nil) (local append-to-result #(set result (.. (or result "") $2))) (self:eval code - (self:coro-handlers (coroutine.running) {:value append-to-result :out append-to-result})) + (self:coro-handlers (coroutine.running) + (lume.merge {:value append-to-result :out append-to-result} + (or ?handlers {})))) (coroutine.yield) (or result "")) (fn Machine.dbgcmd [self cmd ?handlers] @@ -130,10 +172,14 @@ (fn Machine.hotswap [self prg-old prg-new] (local addr (prg-old:lookup-addr :debug-stub)) (self:set-bp addr - (fn [] (self:clear-bp addr) - (prg-new:upload self) - (self:jump (prg-new:lookup-addr :on-hotswap)) - (self:continue)))) + #(util.in-coro (fn [] + (self:clear-bp addr) + (local hotswap (prg-old:read-hotswap self)) + (prg-new:upload self) + (prg-new:write-hotswap self hotswap) + (self:jump (prg-new:lookup-addr :on-hotswap)) + (self:continue))))) + (fn Machine.overlay [self prg-overlay] (self:step) (prg-overlay:upload self) diff --git a/link/nrepl-session.fnl b/link/nrepl-session.fnl index 94d7545..2ce491b 100644 --- a/link/nrepl-session.fnl +++ b/link/nrepl-session.fnl @@ -19,7 +19,7 @@ (fn Session.shutdown-session [self] (set self.queue []) (set self.in-progress false) - (set self.sesion nil)) + (set self.session nil)) (fn Session.cleanup-handlers [self] {:status/done #(self:done-msg) @@ -63,9 +63,11 @@ (fn Session.eval [self code ?handlers] (self:send {:op :eval : code} ?handlers)) +(fn Session.input-handler [self input] + {:status/need-input #(self:send-oob {:op :stdin :stdin input})}) + (fn Session.eval-input [self code input ?handlers] (self:send {:op :eval : code} - (lume.merge (or ?handlers {}) - {:status/need-input #(self:send-oob {:op :stdin :stdin input})}))) + (lume.merge (or ?handlers {}) (self:input-handler input)))) Session diff --git a/link/serial.fnl b/link/serial.fnl index c06a7c7..069973a 100644 --- a/link/serial.fnl +++ b/link/serial.fnl @@ -1,5 +1,5 @@ ; using https://github.com/srdgame/librs232 -(local rs232 (require :luars232)) +(local (_ rs232) (pcall #(require :luars232))) (local command (require "core.command")) (fn check [err ...] diff --git a/main.lua b/main.lua index f2a8305..e7b4c91 100644 --- a/main.lua +++ b/main.lua @@ -1,11 +1,11 @@ -- bootstrap the compiler fennel = require("lib.fennel") -table.insert(package.loaders, fennel.make_searcher({correlate=true})) +table.insert(package.loaders, fennel.make_searcher()) fv = fennel.view pp = function(x) print(fv(x)) end lume = require("lib.lume") -- these set global variables and can't be required after requiring core.strict -luars232 = require("luars232") +_, luars232 = pcall(function () require("luars232") end) _coroutine_resume = coroutine.resume function coroutine.resume(...) diff --git a/game/boop.fnl b/neuttower/boop.fnl similarity index 98% rename from game/boop.fnl rename to neuttower/boop.fnl index b118b55..fdfc9f0 100644 --- a/game/boop.fnl +++ b/neuttower/boop.fnl @@ -1,4 +1,4 @@ -(local {: vm} (require :game.defs)) +(local {: vm} (require :neuttower.defs)) (local speaker :0xc030) (vm:def :blipmem ; count p -- diff --git a/game/bosskey.fnl b/neuttower/bosskey.fnl similarity index 97% rename from game/bosskey.fnl rename to neuttower/bosskey.fnl index bad5bfa..60db39e 100644 --- a/game/bosskey.fnl +++ b/neuttower/bosskey.fnl @@ -1,5 +1,5 @@ (local util (require :lib.util)) -(local {: vm : prg : astr : style} (util.require :game.defs)) +(local {: vm : prg : astr : style} (util.require :neuttower.defs)) (vm:word :boss-key :textmode :page2 (vm:until :read-key) :hires :page1) ; if we upload to page 2 we don't have to worry about clobbering screen holes diff --git a/game/cheat.fnl b/neuttower/cheat.fnl similarity index 95% rename from game/cheat.fnl rename to neuttower/cheat.fnl index ebd6be4..0483d5d 100644 --- a/game/cheat.fnl +++ b/neuttower/cheat.fnl @@ -1,4 +1,4 @@ -(local {: vm : say-runon : say} (require :game.defs)) +(local {: vm : say-runon : say} (require :neuttower.defs)) (fn defcheat [name ...] (local cheatdata (.. name "-data")) diff --git a/game/defs.fnl b/neuttower/defs.fnl similarity index 92% rename from game/defs.fnl rename to neuttower/defs.fnl index d2575c9..636e73d 100644 --- a/game/defs.fnl +++ b/neuttower/defs.fnl @@ -4,6 +4,7 @@ (local asm (require :asm.asm)) (local VM (require :asm.vm)) (local tiles (require :game.tiles)) +(local files (require :game.files)) (local Prodos (require :asm.prodos)) (local prg (asm.new)) @@ -133,7 +134,7 @@ (fn append-map [map org label] (org:append [:align 0x100] label - [:bytes (map.map:fromhex)] + [:bytes map.map] [:db (length map.objects)] [:dw (tiles.encode-yx map.jaye)] [:dw (tiles.encode-yx map.neut)] @@ -152,18 +153,18 @@ (vm:word :map-specific-move :map 250 :+ :execute) (vm:word :map-specific-load :map 253 :+ :execute) -(fn deflevel [mapfile label] +(fn deflevel [ilevel label] (local level prg) ; todo: (asm.new prg) - if we want to load levels as an overlay (local org level.vm.code) ; (level:org org.level.org) - if we want to give level data a stable loxation - (local map (readjson mapfile)) - (local entity (require :game.entity)) + (local map (. files.game.levels ilevel)) + (local entity (require :neuttower.entity)) (append-map map org label) (entity.append-from-map map org label) (set level.vm.code org) level) (fn say-runon [portrait ...] - (local result [:vm (.. :draw-p portrait)]) + (local result [:vm (.. :draw-portrait- portrait)]) (local lines [...]) (local ilineOffset (if (< (length lines) 4) 1 0)) (each [iline line (ipairs lines)] @@ -175,10 +176,9 @@ (table.insert result :dismiss-dialog) result) -(local tilelist (tiles.loadgfx tiles.fn-tiles)) -(fn itile [label] (tiles.find-itile tilelist label)) +(fn itile [label] (tiles.find-itile files.game.tiles label)) (set vm.code org.code) -{: vm : prg : mapw : maph : mon : org : achar : astr : style : rot8l : deflevel : say : say-runon : itile : tilelist : controlstate} +{: vm : prg : mapw : maph : mon : org : achar : astr : style : rot8l : deflevel : say : say-runon : itile : controlstate} diff --git a/game/disk.fnl b/neuttower/disk.fnl similarity index 95% rename from game/disk.fnl rename to neuttower/disk.fnl index de61ad5..4a7d53c 100644 --- a/game/disk.fnl +++ b/neuttower/disk.fnl @@ -3,7 +3,7 @@ (local Prodos (require :asm.prodos)) (local util (require :lib.util)) (local {: lo : hi} util) -(local {: org} (require :game.defs)) +(local {: org} (require :neuttower.defs)) (fn append-boot-loader [prg] (local vm prg.vm) @@ -75,8 +75,8 @@ (create-sys-loader disk :NEUT game) - (disk:add-file "TITLE.SCREEN" Prodos.file-type.BIN 0x2000 (: (util.readjson "game/title.screen") :fromhex)) - (disk:add-file "ELEVATOR.SCREEN" Prodos.file-type.BIN 0x2000 (: (util.readjson "game/end.screen") :fromhex)) + (disk:add-file "TITLE.SCREEN" Prodos.file-type.BIN 0x2000 (: (util.readjson "neuttower/title.screen") :fromhex)) + (disk:add-file "ELEVATOR.SCREEN" Prodos.file-type.BIN 0x2000 (: (util.readjson "neuttower/end.screen") :fromhex)) (each [_ file (ipairs game.files)] (disk:add-file file.filename Prodos.file-type.BIN file.org (. game.org-to-block file.org :bytes))) diff --git a/game/end.screen b/neuttower/end.screen similarity index 100% rename from game/end.screen rename to neuttower/end.screen diff --git a/game/entity.fnl b/neuttower/entity.fnl similarity index 99% rename from game/entity.fnl rename to neuttower/entity.fnl index ef76022..28da7c5 100644 --- a/game/entity.fnl +++ b/neuttower/entity.fnl @@ -1,6 +1,6 @@ (local util (require :lib.util)) (local tiles (util.require :game.tiles)) -(local {: vm : org : itile : say : say-runon : controlstate} (require :game.defs)) +(local {: vm : org : itile : say : say-runon : controlstate} (require :neuttower.defs)) (local {: lo : hi} util) ; Entity memory layout: diff --git a/neuttower/footer.fnl b/neuttower/footer.fnl new file mode 100644 index 0000000..9e2659c --- /dev/null +++ b/neuttower/footer.fnl @@ -0,0 +1,55 @@ +(local {: vm : org} (require :neuttower.defs)) +(local {: hi : lo} (require :lib.util)) + +(vm:def :draw-pchar ; pscreen pchar -- + [:block + [:ldy 7] [:clc] + :loop + [:lda [vm.TOP :x]] + [:sta [vm.ST1 :x]] + [:inc vm.TOP :x] + [:lda vm.ST1H :x] [:adc 4] [:sta vm.ST1H :x] + [:dey] + [:bne :loop] + ] + (vm:drop) (vm:drop)) + +(vm:def :lookup-pchar ; c -- pchar + [:sec] + [:lda vm.TOP :x] + [:sbc 0x20] + [:sta vm.TOP :x] + [:lda 0] + [:asl vm.TOP :x] [:rol :a] ;x2 + [:asl vm.TOP :x] [:rol :a] ;x4 + [:asl vm.TOP :x] [:rol :a] ;x8 + [:adc #(hi ($1:lookup-addr :font))] + [:sta vm.TOPH :x]) + +(vm:word :draw-char ; pscreen c -- + :lookup-pchar :draw-pchar) +(vm:word :draw-digit ; pscreen n -- + 0x30 :+ :draw-char) + +(vm:word :snooze (vm:for)) +(vm:word :textsnooze 0x30 :snooze) +(vm:word :draw-text1 0x2257 :draw-text) +(vm:word :draw-text2 0x22d7 :draw-text) +(vm:word :draw-text3 0x2357 :draw-text) +(vm:word :draw-text4 0x23d7 :draw-text) +(vm:word :draw-text ; st pscreen -- + (vm:while [:over :bget :dup] ; st pscreen c + :over :swap :draw-char ; st pscreen + :textsnooze + :inc :swap :inc :swap) + :drop :drop :drop) +(vm:word :cleartext + 0x2257 :clearline 0x22d7 :clearline 0x2357 :clearline 0x23d7 :clearline) + +(vm:word :wait-for-return (vm:until :read-key (string.byte "\r") :=)) +(vm:word :dismiss-dialog :wait-for-return :cleartext) + +(vm:var :footer-displayed vm.false) +(vm:word :show-footer :footer-displayed :get :not (vm:when vm.true :footer-displayed :set :drawfooter)) +(vm:word :hide-footer :footer-displayed :get (vm:when vm.false :footer-displayed :set :clearfooter)) + diff --git a/neuttower/game.json b/neuttower/game.json new file mode 100644 index 0000000..e31a7cb --- /dev/null +++ b/neuttower/game.json @@ -0,0 +1 @@ +{"levelflags":["gord-following"],"tileflags":["walkable","neutable","debris","sittable"],"levels":[{"loadword":"earthquake","map":"212121214121212121212121212141212121212161026161610261616102616161616102616161216143C0C0C2C0C0C0C0C0C0C081C0C0C0C0C0612161C0C08282C0C0C082C0C0C061C0C0C0C0C2024161C0C0C0C0C0C0C0C0C2C082C182C0C0E082612161C2C08282C0C0C0C082C0C061616161616161216161616161C16181616161616143C0C0C282612161C0C0C0C0C0C0C0C0C0C0C061C0C0C0C0C0022161E0828282C0C0C0C0C2C0C081C0C0C0C003612161C2C2C2C0C0C0C0C0C0C0C061C0C0C0C0C06141610303C043C2C0C0C0C0C0C061C0C0C003C061216161616161616161228161616161616161610221","tickword":"","jaye":{"y":9,"x":15},"gord-following":false,"moveword":"","objects":[{"x":8,"func":"door","name":"","linkword":"","y":6},{"link":3,"y":4,"func":"firstterm","name":"","linkword":"","x":2},{"link":2,"y":8,"func":"neutterm","name":"","linkword":"","x":17},{"link":8,"y":8,"func":"switch","name":"","linkword":"","x":13},{"x":9,"link":6,"func":"exitscanner","linkword":"","name":"","y":1},{"x":10,"linkentity":"level2","func":"exitdoor","y":1,"linkword":"exitlevel","name":""},{"x":6,"link":1,"func":"switch","linkword":"","name":"","y":6},{"x":13,"linkentity":"","func":"firstdoor","linkword":"","name":"","y":10}]},{"neut":{"y":12,"x":10},"map":"616161616161626161618161616161616161612161C0C0C06361C0C0E0C0C0C0C0C06103C0C0022161E0C0C0C081C0C0C0C0C0C0C0C081C0C0E0614161C0C0C0C06143C0C0C0C0C0C04322C0C0C0222161C0C0C0C061618161616161816161C0C0C00221616181616161C0C0C06143C0C0C061618161612161C0C0C06361E0C0C061C0C0C0C0C1E0C003612122C0C0C0C061C0C0C061C0E0C0C061C0C0C0022161C0C0C0C061616161616261616161C0C0C061416101C0C0C081C0C0E061C0C0E0C081C0C0C0022162C0C0C02361E0C0C06143C0C0C061E2A2E061216161C1616261612281616122226162C1C1616121","loadword":"","jaye":{"y":11,"x":11},"tickword":"","moveword":"","objects":[{"link":2,"x":9,"y":11,"linkword":"","name":"","func":"term"},{"link":6,"x":2,"y":3,"linkword":"","name":"","func":"term"},{"x":6,"func":"door","name":"","linkword":"","y":10},{"link":5,"x":1,"y":5,"linkword":"","name":"","func":"scan"},{"x":8,"func":"door","name":"","linkword":"","y":8},{"link":2,"x":7,"y":6,"linkword":"","name":"","func":"term"},{"link":8,"x":12,"y":1,"linkword":"","name":"","func":"scan"},{"x":17,"func":"door","name":"","linkword":"","y":7},{"link":10,"x":13,"y":1,"linkword":"","name":"","func":"scan"},{"x":13,"func":"door","name":"","linkword":"","y":8},{"link":12,"x":15,"y":6,"linkword":"","name":"","func":"switch"},{"link":13,"x":2,"y":10,"linkword":"","name":"","func":"term"},{"link":12,"x":12,"y":5,"linkword":"","name":"","func":"term"},{"link":15,"x":15,"y":9,"linkword":"","name":"","func":"scan"},{"x":15,"func":"door","name":"","linkword":"","y":10},{"link":17,"x":16,"y":6,"linkword":"","name":"","func":"term"},{"link":16,"x":18,"y":10,"linkword":"","name":"","func":"term"},{"x":15,"func":"door","name":"","linkword":"","y":3},{"link":18,"x":19,"y":9,"linkword":"","name":"","func":"scan"},{"link":21,"x":13,"y":3,"linkword":"","name":"","func":"term"},{"link":20,"x":18,"y":2,"linkword":"","name":"","func":"term"},{"link":23,"x":8,"y":1,"linkword":"","name":"","func":"scan"},{"x":9,"linkentity":"level3","func":"exitdoor","name":"","linkword":"exitlevel","y":1},{"link":3,"x":16,"y":1,"linkword":"","name":"","func":"switch"},{"link":26,"x":3,"y":1,"linkword":"","name":"","func":"switch"},{"x":6,"func":"door","name":"","linkword":"","y":3},{"link":28,"x":9,"y":3,"linkword":"","name":"","func":"term"},{"link":27,"x":7,"y":2,"linkword":"","name":"","func":"term"},{"x":3,"func":"door","name":"","linkword":"","y":7},{"link":29,"x":17,"y":1,"linkword":"","name":"","func":"switch"}]},{"neut":{"y":12,"x":8},"map":"616161616161616181616161616161616161612161C063C0C0C0C0C0C0C06143C0E0C2C0C043022161C0C0C0C0C0C0C0C0C022C0C0C0C0C0C0C0614161C0C0C2C0C0C0C0C0C081C0C0C0C0C0C0C0612161C08282A2C0C0C0C0436123C0C0C0C0C0030221616161616161616261616161618161616161612161C063C0C02301C3C163C0C0C0C0C0822363022122C0C0C0C0C0C0A2A1C0C0C0C0C0C0C2C0C0614181C0C0C0C0E2C0C061C0C0C0C0C0C0C0C2C061216123C0C0C0C083C061E0C2C0C0C0C0438203022161610261610261616161026161026161026161212121212141212121212121212121412121212121","loadword":"level3-load","jaye":{"y":11,"x":9},"tickword":"","moveword":"","objects":[{"x":1,"link":2,"func":"scan","linkword":"","name":"","y":5},{"x":1,"linkentity":"level4","func":"exitdoor","linkword":"exitlevel","name":"","y":4},{"x":7,"link":4,"func":"gordterm","linkword":"","name":"","y":6},{"x":14,"link":7,"func":"term","linkword":"","name":"","y":11},{"x":9,"link":6,"func":"switch","linkword":"","name":"gordswitch","y":6},{"x":11,"func":"door","linkword":"","name":"","y":9},{"x":10,"link":3,"func":"term","linkword":"","name":"","y":3},{"x":11,"link":9,"func":"scan","linkword":"","name":"","y":10},{"x":14,"func":"door","linkword":"","name":"","y":7},{"x":7,"func":"meetrexx","y":3,"linkword":"","name":""},{"x":8,"func":"meetgord","y":6,"linkword":"","name":""},{"x":8,"func":"gordtable","y":5,"name":"","linkword":""}]},{"neut":{"y":5,"x":20},"map":"61616161616161616261616161616161616161616143C0C0C082E082C0C0636163C0C083C0C0436161C0C0C0C0C0C2C0C0C0C061C0C0C0C0C0C0C06161C0C0C0C0C0C0C0C0C0C061C0C0C0C0C0C0C2616103C0C0A3C0C0C003C0C06163C0C0C0C082A2616161616161C1C1C16161812261618161616161616143C0C0C0C0C0C04361C0C0C0C0C0C0C0C0636181C0C0C0C0C0C0C0C081C0C0C0C0C0C0C0C0C06122C0C0C0C0C0E2C0C0C1A2E2C0C0C0C0C0C0C08161E082828282E0C02361A2C0C0C0C0C0C003236161026161610222616102616161610261616102612121214121212121212121212121412121212121","loadword":"","gord-following":true,"jaye":{"y":4,"x":19},"tickword":"","moveword":"","objects":[{"x":7,"func":"term","y":11,"name":"","linkword":"term-dual-link"},{"x":2,"func":"term","y":3,"name":"term-exit","linkword":"","link":1},{"x":7,"func":"term","y":3,"name":"term-scan","linkword":"","link":1},{"x":11,"func":"door","y":7,"name":"","linkword":""},{"x":12,"func":"scan","y":7,"name":"","linkword":"","link":4},{"x":15,"func":"door","y":7,"name":"","linkword":""},{"x":7,"func":"switch","y":7,"name":"","linkword":"","link":3},{"x":10,"func":"door","y":5,"name":"","linkword":""},{"x":10,"func":"switch","y":4,"name":"","linkword":"","link":4},{"x":16,"func":"rexx","y":11,"name":"","linkword":""},{"x":7,"func":"scan","y":2,"name":"","linkword":"","link":8},{"x":1,"func":"scan","y":4,"name":"","linkword":"","link":13},{"x":1,"linkentity":"level5","func":"exitdoor","y":5,"name":"","linkword":"exitlevel"},{"x":5,"func":"rexx","y":8,"name":"","linkword":""},{"x":7,"func":"tutorial-chair","name":"","linkword":"","y":10},{"x":8,"link":6,"func":"switch","linkword":"","name":"","y":7},{"x":6,"link":2,"func":"switch","linkword":"","name":"","y":7}]},{"neut":{"y":6,"x":20},"map":"21616161228161616161616161616161616162612102E0C0C0C0C0E0C04361C0C0C0C0C0A3C0C0612161C0C0C0C0C0C0C0C022C0C0C08282C0828261416103C0C0C0C0C0C0C081C0C0C0C0C0C0C0C2C1210203C0C0C0C0C0C04361C0C0C08282838282622161616161816161616161618161616161616161216163C0C0C0C0E0C0C06143C0C0C0C0C0C063612102C0C0C0C0C0C0C0C061C0C0C0C0C0C0C0C0814161C0C0C0C0E2C0E2836101C0C0C0C0C0C0C0C1216143C0C0C082A2A2A281C0C0C043034382E26121610261610261C16102616161026161610261612121212121214121212121212121214121212121","loadword":"","tickword":"doortimer-tick","jaye":{"y":5,"x":19},"gord-following":true,"moveword":"move-garbagerexx","objects":[{"x":17,"func":"garbagerexx","linkword":"","name":"south-rexx","y":11},{"x":17,"func":"garbagerexx","linkword":"","name":"","y":8},{"x":20,"link":13,"func":"do-timedswitch","linkword":"","name":"timedswitch","y":9},{"x":20,"link":16,"func":"switch","linkword":"","name":"","y":4},{"x":12,"link":9,"func":"term","linkword":"","name":"","y":4},{"x":10,"func":"healthyrexx","linkword":"","name":"","y":4},{"x":8,"link":17,"func":"switch","linkword":"","name":"","y":2},{"x":8,"link":10,"func":"term","linkword":"","name":"","y":6},{"x":8,"link":5,"func":"term","linkword":"","name":"","y":11},{"x":3,"link":8,"func":"term","linkword":"","name":"","y":11},{"x":5,"link":12,"func":"scan","linkword":"","name":"","y":12},{"x":6,"linkentity":"level6","func":"exitdoor","linkword":"exitlevel","name":"","y":12},{"x":6,"func":"door","linkword":"","name":"","y":7},{"x":11,"func":"explodingdoor","linkword":"","name":"","y":9},{"x":11,"link":14,"func":"scan","linkword":"","name":"","y":10},{"x":13,"func":"door","linkword":"","name":"","y":7},{"x":11,"func":"door","linkword":"","name":"","y":3}]},{"neut":{"y":1,"x":5},"map":"210261616161616161616161812261616161E1614161C2C081C0C0C0C0C0C0C0C0C0E0C081C0C0622161E04384C0C0C043C0C0C043C0C0C0848201612102616161C0C061610281026161C0C0616161612161C2C0C0C0C061E2C0C0C0A261C0C0C0C0E2612161E003C0C0C061C0C0C0C08261C0C0C003E06121026161C0C0C061E08282E2E061C0C0C06161612161C2C0C0C0C06103C0C0C02361C0C0C0C0C2614161E003C0C0C0616102C1026161C0C0C003E06121026184C0C0C0C043C043C063C0C0C0846161612161E081C0C0C0C0C0C0C0C0C0C0C0C081C083612161612261816161616161616161616161616161","loadword":"","tickword":"","jaye":{"y":2,"x":6},"gord-following":true,"moveword":"","objects":[{"link":6,"y":10,"func":"c4","name":"","linkword":"linkloop","x":3},{"link":3,"y":10,"func":"keypad2","name":"","linkword":"","x":5},{"x":5,"func":"door","y":11,"linkword":"","name":""},{"x":11,"func":"door","y":9,"linkword":"","name":""},{"link":17,"y":6,"func":"c9","name":"","linkword":"linkloop","x":13},{"link":5,"y":6,"func":"c8","name":"","linkword":"linkloop","x":9},{"x":11,"func":"switch","y":4,"linkword":"","name":"","link":23},{"link":1,"y":7,"func":"c3","name":"","linkword":"linkloop","x":3},{"link":8,"y":4,"func":"c2","name":"","linkword":"linkloop","x":3},{"link":23,"y":2,"func":"c1","name":"","linkword":"linkloop","x":3},{"x":4,"func":"door","y":2,"linkword":"","name":""},{"link":11,"y":3,"func":"keypad1","name":"","linkword":"","x":4},{"link":4,"y":1,"func":"scan","name":"","linkword":"","x":4},{"x":17,"func":"door","y":2,"linkword":"","name":""},{"link":14,"y":3,"func":"keypad3","name":"","linkword":"","x":17},{"x":19,"func":"rexx","y":2,"linkword":"","name":""},{"link":18,"y":4,"func":"c5","name":"","linkword":"linkloop","x":19},{"link":19,"y":7,"func":"c6","name":"","linkword":"linkloop","x":19},{"link":10,"y":10,"func":"c7","name":"firewall","linkword":"linkloop","x":19},{"link":19,"y":12,"func":"switch","name":"","linkword":"","x":19},{"link":22,"y":10,"func":"keypad4","name":"","linkword":"","x":17},{"x":17,"func":"door","y":11,"linkword":"","name":""},{"link":9,"y":11,"func":"cx","name":"","linkword":"linkloop","x":15},{"link":25,"y":12,"func":"scan","name":"","linkword":"","x":14},{"x":13,"linkentity":"","func":"exitdoor","y":12,"linkword":"endgame","name":""}]}],"tilesets":{"jaye-tileset":"gfx","neut-tileset":"neut"},"tiles":[{"neut":"5F5F1F03090923436943230909031F5F7A7A784111104542174245101141787A","label":"neut1","flags":[],"word":"","gfx":"7F7F1F03090923436943230909031F7F7F7F784111104542174245101141787F"},{"neut":"5F1F03090923436943230909031F5F5F7A784111104542174245101141787A7A","label":"neut2","flags":[],"word":"","gfx":"7F1F03090923436943230909031F7F7F7F784111104542174245101141787F7F"},{"neut":"808080C0C0C0E0C0D0C8C04040404080808083058585828A9282820A08081980","label":"jaye-e","flags":[],"word":"","gfx":"808080C0C0C0E0C0D0C8C04040404080808083058585828A9282820A08081980"},{"neut":"8080C020A0A0C0C0D0C8C0501010188080808183838782828A8A920202020380","label":"jaye-w","flags":[],"word":"","gfx":"8080C020A0A0C0C0D0C8C0501010188080808183838782828A8A920202020380"},{"neut":"8080E030B0B098C0D0D0C840404060808080870D8D8D99828A8A920202020780","label":"jaye-s","flags":[],"word":"","gfx":"8080E030B0B098C0D0D0C840404060808080870D8D8D99828A8A920202020780"},{"neut":"8080C0E0E0E0B0C0D0C8C040404060808080838787878D828A92820202020780","label":"jaye-n","flags":[],"word":"","gfx":"8080C0E0E0E0B0C0D0C8C040404060808080838787878D828A92820202020780"},{"neut":"8080808080808080808080808080808080808080808080808080808080808080","label":"t-floor","flags":{"walkable":true},"word":"","gfx":"80808C8080808080B08080808C808080808C80808083B0808080808080868080"},{"neut":"80FC8C8C8C8CFC80FCFEFE8080808080809F989898989F809F8F878080808080","label":"termoff","flags":[],"word":"term","gfx":"007C0C0C0C0C7C007C7E7EAA88888800001F181818181F001F0F979584848400"},{"neut":"507C2C2C2C2C7C557D7D7E005F5F5F5F0A3F353535353F2A3F2F67707A7A7A7A","label":"termon","flags":{"neutable":true},"word":"term","gfx":"007C2C0C0C2C7C007C7E7EAA88888800001F18191C191F001F0F979584848400"},{"neut":"D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","label":"","flags":[],"word":"","gfx":"D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5D5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},{"neut":"D5D5D5D581F9F9F9F9C195D5D5D5D5D5AAAAAAAAAA809F9F9F9F80AAAAAAAAAA","label":"","flags":[],"word":"","gfx":"D5D5D5D5D5F5F5FDDDD5D5D5D5D5D5D5AAAAAAAAAEAEBFBFBFABAAAAAAAAAAAA"},{"neut":"5F5F5F5F5F5F57555555575F5F5F5F5F7A7A7A7A7A7A6A2A2A2A6A7A7A7A7A7A","label":"","flags":{"neutable":true},"word":"","gfx":"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"},{"neut":"8080F8F8F8F8F8F8F8F8F8F8F8F8808080808F8F8F8F8F8F8F8F8F8F8F8F8080","label":"doorclosed","flags":[],"word":"door","gfx":"FF8FA7B3B3B3B3B3B3B3B3B3B3B383FFFFF8F2E6E6E6E6E6E6E6E6E6E6E6E0FF"},{"neut":"8080FC8C8C8C8C8C8C8C8C8C8CFC808080809F989898989898989898989F8080","label":"dooropen","flags":{"walkable":true},"word":"door","gfx":"FF8F87838383838383838383838383FFFFF8F0E0E0E0E0E0E0E0E0E0E0E0E0FF"},{"neut":"5F5F5F5F0F0705050505070F5F5F5F5F7A7A7A7A70602020202060707A7A7A7A","label":"switchoff","flags":{"neutable":true},"word":"switch","gfx":"FFFFCFCFCFCF898183838787878FFFFFFFFCE4E4E4E4E0E0E0E0E0E0F0F8FFFF"},{"neut":"5F5F5F5F0F0725252525070F5F5F5F5F7A7A7A7A70602525252560707A7A7A7A","label":"switchon","flags":{"neutable":true},"word":"switch","gfx":"FFFFCFCFCFCF898123232727878FFFFFFFFCE4E4E4E0E0616565656571F8FFFF"},{"neut":"808080D0D0D0D0D0D0D0D0D0D08080808080808A8A8A8A8A8A8A8A8A8A808080","label":"","flags":[],"word":"","gfx":"FFFF83D3D3D3D3D3D3D3D3D3D383FFFFFFFFC0CACACECACBCACACACACAC0FFFF"},{"neut":"5F5F5F5F7F0F0D0D0D0D0F0F7F5F5F5F7A7A7A7A7F703030303070707F7A7A7A","label":"scanoff","flags":{"neutable":true},"word":"scan","gfx":"FFFFAFEBFBFBFBBBBBFBFBFBEBAFFFFFFFFFF5D7DFDFDFDDDDDFDFDFD7F5FFFF"},{"neut":"5F5F5F5F7F2F2D2D2D2D2F2F7F5F5F5F7A7A7A7A7F753535353575757F7A7A7A","label":"scanon","flags":{"neutable":true},"word":"scan","gfx":"FFFF2F2B2B2B6B6B6B6B2B2B2B2FFFFFFFFF755555555757575755555575FFFF"},{"neut":"9EF3C68CB3E7F88086BEE0E0B09898C6F8CFE1B18C8C988E868C99B0E6CE8F99","label":"","flags":[],"word":"","gfx":"FFF3C78FBFFCF98187BFFFFFBF9F9FC7FFCFE1F1FCFCF8FEFEFCF9F0E6CE8F9F"},{"neut":"8080808080D4848484D48484848480808080808080AAA0A1A0AAA0A0A0A08080","label":"","flags":[],"word":"","gfx":"80808C80808080A8AAAAAAA888888880808C8080808380859595958584848480"},{"neut":"8080808080D4808480D48084808480808085848581AA80A180AA80A080A08080","label":"broken-table","flags":{"debris":true},"word":"","gfx":"80808C8080A0A0A8AAAA8AA0A8808080808C8081919090848594959585858080"},{"neut":"808080D09090D0C0C0D48484D490B8808080808A88888A8282AAA0A0AA889C80","label":"t-chair","flags":{"sittable":true},"word":"","gfx":"00005054545450404054545010383800000C0A2A2A2A0A03032A2A0A081C1C00"},{"neut":"808080809080D080C0808480D480B8808085848589808A818280A080AA809C80","label":"","flags":{"debris":true},"word":"","gfx":"0000001C1C10545040606010545454000030070702020A0A0100020A080A0200"},{"neut":"8080808080D48484D48484D48484D48080808080808A88888A88888A88888A80","label":"","flags":[],"word":"","gfx":"80A0A8AA92D2D2AAC2C2AA92D2AA808080959595949494959494959494858080"},{"neut":"8080808080808480D48084808480D48080858485818089808A80888088808A80","label":"","flags":{"debris":true},"word":"","gfx":"80808C808080A8C292AAAAAAAA8AC0D0808C80808083959290959194948580A8"},{"neut":"8080D4C4D4C0D4C4D4C0C0D48484D48080808A888A808A888A80808A88888A80","label":"","flags":[],"word":"","gfx":"80806008282A0800202880A8A8A8A08080980000141501051511819595958580"},{"neut":"8080D4809480D480D480C0808480D4808085848589808A818A80808088808A80","label":"","flags":{"debris":true},"word":"","gfx":"80808C808080A0A8AAAA8AA2AAAAAA80808C0000000330010105051511010514"},{"neut":"80D0D0D0C0D4C4C4CECAC0E0A0808080808A8B8A82AAA2A2F2D2828785808080","label":"t-rexx","flags":[],"word":"rexx","gfx":"80D0D0D0C0D4C4C4CECAC0E0A0808080808A8B8A82AAA2A2F2D2828785808080"},{"neut":"F8989898BE8686868686BEB098F880809F989898FCE0E0E0E0E0FC8C989F8080","label":"t-rexxstop","flags":[],"word":"rexx","gfx":"F8989898BE8686868686BEB098F880809F989898FCE0E0E0E0E0FC8C989F8080"},{"neut":"8080808088A8A8E0E0F0C8C0C0C08080808080808080808183838282829A8080","label":"gord-ground","flags":[],"word":"","gfx":"80808C8088A8A8E0E0F0C8C0CCC08080808C80808080B08183838282829A8080"},{"neut":"808080A0A080F0F0F0E8D0D09090988080808081818083838385828282828680","label":"gord-s","flags":[],"word":"","gfx":"808080A0A080F0F0F0E8D0D09090988080808081818083838385828282828680"},{"neut":"80C09090A080F0F0F0F0D0D09090988080808282818083838383828282828680","label":"gord-n","flags":[],"word":"","gfx":"80C09090A080F0F0F0F0D0D09090988080808282818083838383828282828680"},{"neut":"808080A0A080F0F0F0F0D0D09090B08080808181818081838581808282828680","label":"gord-e","flags":[],"word":"","gfx":"808080A0A080F0F0F0F0D0D09090B08080808181818081838581808282828680"},{"neut":"8080A0A0A080E0F0E8E0D0D09090988080808081818083838383828282828380","label":"gord-w","flags":[],"word":"","gfx":"8080A0A0A080E0F0E8E0D0D09090988080808081818083838383828282828380"},{"neut":"8088A8A880F0F0F8B8D4D4C4C4E486808080808A88888A8181AAA0A0AA889C80","label":"gord-sit","flags":[],"word":"","gfx":"0088A8A8047470F8B8D4D4C4C4E40600000C0A2A2A2A0A81812A2A0A081C1C00"},{"neut":"FF81F9B9E9B9E9B9E9B9E9B9F9F981FFFFE0E7E7E5E7E5E7E5E7E5E7E7E7E0FF","label":"t-keyoff","flags":[],"word":"keypad","gfx":"FF81F9B9E9B9E9B9E9B9E9B9F9F981FFFFE0E7E7E5E7E5E7E5E7E5E7E7E7E0FF"},{"neut":"7F01793969396939693969397979017F7F60676765676567656765676767607F","label":"t-keyon","flags":[],"word":"keypad","gfx":"7F01793969396939693969397979017F7F60676765676567656765676767607F"},{"neut":"5F8FA7C7A389D189838FA7C7A70F5F5F7AF0C4928A91C0F1F2C1928A90427A7A","label":"libb1","flags":[],"word":"","gfx":"FF8FA7C7A389D189838FA7C7A78FFFFFFFF0C4928A91C0F1F2C1928A90C7FFFF"},{"neut":"5F5F8FA7C7A389D189838FA7C7A70F5F7A7AF0C4928A91C0F1F2C1928A90427A","label":"libb2","flags":[],"word":"","gfx":"FFFF8FA7C7A389D189838FA7C7A78FFFFFFFF0C4928A91C0F1F2C1928A90C7FF"},{"neut":"5F5F5F8FA3A9A9A3A7A7A7A7A70F5F5F7A7A7A7A7AE0C5959595909292427A7A","label":"t-chuck","flags":[],"word":"","gfx":"FFFFFF8FA3A9A9A3A7A7A7A7A78FFFFFFFFFFFFFFCE0C5959595909292C7FFFF"},{"neut":"5F5F8FA3A9A9A3A7A7A7A7A78F5F5F5F7A7A7A7860C5959595909292C27A7A7A","label":"t-chuck2","flags":[],"word":"","gfx":"FFFF8FA3A9A9A3A7A7A7A7A78FFFFFFFFFFFFFFCE0C5959595909292C7FFFFFF"}],"portraits":[{"gfx":"8080808080E0E0F0F8FC2CBCACACACAC80809CFEFFFFFFD7D5D5555D4F5DD5D5BCB8B8B8F8F8F8F8FCFCFEFE86D0D0D495D5D5E5D5D5D5D7C797D7D0AAAAAAAA808086BFFFFFFFFAEAAA2A2E3C2EAAAA80808080808183878787058F8D8D8D8FAAAAAAA7AAAAEAEAE2E8EA8AD5D5D5D58F8F8787878F8F8F9F9FBFBE808A8AAA","label":"jaye","flags":[]},{"gfx":"00002020000000004808080800202028004040011404450144010805445420352020000808084800000000202000000020544405080144014504140140400000000202012921220122011021222A052D00000405000001011211101100040415052A222110012201222129010202000004040011101112010100000504000000","label":"neut","flags":[]},{"gfx":"80C090808484848484848484A8A8A8A8AA8080808080D4D4D5D5F59DFDD5D5D5A8A08080808080808080808080E0F8FC95D5D5D5F595D5D4D084D4D4D4D7FFFFD58080808080AAAAAAAABAE2FAAAAAA280828880A0A0A0A1A1A1A1A195959595A0AAAAAAAFA8AAAA8AA0AAAAAAEAFFFF95858181818181808080808080879FBF","label":"gord","flags":[]},{"gfx":"808080808080C0C0C0C0C0C0C0C0C0C0808080808080AAAAFAFAFAAAAAAAAAAA8080808080C0D0D0D0D0D0D0D0D0D0D0A0A0A0A0A0AAAAAAA8A8A8A8A8A8A8A8808080808080D5D5DFDFDFD5D5D5D5D5808080808080828282828282828282828585858585D5D5D595959595959595958080808080828A8A8A8A8A8A8A8A8A8A","label":"rexx","flags":[]},{"gfx":"808080F8F8F8F8F8F8B8B8F8F8B8B8F8808080FFFFFFFFDFDFFDFDDFDFFDFDDFF8B8B8F8F8B8B8F8F8F8F8F8F8D0D080DFFDFDDFDFFDFDDFDFFFFFFFFFAAAA80808080FFFFFFFFFEFEAFAFFEFEAFAFFE8080808FAFAFAFAFAFAFAFAFAFAFAFAFFEAFAFFEFEAFAFFEFEFFFFFFFFD5D580AFAFAFAFAFAFAFAFAFAFAFAFAFAAAA80","label":"pady","flags":[]},{"gfx":"0080C07070303030303030303030303000AAAA7F0045001400150051005400003030707000703018187C4C7E000000000000007F007F0000007F017F0000000000D5D57F400A0028000A002000080000008A8A8B8B8B8B8B8B8B8B8B8B8B8B8B0000407F007F6030301F187F000000008B8B8B03000F0C060603030100000000","label":"term","flags":[]},{"gfx":"80808080808080A0C0A0C0A0808088AA8080808080808185AA858285C1D0A0D0D4AA94AA94AA888080A0C0A0C0A08080AAD2A2D2A2D2A2D2C185AA858285818080A0A8D0A8A09090D581A1C1A0C2A1C280808180818080808081858285828582A1828182959291929090D5808080808085818080808084958A958A958A958480","label":"libb","flags":[]},{"gfx":"808080808080808088A082A8A0AAA8AA849494D4D4D4D0D5D4D1D5D58595D5D5AAAA8AAA8AAA82AA828A808280808080D4D5D4D5D4D5D4D4D4D4D0C0C0C080808080808080A288A2A08AA2AAAAAAAAAA84949495958585808181848184848581AAAAAA8AAA8AAA8AAAAAA8A2A2AAAA8080818181818181818585818185818080","label":"chuck","flags":[]}],"font":[{"flags":[],"gfx":"8080808080808080"},{"flags":[],"gfx":"8C8C8C8C88808C80"},{"flags":[],"gfx":"B3B3928080808080"},{"flags":[],"gfx":"B6FFB6B6B6FFB680"},{"flags":[],"gfx":"8CBE839EB09F8C80"},{"flags":[],"gfx":"80A3938884B2B180"},{"flags":[],"gfx":"8E9B9BCEBBB3EE80"},{"flags":[],"gfx":"8C8C888080808080"},{"flags":[],"gfx":"988C8C8C8C8C9880"},{"flags":[],"gfx":"8C98989898988C80"},{"flags":[],"gfx":"8CAD9E8C9EAD8C80"},{"flags":[],"gfx":"808C8CBF8C8C8080"},{"flags":[],"gfx":"808080808C8C8880"},{"flags":[],"gfx":"8080809C80808080"},{"flags":[],"gfx":"80808080808C8C80"},{"flags":[],"gfx":"80A0B0988C868280"},{"flags":[],"gfx":"9CB6B6BEB6B69C80"},{"flags":[],"gfx":"989C989898989880"},{"flags":[],"gfx":"9CB6B0988C86BE80"},{"flags":[],"gfx":"9CB6B098B0B69C80"},{"flags":[],"gfx":"9C9E9B9BBF989880"},{"flags":[],"gfx":"BE86869EB0B09E80"},{"flags":[],"gfx":"9C86869EB6B69C80"},{"flags":[],"gfx":"BEB0B0988C8C8C80"},{"flags":[],"gfx":"9CB6B69CB6B69C80"},{"flags":[],"gfx":"9CB6B6BCB0B09C80"},{"flags":[],"gfx":"808C8C808C8C8080"},{"flags":[],"gfx":"808C8C808C8C8880"},{"flags":[],"gfx":"B0988C868C98B080"},{"flags":[],"gfx":"8080BE80BE808080"},{"flags":[],"gfx":"868C98B0988C8680"},{"flags":[],"gfx":"9CB6B0988C808C80"},{"flags":[],"gfx":"9EB3B3BBBB839E80"},{"flags":[],"gfx":"9CB6B6B6BEB6B680"},{"flags":[],"gfx":"9EB6B69EB6B69E80"},{"flags":[],"gfx":"9CB6868686B69C80"},{"flags":[],"gfx":"9EB6B6B6B6B69E80"},{"flags":[],"gfx":"BE86869E8686BE80"},{"flags":[],"gfx":"BE86869E86868680"},{"flags":[],"gfx":"9EB383BBB3B39E80"},{"flags":[],"gfx":"B6B6B6BEB6B6B680"},{"flags":[],"gfx":"8C8C8C8C8C8C8C80"},{"flags":[],"gfx":"B0B0B0B0B6BE9C80"},{"flags":[],"gfx":"B6B69E9EB6B6B680"},{"flags":[],"gfx":"868686868686BE80"},{"flags":[],"gfx":"92BFBFBFB3B3B380"},{"flags":[],"gfx":"9EB6B6B6B6B6B680"},{"flags":[],"gfx":"9CB6B6B6B6B69C80"},{"flags":[],"gfx":"9EB6B69E86868680"},{"flags":[],"gfx":"9CB6B6B6BEB6BC80"},{"flags":[],"gfx":"9EB6B69EB6B6B680"},{"flags":[],"gfx":"9CB6869CB0B69C80"},{"flags":[],"gfx":"BFBF8C8C8C8C8C80"},{"flags":[],"gfx":"B6B6B6B6B6B69C80"},{"flags":[],"gfx":"B6B6B69C9C9C8880"},{"flags":[],"gfx":"B3B3B3BFBFBF9280"},{"flags":[],"gfx":"B6B6B69CB6B6B680"},{"flags":[],"gfx":"B6B6B6BCB0B09C80"},{"flags":[],"gfx":"BEBEB0988CBEBE80"},{"flags":[],"gfx":"BC8C8C8C8C8CBC80"},{"flags":[],"gfx":"8082868C98B0A080"},{"flags":[],"gfx":"9E98989898989E80"},{"flags":[],"gfx":"8894808080808080"},{"flags":[],"gfx":"808080808080BE80"}],"players":["jaye","neut"]} \ No newline at end of file diff --git a/game/gfx.fnl b/neuttower/gfx.fnl similarity index 98% rename from game/gfx.fnl rename to neuttower/gfx.fnl index 6333f22..14c64d6 100644 --- a/game/gfx.fnl +++ b/neuttower/gfx.fnl @@ -1,5 +1,5 @@ (local {: lo : hi} (require :lib.util)) -(local {: vm : mapw : maph : org} (require :game.defs)) +(local {: vm : mapw : maph : org} (require :neuttower.defs)) ; Graphics routines (vm:def :mixed [:sta :0xc053]) diff --git a/neuttower/init.fnl b/neuttower/init.fnl new file mode 100644 index 0000000..06e2c75 --- /dev/null +++ b/neuttower/init.fnl @@ -0,0 +1,65 @@ +(local util (require :lib.util)) +(local {: lo : hi : readjson} util) +(local tile (util.reload :game.tiles)) +(local files (require :game.files)) +(local {: prg : vm : org} (util.reload :neuttower.defs)) + +(local disk (util.reload :neuttower.disk)) + +(util.reload :neuttower.gfx) +(util.reload :neuttower.footer) +(util.reload :neuttower.map) +(util.reload :neuttower.entity) +(util.reload :neuttower.player) +(util.reload :neuttower.boop) +(util.reload :neuttower.cheat) + +(tile.appendtiles org.code) +(org.code:append [:align 0x100] :font) +(tile.appendgfx org.code files.game.font) +(tile.append-portraitwords vm {:neut #[:vm :chuck-mode :get (vm:if [:lit :portrait-chuck] [:lit :portrait-neut])]}) + +(util.reload :neuttower.level1) +(util.reload :neuttower.level2) +(util.reload :neuttower.level3) +(util.reload :neuttower.level4) +(util.reload :neuttower.level5) +(util.reload :neuttower.level6) + +(util.reload :neuttower.bosskey) + +(vm:var :tick-count) +(vm:word :handle-key :tick :read-key :dup :cheat-key :player-key :hide-footer) +(vm:word :tick :map-specific-tick :tick-count :get 1 :+ :tick-count :set :player-redraw :rnd :drop) + +(vm:var :next-level 0) +(vm:word :load-next-level :next-level :get :dup (vm:if [:load-level 0 :next-level :set] [:drop])) +(vm:word :load-level ; level-ptr -- + :lit :map-ptr :set :reload-level) + +(vm:word :reload-level + :map-jaye-yx :jaye-yx :set + :map-neut-yx :neut-yx :set + :map-gord-yx :gord-yx :set + 0 :gord-dir :set + 0xffff :rexx-yx :set + :map-specific-load + :full-redraw) + +(vm.code:append :main + [:jsr :reset] + [:jsr :interpret] + [:vm :hires + :lit :level1 :load-level + (vm:forever + (vm:hotswap-sync :lit :level6 :load-level) + :interactive-eval-checkpoint + :handle-key + ) + :quit]) + +(disk.append-boot-loader prg) +(prg:assemble) +(disk.write prg) + +prg diff --git a/game/level1.fnl b/neuttower/level1.fnl similarity index 88% rename from game/level1.fnl rename to neuttower/level1.fnl index cfdae1d..1d8ce57 100644 --- a/game/level1.fnl +++ b/neuttower/level1.fnl @@ -1,12 +1,13 @@ (local {: readjson} (require :lib.util)) -(local {: deflevel : say : itile : controlstate : tilelist} (require :game.defs)) -(local {: ev} (require :game.entity)) +(local {: deflevel : say : itile : controlstate} (require :neuttower.defs)) +(local {: ev} (require :neuttower.entity)) (local {: decode-itile : encode-yx} (require :game.tiles)) -(local level (deflevel "game/map1.json" :level1)) +(local files (require :game.files)) +(local level (deflevel 1 :level1)) (local vm level.vm) -(let [map (readjson "game/map1.json") - maptiles (map.map:fromhex) +(let [map (. files.game.levels 1) + maptiles map.map furniture-yx []] (for [ibyte 1 (length maptiles)] (let [btile (maptiles:sub ibyte ibyte) @@ -14,7 +15,7 @@ itile (+ (decode-itile enctile) 1) mx (+ (% (- ibyte 1) 20) 1) my (- 12 (math.floor (/ (- ibyte 1) 20)))] - (when (. tilelist itile :flags :debris) + (when (. files.game.tiles itile :flags :debris) (table.insert furniture-yx (encode-yx {:x mx :y my}))))) (vm.code:append :furniture-yx) (for [_ 1 10] diff --git a/neuttower/level2.fnl b/neuttower/level2.fnl new file mode 100644 index 0000000..b00300c --- /dev/null +++ b/neuttower/level2.fnl @@ -0,0 +1,6 @@ +(local {: deflevel : say : itile} (require :neuttower.defs)) +(local {: ev} (require :neuttower.entity)) +(local level (deflevel 2 :level2)) +(local vm level.vm) + +level diff --git a/game/level3.fnl b/neuttower/level3.fnl similarity index 94% rename from game/level3.fnl rename to neuttower/level3.fnl index 19d8cdc..5ab6556 100644 --- a/game/level3.fnl +++ b/neuttower/level3.fnl @@ -1,8 +1,8 @@ -(local {: deflevel : say : itile : controlstate} (require :game.defs)) -(local {: ev} (require :game.entity)) -(local level (deflevel "game/map3.json" :level3)) +(local {: deflevel : say : itile : controlstate} (require :neuttower.defs)) +(local {: ev} (require :neuttower.entity)) +(local level (deflevel 3 :level3)) (local tile (require :game.tiles)) -(local {: walkable : neutable : debris} tile.flag-to-bit) +(local {: walkable : neutable : debris} (tile.flag-to-bit)) (local vm level.vm) diff --git a/game/level4.fnl b/neuttower/level4.fnl similarity index 78% rename from game/level4.fnl rename to neuttower/level4.fnl index 164ae7d..9463c2e 100644 --- a/game/level4.fnl +++ b/neuttower/level4.fnl @@ -1,6 +1,6 @@ -(local {: deflevel : say : itile} (require :game.defs)) -(local {: ev} (require :game.entity)) -(local level (deflevel "game/map4.json" :level4)) +(local {: deflevel : say : itile} (require :neuttower.defs)) +(local {: ev} (require :neuttower.entity)) +(local level (deflevel 4 :level4)) (local vm level.vm) (vm:word :term-dual-link diff --git a/game/level5.fnl b/neuttower/level5.fnl similarity index 90% rename from game/level5.fnl rename to neuttower/level5.fnl index 33981f3..de61e09 100644 --- a/game/level5.fnl +++ b/neuttower/level5.fnl @@ -1,14 +1,15 @@ -(local {: deflevel : say : itile : controlstate : tilelist} (require :game.defs)) -(local {: ev} (require :game.entity)) +(local {: deflevel : say : itile : controlstate} (require :neuttower.defs)) +(local {: ev} (require :neuttower.entity)) (local tile (require :game.tiles)) -(local {: notes} (require :game.boop)) -(local {: walkable : neutable : debris : sittable} tile.flag-to-bit) -(local level (deflevel "game/map5.json" :level5)) +(local files (require :game.files)) +(local {: notes} (require :neuttower.boop)) +(local {: walkable : neutable : debris : sittable} (tile.flag-to-bit)) +(local level (deflevel 5 :level5)) (local vm level.vm) (vm:word :snd-dropgarbage (notes [:a1] 0x02 0xf0)) (vm.code:append :debristiles) -(each [itile tiledef (ipairs tilelist)] +(each [itile tiledef (ipairs files.game.tiles)] (when tiledef.flags.debris (vm.code:append [:db (tile.encode-itile itile)]))) (vm:word :randomgarbage :rnd 0x03 :& :lit :debristiles :+ :bget) diff --git a/game/level6.fnl b/neuttower/level6.fnl similarity index 98% rename from game/level6.fnl rename to neuttower/level6.fnl index 23e4bf8..ceb269f 100644 --- a/game/level6.fnl +++ b/neuttower/level6.fnl @@ -1,8 +1,8 @@ -(local {: deflevel : say : say-runon : itile : controlstate} (require :game.defs)) -(local {: ev} (require :game.entity)) +(local {: deflevel : say : say-runon : itile : controlstate} (require :neuttower.defs)) +(local {: ev} (require :neuttower.entity)) (local tile (require :game.tiles)) -(local {: walkable : neutable : debris : sittable} tile.flag-to-bit) -(local level (deflevel "game/map6.json" :level6)) +(local {: walkable : neutable : debris : sittable} (tile.flag-to-bit)) +(local level (deflevel 6 :level6)) (local vm level.vm) (vm:word :linkloop ; e -- e diff --git a/neuttower/map.fnl b/neuttower/map.fnl new file mode 100644 index 0000000..37bd922 --- /dev/null +++ b/neuttower/map.fnl @@ -0,0 +1,39 @@ +(local {: lo : hi} (require :lib.util)) +(local {: vm : mapw : maph : rot8l} (require :neuttower.defs)) + +(vm:def :lookup-flags ; itile -- flags + [:lda vm.TOP :x] + (rot8l 3) ; lllhhhhh > hhhhhlll + [:adc #(lo ($1:lookup-addr :tileflags))] + [:sta vm.W] + [:lda #(hi ($1:lookup-addr :tileflags))] + [:adc 0] + [:sta vm.WH] + [:ldy 0] [:lda [vm.W] :y] + [:sta vm.TOP :x]) + +(vm:def :map-at ; yx -- pmap + [:lda (- maph 1)] + [:sec] + [:sbc vm.TOPH :x] + [:asl :a] ; x2 + [:asl :a] ; x4 + [:sta vm.TOPH :x] + [:asl :a] ; x8 + [:asl :a] ; x16 + [:clc] [:adc vm.TOPH :x] ; x20 + [:adc vm.TOP :x] + [:sta vm.TOP :x] + [:lda :map-page] + [:sta vm.TOPH :x]) +(vm:word :itile-at ; yx -- itile + :map-at :bget) + +(vm:word :update-itile ; yx itile -- + :over :map-at :bset :drawtile-at) + +(vm:word :drawtile-at ; yx -- + :dup :yx>screen :swap + :itile-at :lookup-tile + :drawtile) + diff --git a/game/player.fnl b/neuttower/player.fnl similarity index 97% rename from game/player.fnl rename to neuttower/player.fnl index 7e9b8f4..7f7d5ab 100644 --- a/game/player.fnl +++ b/neuttower/player.fnl @@ -1,7 +1,7 @@ (local tile (require :game.tiles)) -(local {: vm : mapw : maph : itile : controlstate} (require :game.defs)) +(local {: vm : mapw : maph : itile : controlstate} (require :neuttower.defs)) -(local {: walkable : neutable : debris : sittable} tile.flag-to-bit) +(local {: walkable : neutable : debris : sittable} (tile.flag-to-bit)) (vm:word :movement-dir ; key -- dyx (vm:case [(string.byte "I") 0xff00] diff --git a/neuttower/tiles.fnl b/neuttower/tiles.fnl new file mode 100644 index 0000000..6c80f1b --- /dev/null +++ b/neuttower/tiles.fnl @@ -0,0 +1,84 @@ +(local util (require :lib.util)) +(local lume (require :lib.lume)) + +(local flags [:walkable :neutable :debris :sittable]) +(local flag-to-bit {}) +(each [iflag flag (ipairs flags)] + (tset flag-to-bit flag (bit.lshift 1 (- iflag 1)))) + +(local encoded-tile-fields [:gfx :neut :mask]) +(fn convert [tile field method] + (local oldval (. tile field)) + (when oldval + (tset tile field (: oldval method))) + tile) +(fn convert-all [tile method] + (each [_ field (ipairs encoded-tile-fields)] + (convert tile field method)) + tile) + +(fn deserialize [tile] + (match (type tile) + :string {:gfx (tile:fromhex) :flags {}} + :table (convert-all tile :fromhex))) + +(fn serialize [tile] (convert-all (lume.clone tile) :tohex)) + +(local fn-tiles "game/tiles.json") +(local fn-portraits "game/portraits.json") +(local fn-font "game/font.json") + +(fn loadgfx [filename] (lume.map (util.readjson filename) deserialize)) +(fn savegfx [filename gfx] (util.writejson filename (lume.map gfx serialize))) + +(fn appendgfx [org gfx ?key ?ignore-labels] + (each [_ g (ipairs gfx)] + (when (and g.label (not ?ignore-labels)) (org:append g.label)) + (org:append [:bytes (. g (or ?key :gfx))]))) + +(fn appendtiles [org] + (local tiles (loadgfx fn-tiles)) + (org:append [:align 0x100] :jaye-tileset) + (appendgfx org tiles) + (org:append [:align 0x100] :neut-tileset) + (appendgfx org tiles :neut true) + (appendgfx org (loadgfx fn-portraits)) + (org:append :tileflags) + (each [_ tile (ipairs tiles)] + (var flags 0) + (each [flag _ (pairs tile.flags)] + (set flags (bit.bor flags (. flag-to-bit flag)))) + (org:append [:db flags]))) + +(fn append-portraitwords [vm ?overrides] + (local overrides (or ?overrides {})) + (each [_ p (ipairs (loadgfx fn-portraits))] + (let [wordname (.. :draw- p.label) + override (. overrides p.label)] + (vm:word (.. :draw- p.label) :show-footer + (if override (override p.label) [:vm :lit p.label]) + :draw-portrait)))) + +(fn encode-yx [xy] + (if xy (bit.bor (bit.lshift (- xy.y 1) 8) (- xy.x 1)) 0xffff)) + +(fn encode-itile [itile] + (bit.bor + (bit.lshift (bit.band (- itile 1) 0x07) 5) + (bit.rshift (bit.band (- itile 1) 0xf8) 3))) + +(fn decode-itile [enctile] + (+ 1 (bit.bor + (bit.lshift (bit.band enctile 0x1f) 3) + (bit.rshift (bit.band enctile 0xe0) 5)))) + +(fn find-itile [tiles label ?itilenext] + (local itile (or ?itilenext 1)) + (local tile (. tiles itile)) + (assert (not= tile nil) (.. "No such tile " label)) + (if (= tile.label label) (encode-itile itile) + (find-itile tiles label (+ itile 1)))) + +{: loadgfx : savegfx : appendtiles : appendgfx : append-portraitwords : flags : flag-to-bit : find-itile + : fn-tiles : fn-portraits : fn-font : encode-yx : encode-itile : decode-itile} + diff --git a/game/title.screen b/neuttower/title.screen similarity index 100% rename from game/title.screen rename to neuttower/title.screen diff --git a/presentation/commands.fnl b/presentation/commands.fnl new file mode 100644 index 0000000..2446b9e --- /dev/null +++ b/presentation/commands.fnl @@ -0,0 +1,31 @@ +(local util (require :lib.util)) +(local core (require :core)) +(local command (require :core.command)) +(local keymap (require :core.keymap)) +(local SlideshowView (require :presentation.engine)) + +(fn set-scale [multiplier] + (set _G.SCALE (* (love.graphics.getDPIScale) multiplier)) + (util.hotswap :core.style)) + +(command.add nil { + "presentation:start" (fn [] + (let [node (core.root_view:get_active_node)] + (node:add_view (SlideshowView (util.reload :presentation.slides)))) + ) + "presentation:scale-up" #(set-scale 2) + "presentation:restore-scale" #(set-scale 1) +}) +(command.add :presentation.engine { + "presentation:next" #(core.active_view:advance) + "presentation:prev" #(core.active_view:back) + "presentation:next-slide" #(core.active_view:next-slide) + "presentation:prev-slide" #(core.active_view:prev-slide) +}) +(keymap.add { + "left" "presentation:prev" + "right" "presentation:next" + "ctrl+left" "presentation:prev-slide" + "ctrl+right" "presentation:next-slide" +}) + diff --git a/presentation/engine.fnl b/presentation/engine.fnl new file mode 100644 index 0000000..2fcdf01 --- /dev/null +++ b/presentation/engine.fnl @@ -0,0 +1,107 @@ +(local lume (require :lib.lume)) +(local style (require :core.style)) +(local common (require :core.common)) +(local View (require :core.view)) + +(local SlideshowView (View:extend)) +(fn SlideshowView.parse [slides] + (var style nil) + (icollect [_ slide (ipairs slides)] + (icollect [_ elem (ipairs slide)] + (match (type elem) + (where :table elem.style) (do (set style elem) nil) + :table elem + :string (lume.merge style {:text elem}))))) + +(fn SlideshowView.new [self slides] + (SlideshowView.super.new self) + (set self.slides slides) + (set self.imagecache {}) + (set self.islide 1) + (set self.ielem 0) + (self:advance)) + +(fn SlideshowView.next-slide [self] + (set self.islide (if (>= self.islide (length self.slides)) 1 (+ self.islide 1))) + (set self.ielem 0) + (self:advance)) + +(fn SlideshowView.prev-slide [self] + (set self.islide (if (<= self.islide 1) (length self.slides) (- self.islide 1))) + (set self.ielem (+ 1 (length (. self.slides self.islide)))) + (self:back)) + +(fn SlideshowView.ielemNext [self ielem di] + (let [slide (. self.slides self.islide) + elem (. slide ielem)] + (when elem + (if elem.pause-after ielem + (self:ielemNext (+ ielem di) di))))) + +(fn SlideshowView.advance [self] + (let [ielemNext (self:ielemNext (+ self.ielem 1) 1)] + (if ielemNext (set self.ielem ielemNext) + (self:next-slide)))) + +(fn SlideshowView.back [self] + (let [ielemNext (self:ielemNext (- self.ielem 1) -1)] + (if ielemNext (set self.ielem ielemNext) + (self:prev-slide)))) + +(fn SlideshowView.load-image [self {:image filename}] + (when (= (. self.imagecache filename) nil) + (tset self.imagecache filename (love.graphics.newImage filename))) + (. self.imagecache filename)) + +(fn SlideshowView.justify [self element width] + (match element.justify + :center (/ (- self.size.x width) 2) + :right (- self.size.x width style.padding.x) + _ style.padding.x)) + +(fn SlideshowView.this-y [self element y] + (if element.topPadding (+ y element.topPadding) + (+ y style.padding.y))) + +(fn SlideshowView.next-y [self element height y] + (if element.lowerPadding (+ y height element.lowerPadding) + element.overlay y + (+ y height style.padding.y))) + +(fn SlideshowView.word-wrap [self element] + (let [letter-width (element.font:get_width "m") + screen-width (- self.size.x style.padding.x style.padding.x) + max-letters (math.floor (/ screen-width letter-width)) + wrapped (lume.wordwrap element.text max-letters) + lines (icollect [line (string.gmatch wrapped "([^\n]+)")] line)] + lines)) + +(fn SlideshowView.render-element [self element y] + (if element.text + (let [lines (self:word-wrap element) + line-height (element.font:get_height) + full-height (+ (* line-height (length lines)) (* style.padding.y (- (length lines) 1)))] + (each [iline line (ipairs lines)] + (let [width (element.font:get_width line) ;; todo: word-wrapping + x (+ self.position.x (self:justify element width)) + yline (+ y (* (+ (element.font:get_height) style.padding.y) (- iline 1)))] + (renderer.draw_text element.font line x yline element.color))) + (self:next-y element full-height y)) + + element.image + (let [image (self:load-image element) + x (+ self.position.x (self:justify element (image:getWidth)))] + (love.graphics.setColor 1 1 1 element.alpha) + (love.graphics.draw image x y) + (self:next-y element (image:getHeight) y)) + y)) + +(fn SlideshowView.draw [self] + (self:draw_background style.background) + (var y self.position.y) + (each [ielem element (ipairs (. self.slides self.islide)) :until (> ielem self.ielem)] + (set y (self:render-element element (self:this-y element y))))) + +(fn SlideshowView.get_name [self] "] KFest 2021") + +SlideshowView diff --git a/presentation/font/FreeLicense.txt b/presentation/font/FreeLicense.txt new file mode 100644 index 0000000..e48a09b --- /dev/null +++ b/presentation/font/FreeLicense.txt @@ -0,0 +1,20 @@ +KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE +version 1.2f + +Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions: + +1. The User may not sell copies of the Software for a fee. + +1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software. + +2. The User may not modify, reverse-engineer, or create any derivative works of the Software. + +3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font! + +3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license. + +4. This license becomes null and void if any of the above conditions are not met. + +5. Kreative Software reserves the right to change this license at any time without notice. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE. diff --git a/presentation/font/PRNumber3.ttf b/presentation/font/PRNumber3.ttf new file mode 100644 index 0000000..4843041 Binary files /dev/null and b/presentation/font/PRNumber3.ttf differ diff --git a/presentation/font/PrintChar21.ttf b/presentation/font/PrintChar21.ttf new file mode 100644 index 0000000..7204e97 Binary files /dev/null and b/presentation/font/PrintChar21.ttf differ diff --git a/presentation/font/Shaston320.ttf b/presentation/font/Shaston320.ttf new file mode 100644 index 0000000..f4328fe Binary files /dev/null and b/presentation/font/Shaston320.ttf differ diff --git a/presentation/font/Shaston640.ttf b/presentation/font/Shaston640.ttf new file mode 100644 index 0000000..14109fb Binary files /dev/null and b/presentation/font/Shaston640.ttf differ diff --git a/presentation/font/ShastonHi320.ttf b/presentation/font/ShastonHi320.ttf new file mode 100644 index 0000000..fd77def Binary files /dev/null and b/presentation/font/ShastonHi320.ttf differ diff --git a/presentation/font/ShastonHi640.ttf b/presentation/font/ShastonHi640.ttf new file mode 100644 index 0000000..cfabff2 Binary files /dev/null and b/presentation/font/ShastonHi640.ttf differ diff --git a/presentation/pics/assembly-markup.png b/presentation/pics/assembly-markup.png new file mode 100644 index 0000000..88ae8dc Binary files /dev/null and b/presentation/pics/assembly-markup.png differ diff --git a/presentation/pics/ggj2020.jpeg b/presentation/pics/ggj2020.jpeg new file mode 100644 index 0000000..ce86418 Binary files /dev/null and b/presentation/pics/ggj2020.jpeg differ diff --git a/presentation/pics/pete286.jpeg b/presentation/pics/pete286.jpeg new file mode 100644 index 0000000..99a3397 Binary files /dev/null and b/presentation/pics/pete286.jpeg differ diff --git a/presentation/pics/thinkhard.png b/presentation/pics/thinkhard.png new file mode 100644 index 0000000..be1b5d8 Binary files /dev/null and b/presentation/pics/thinkhard.png differ diff --git a/presentation/slides.fnl b/presentation/slides.fnl new file mode 100644 index 0000000..6e93e09 --- /dev/null +++ b/presentation/slides.fnl @@ -0,0 +1,91 @@ +(local util (require :lib.util)) +(local lume (require :lib.lume)) +(local {: parse} (util.require :presentation.engine)) +(local style (require :core.style)) +(local h + {:style true + :font (renderer.font.load "presentation/font/PrintChar21.ttf" (* 64 SCALE)) + :color style.caret + :justify :center + :topPadding (* style.padding.y 2) + :lowerPadding 64}) +(local ** + {:style true + :font (renderer.font.load "presentation/font/PRNumber3.ttf" (* 32 SCALE)) + :color style.text + :justify :left + :pause-after true}) +(fn p [style] (lume.merge style {:pause-after true})) +(fn np [style] (lume.merge style {:pause-after false})) + +(fn bgimg [filename] {:image filename :justify :center :overlay true :alpha 0.3 :topPadding 0}) + +(parse [ + [h "" "" + "Honeylisp" "" + (np **) "Jeremy Penner" + "https://spindleyq.itch.io/" + "https://blog.information-superhighway.net/" + "https://bitbucket.org/SpindleyQ/honeylisp" + "https://gamemaking.social/@SpindleyQ" + "https://twitter.com/SpindleyQ" + {:pause-after true}] + [(bgimg "presentation/pics/pete286.jpeg") + h "Some Background" + ** "In 2019 I built a 16-bit MS-DOS game engine." + "* Built on hardware" + "* Using only period-appropriate software (Turbo C, NeoPaint)" + "* Powered by Forth" + "* Integrated custom tools" + "* Interactive development via serial terminal"] + [(bgimg "presentation/pics/ggj2020.jpeg") + h "Neut Tower" + ** "In 2020, I did the Global Game Jam on my 286." + "Finished 'Shareware Episode 1' a couple of months later."] + [h "The Idea" + ** "What if I took a similar DIY approach with modern tools?" + "* I'd done Forth; what about Lisp?" + "* How far can I push fast iterative development?" + "* Could I integrate an editor?" + "* How can I leverage emulation?"] + [h "Honeylisp" + ** "* Written in Fennel, a Lisp that compiles to Lua" + "* Assembler" + "* Forth-like 'virtual machine' / inner interpreter" + "* 'lite' editor, ported to love2d" + " * Integrated custom editors" + "* MAME integration" + " * Upload new builds directly into RAM" + " * Interactive code injection" + " * Hot code reload" + "* Tape upload" + "* ProDOS disk image generation"] + ;; DEMO before tech dive + [h "Assembler" + ** "Represent instructions using Fennel data literals" + " [:lda 0xff]" + "Represent labels with Fennel strings" + " :loop [:bne :loop]" + "Lexical scope with nested blocks" + " [:block :loop (generate-loop-code) [:bne :loop]]"] + [h "Wait WTF Is An Assembler" + ** "It's just converting mnemonics to bytes, right?" + {:image "presentation/pics/assembly-markup.png" :justify :center :pause-after true} + "Whoooops, actually the hard part is converting labels to addresses" + "Zero-page instructions are a different size, which messes up data layout!" + "Initial pass is needed to gather all symbols to determine sizes" + "What about data?" + " [:db 123] [:dw 12345] [:bytes \"HELLO WORLD\"] [:ref :hello]" + "Must be able to line up bytes on page boundaries" + " [:align 0x100]"] + [h "Virtual Machine" + {:image "presentation/pics/thinkhard.png" :justify :center} + ** "Not super keen on writing a complicated compiler" + "I'm already very comfortable with Forth" + "Let's build a stack machine!" + "\"Direct threaded\" inner interpreter" + "\"Immediate words\" can be Fennel functions that generate code!"] + [h "Extensible Assembler??" + ** "How do you turn code into bytes?" + " [:vm 1 2 :+ :.]"] +]) diff --git a/vendor/jeejah/jeejah/fenneleval.lua b/vendor/jeejah/jeejah/fenneleval.lua index ffac3ce..f3968c6 100644 --- a/vendor/jeejah/jeejah/fenneleval.lua +++ b/vendor/jeejah/jeejah/fenneleval.lua @@ -36,7 +36,7 @@ local make_repl = function(session, repls) env.io = {} end env.print = print_for(session.write) - env.io.write = session.write + env.io.write = function(...) return session.io_write(...) end env.io.read = function() session.needinput() local input, done = coroutine.yield() @@ -63,6 +63,9 @@ return function(conn, msg, session, send, response_for) session.values = function(xs) send(conn, response_for(msg, {value=table.concat(xs, "\n") .. "\n"})) end + session.io_write = function(...) + send(conn, response_for(msg, {out=table.concat({...})})) + end session.done = function() send(conn, response_for(msg, {status={"done"}})) end diff --git a/vendor/lite/data/core/init.lua b/vendor/lite/data/core/init.lua index 3af4844..2e89969 100644 --- a/vendor/lite/data/core/init.lua +++ b/vendor/lite/data/core/init.lua @@ -317,7 +317,7 @@ end function core.try(fn, ...) local err local ok, res = xpcall(fn, function(msg) - print(debug.traceback()) + print(msg, debug.traceback()) local item = core.error("%s", msg) item.info = debug.traceback(nil, 2):gsub("\t", "") err = msg diff --git a/vendor/lite/data/core/statusview.lua b/vendor/lite/data/core/statusview.lua index 67a4c8b..3f09de9 100644 --- a/vendor/lite/data/core/statusview.lua +++ b/vendor/lite/data/core/statusview.lua @@ -57,7 +57,7 @@ local function draw_items(self, items, x, y, draw_fn) local color = style.text for _, item in ipairs(items) do - if type(item) == "userdata" then + if type(item) == "userdata" or (type(item) == "table" and item.get_width) then font = item elseif type(item) == "table" then color = item diff --git a/wrap.fnl b/wrap.fnl index fe27eb4..8624430 100644 --- a/wrap.fnl +++ b/wrap.fnl @@ -5,8 +5,23 @@ (require :link.command) (local core (require :core)) (local command (require :core.command)) +(local common (require :core.common)) (local keymap (require :core.keymap)) (local translate (require :core.doc.translate)) +(local files (require :game.files)) + +(command.add nil { + "honeylisp:open-project" (fn [] + (core.command_view:enter "Open Project" + (fn [text item] + (files.reload (or (and item item.text) text)) + (core.log "Opened")) + (fn [text] + (local files []) + (each [_ item (pairs core.project_files)] + (when (and (= item.type :file) (item.filename:find "^.*/game%.json")) + (table.insert files item.filename))) + (common.fuzzy_match files text))))}) (command.add #(link.machine:connected?) { "honeylisp:upload" (fn []