big-endian off
+: context-callstack-top-offset ( -- n ) 0 bootstrap-cells ; inline
+: context-callstack-bottom-offset ( -- n ) 2 bootstrap-cells ; inline
+: context-datastack-offset ( -- n ) 3 bootstrap-cells ; inline
+: context-retainstack-offset ( -- n ) 4 bootstrap-cells ; inline
+: context-callstack-save-offset ( -- n ) 5 bootstrap-cells ; inline
+: context-callstack-seg-offset ( -- n ) 8 bootstrap-cells ; inline
+
! X0-X17 volatile scratch registers
! X0-X8 parameter registers
! X0 result register
: store1 ( -- ) 0 ds-reg temp1 STRuoff ;
: store0/1 ( -- ) -8 ds-reg temp1 temp0 STPsoff ;
: store0/2 ( -- ) -8 ds-reg temp2 temp0 STPsoff ;
+: store2/0 ( -- ) -8 ds-reg temp0 temp2 STPsoff ;
: store1/0 ( -- ) -8 ds-reg temp0 temp1 STPsoff ;
: store1/2 ( -- ) -16 ds-reg temp2 temp1 STPsoff ;
+! add tag bits to integers
:: tag ( reg -- ) tag-bits get reg reg LSLi ;
+! remove tag bits
:: untag ( reg -- ) tag-bits get reg reg ASRi ;
+
: tagged>offset0 ( -- ) 1 temp0 temp0 ASRi ;
+! pops an item from the data stack and pushes it
+! onto the retain stack (used for dip-like operations)
: >r ( -- ) pop0 pushr ;
+
+! pops an item from the retain stack and pushes it
+! onto the data stack (used for dip-like operations)
: r> ( -- ) popr push0 ;
: absolute-jump ( -- word class )
3 words Br
NOP NOP f rc-absolute-cell ; inline
+! This is used when a word is called at the end of a quotation.
+! JIT-WORD-CALL is used for other word calls.
[
- ! ! pic-tail-reg 5 [RIP+] LEA
! why do we store the address after JMP in EBX, where is it
! picked up?
4 pic-tail-reg ADR
- ! ! 0 JMP f rc-relative rel-word-pic-tail
- ! 0 Br f rc-relative-arm64-branch rel-word-pic-tail
absolute-jump rel-word-pic-tail
] JIT-WORD-JUMP jit-define
+! This is used when a word is called.
+! JIT-WORD-JUMP is used if the word is the last piece of code in a quotation.
[
- ! ! 0 CALL f rc-relative rel-word-pic
- ! push-link-reg
- ! 0 BL f rc-relative-arm64-branch rel-word-pic
- ! pop-link-reg
absolute-call rel-word-pic
] JIT-WORD-CALL jit-define
: jit-call ( name -- )
- ! RAX 0 MOV f rc-absolute-cell rel-dlsym
- ! RAX CALL ;
absolute-call rel-dlsym ;
:: jit-call-1arg ( arg1s name -- )
- ! arg1 arg1s MOVr
- ! name jit-call ;
arg1s arg1 MOVr
name jit-call ;
arg2s arg2 MOVr
name jit-call ;
+! loads the address of the vm struct.
+! A no-op on ARM (vm-reg always contains this address).
: jit-load-vm ( -- ) ;
+! Loads the address of the ctx struct into ctx-reg.
: jit-load-context ( -- )
- ! ctx-reg vm-reg vm-context-offset [+] MOV ;
vm-context-offset vm-reg ctx-reg LDRuoff ;
+! Saves the addresses of the callstack, datastack, and retainstack tops
+! into the corresponding fields in the ctx struct.
: jit-save-context ( -- )
jit-load-context
! The reason for -8 I think is because we are anticipating a CALL
! instruction. After the call instruction, the contexts frame_top
! will point to the origin jump address.
- ! R11 RSP -8 [+] LEA
- ! ctx-reg context-callstack-top-offset [+] R11 MOV
stack-reg temp0 MOVsp
16 temp0 temp0 SUBi
context-callstack-top-offset ctx-reg temp0 STRuoff
- ! ctx-reg context-datastack-offset [+] ds-reg MOV
- ! ctx-reg context-retainstack-offset [+] rs-reg MOV ;
context-datastack-offset ctx-reg ds-reg STRuoff
context-retainstack-offset ctx-reg rs-reg STRuoff ;
-! ctx-reg must already have been loaded
+! Retrieves the addresses of the datastack and retainstack tops
+! from the corresponding fields in the ctx struct.
+! ctx-reg must already have been loaded.
: jit-restore-context ( -- )
- ! ds-reg ctx-reg context-datastack-offset [+] MOV
- ! rs-reg ctx-reg context-retainstack-offset [+] MOV ;
context-datastack-offset ctx-reg ds-reg LDRuoff
context-retainstack-offset ctx-reg rs-reg LDRuoff ;
jit-restore-context
] JIT-PRIMITIVE jit-define
+! Used to a call a quotation if the quotation is the last piece of code
: jit-jump-quot ( -- )
- ! arg1 quot-entry-point-offset [+] JMP ;
quot-entry-point-offset arg1 temp0 LDUR
temp0 BR ;
+! Used to call a quotation if the quotation is not the last piece of code
: jit-call-quot ( -- )
- ! arg1 quot-entry-point-offset [+] CALL ;
push-link-reg
quot-entry-point-offset arg1 temp0 LDUR
temp0 BLR
pop-link-reg ;
+! calls a quotation
+[
+ pop-arg1
+]
+[ jit-call-quot ]
+[ jit-jump-quot ]
+\ (call) define-combinator-primitive
+
+[
+ jit-save-context
+ vm-reg arg2 MOVr
+ "lazy_jit_compile" jit-call
+]
+[ jit-call-quot ]
+[ jit-jump-quot ]
+\ lazy-jit-compile define-combinator-primitive
+
[
! temp2 0 MOV f rc-absolute-cell rel-literal
! temp1 temp2 CMP
NOP NOP f rc-absolute-cell rel-literal
] PIC-CHECK-TUPLE jit-define
+
! Inline cache miss entry points
: jit-load-return-address ( -- )
! RBX RSP stack-frame-size bootstrap-cell - [+] MOV ;
ctx-reg jit-update-tib ;
: jit-pop-context-and-param ( -- )
- ! arg1 ds-reg [] MOV
- ! arg1 arg1 alien-offset [+] MOV
- ! arg2 ds-reg -8 [+] MOV
- ! ds-reg 16 SUB ;
pop-arg1
alien-offset arg1 arg1 ADDi
0 arg1 arg1 LDRuoff
pop-arg2 ;
: jit-push-param ( -- )
- ! ds-reg 8 ADD
- ! ds-reg [] arg2 MOV ;
push-arg2 ;
: jit-set-context ( -- )
jit-pop-context-and-param
jit-save-context
arg1 jit-switch-context
- ! RSP 8 ADD
16 stack-reg stack-reg ADDi
jit-push-param ;
: jit-pop-quot-and-param ( -- )
- ! arg1 ds-reg [] MOV
- ! arg2 ds-reg -8 [+] MOV
- ! ds-reg 16 SUB ;
pop-arg1 pop-arg2 ;
: jit-start-context ( -- )
jit-jump-quot ;
[
- ! 0 [RIP+] EAX MOV rc-relative rel-safepoint
3 words temp0 LDRl
0 temp0 W0 STRuoff
3 words Br
NOP NOP rc-absolute-cell rel-safepoint
] JIT-SAFEPOINT jit-define
-! C to Factor entry point
+! The main C to Factor entry point.
+! Sets up and executes the boot quote,
+! then performs a teardown and returns into C++.
[
- 0xabcd BRK
! ! Optimizing compiler's side of callback accesses
! ! arguments that are on the stack via the frame pointer.
! ! On x86-32 fastcall, and x86-64, some arguments are passed
! frame-reg PUSH
! frame-reg stack-reg MOV
- ! ! Save all non-volatile registers
- ! nv-regs [ PUSH ] each
+ ! Save all non-volatile registers
-16 SP X19 X18 STPpre
-16 SP X21 X20 STPpre
-16 SP X23 X22 STPpre
jit-save-tib
- ! ! Load VM into vm-reg
- ! vm-reg 0 MOV 0 rc-absolute-cell rel-vm
+ ! Load VM into vm-reg
2 words vm-reg LDRl
3 words Br
NOP NOP 0 rc-absolute-cell rel-vm
- ! ! Save old context
- ! nv-reg vm-reg vm-context-offset [+] MOV
- ! nv-reg PUSH
+ ! Save old context
vm-context-offset vm-reg ctx-reg LDRuoff
8 SP ctx-reg STRuoff
- ! ! Switch over to the spare context
- ! nv-reg vm-reg vm-spare-context-offset [+] MOV
- ! vm-reg vm-context-offset [+] nv-reg MOV
+ ! Switch over to the spare context
vm-spare-context-offset vm-reg ctx-reg LDRuoff
vm-context-offset vm-reg ctx-reg STRuoff
- ! ! Save C callstack pointer
- ! nv-reg context-callstack-save-offset [+] stack-reg MOV
-
+ ! Save C callstack pointer
stack-reg temp0 MOVsp
context-callstack-save-offset ctx-reg temp0 STRuoff
- ! stack-reg X24 MOVsp
- ! NOP
- ! ! Load Factor stack pointers
- ! stack-reg nv-reg context-callstack-bottom-offset [+] MOV
+ ! Load Factor stack pointers
context-callstack-bottom-offset ctx-reg temp0 LDRuoff
temp0 stack-reg MOVsp
ctx-reg jit-update-tib
jit-install-seh
- ! rs-reg nv-reg context-retainstack-offset [+] MOV
- ! ds-reg nv-reg context-datastack-offset [+] MOV
context-retainstack-offset ctx-reg rs-reg LDRuoff
context-datastack-offset ctx-reg ds-reg LDRuoff
- ! ! Call into Factor code
- ! link-reg 0 MOV f rc-absolute-cell rel-word
- ! link-reg CALL
+ ! Call into Factor code
3 words temp0 LDRl
temp0 BLR
3 words Br
NOP NOP f rc-absolute-cell rel-word
- ! ! Load C callstack pointer
- ! nv-reg vm-reg vm-context-offset [+] MOV
- ! stack-reg nv-reg context-callstack-save-offset [+] MOV
+ ! Load C callstack pointer
vm-context-offset vm-reg ctx-reg LDRuoff
context-callstack-save-offset ctx-reg temp0 LDRuoff
temp0 stack-reg MOVsp
- ! X24 stack-reg MOVsp
- ! NOP
- ! ! Load old context
- ! nv-reg POP
- ! vm-reg vm-context-offset [+] nv-reg MOV
+ ! Load old context
8 SP ctx-reg LDRuoff
vm-context-offset vm-reg ctx-reg STRuoff
jit-restore-tib
- ! ! Restore non-volatile registers
- ! nv-regs <reversed> [ POP ] each
- ! frame-reg POP
+ ! Restore non-volatile registers
16 SP X30 LDRpost
16 SP X29 X28 LDPpost
16 SP X27 X26 LDPpost
16 SP X21 X20 LDPpost
16 SP X19 X18 LDPpost
- ! ! Callbacks which return structs, or use stdcall/fastcall/thiscall,
- ! ! need a parameter here.
+ ! Callbacks which return structs, or use stdcall/fastcall/thiscall,
+ ! need a parameter here.
- ! ! See the comment for M\ x86.32 stack-cleanup in cpu.x86.32
- ! 0xffff RET f rc-absolute-2 rel-untagged
- 4 words temp0 ADR
- 2 temp0 temp0 LDRHuoff
- temp0 stack-reg stack-reg ADDr
f RET
- NOP f rc-absolute-2 rel-untagged
] CALLBACK-STUB jit-define
+! pushes a literal value to the stack
[
- ! ! load literal
- ! temp0 0 MOV f rc-absolute-cell rel-literal
+ ! load literal
2 words temp0 LDRl
3 words Br
NOP NOP f rc-absolute-cell rel-literal
- ! ! increment datastack pointer
- ! ds-reg bootstrap-cell ADD
- ! ! store literal on datastack
- ! ds-reg [] temp0 MOV
+ ! store literal on datastack
push0
] JIT-PUSH-LITERAL jit-define
16 SP X3 X2 LDPpost
16 SP X1 X0 LDPpost ;
+! if-statement control flow
[
- ! ! load boolean
- ! temp0 ds-reg [] MOV
- ! ! pop boolean
- ! ds-reg bootstrap-cell SUB
+ ! pop boolean
pop0
- ! ! compare boolean with f
- ! temp0 \ f type-number CMP
+ ! compare boolean with f
\ f type-number temp0 CMPi
- ! ! jump to true branch if not equal
- ! ! 0 JNE f rc-relative rel-word
- ! 0 NE B.cond f rc-relative-arm64-bcond rel-word
+ ! skip over true branch if equal
5 words EQ B.cond
+ ! jump to true branch
absolute-jump rel-word
- ! ! jump to false branch if equal
- ! ! 0 JMP f rc-relative rel-word
- ! 0 Br f rc-relative-arm64-branch rel-word
+ ! jump to false branch
absolute-jump rel-word
] JIT-IF jit-define
+! calls the second item on the stack
[
>r
- ! ! 0 CALL f rc-relative rel-word
- ! push-link-reg
- ! 0 Br f rc-relative-arm64-branch rel-word
- ! pop-link-reg
absolute-call rel-word
r>
] JIT-DIP jit-define
+! calls the third item on the stack
[
>r >r
- ! ! 0 CALL f rc-relative rel-word
- ! push-link-reg
- ! 0 Br f rc-relative-arm64-branch rel-word
- ! pop-link-reg
absolute-call rel-word
r> r>
] JIT-2DIP jit-define
+! calls the fourth item on the stack
[
>r >r >r
- ! ! 0 CALL f rc-relative rel-word
- ! push-link-reg
- ! 0 Br f rc-relative-arm64-branch rel-word
- ! pop-link-reg
absolute-call rel-word
r> r> r>
] JIT-3DIP jit-define
+! executes a word pushed onto the stack with \
[
! ! load from stack
! temp0 ds-reg [] MOV
] JIT-EXECUTE jit-define
! https://elixir.bootlin.com/linux/latest/source/arch/arm64/kernel/stacktrace.c#L22
+! Performs setup for a quotation
[
! ! make room for LR plus magic number of callback, 16byte align
- ! x64 ! stack-reg stack-frame-size bootstrap-cell - SUB
stack-frame-size stack-reg stack-reg SUBi
push-link-reg
] JIT-PROLOG jit-define
+! Performs teardown for a quotation
[
- ! x64 ! stack-reg stack-frame-size bootstrap-cell - ADD
pop-link-reg
stack-frame-size stack-reg stack-reg ADDi
] JIT-EPILOG jit-define
+! returns to the outer stack frame
[ f RET ] JIT-RETURN jit-define
! ! ! Polymorphic inline caches
NOP f rc-absolute-1 rel-untagged
] PIC-LOAD jit-define
+! ! Factor 2024 Clinic Code:
+! ! this arm relocation could actually work
+! ! due to the small bitwidth required
+! 0 0 temp2 MOVZ f rc-absolute-arm64-movz rel-untagged
+! temp2 temp2 UXTB
+! temp2 ds-reg temp1 LDRr
+
[
! temp1/32 tag-mask get AND
tag-mask get temp1 temp1 ANDi
NOP f rc-absolute-1 rel-untagged
] PIC-CHECK-TAG jit-define
+! ! Factor 2024 Clinic Code:
+! ! this arm relocation could actually work
+! ! due to the small bitwidth required
+! 0 0 temp2 MOVZ f rc-absolute-arm64-movz rel-untagged
+! temp2 temp2 UXTB
+! temp2 temp1 CMPr
+
+
[
! ! 0 JE f rc-relative rel-word
! 0 EQ B.cond f rc-relative-arm64-bcond rel-word
] jit-conditional
] MEGA-LOOKUP jit-define
-! Comparisons
+! helper for comparison operations which return a boolean value
: jit-compare ( cond -- )
- ! ! load t
- ! temp3 0 MOV t rc-absolute-cell rel-literal
+ ! load t
2 words temp3 LDRl
3 words Br
NOP NOP t rc-absolute-cell rel-literal
- ! ! load f
- ! temp1 \ f type-number MOV
+ ! load f
\ f type-number temp2 MOVwi
- ! ! load first value
- ! temp0 ds-reg [] MOV
- ! ! adjust stack pointer
- ! ds-reg bootstrap-cell SUB
+ ! load values
load1/0
- ! ! compare with second value
- ! ds-reg [] temp0 CMP
+ ! compare
temp1 temp0 CMPr
- ! ! move t if true
- ! [ temp1 temp3 ] dip execute( dst src -- )
+ ! move t if true (f otherwise)
[ temp2 temp3 temp0 ] dip CSEL
- ! ! store
- ! ds-reg [] temp1 MOV
+ ! store
1 push-down0 ;
! Math
-! Overflowing fixnum arithmetic
+! Overflowing fixnum (integer) arithmetic
: jit-overflow ( insn func -- )
- ! ds-reg 8 SUB
jit-save-context
- ! arg1 ds-reg [] MOV
- ! arg2 ds-reg 8 [+] MOV
load-arg1/2
- ! arg3 arg1 MOV
- ! [ [ arg3 arg2 ] dip call ] dip
[ [ arg2 arg1 arg3 ] dip call ] dip
- ! ds-reg [] arg3 MOV
push-down-arg3
- ! [ JNO ]
- [ VC B.cond ] [
- ! arg3 vm-reg MOV
+ [ 8 fixnum+fast VC B.cond ] [
vm-reg arg3 MOVr
jit-call
] jit-conditional ; inline
+! non-overflowing fixnum (integer) arithmetic
: jit-math ( insn -- )
- ! ! load second input
- ! temp0 ds-reg [] MOV
- ! ! pop stack
- ! ds-reg bootstrap-cell SUB
+ ! load inputs
load1/0
- ! ! compute result
- ! [ ds-reg [] temp0 ] dip execute( dst src -- )
+ ! compute result
[ temp0 temp1 temp0 ] dip execute( arg2 arg1 dst -- )
+ ! store result
1 push-down0 ;
+! fixnum (integer) division and modulo operations.
+! Does not tag or push results.
: jit-fixnum-/mod ( -- )
- ! ! load second parameter
- ! temp1 ds-reg [] MOV
- ! ! load first parameter
- ! div-arg ds-reg bootstrap-cell neg [+] MOV
+ ! load parameters
load1/0
- ! ! divide
+ ! divide
temp0 temp1 temp2 SDIV
temp1 temp0 temp2 temp0 MSUB ;
{ (start-context-and-delete) [ jit-start-context-and-delete ] }
! ## Entry points
+ ! called by callback-stub.
+ ! this contains some C++ setup/teardown,
+ ! as well as the actual call into the boot quote.
{ c-to-factor [
arg1 arg2 MOVr
vm-reg "begin_callback" jit-call-1arg
] }
! ## Math
- { fixnum+ [ [ ADDr ] "overflow_fixnum_add" jit-overflow ] }
- { fixnum- [ [ SUBr ] "overflow_fixnum_subtract" jit-overflow ] }
+ ! Overflowing fixnum (integer) addition
+ { fixnum+ [
+ [ ADDr ] "overflow_fixnum_add" jit-overflow ] }
+ ! Overflowing fixnum (integer) subtraction
+ { fixnum- [
+ [ SUBr ] "overflow_fixnum_subtract" jit-overflow ] }
+ ! Overflowing fixnum (integer) multiplication
{ fixnum* [
- ! ds-reg 8 SUB
- jit-save-context
- ! RCX ds-reg [] MOV
- ! RBX ds-reg 8 [+] MOV
- load1/0
- ! RBX tag-bits get SAR
- temp0 untag
- ! RAX RCX MOV
- ! RBX IMUL
- ! RAX * RBX = RDX:RAX
- temp1 temp0 temp0 MUL
- ! ds-reg [] RAX MOV
- 1 push-down0
- ! [ JNO ]
- [ VC B.cond ] [
- ! arg1 RCX MOV
- temp1 arg1 MOVr
- ! arg1 tag-bits get SAR
- temp1 untag
- ! arg2 RBX MOV
- temp0 arg2 MOVr
- ! arg3 vm-reg MOV
- vm-reg arg3 MOVr
- "overflow_fixnum_multiply" jit-call
- ] jit-conditional
- ] }
+ [ MUL ] "overflow_fixnum_multiply" jit-overflow ] }
! ## Misc
{ fpu-state [
FPSR XZR MSRr
FPCR arg1 MRS
] }
+
+! ! Factor 2024 Clinic Code:
+! FPCR arg1 MRS
+! FPSR XZR MSRr
+
{ set-fpu-state [
! RSP 2 SUB
! RSP [] arg1 16-bit-version-of MOV
f RET
] }
- ! ## Fixnums
+ ! ! Factor 2024 Clinic Code:
+ ! ! we think the below two lines
+ ! ! 2 temp0 temp0 SUBi ! callstack-length-offset
+ ! ! 0 temp0 arg3 LDRuoff
+ ! ! may need to be replaced with:
+ ! callstack-length-offset arg4 arg3 LDRuoff
+
- ! ### Add
+ ! ## Fixnums
+ ! Non-overflowing fixnum (integer) addition
{ fixnum+fast [ \ ADDr jit-math ] }
! ### Bit manipulation
+ ! fixnum (integer) bitwise AND
{ fixnum-bitand [ \ ANDr jit-math ] }
+
+ ! fixnum (integer) bitwise NOT
{ fixnum-bitnot [
- ! ! complement
- ! ds-reg [] NOT
load0
+ ! complement
temp0 temp0 MVN
- ! ! clear tag bits
- ! ds-reg [] tag-mask get XOR
+ ! clear tag bits
tag-mask get temp0 temp0 EORi
store0
] }
+
+ ! fixnum (integer) bitwise OR
{ fixnum-bitor [ \ ORRr jit-math ] }
+
+ ! fixnum (integer) bitwise XOR
{ fixnum-bitxor [ \ EORr jit-math ] }
+
+ ! fixnum (integer) bitwise shift (positive = left, negative = right)
{ fixnum-shift-fast [
- ! ! load shift count
- ! shift-arg ds-reg [] MOV
- ! ! adjust stack pointer
- ! ds-reg bootstrap-cell SUB
- ! ! load value
- ! temp3 ds-reg [] MOV
+ ! load shift count and value
load1/0
- ! ! untag shift count
- ! shift-arg tag-bits get SAR
+ ! untag shift count
temp0 untag
- ! ! make a copy
- ! temp2 temp3 MOV
+ ! make a copy
temp1 temp2 MOVr
- ! ! compute positive shift value in temp2
- ! temp2 CL SHL
+ ! compute positive shift value in temp1
temp0 temp1 temp1 LSLr
- ! ! compute negative shift value in temp3
- ! shift-arg NEG
+ ! compute negative shift value in temp2
temp0 temp0 NEG
- ! temp3 CL SAR
temp0 temp2 temp2 ASRr
- ! temp3 tag-mask get bitnot AND
tag-mask get bitnot temp2 temp2 ANDi
- ! ! if shift count was negative, move temp3 to temp2
- ! shift-arg 0 CMP
- ! temp2 temp3 CMOVGE
- temp2 temp1 temp0 PL CSEL
- ! ! push to stack
- ! ds-reg [] temp2 MOV
+ ! if shift count was negative
+ ! choose temp2 (else temp1)
+ 0 temp0 CMPi
+ temp2 temp1 temp0 MI CSEL
+ ! push to stack
1 push-down0
] }
! ### Comparisons
+ ! returns true if both arguments are fixnums, and false otherwise
{ both-fixnums? [
- ! temp0 ds-reg [] MOV
- ! ds-reg bootstrap-cell SUB
load1/0
- ! temp0 ds-reg [] OR
temp1 temp0 temp0 ORRr
- ! temp0 tag-mask get TEST
tag-mask get temp0 TSTi
- ! temp0 \ f type-number MOV
\ f type-number temp0 MOVwi
- ! temp1 1 tag-fixnum MOV
1 tag-fixnum temp1 MOVwi
- ! temp0 temp1 CMOVE
temp0 temp1 temp0 EQ CSEL
- ! ds-reg [] temp0 MOV
1 push-down0
] }
+
+ ! fixnum (integer) equality comparison
{ eq? [ EQ jit-compare ] }
+ ! fixnum (integer) greater-than comparison
{ fixnum> [ GT jit-compare ] }
+ ! fixnum (integer) greater-than-or-equal comparison
{ fixnum>= [ GE jit-compare ] }
+ ! fixnum (integer) less-than comparison
{ fixnum< [ LT jit-compare ] }
+ ! fixnum (integer) less-than-or-equal comparison
{ fixnum<= [ LE jit-compare ] }
! ### Div/mod
+ ! fixnum (integer) modulo
{ fixnum-mod [
jit-fixnum-/mod
- ! ! adjust stack pointer
- ! ds-reg bootstrap-cell SUB
- ! ! push to stack
- ! ds-reg [] mod-arg MOV
+ ! push to stack
1 push-down0
] }
+ ! fixnum (integer) division
{ fixnum/i-fast [
jit-fixnum-/mod
- ! ! adjust stack pointer
- ! ds-reg bootstrap-cell SUB
- ! ! tag it
- ! div-arg tag-bits get SHL
+ ! tag it
tag-bits get temp2 temp0 LSLi
- ! ! push to stack
- ! ds-reg [] div-arg MOV
+ ! push to stack
1 push-down0
] }
+ ! fixnum (integer) division and modulo
{ fixnum/mod-fast [
jit-fixnum-/mod
- ! ! tag it
- ! div-arg tag-bits get SHL
+ ! tag it
temp2 tag
- ! ! push to stack
- ! ds-reg [] mod-arg MOV
- ! ds-reg bootstrap-cell neg [+] div-arg MOV
- store0/2
+ ! push to stack
+ store2/0
] }
! ### Mul
+ ! Non-overflowing fixnum (integer) multiplication
{ fixnum*fast [
- ! ! load second input
- ! temp0 ds-reg [] MOV
- ! ! pop stack
- ! ds-reg bootstrap-cell SUB
- ! ! load first input
- ! temp1 ds-reg [] MOV
+ ! load both inputs
load1/0
- ! ! untag second input
- ! temp0 tag-bits get SAR
+ ! untag second input
temp0 untag
- ! ! multiply
- ! temp0 temp1 IMUL2
+ ! multiply
temp1 temp0 temp0 MUL
- ! ! push result
- ! ds-reg [] temp0 MOV
+ ! push result
1 push-down0
] }
! ### Sub
+ ! Non-overflowing fixnum (integer) subtraction
{ fixnum-fast [ \ SUBr jit-math ] }
! ## Locals
+ ! Drops all current locals stored on the retainstack.
{ drop-locals [
- ! ! load local count
- ! temp0 ds-reg [] MOV
- ! ! adjust stack pointer
- ! ds-reg bootstrap-cell SUB
+ ! load local count
pop0
- ! ! turn local number into offset
+ ! turn local number into offset
tagged>offset0
- ! ! decrement retain stack pointer
- ! rs-reg temp0 SUB
+ ! decrement retain stack pointer
temp0 rs-reg rs-reg SUBr
] }
+
+ ! Gets the nth local stored on the retainstack.
{ get-local [
- ! ! load local number
- ! temp0 ds-reg [] MOV
+ ! load local number
load0
- ! ! turn local number into offset
+ ! turn local number into offset
tagged>offset0
- ! ! load local value
- ! temp0 rs-reg temp0 [+] MOV
+ ! load local value
temp0 rs-reg temp0 LDRr
- ! ! push to stack
- ! ds-reg [] temp0 MOV
+ ! push to stack
store0
] }
+
+ ! Turns the top item on the datastack
+ ! into a local stored on the retainstack.
{ load-local [ >r ] }
! ## Objects
+ ! Reads the nth slot of a given object. (non-bounds-checking)
{ slot [
- ! ! load slot number
- ! temp0 ds-reg [] MOV
- ! ! adjust stack pointer
- ! ds-reg bootstrap-cell SUB
- ! ! load object
- ! temp1 ds-reg [] MOV
+ ! load object and slot number
load1/0
- ! ! turn slot number into offset
+ ! turn slot number into offset
tagged>offset0
- ! ! mask off tag
- ! temp1 tag-bits get SHR
- ! temp1 tag-bits get SHL
+ ! mask off tag
tag-mask get bitnot temp1 temp1 ANDi
- ! ! load slot value
- ! temp0 temp1 temp0 [+] MOV
+ ! load slot value
temp1 temp0 temp0 LDRr
- ! ! push to stack
- ! ds-reg [] temp0 MOV
+ ! push to stack
1 push-down0
] }
+
+ ! nth string element selector (non-bounds-checking)
{ string-nth-fast [
- ! ! load string index from stack
- ! temp0 ds-reg bootstrap-cell neg [+] MOV
- ! temp0 tag-bits get SHR
- ! ! load string from stack
- ! temp1 ds-reg [] MOV
+ ! load string index and string from stack
load1/0
- ! ! load character
- ! temp0 8-bit-version-of temp0 temp1 string-offset [++] MOV
- ! temp0 temp0 8-bit-version-of MOVZX
- ! temp0 tag-bits get SHL
+ temp1 untag
+ ! load character
+ string-offset temp0 temp0 ADDi
temp1 temp0 temp0 LDRBr
temp0 tag
- ! ! store character to stack
- ! ds-reg bootstrap-cell SUB
- ! ds-reg [] temp0 MOV
+ ! store character to stack
1 push-down0
] }
+
+ ! add tag bits to integers
+ ! (the local word tag just shifts left)
{ tag [
- ! ! load from stack
- ! temp0 ds-reg [] MOV
+ ! load from stack
load0
- ! ! compute tag
- ! temp0/32 tag-mask get AND
+ ! compute tag
tag-mask get temp0 temp0 ANDi
- ! ! tag the tag
- ! temp0/32 tag-bits get SHL
+ ! tag the tag
temp0 tag
- ! ! push to stack
- ! ds-reg [] temp0 MOV
+ ! push to stack
store0
] }
- ! ! ## Shufflers
+ ! ## Shufflers
- ! ! ### Drops
+ ! ### Drops
+ ! drops the top n stack items
{ drop [ 1 ndrop ] }
{ 2drop [ 2 ndrop ] }
{ 3drop [ 3 ndrop ] }
{ 4drop [ 4 ndrop ] }
- ! ! ### Dups
+ ! ### Dups
+ ! duplicates the top n stack items in order
{ dup [ load0 push0 ] }
{ 2dup [ load1/0 push1 push0 ] }
{ 3dup [ load2 load1/0 push2 push1 push0 ] }
{ 4dup [ load3/2 load1/0 push3 push2 push1 push0 ] }
+ ! duplicates the second stack item and puts it below the top stack item
{ dupd [ load1/0 store1 push0 ] }
- ! ! ### Misc shufflers
+ ! ### Misc shufflers
+ ! Duplicates the second stack item and puts it above the top stack item
{ over [ load1 push1 ] }
+ ! Duplicates the the third stack item and puts it above the top stack item
{ pick [ load2 push2 ] }
- ! ! ### Nips
+ ! ### Nips
+ ! Drops the second stack item
{ nip [ load0 1 push-down0 ] }
+ ! Drops the second and third stack items
{ 2nip [ load0 2 push-down0 ] }
- ! ! ### Swaps
+ ! ### Swaps
+ ! Rotates the top three elements of the stack (1st -> 3rd)
{ -rot [ pop0 load2/1* store0/2 push1 ] }
+ ! Rotates the top three elements of the stack (1st -> 2nd)
{ rot [ pop0 load2/1* store1/0 push2 ] }
+ ! Swaps the top two elements of the stack
{ swap [ load1/0 store0/1 ] }
+ ! Swaps the second and third elements of the stack
{ swapd [ load2/1 store1/2 ] }
! ## Signal handling
! See https://factorcode.org/license.txt for BSD license.
USING: accessors arrays assocs assocs.extras calendar
calendar.parser combinators combinators.short-circuit
-combinators.smart grouping http.download images.loader
-images.viewer io io.directories json json.http kernel math
-math.combinatorics math.order math.parser math.statistics
-namespaces sequences sequences.deep sequences.extras sets
-sorting sorting.specification splitting strings ui.gadgets.panes
-unicode urls ;
+combinators.smart formatting grouping http.download
+images.loader images.viewer io io.directories json json.http
+kernel math math.combinatorics math.order math.parser
+math.statistics namespaces random sequences sequences.deep
+sequences.extras sequences.generalizations sets sorting
+sorting.specification splitting splitting.extras strings
+ui.gadgets.panes unicode urls ;
IN: scryfall
CONSTANT: scryfall-oracle-json-path "resource:scryfall-oracle-json"
: load-scryfall-json ( type path -- uri )
[ find-scryfall-json "download_uri" of ] dip
- 10 days download-outdated-as path>json ;
+ 30 days download-outdated-as path>json ;
MEMO: mtg-oracle-cards ( -- json )
"oracle_cards" scryfall-oracle-json-path load-scryfall-json ;
: type-line-of ( assoc -- string ) "type_line" of parse-type-line ;
+: types-of ( assoc -- seq ) type-line-of [ first ] map concat ;
+: subtypes-of ( assoc -- seq ) type-line-of [ second ] map concat ;
+
! cards can have several type lines (one for each face)
: any-type? ( json name -- ? )
[ type-line-of ] dip >lower '[ first [ >lower ] map _ member-of? ] any? ;
: filter-artifact ( seq -- seq' ) [ "Artifact" any-type? ] filter ;
: filter-artifact-subtype ( seq text -- seq' ) [ filter-artifact ] dip filter-subtype ;
+: reject-basic ( seq -- seq' ) [ "Basic" any-type? ] reject ;
+: reject-land ( seq -- seq' ) [ "Land" any-type? ] reject ;
+: reject-creature ( seq -- seq' ) [ "Creature" any-type? ] reject ;
+: reject-emblem ( seq -- seq' ) [ "Emblem" any-type? ] reject ;
+: reject-enchantment ( seq -- seq' ) [ "Enchantment" any-type? ] reject ;
+: reject-instant ( seq -- seq' ) [ "Instant" any-type? ] reject ;
+: reject-sorcery ( seq -- seq' ) [ "Sorcery" any-type? ] reject ;
+: reject-planeswalker ( seq -- seq' ) [ "Planeswalker" any-type? ] reject ;
+: reject-legendary ( seq -- seq' ) [ "Legendary" any-type? ] reject ;
+: reject-battle ( seq -- seq' ) [ "Battle" any-type? ] reject ;
+: reject-artifact ( seq -- seq' ) [ "Artifact" any-type? ] reject ;
+
: filter-mounts ( seq -- seq' ) "mount" filter-subtype ;
: filter-vehicles ( seq -- seq' ) "vehicle" filter-subtype ;
: filter-adventure ( seq -- seq' ) "adventure" filter-subtype ;
[ "type_line" of ] map-card-faces
concat members sort ;
-: filter-card-faces ( json quot -- seq )
+: card>faces ( assoc -- seq )
+ [ "card_faces" of ] [ ] [ 1array ] ?if ;
+
+: filter-card-faces-sub-card ( seq quot -- seq )
+ [ [ card>faces ] map concat ] dip filter ; inline
+
+: filter-card-faces-sub-card-prop ( seq string prop -- seq' )
+ swap '[ _ of _ subseq-of? ] filter-card-faces-sub-card ;
+
+: filter-card-faces-sub-card-iprop ( seq string prop -- seq' )
+ swap >lower '[ _ of >lower _ subseq-of? ] filter-card-faces-sub-card ;
+
+: filter-card-faces-main-card ( seq quot -- seq )
dup '[ [ "card_faces" of ] [ _ any? ] _ ?if ] filter ; inline
-: filter-card-faces-prop ( seq string prop -- seq' )
- swap '[ _ of _ subseq-of? ] filter-card-faces ;
+: filter-card-faces-main-card-prop ( seq string prop -- seq' )
+ swap '[ _ of _ subseq-of? ] filter-card-faces-main-card ;
+
+: filter-card-faces-main-card-iprop ( seq string prop -- seq' )
+ swap >lower '[ _ of >lower _ subseq-of? ] filter-card-faces-main-card ;
-: filter-card-faces-iprop ( seq string prop -- seq' )
- swap >lower '[ _ of >lower _ subseq-of? ] filter-card-faces ;
+: filter-card-faces-main-card-iprop-member ( seq string prop -- seq' )
+ swap >lower '[ _ of [ >lower ] map _ member-of? ] filter-card-faces-main-card ;
: filter-by-flavor-text ( seq string -- seq' )
- "flavor_text" filter-card-faces-prop ;
+ "flavor_text" filter-card-faces-main-card-prop ;
: filter-by-flavor-itext ( seq string -- seq' )
- "flavor_text" filter-card-faces-iprop ;
+ "flavor_text" filter-card-faces-main-card-iprop ;
: filter-by-oracle-text ( seq string -- seq' )
- "oracle_text" filter-card-faces-prop ;
+ "oracle_text" filter-card-faces-main-card-prop ;
: filter-by-oracle-itext ( seq string -- seq' )
- "oracle_text" filter-card-faces-iprop ;
+ "oracle_text" filter-card-faces-main-card-iprop ;
+
+: filter-by-keyword ( seq string -- seq' )
+ "keywords" filter-card-faces-main-card-iprop-member ;
: filter-by-name-text ( seq string -- seq' ) "name" filter-by-text-prop ;
: filter-by-name-itext ( seq string -- seq' ) "name" filter-by-itext-prop ;
: filter-create-map-token ( seq -- seq' ) "create a map token" filter-by-oracle-itext ;
: filter-map-token ( seq -- seq' ) "map token" filter-by-oracle-itext ;
-: filter-affinity ( seq -- seq' ) "affinity" filter-by-oracle-itext ;
-: filter-backup ( seq -- seq' ) "backup" filter-by-oracle-itext ;
-: filter-blitz ( seq -- seq' ) "blitz" filter-by-oracle-itext ;
-: filter-compleated ( seq -- seq' ) "compleated" filter-by-oracle-itext ;
-: filter-corrupted ( seq -- seq' ) "corrupted" filter-by-oracle-itext ;
-: filter-counter ( seq -- seq' ) "counter" filter-by-oracle-itext ;
-: filter-crew ( seq -- seq' ) "crew" filter-by-oracle-itext ;
-: filter-cycling ( seq -- seq' ) "cycling" filter-by-oracle-itext ;
-: filter-deathtouch ( seq -- seq' ) "deathtouch" filter-by-oracle-itext ;
-: filter-defender ( seq -- seq' ) "defender" filter-by-oracle-itext ;
-: filter-descend ( seq -- seq' ) "descend" filter-by-oracle-itext ;
-: filter-destroy-target ( seq -- seq' ) "destroy target" filter-by-oracle-itext ;
-: filter-discover ( seq -- seq' ) "discover" filter-by-oracle-itext ;
-: filter-disguise ( seq -- seq' ) "disguise" filter-by-oracle-itext ;
-: filter-domain ( seq -- seq' ) "domain" filter-by-oracle-itext ;
-: filter-double-strike ( seq -- seq' ) "double strike" filter-by-oracle-itext ;
-: filter-equip ( seq -- seq' ) "equip" filter-by-oracle-itext ;
-: filter-equip-n ( seq -- seq' ) "equip {" filter-by-oracle-itext ;
-: filter-exile ( seq -- seq' ) "exile" filter-by-oracle-itext ;
-: filter-fights ( seq -- seq' ) "fights" filter-by-oracle-itext ;
-: filter-first-strike ( seq -- seq' ) "first strike" filter-by-oracle-itext ;
-: filter-flash ( seq -- seq' ) "flash" filter-by-oracle-itext ;
-: filter-flying ( seq -- seq' ) "flying" filter-by-oracle-itext ;
-: filter-for-mirrodin ( seq -- seq' ) "for mirrodin!" filter-by-oracle-itext ;
-: filter-graveyard ( seq -- seq' ) "graveyard" filter-by-oracle-itext ;
-: filter-haste ( seq -- seq' ) "haste" filter-by-oracle-itext ;
-: filter-hideaway ( seq -- seq' ) "hideaway" filter-by-oracle-itext ;
-: filter-hexproof ( seq -- seq' ) "hexproof" filter-by-oracle-itext ;
-: filter-indestructible ( seq -- seq' ) "indestructible" filter-by-oracle-itext ;
-: filter-investigate ( seq -- seq' ) "investigate" filter-by-oracle-itext ;
-: filter-lifelink ( seq -- seq' ) "lifelink" filter-by-oracle-itext ;
-: filter-madness ( seq -- seq' ) "madness" filter-by-oracle-itext ;
-: filter-menace ( seq -- seq' ) "menace" filter-by-oracle-itext ;
-: filter-mill ( seq -- seq' ) "mill" filter-by-oracle-itext ;
-: filter-ninjutsu ( seq -- seq' ) "ninjutsu" filter-by-oracle-itext ;
-: filter-proliferate ( seq -- seq' ) "proliferate" filter-by-oracle-itext ;
-: filter-protection ( seq -- seq' ) "protection" filter-by-oracle-itext ;
-: filter-prowess ( seq -- seq' ) "prowess" filter-by-oracle-itext ;
-: filter-reach ( seq -- seq' ) "reach" filter-by-oracle-itext ;
-: filter-read-ahead ( seq -- seq' ) "read ahead" filter-by-oracle-itext ;
-: filter-reconfigure ( seq -- seq' ) "reconfigure" filter-by-oracle-itext ;
-: filter-role ( seq -- seq' ) "role" filter-by-oracle-itext ;
-: filter-sacrifice ( seq -- seq' ) "sacrifice" filter-by-oracle-itext ;
-: filter-scry ( seq -- seq' ) "scry" filter-by-oracle-itext ;
-: filter-shroud ( seq -- seq' ) "shroud" filter-by-oracle-itext ;
-: filter-token ( seq -- seq' ) "token" filter-by-oracle-itext ;
-: filter-toxic ( seq -- seq' ) "toxic" filter-by-oracle-itext ;
-: filter-trample ( seq -- seq' ) "trample" filter-by-oracle-itext ;
-: filter-vehicle ( seq -- seq' ) "vehicle" filter-by-oracle-itext ;
-: filter-vigilance ( seq -- seq' ) "vigilance" filter-by-oracle-itext ;
-: filter-ward ( seq -- seq' ) "ward" filter-by-oracle-itext ;
+: filter-adamant-text ( seq -- seq' ) "adamant" filter-by-oracle-itext ;
+: filter-adapt-text ( seq -- seq' ) "adapt" filter-by-oracle-itext ;
+: filter-addendum-text ( seq -- seq' ) "addendum" filter-by-oracle-itext ;
+: filter-affinity-text ( seq -- seq' ) "affinity" filter-by-oracle-itext ;
+: filter-afflict-text ( seq -- seq' ) "afflict" filter-by-oracle-itext ;
+: filter-afterlife-text ( seq -- seq' ) "afterlife" filter-by-oracle-itext ;
+: filter-aftermath-text ( seq -- seq' ) "aftermath" filter-by-oracle-itext ;
+: filter-alliance-text ( seq -- seq' ) "alliance" filter-by-oracle-itext ;
+: filter-amass-text ( seq -- seq' ) "amass" filter-by-oracle-itext ;
+: filter-amplify-text ( seq -- seq' ) "amplify" filter-by-oracle-itext ;
+: filter-annihilator-text ( seq -- seq' ) "annihilator" filter-by-oracle-itext ;
+: filter-ascend-text ( seq -- seq' ) "ascend" filter-by-oracle-itext ;
+: filter-assemble-text ( seq -- seq' ) "assemble" filter-by-oracle-itext ;
+: filter-assist-text ( seq -- seq' ) "assist" filter-by-oracle-itext ;
+: filter-augment-text ( seq -- seq' ) "augment" filter-by-oracle-itext ;
+: filter-awaken-text ( seq -- seq' ) "awaken" filter-by-oracle-itext ;
+: filter-backup-text ( seq -- seq' ) "backup" filter-by-oracle-itext ;
+: filter-banding-text ( seq -- seq' ) "banding" filter-by-oracle-itext ;
+: filter-bargain-text ( seq -- seq' ) "bargain" filter-by-oracle-itext ;
+: filter-basic-landcycling-text ( seq -- seq' ) "basic landcycling" filter-by-oracle-itext ;
+: filter-battalion-text ( seq -- seq' ) "battalion" filter-by-oracle-itext ;
+: filter-battle-cry-text ( seq -- seq' ) "battle cry" filter-by-oracle-itext ;
+: filter-bestow-text ( seq -- seq' ) "bestow" filter-by-oracle-itext ;
+: filter-blitz-text ( seq -- seq' ) "blitz" filter-by-oracle-itext ;
+: filter-bloodrush-text ( seq -- seq' ) "bloodrush" filter-by-oracle-itext ;
+: filter-bloodthirst-text ( seq -- seq' ) "bloodthirst" filter-by-oracle-itext ;
+: filter-boast-text ( seq -- seq' ) "boast" filter-by-oracle-itext ;
+: filter-bolster-text ( seq -- seq' ) "bolster" filter-by-oracle-itext ;
+: filter-bushido-text ( seq -- seq' ) "bushido" filter-by-oracle-itext ;
+: filter-buyback-text ( seq -- seq' ) "buyback" filter-by-oracle-itext ;
+: filter-cascade-text ( seq -- seq' ) "cascade" filter-by-oracle-itext ;
+: filter-casualty-text ( seq -- seq' ) "casualty" filter-by-oracle-itext ;
+: filter-celebration-text ( seq -- seq' ) "celebration" filter-by-oracle-itext ;
+: filter-champion-text ( seq -- seq' ) "champion" filter-by-oracle-itext ;
+: filter-changeling-text ( seq -- seq' ) "changeling" filter-by-oracle-itext ;
+: filter-channel-text ( seq -- seq' ) "channel" filter-by-oracle-itext ;
+: filter-choose-a-background-text ( seq -- seq' ) "choose a background" filter-by-oracle-itext ;
+: filter-chroma-text ( seq -- seq' ) "chroma" filter-by-oracle-itext ;
+: filter-cipher-text ( seq -- seq' ) "cipher" filter-by-oracle-itext ;
+: filter-clash-text ( seq -- seq' ) "clash" filter-by-oracle-itext ;
+: filter-cleave-text ( seq -- seq' ) "cleave" filter-by-oracle-itext ;
+: filter-cloak-text ( seq -- seq' ) "cloak" filter-by-oracle-itext ;
+: filter-cohort-text ( seq -- seq' ) "cohort" filter-by-oracle-itext ;
+: filter-collect-evidence-text ( seq -- seq' ) "collect evidence" filter-by-oracle-itext ;
+: filter-companion-text ( seq -- seq' ) "companion" filter-by-oracle-itext ;
+: filter-compleated-text ( seq -- seq' ) "compleated" filter-by-oracle-itext ;
+: filter-conjure-text ( seq -- seq' ) "conjure" filter-by-oracle-itext ;
+: filter-connive-text ( seq -- seq' ) "connive" filter-by-oracle-itext ;
+: filter-conspire-text ( seq -- seq' ) "conspire" filter-by-oracle-itext ;
+: filter-constellation-text ( seq -- seq' ) "constellation" filter-by-oracle-itext ;
+: filter-converge-text ( seq -- seq' ) "converge" filter-by-oracle-itext ;
+: filter-convert-text ( seq -- seq' ) "convert" filter-by-oracle-itext ;
+: filter-convoke-text ( seq -- seq' ) "convoke" filter-by-oracle-itext ;
+: filter-corrupted-text ( seq -- seq' ) "corrupted" filter-by-oracle-itext ;
+: filter-council's-dilemma-text ( seq -- seq' ) "council's dilemma" filter-by-oracle-itext ;
+: filter-coven-text ( seq -- seq' ) "coven" filter-by-oracle-itext ;
+: filter-craft-text ( seq -- seq' ) "craft" filter-by-oracle-itext ;
+: filter-crew-text ( seq -- seq' ) "crew" filter-by-oracle-itext ;
+: filter-cumulative-upkeep-text ( seq -- seq' ) "cumulative upkeep" filter-by-oracle-itext ;
+: filter-cycling-text ( seq -- seq' ) "cycling" filter-by-oracle-itext ;
+: filter-dash-text ( seq -- seq' ) "dash" filter-by-oracle-itext ;
+: filter-daybound-text ( seq -- seq' ) "daybound" filter-by-oracle-itext ;
+: filter-deathtouch-text ( seq -- seq' ) "deathtouch" filter-by-oracle-itext ;
+: filter-defender-text ( seq -- seq' ) "defender" filter-by-oracle-itext ;
+: filter-delirium-text ( seq -- seq' ) "delirium" filter-by-oracle-itext ;
+: filter-delve-text ( seq -- seq' ) "delve" filter-by-oracle-itext ;
+: filter-descend-text ( seq -- seq' ) "descend" filter-by-oracle-itext ;
+: filter-detain-text ( seq -- seq' ) "detain" filter-by-oracle-itext ;
+: filter-dethrone-text ( seq -- seq' ) "dethrone" filter-by-oracle-itext ;
+: filter-devoid-text ( seq -- seq' ) "devoid" filter-by-oracle-itext ;
+: filter-devour-text ( seq -- seq' ) "devour" filter-by-oracle-itext ;
+: filter-discover-text ( seq -- seq' ) "discover" filter-by-oracle-itext ;
+: filter-disguise-text ( seq -- seq' ) "disguise" filter-by-oracle-itext ;
+: filter-disturb-text ( seq -- seq' ) "disturb" filter-by-oracle-itext ;
+: filter-doctor's-companion-text ( seq -- seq' ) "doctor's companion" filter-by-oracle-itext ;
+: filter-domain-text ( seq -- seq' ) "domain" filter-by-oracle-itext ;
+: filter-double-strike-text ( seq -- seq' ) "double strike" filter-by-oracle-itext ;
+: filter-dredge-text ( seq -- seq' ) "dredge" filter-by-oracle-itext ;
+: filter-echo-text ( seq -- seq' ) "echo" filter-by-oracle-itext ;
+: filter-embalm-text ( seq -- seq' ) "embalm" filter-by-oracle-itext ;
+: filter-emerge-text ( seq -- seq' ) "emerge" filter-by-oracle-itext ;
+: filter-eminence-text ( seq -- seq' ) "eminence" filter-by-oracle-itext ;
+: filter-enchant-text ( seq -- seq' ) "enchant" filter-by-oracle-itext ;
+: filter-encore-text ( seq -- seq' ) "encore" filter-by-oracle-itext ;
+: filter-enlist-text ( seq -- seq' ) "enlist" filter-by-oracle-itext ;
+: filter-enrage-text ( seq -- seq' ) "enrage" filter-by-oracle-itext ;
+: filter-entwine-text ( seq -- seq' ) "entwine" filter-by-oracle-itext ;
+: filter-equip-text ( seq -- seq' ) "equip" filter-by-oracle-itext ;
+: filter-escalate-text ( seq -- seq' ) "escalate" filter-by-oracle-itext ;
+: filter-escape-text ( seq -- seq' ) "escape" filter-by-oracle-itext ;
+: filter-eternalize-text ( seq -- seq' ) "eternalize" filter-by-oracle-itext ;
+: filter-evoke-text ( seq -- seq' ) "evoke" filter-by-oracle-itext ;
+: filter-evolve-text ( seq -- seq' ) "evolve" filter-by-oracle-itext ;
+: filter-exalted-text ( seq -- seq' ) "exalted" filter-by-oracle-itext ;
+: filter-exert-text ( seq -- seq' ) "exert" filter-by-oracle-itext ;
+: filter-exploit-text ( seq -- seq' ) "exploit" filter-by-oracle-itext ;
+: filter-explore-text ( seq -- seq' ) "explore" filter-by-oracle-itext ;
+: filter-extort-text ( seq -- seq' ) "extort" filter-by-oracle-itext ;
+: filter-fabricate-text ( seq -- seq' ) "fabricate" filter-by-oracle-itext ;
+: filter-fading-text ( seq -- seq' ) "fading" filter-by-oracle-itext ;
+: filter-fateful-hour-text ( seq -- seq' ) "fateful hour" filter-by-oracle-itext ;
+: filter-fathomless-descent-text ( seq -- seq' ) "fathomless descent" filter-by-oracle-itext ;
+: filter-fear-text ( seq -- seq' ) "fear" filter-by-oracle-itext ;
+: filter-ferocious-text ( seq -- seq' ) "ferocious" filter-by-oracle-itext ;
+: filter-fight-text ( seq -- seq' ) "fight" filter-by-oracle-itext ;
+: filter-first-strike-text ( seq -- seq' ) "first strike" filter-by-oracle-itext ;
+: filter-flanking-text ( seq -- seq' ) "flanking" filter-by-oracle-itext ;
+: filter-flash-text ( seq -- seq' ) "flash" filter-by-oracle-itext ;
+: filter-flashback-text ( seq -- seq' ) "flashback" filter-by-oracle-itext ;
+: filter-flying-text ( seq -- seq' ) "flying" filter-by-oracle-itext ;
+: filter-food-text ( seq -- seq' ) "food" filter-by-oracle-itext ;
+: filter-for-mirrodin!-text ( seq -- seq' ) "for mirrodin!" filter-by-oracle-itext ;
+: filter-forecast-text ( seq -- seq' ) "forecast" filter-by-oracle-itext ;
+: filter-forestcycling-text ( seq -- seq' ) "forestcycling" filter-by-oracle-itext ;
+: filter-forestwalk-text ( seq -- seq' ) "forestwalk" filter-by-oracle-itext ;
+: filter-foretell-text ( seq -- seq' ) "foretell" filter-by-oracle-itext ;
+: filter-formidable-text ( seq -- seq' ) "formidable" filter-by-oracle-itext ;
+: filter-friends-forever-text ( seq -- seq' ) "friends forever" filter-by-oracle-itext ;
+: filter-fuse-text ( seq -- seq' ) "fuse" filter-by-oracle-itext ;
+: filter-goad-text ( seq -- seq' ) "goad" filter-by-oracle-itext ;
+: filter-graft-text ( seq -- seq' ) "graft" filter-by-oracle-itext ;
+: filter-haste-text ( seq -- seq' ) "haste" filter-by-oracle-itext ;
+: filter-haunt-text ( seq -- seq' ) "haunt" filter-by-oracle-itext ;
+: filter-hellbent-text ( seq -- seq' ) "hellbent" filter-by-oracle-itext ;
+: filter-hero's-reward-text ( seq -- seq' ) "hero's reward" filter-by-oracle-itext ;
+: filter-heroic-text ( seq -- seq' ) "heroic" filter-by-oracle-itext ;
+: filter-hexproof-text ( seq -- seq' ) "hexproof" filter-by-oracle-itext ;
+: filter-hexproof-from-text ( seq -- seq' ) "hexproof from" filter-by-oracle-itext ;
+: filter-hidden-agenda-text ( seq -- seq' ) "hidden agenda" filter-by-oracle-itext ;
+: filter-hideaway-text ( seq -- seq' ) "hideaway" filter-by-oracle-itext ;
+: filter-horsemanship-text ( seq -- seq' ) "horsemanship" filter-by-oracle-itext ;
+: filter-imprint-text ( seq -- seq' ) "imprint" filter-by-oracle-itext ;
+: filter-improvise-text ( seq -- seq' ) "improvise" filter-by-oracle-itext ;
+: filter-incubate-text ( seq -- seq' ) "incubate" filter-by-oracle-itext ;
+: filter-indestructible-text ( seq -- seq' ) "indestructible" filter-by-oracle-itext ;
+: filter-infect-text ( seq -- seq' ) "infect" filter-by-oracle-itext ;
+: filter-ingest-text ( seq -- seq' ) "ingest" filter-by-oracle-itext ;
+: filter-inspired-text ( seq -- seq' ) "inspired" filter-by-oracle-itext ;
+: filter-intensity-text ( seq -- seq' ) "intensity" filter-by-oracle-itext ;
+: filter-intimidate-text ( seq -- seq' ) "intimidate" filter-by-oracle-itext ;
+: filter-investigate-text ( seq -- seq' ) "investigate" filter-by-oracle-itext ;
+: filter-islandcycling-text ( seq -- seq' ) "islandcycling" filter-by-oracle-itext ;
+: filter-islandwalk-text ( seq -- seq' ) "islandwalk" filter-by-oracle-itext ;
+: filter-jump-start-text ( seq -- seq' ) "jump-start" filter-by-oracle-itext ;
+: filter-kicker-text ( seq -- seq' ) "kicker" filter-by-oracle-itext ;
+: filter-kinship-text ( seq -- seq' ) "kinship" filter-by-oracle-itext ;
+: filter-landcycling-text ( seq -- seq' ) "landcycling" filter-by-oracle-itext ;
+: filter-landfall-text ( seq -- seq' ) "landfall" filter-by-oracle-itext ;
+: filter-landwalk-text ( seq -- seq' ) "landwalk" filter-by-oracle-itext ;
+: filter-learn-text ( seq -- seq' ) "learn" filter-by-oracle-itext ;
+: filter-level-up-text ( seq -- seq' ) "level up" filter-by-oracle-itext ;
+: filter-lieutenant-text ( seq -- seq' ) "lieutenant" filter-by-oracle-itext ;
+: filter-lifelink-text ( seq -- seq' ) "lifelink" filter-by-oracle-itext ;
+: filter-living-metal-text ( seq -- seq' ) "living metal" filter-by-oracle-itext ;
+: filter-living-weapon-text ( seq -- seq' ) "living weapon" filter-by-oracle-itext ;
+: filter-madness-text ( seq -- seq' ) "madness" filter-by-oracle-itext ;
+: filter-magecraft-text ( seq -- seq' ) "magecraft" filter-by-oracle-itext ;
+: filter-manifest-text ( seq -- seq' ) "manifest" filter-by-oracle-itext ;
+: filter-megamorph-text ( seq -- seq' ) "megamorph" filter-by-oracle-itext ;
+: filter-meld-text ( seq -- seq' ) "meld" filter-by-oracle-itext ;
+: filter-melee-text ( seq -- seq' ) "melee" filter-by-oracle-itext ;
+: filter-menace-text ( seq -- seq' ) "menace" filter-by-oracle-itext ;
+: filter-mentor-text ( seq -- seq' ) "mentor" filter-by-oracle-itext ;
+: filter-metalcraft-text ( seq -- seq' ) "metalcraft" filter-by-oracle-itext ;
+: filter-mill-text ( seq -- seq' ) "mill" filter-by-oracle-itext ;
+: filter-miracle-text ( seq -- seq' ) "miracle" filter-by-oracle-itext ;
+: filter-modular-text ( seq -- seq' ) "modular" filter-by-oracle-itext ;
+: filter-monstrosity-text ( seq -- seq' ) "monstrosity" filter-by-oracle-itext ;
+: filter-morbid-text ( seq -- seq' ) "morbid" filter-by-oracle-itext ;
+: filter-more-than-meets-the-eye-text ( seq -- seq' ) "more than meets the eye" filter-by-oracle-itext ;
+: filter-morph-text ( seq -- seq' ) "morph" filter-by-oracle-itext ;
+: filter-mountaincycling-text ( seq -- seq' ) "mountaincycling" filter-by-oracle-itext ;
+: filter-mountainwalk-text ( seq -- seq' ) "mountainwalk" filter-by-oracle-itext ;
+: filter-multikicker-text ( seq -- seq' ) "multikicker" filter-by-oracle-itext ;
+: filter-mutate-text ( seq -- seq' ) "mutate" filter-by-oracle-itext ;
+: filter-myriad-text ( seq -- seq' ) "myriad" filter-by-oracle-itext ;
+: filter-nightbound-text ( seq -- seq' ) "nightbound" filter-by-oracle-itext ;
+: filter-ninjutsu-text ( seq -- seq' ) "ninjutsu" filter-by-oracle-itext ;
+: filter-offering-text ( seq -- seq' ) "offering" filter-by-oracle-itext ;
+: filter-open-an-attraction-text ( seq -- seq' ) "open an attraction" filter-by-oracle-itext ;
+: filter-outlast-text ( seq -- seq' ) "outlast" filter-by-oracle-itext ;
+: filter-overload-text ( seq -- seq' ) "overload" filter-by-oracle-itext ;
+: filter-pack-tactics-text ( seq -- seq' ) "pack tactics" filter-by-oracle-itext ;
+: filter-paradox-text ( seq -- seq' ) "paradox" filter-by-oracle-itext ;
+: filter-parley-text ( seq -- seq' ) "parley" filter-by-oracle-itext ;
+: filter-partner-text ( seq -- seq' ) "partner" filter-by-oracle-itext ;
+: filter-partner-with-text ( seq -- seq' ) "partner with" filter-by-oracle-itext ;
+: filter-persist-text ( seq -- seq' ) "persist" filter-by-oracle-itext ;
+: filter-phasing-text ( seq -- seq' ) "phasing" filter-by-oracle-itext ;
+: filter-plainscycling-text ( seq -- seq' ) "plainscycling" filter-by-oracle-itext ;
+: filter-plot-text ( seq -- seq' ) "plot" filter-by-oracle-itext ;
+: filter-populate-text ( seq -- seq' ) "populate" filter-by-oracle-itext ;
+: filter-proliferate-text ( seq -- seq' ) "proliferate" filter-by-oracle-itext ;
+: filter-protection-text ( seq -- seq' ) "protection" filter-by-oracle-itext ;
+: filter-prototype-text ( seq -- seq' ) "prototype" filter-by-oracle-itext ;
+: filter-provoke-text ( seq -- seq' ) "provoke" filter-by-oracle-itext ;
+: filter-prowess-text ( seq -- seq' ) "prowess" filter-by-oracle-itext ;
+: filter-prowl-text ( seq -- seq' ) "prowl" filter-by-oracle-itext ;
+: filter-radiance-text ( seq -- seq' ) "radiance" filter-by-oracle-itext ;
+: filter-raid-text ( seq -- seq' ) "raid" filter-by-oracle-itext ;
+: filter-rally-text ( seq -- seq' ) "rally" filter-by-oracle-itext ;
+: filter-rampage-text ( seq -- seq' ) "rampage" filter-by-oracle-itext ;
+: filter-ravenous-text ( seq -- seq' ) "ravenous" filter-by-oracle-itext ;
+: filter-reach-text ( seq -- seq' ) "reach" filter-by-oracle-itext ;
+: filter-read-ahead-text ( seq -- seq' ) "read ahead" filter-by-oracle-itext ;
+: filter-rebound-text ( seq -- seq' ) "rebound" filter-by-oracle-itext ;
+: filter-reconfigure-text ( seq -- seq' ) "reconfigure" filter-by-oracle-itext ;
+: filter-recover-text ( seq -- seq' ) "recover" filter-by-oracle-itext ;
+: filter-reinforce-text ( seq -- seq' ) "reinforce" filter-by-oracle-itext ;
+: filter-renown-text ( seq -- seq' ) "renown" filter-by-oracle-itext ;
+: filter-replicate-text ( seq -- seq' ) "replicate" filter-by-oracle-itext ;
+: filter-retrace-text ( seq -- seq' ) "retrace" filter-by-oracle-itext ;
+: filter-revolt-text ( seq -- seq' ) "revolt" filter-by-oracle-itext ;
+: filter-riot-text ( seq -- seq' ) "riot" filter-by-oracle-itext ;
+: filter-role-token-text ( seq -- seq' ) "role token" filter-by-oracle-itext ;
+: filter-saddle-text ( seq -- seq' ) "saddle" filter-by-oracle-itext ;
+: filter-scavenge-text ( seq -- seq' ) "scavenge" filter-by-oracle-itext ;
+: filter-scry-text ( seq -- seq' ) "scry" filter-by-oracle-itext ;
+: filter-seek-text ( seq -- seq' ) "seek" filter-by-oracle-itext ;
+: filter-shadow-text ( seq -- seq' ) "shadow" filter-by-oracle-itext ;
+: filter-shroud-text ( seq -- seq' ) "shroud" filter-by-oracle-itext ;
+: filter-skulk-text ( seq -- seq' ) "skulk" filter-by-oracle-itext ;
+: filter-soulbond-text ( seq -- seq' ) "soulbond" filter-by-oracle-itext ;
+: filter-soulshift-text ( seq -- seq' ) "soulshift" filter-by-oracle-itext ;
+: filter-specialize-text ( seq -- seq' ) "specialize" filter-by-oracle-itext ;
+: filter-spectacle-text ( seq -- seq' ) "spectacle" filter-by-oracle-itext ;
+: filter-spell-mastery-text ( seq -- seq' ) "spell mastery" filter-by-oracle-itext ;
+: filter-splice-text ( seq -- seq' ) "splice" filter-by-oracle-itext ;
+: filter-split-second-text ( seq -- seq' ) "split second" filter-by-oracle-itext ;
+: filter-spree-text ( seq -- seq' ) "spree" filter-by-oracle-itext ;
+: filter-squad-text ( seq -- seq' ) "squad" filter-by-oracle-itext ;
+: filter-storm-text ( seq -- seq' ) "storm" filter-by-oracle-itext ;
+: filter-strive-text ( seq -- seq' ) "strive" filter-by-oracle-itext ;
+: filter-sunburst-text ( seq -- seq' ) "sunburst" filter-by-oracle-itext ;
+: filter-support-text ( seq -- seq' ) "support" filter-by-oracle-itext ;
+: filter-surge-text ( seq -- seq' ) "surge" filter-by-oracle-itext ;
+: filter-surveil-text ( seq -- seq' ) "surveil" filter-by-oracle-itext ;
+: filter-suspect-text ( seq -- seq' ) "suspect" filter-by-oracle-itext ;
+: filter-suspend-text ( seq -- seq' ) "suspend" filter-by-oracle-itext ;
+: filter-swampcycling-text ( seq -- seq' ) "swampcycling" filter-by-oracle-itext ;
+: filter-swampwalk-text ( seq -- seq' ) "swampwalk" filter-by-oracle-itext ;
+: filter-threshold-text ( seq -- seq' ) "threshold" filter-by-oracle-itext ;
+: filter-time-travel-text ( seq -- seq' ) "time travel" filter-by-oracle-itext ;
+: filter-totem-armor-text ( seq -- seq' ) "totem armor" filter-by-oracle-itext ;
+: filter-toxic-text ( seq -- seq' ) "toxic" filter-by-oracle-itext ;
+: filter-training-text ( seq -- seq' ) "training" filter-by-oracle-itext ;
+: filter-trample-text ( seq -- seq' ) "trample" filter-by-oracle-itext ;
+: filter-transform-text ( seq -- seq' ) "transform" filter-by-oracle-itext ;
+: filter-transmute-text ( seq -- seq' ) "transmute" filter-by-oracle-itext ;
+: filter-treasure-text ( seq -- seq' ) "treasure" filter-by-oracle-itext ;
+: filter-tribute-text ( seq -- seq' ) "tribute" filter-by-oracle-itext ;
+: filter-typecycling-text ( seq -- seq' ) "typecycling" filter-by-oracle-itext ;
+: filter-undergrowth-text ( seq -- seq' ) "undergrowth" filter-by-oracle-itext ;
+: filter-undying-text ( seq -- seq' ) "undying" filter-by-oracle-itext ;
+: filter-unearth-text ( seq -- seq' ) "unearth" filter-by-oracle-itext ;
+: filter-unleash-text ( seq -- seq' ) "unleash" filter-by-oracle-itext ;
+: filter-vanishing-text ( seq -- seq' ) "vanishing" filter-by-oracle-itext ;
+: filter-venture-into-the-dungeon-text ( seq -- seq' ) "venture into the dungeon" filter-by-oracle-itext ;
+: filter-vigilance-text ( seq -- seq' ) "vigilance" filter-by-oracle-itext ;
+: filter-ward-text ( seq -- seq' ) "ward" filter-by-oracle-itext ;
+: filter-will-of-the-council-text ( seq -- seq' ) "will of the council" filter-by-oracle-itext ;
+: filter-wither-text ( seq -- seq' ) "wither" filter-by-oracle-itext ;
: filter-day ( seq -- seq' ) "day" filter-by-oracle-itext ;
: filter-night ( seq -- seq' ) "night" filter-by-oracle-itext ;
: filter-daybound ( seq -- seq' ) "daybound" filter-by-oracle-itext ;
: filter-nightbound ( seq -- seq' ) "nightbound" filter-by-oracle-itext ;
+: filter-cave ( seq -- seq' ) "cave" filter-land-subtype ;
+: filter-sphere ( seq -- seq' ) "sphere" filter-land-subtype ;
+
: filter-mount ( seq -- seq' ) "mount" filter-by-oracle-itext ;
: filter-outlaw ( seq -- seq' )
{ "Assassin" "Mercenary" "Pirate" "Rogue" "Warlock" } filter-subtype-intersects ;
: filter-saddle ( seq -- seq' ) "saddle" filter-by-oracle-itext ;
: filter-spree ( seq -- seq' ) "saddle" filter-by-oracle-itext ;
+: filter-adamant-keyword ( seq -- seq' ) "adamant" filter-by-keyword ;
+: filter-adapt-keyword ( seq -- seq' ) "adapt" filter-by-keyword ;
+: filter-addendum-keyword ( seq -- seq' ) "addendum" filter-by-keyword ;
+: filter-affinity-keyword ( seq -- seq' ) "affinity" filter-by-keyword ;
+: filter-afflict-keyword ( seq -- seq' ) "afflict" filter-by-keyword ;
+: filter-afterlife-keyword ( seq -- seq' ) "afterlife" filter-by-keyword ;
+: filter-aftermath-keyword ( seq -- seq' ) "aftermath" filter-by-keyword ;
+: filter-alliance-keyword ( seq -- seq' ) "alliance" filter-by-keyword ;
+: filter-amass-keyword ( seq -- seq' ) "amass" filter-by-keyword ;
+: filter-amplify-keyword ( seq -- seq' ) "amplify" filter-by-keyword ;
+: filter-annihilator-keyword ( seq -- seq' ) "annihilator" filter-by-keyword ;
+: filter-ascend-keyword ( seq -- seq' ) "ascend" filter-by-keyword ;
+: filter-assemble-keyword ( seq -- seq' ) "assemble" filter-by-keyword ;
+: filter-assist-keyword ( seq -- seq' ) "assist" filter-by-keyword ;
+: filter-augment-keyword ( seq -- seq' ) "augment" filter-by-keyword ;
+: filter-awaken-keyword ( seq -- seq' ) "awaken" filter-by-keyword ;
+: filter-backup-keyword ( seq -- seq' ) "backup" filter-by-keyword ;
+: filter-banding-keyword ( seq -- seq' ) "banding" filter-by-keyword ;
+: filter-bargain-keyword ( seq -- seq' ) "bargain" filter-by-keyword ;
+: filter-basic-landcycling-keyword ( seq -- seq' ) "basic-landcycling" filter-by-keyword ;
+: filter-battalion-keyword ( seq -- seq' ) "battalion" filter-by-keyword ;
+: filter-battle-cry-keyword ( seq -- seq' ) "battle-cry" filter-by-keyword ;
+: filter-bestow-keyword ( seq -- seq' ) "bestow" filter-by-keyword ;
+: filter-blitz-keyword ( seq -- seq' ) "blitz" filter-by-keyword ;
+: filter-bloodrush-keyword ( seq -- seq' ) "bloodrush" filter-by-keyword ;
+: filter-bloodthirst-keyword ( seq -- seq' ) "bloodthirst" filter-by-keyword ;
+: filter-boast-keyword ( seq -- seq' ) "boast" filter-by-keyword ;
+: filter-bolster-keyword ( seq -- seq' ) "bolster" filter-by-keyword ;
+: filter-bushido-keyword ( seq -- seq' ) "bushido" filter-by-keyword ;
+: filter-buyback-keyword ( seq -- seq' ) "buyback" filter-by-keyword ;
+: filter-cascade-keyword ( seq -- seq' ) "cascade" filter-by-keyword ;
+: filter-casualty-keyword ( seq -- seq' ) "casualty" filter-by-keyword ;
+: filter-celebration-keyword ( seq -- seq' ) "celebration" filter-by-keyword ;
+: filter-champion-keyword ( seq -- seq' ) "champion" filter-by-keyword ;
+: filter-changeling-keyword ( seq -- seq' ) "changeling" filter-by-keyword ;
+: filter-channel-keyword ( seq -- seq' ) "channel" filter-by-keyword ;
+: filter-choose-a-background-keyword ( seq -- seq' ) "choose-a-background" filter-by-keyword ;
+: filter-chroma-keyword ( seq -- seq' ) "chroma" filter-by-keyword ;
+: filter-cipher-keyword ( seq -- seq' ) "cipher" filter-by-keyword ;
+: filter-clash-keyword ( seq -- seq' ) "clash" filter-by-keyword ;
+: filter-cleave-keyword ( seq -- seq' ) "cleave" filter-by-keyword ;
+: filter-cloak-keyword ( seq -- seq' ) "cloak" filter-by-keyword ;
+: filter-cohort-keyword ( seq -- seq' ) "cohort" filter-by-keyword ;
+: filter-collect-evidence-keyword ( seq -- seq' ) "collect-evidence" filter-by-keyword ;
+: filter-companion-keyword ( seq -- seq' ) "companion" filter-by-keyword ;
+: filter-compleated-keyword ( seq -- seq' ) "compleated" filter-by-keyword ;
+: filter-conjure-keyword ( seq -- seq' ) "conjure" filter-by-keyword ;
+: filter-connive-keyword ( seq -- seq' ) "connive" filter-by-keyword ;
+: filter-conspire-keyword ( seq -- seq' ) "conspire" filter-by-keyword ;
+: filter-constellation-keyword ( seq -- seq' ) "constellation" filter-by-keyword ;
+: filter-converge-keyword ( seq -- seq' ) "converge" filter-by-keyword ;
+: filter-convert-keyword ( seq -- seq' ) "convert" filter-by-keyword ;
+: filter-convoke-keyword ( seq -- seq' ) "convoke" filter-by-keyword ;
+: filter-corrupted-keyword ( seq -- seq' ) "corrupted" filter-by-keyword ;
+: filter-council's-dilemma-keyword ( seq -- seq' ) "council's-dilemma" filter-by-keyword ;
+: filter-coven-keyword ( seq -- seq' ) "coven" filter-by-keyword ;
+: filter-craft-keyword ( seq -- seq' ) "craft" filter-by-keyword ;
+: filter-crew-keyword ( seq -- seq' ) "crew" filter-by-keyword ;
+: filter-cumulative-upkeep-keyword ( seq -- seq' ) "cumulative-upkeep" filter-by-keyword ;
+: filter-cycling-keyword ( seq -- seq' ) "cycling" filter-by-keyword ;
+: filter-dash-keyword ( seq -- seq' ) "dash" filter-by-keyword ;
+: filter-daybound-keyword ( seq -- seq' ) "daybound" filter-by-keyword ;
+: filter-deathtouch-keyword ( seq -- seq' ) "deathtouch" filter-by-keyword ;
+: filter-defender-keyword ( seq -- seq' ) "defender" filter-by-keyword ;
+: filter-delirium-keyword ( seq -- seq' ) "delirium" filter-by-keyword ;
+: filter-delve-keyword ( seq -- seq' ) "delve" filter-by-keyword ;
+: filter-descend-keyword ( seq -- seq' ) "descend" filter-by-keyword ;
+: filter-detain-keyword ( seq -- seq' ) "detain" filter-by-keyword ;
+: filter-dethrone-keyword ( seq -- seq' ) "dethrone" filter-by-keyword ;
+: filter-devoid-keyword ( seq -- seq' ) "devoid" filter-by-keyword ;
+: filter-devour-keyword ( seq -- seq' ) "devour" filter-by-keyword ;
+: filter-discover-keyword ( seq -- seq' ) "discover" filter-by-keyword ;
+: filter-disguise-keyword ( seq -- seq' ) "disguise" filter-by-keyword ;
+: filter-disturb-keyword ( seq -- seq' ) "disturb" filter-by-keyword ;
+: filter-doctor's-companion-keyword ( seq -- seq' ) "doctor's-companion" filter-by-keyword ;
+: filter-domain-keyword ( seq -- seq' ) "domain" filter-by-keyword ;
+: filter-double-strike-keyword ( seq -- seq' ) "double-strike" filter-by-keyword ;
+: filter-dredge-keyword ( seq -- seq' ) "dredge" filter-by-keyword ;
+: filter-echo-keyword ( seq -- seq' ) "echo" filter-by-keyword ;
+: filter-embalm-keyword ( seq -- seq' ) "embalm" filter-by-keyword ;
+: filter-emerge-keyword ( seq -- seq' ) "emerge" filter-by-keyword ;
+: filter-eminence-keyword ( seq -- seq' ) "eminence" filter-by-keyword ;
+: filter-enchant-keyword ( seq -- seq' ) "enchant" filter-by-keyword ;
+: filter-encore-keyword ( seq -- seq' ) "encore" filter-by-keyword ;
+: filter-enlist-keyword ( seq -- seq' ) "enlist" filter-by-keyword ;
+: filter-enrage-keyword ( seq -- seq' ) "enrage" filter-by-keyword ;
+: filter-entwine-keyword ( seq -- seq' ) "entwine" filter-by-keyword ;
+: filter-equip-keyword ( seq -- seq' ) "equip" filter-by-keyword ;
+: filter-escalate-keyword ( seq -- seq' ) "escalate" filter-by-keyword ;
+: filter-escape-keyword ( seq -- seq' ) "escape" filter-by-keyword ;
+: filter-eternalize-keyword ( seq -- seq' ) "eternalize" filter-by-keyword ;
+: filter-evoke-keyword ( seq -- seq' ) "evoke" filter-by-keyword ;
+: filter-evolve-keyword ( seq -- seq' ) "evolve" filter-by-keyword ;
+: filter-exalted-keyword ( seq -- seq' ) "exalted" filter-by-keyword ;
+: filter-exert-keyword ( seq -- seq' ) "exert" filter-by-keyword ;
+: filter-exploit-keyword ( seq -- seq' ) "exploit" filter-by-keyword ;
+: filter-explore-keyword ( seq -- seq' ) "explore" filter-by-keyword ;
+: filter-extort-keyword ( seq -- seq' ) "extort" filter-by-keyword ;
+: filter-fabricate-keyword ( seq -- seq' ) "fabricate" filter-by-keyword ;
+: filter-fading-keyword ( seq -- seq' ) "fading" filter-by-keyword ;
+: filter-fateful-hour-keyword ( seq -- seq' ) "fateful-hour" filter-by-keyword ;
+: filter-fathomless-descent-keyword ( seq -- seq' ) "fathomless-descent" filter-by-keyword ;
+: filter-fear-keyword ( seq -- seq' ) "fear" filter-by-keyword ;
+: filter-ferocious-keyword ( seq -- seq' ) "ferocious" filter-by-keyword ;
+: filter-fight-keyword ( seq -- seq' ) "fight" filter-by-keyword ;
+: filter-first-strike-keyword ( seq -- seq' ) "first-strike" filter-by-keyword ;
+: filter-flanking-keyword ( seq -- seq' ) "flanking" filter-by-keyword ;
+: filter-flash-keyword ( seq -- seq' ) "flash" filter-by-keyword ;
+: filter-flashback-keyword ( seq -- seq' ) "flashback" filter-by-keyword ;
+: filter-flying-keyword ( seq -- seq' ) "flying" filter-by-keyword ;
+: filter-food-keyword ( seq -- seq' ) "food" filter-by-keyword ;
+: filter-for-mirrodin!-keyword ( seq -- seq' ) "for-mirrodin!" filter-by-keyword ;
+: filter-forecast-keyword ( seq -- seq' ) "forecast" filter-by-keyword ;
+: filter-forestcycling-keyword ( seq -- seq' ) "forestcycling" filter-by-keyword ;
+: filter-forestwalk-keyword ( seq -- seq' ) "forestwalk" filter-by-keyword ;
+: filter-foretell-keyword ( seq -- seq' ) "foretell" filter-by-keyword ;
+: filter-formidable-keyword ( seq -- seq' ) "formidable" filter-by-keyword ;
+: filter-friends-forever-keyword ( seq -- seq' ) "friends-forever" filter-by-keyword ;
+: filter-fuse-keyword ( seq -- seq' ) "fuse" filter-by-keyword ;
+: filter-goad-keyword ( seq -- seq' ) "goad" filter-by-keyword ;
+: filter-graft-keyword ( seq -- seq' ) "graft" filter-by-keyword ;
+: filter-haste-keyword ( seq -- seq' ) "haste" filter-by-keyword ;
+: filter-haunt-keyword ( seq -- seq' ) "haunt" filter-by-keyword ;
+: filter-hellbent-keyword ( seq -- seq' ) "hellbent" filter-by-keyword ;
+: filter-hero's-reward-keyword ( seq -- seq' ) "hero's-reward" filter-by-keyword ;
+: filter-heroic-keyword ( seq -- seq' ) "heroic" filter-by-keyword ;
+: filter-hexproof-keyword ( seq -- seq' ) "hexproof" filter-by-keyword ;
+: filter-hexproof-from-keyword ( seq -- seq' ) "hexproof-from" filter-by-keyword ;
+: filter-hidden-agenda-keyword ( seq -- seq' ) "hidden-agenda" filter-by-keyword ;
+: filter-hideaway-keyword ( seq -- seq' ) "hideaway" filter-by-keyword ;
+: filter-horsemanship-keyword ( seq -- seq' ) "horsemanship" filter-by-keyword ;
+: filter-imprint-keyword ( seq -- seq' ) "imprint" filter-by-keyword ;
+: filter-improvise-keyword ( seq -- seq' ) "improvise" filter-by-keyword ;
+: filter-incubate-keyword ( seq -- seq' ) "incubate" filter-by-keyword ;
+: filter-indestructible-keyword ( seq -- seq' ) "indestructible" filter-by-keyword ;
+: filter-infect-keyword ( seq -- seq' ) "infect" filter-by-keyword ;
+: filter-ingest-keyword ( seq -- seq' ) "ingest" filter-by-keyword ;
+: filter-inspired-keyword ( seq -- seq' ) "inspired" filter-by-keyword ;
+: filter-intensity-keyword ( seq -- seq' ) "intensity" filter-by-keyword ;
+: filter-intimidate-keyword ( seq -- seq' ) "intimidate" filter-by-keyword ;
+: filter-investigate-keyword ( seq -- seq' ) "investigate" filter-by-keyword ;
+: filter-islandcycling-keyword ( seq -- seq' ) "islandcycling" filter-by-keyword ;
+: filter-islandwalk-keyword ( seq -- seq' ) "islandwalk" filter-by-keyword ;
+: filter-jump-start-keyword ( seq -- seq' ) "jump-start" filter-by-keyword ;
+: filter-kicker-keyword ( seq -- seq' ) "kicker" filter-by-keyword ;
+: filter-kinship-keyword ( seq -- seq' ) "kinship" filter-by-keyword ;
+: filter-landcycling-keyword ( seq -- seq' ) "landcycling" filter-by-keyword ;
+: filter-landfall-keyword ( seq -- seq' ) "landfall" filter-by-keyword ;
+: filter-landwalk-keyword ( seq -- seq' ) "landwalk" filter-by-keyword ;
+: filter-learn-keyword ( seq -- seq' ) "learn" filter-by-keyword ;
+: filter-level-up-keyword ( seq -- seq' ) "level-up" filter-by-keyword ;
+: filter-lieutenant-keyword ( seq -- seq' ) "lieutenant" filter-by-keyword ;
+: filter-lifelink-keyword ( seq -- seq' ) "lifelink" filter-by-keyword ;
+: filter-living-metal-keyword ( seq -- seq' ) "living-metal" filter-by-keyword ;
+: filter-living-weapon-keyword ( seq -- seq' ) "living-weapon" filter-by-keyword ;
+: filter-madness-keyword ( seq -- seq' ) "madness" filter-by-keyword ;
+: filter-magecraft-keyword ( seq -- seq' ) "magecraft" filter-by-keyword ;
+: filter-manifest-keyword ( seq -- seq' ) "manifest" filter-by-keyword ;
+: filter-megamorph-keyword ( seq -- seq' ) "megamorph" filter-by-keyword ;
+: filter-meld-keyword ( seq -- seq' ) "meld" filter-by-keyword ;
+: filter-melee-keyword ( seq -- seq' ) "melee" filter-by-keyword ;
+: filter-menace-keyword ( seq -- seq' ) "menace" filter-by-keyword ;
+: filter-mentor-keyword ( seq -- seq' ) "mentor" filter-by-keyword ;
+: filter-metalcraft-keyword ( seq -- seq' ) "metalcraft" filter-by-keyword ;
+: filter-mill-keyword ( seq -- seq' ) "mill" filter-by-keyword ;
+: filter-miracle-keyword ( seq -- seq' ) "miracle" filter-by-keyword ;
+: filter-modular-keyword ( seq -- seq' ) "modular" filter-by-keyword ;
+: filter-monstrosity-keyword ( seq -- seq' ) "monstrosity" filter-by-keyword ;
+: filter-morbid-keyword ( seq -- seq' ) "morbid" filter-by-keyword ;
+: filter-more-than-meets-the-eye-keyword ( seq -- seq' ) "more-than-meets-the-eye" filter-by-keyword ;
+: filter-morph-keyword ( seq -- seq' ) "morph" filter-by-keyword ;
+: filter-mountaincycling-keyword ( seq -- seq' ) "mountaincycling" filter-by-keyword ;
+: filter-mountainwalk-keyword ( seq -- seq' ) "mountainwalk" filter-by-keyword ;
+: filter-multikicker-keyword ( seq -- seq' ) "multikicker" filter-by-keyword ;
+: filter-mutate-keyword ( seq -- seq' ) "mutate" filter-by-keyword ;
+: filter-myriad-keyword ( seq -- seq' ) "myriad" filter-by-keyword ;
+: filter-nightbound-keyword ( seq -- seq' ) "nightbound" filter-by-keyword ;
+: filter-ninjutsu-keyword ( seq -- seq' ) "ninjutsu" filter-by-keyword ;
+: filter-offering-keyword ( seq -- seq' ) "offering" filter-by-keyword ;
+: filter-open-an-attraction-keyword ( seq -- seq' ) "open-an-attraction" filter-by-keyword ;
+: filter-outlast-keyword ( seq -- seq' ) "outlast" filter-by-keyword ;
+: filter-overload-keyword ( seq -- seq' ) "overload" filter-by-keyword ;
+: filter-pack-tactics-keyword ( seq -- seq' ) "pack-tactics" filter-by-keyword ;
+: filter-paradox-keyword ( seq -- seq' ) "paradox" filter-by-keyword ;
+: filter-parley-keyword ( seq -- seq' ) "parley" filter-by-keyword ;
+: filter-partner-keyword ( seq -- seq' ) "partner" filter-by-keyword ;
+: filter-partner-with-keyword ( seq -- seq' ) "partner-with" filter-by-keyword ;
+: filter-persist-keyword ( seq -- seq' ) "persist" filter-by-keyword ;
+: filter-phasing-keyword ( seq -- seq' ) "phasing" filter-by-keyword ;
+: filter-plainscycling-keyword ( seq -- seq' ) "plainscycling" filter-by-keyword ;
+: filter-plot-keyword ( seq -- seq' ) "plot" filter-by-keyword ;
+: filter-populate-keyword ( seq -- seq' ) "populate" filter-by-keyword ;
+: filter-proliferate-keyword ( seq -- seq' ) "proliferate" filter-by-keyword ;
+: filter-protection-keyword ( seq -- seq' ) "protection" filter-by-keyword ;
+: filter-prototype-keyword ( seq -- seq' ) "prototype" filter-by-keyword ;
+: filter-provoke-keyword ( seq -- seq' ) "provoke" filter-by-keyword ;
+: filter-prowess-keyword ( seq -- seq' ) "prowess" filter-by-keyword ;
+: filter-prowl-keyword ( seq -- seq' ) "prowl" filter-by-keyword ;
+: filter-radiance-keyword ( seq -- seq' ) "radiance" filter-by-keyword ;
+: filter-raid-keyword ( seq -- seq' ) "raid" filter-by-keyword ;
+: filter-rally-keyword ( seq -- seq' ) "rally" filter-by-keyword ;
+: filter-rampage-keyword ( seq -- seq' ) "rampage" filter-by-keyword ;
+: filter-ravenous-keyword ( seq -- seq' ) "ravenous" filter-by-keyword ;
+: filter-reach-keyword ( seq -- seq' ) "reach" filter-by-keyword ;
+: filter-read-ahead-keyword ( seq -- seq' ) "read-ahead" filter-by-keyword ;
+: filter-rebound-keyword ( seq -- seq' ) "rebound" filter-by-keyword ;
+: filter-reconfigure-keyword ( seq -- seq' ) "reconfigure" filter-by-keyword ;
+: filter-recover-keyword ( seq -- seq' ) "recover" filter-by-keyword ;
+: filter-reinforce-keyword ( seq -- seq' ) "reinforce" filter-by-keyword ;
+: filter-renown-keyword ( seq -- seq' ) "renown" filter-by-keyword ;
+: filter-replicate-keyword ( seq -- seq' ) "replicate" filter-by-keyword ;
+: filter-retrace-keyword ( seq -- seq' ) "retrace" filter-by-keyword ;
+: filter-revolt-keyword ( seq -- seq' ) "revolt" filter-by-keyword ;
+: filter-riot-keyword ( seq -- seq' ) "riot" filter-by-keyword ;
+: filter-role-token-keyword ( seq -- seq' ) "role-token" filter-by-keyword ;
+: filter-saddle-keyword ( seq -- seq' ) "saddle" filter-by-keyword ;
+: filter-scavenge-keyword ( seq -- seq' ) "scavenge" filter-by-keyword ;
+: filter-scry-keyword ( seq -- seq' ) "scry" filter-by-keyword ;
+: filter-seek-keyword ( seq -- seq' ) "seek" filter-by-keyword ;
+: filter-shadow-keyword ( seq -- seq' ) "shadow" filter-by-keyword ;
+: filter-shroud-keyword ( seq -- seq' ) "shroud" filter-by-keyword ;
+: filter-skulk-keyword ( seq -- seq' ) "skulk" filter-by-keyword ;
+: filter-soulbond-keyword ( seq -- seq' ) "soulbond" filter-by-keyword ;
+: filter-soulshift-keyword ( seq -- seq' ) "soulshift" filter-by-keyword ;
+: filter-specialize-keyword ( seq -- seq' ) "specialize" filter-by-keyword ;
+: filter-spectacle-keyword ( seq -- seq' ) "spectacle" filter-by-keyword ;
+: filter-spell-mastery-keyword ( seq -- seq' ) "spell-mastery" filter-by-keyword ;
+: filter-splice-keyword ( seq -- seq' ) "splice" filter-by-keyword ;
+: filter-split-second-keyword ( seq -- seq' ) "split-second" filter-by-keyword ;
+: filter-spree-keyword ( seq -- seq' ) "spree" filter-by-keyword ;
+: filter-squad-keyword ( seq -- seq' ) "squad" filter-by-keyword ;
+: filter-storm-keyword ( seq -- seq' ) "storm" filter-by-keyword ;
+: filter-strive-keyword ( seq -- seq' ) "strive" filter-by-keyword ;
+: filter-sunburst-keyword ( seq -- seq' ) "sunburst" filter-by-keyword ;
+: filter-support-keyword ( seq -- seq' ) "support" filter-by-keyword ;
+: filter-surge-keyword ( seq -- seq' ) "surge" filter-by-keyword ;
+: filter-surveil-keyword ( seq -- seq' ) "surveil" filter-by-keyword ;
+: filter-suspect-keyword ( seq -- seq' ) "suspect" filter-by-keyword ;
+: filter-suspend-keyword ( seq -- seq' ) "suspend" filter-by-keyword ;
+: filter-swampcycling-keyword ( seq -- seq' ) "swampcycling" filter-by-keyword ;
+: filter-swampwalk-keyword ( seq -- seq' ) "swampwalk" filter-by-keyword ;
+: filter-threshold-keyword ( seq -- seq' ) "threshold" filter-by-keyword ;
+: filter-time-travel-keyword ( seq -- seq' ) "time-travel" filter-by-keyword ;
+: filter-totem-armor-keyword ( seq -- seq' ) "totem-armor" filter-by-keyword ;
+: filter-toxic-keyword ( seq -- seq' ) "toxic" filter-by-keyword ;
+: filter-training-keyword ( seq -- seq' ) "training" filter-by-keyword ;
+: filter-trample-keyword ( seq -- seq' ) "trample" filter-by-keyword ;
+: filter-transform-keyword ( seq -- seq' ) "transform" filter-by-keyword ;
+: filter-transmute-keyword ( seq -- seq' ) "transmute" filter-by-keyword ;
+: filter-treasure-keyword ( seq -- seq' ) "treasure" filter-by-keyword ;
+: filter-tribute-keyword ( seq -- seq' ) "tribute" filter-by-keyword ;
+: filter-typecycling-keyword ( seq -- seq' ) "typecycling" filter-by-keyword ;
+: filter-undergrowth-keyword ( seq -- seq' ) "undergrowth" filter-by-keyword ;
+: filter-undying-keyword ( seq -- seq' ) "undying" filter-by-keyword ;
+: filter-unearth-keyword ( seq -- seq' ) "unearth" filter-by-keyword ;
+: filter-unleash-keyword ( seq -- seq' ) "unleash" filter-by-keyword ;
+: filter-vanishing-keyword ( seq -- seq' ) "vanishing" filter-by-keyword ;
+: filter-venture-into-the-dungeon-keyword ( seq -- seq' ) "venture-into-the-dungeon" filter-by-keyword ;
+: filter-vigilance-keyword ( seq -- seq' ) "vigilance" filter-by-keyword ;
+: filter-ward-keyword ( seq -- seq' ) "ward" filter-by-keyword ;
+: filter-will-of-the-council-keyword ( seq -- seq' ) "will-of-the-council" filter-by-keyword ;
+: filter-wither-keyword ( seq -- seq' ) "wither" filter-by-keyword ;
+
: power>n ( string -- n/f )
[ "*" = ] [ drop -1 ] [ string>number ] ?if ;
: mtg>= ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ >= ] } 2&& ;
: mtg= ( string/n/f n -- seq' ) [ power>n ] dip { [ and ] [ = ] } 2&& ;
-: filter-power=* ( seq -- seq' ) [ "power" of "*" = ] filter-card-faces ;
-: filter-toughness=* ( seq -- seq' ) [ "toughness" of "*" = ] filter-card-faces ;
+: filter-power=* ( seq -- seq' ) [ "power" of "*" = ] filter-card-faces-main-card ;
+: filter-toughness=* ( seq -- seq' ) [ "toughness" of "*" = ] filter-card-faces-main-card ;
-: filter-power= ( seq n -- seq' ) '[ "power" of _ mtg= ] filter-card-faces ;
-: filter-power< ( seq n -- seq' ) '[ "power" of _ mtg< ] filter-card-faces ;
-: filter-power> ( seq n -- seq' ) '[ "power" of _ mtg> ] filter-card-faces ;
-: filter-power<= ( seq n -- seq' ) '[ "power" of _ mtg<= ] filter-card-faces ;
-: filter-power>= ( seq n -- seq' ) '[ "power" of _ mtg>= ] filter-card-faces ;
+: filter-power= ( seq n -- seq' ) '[ "power" of _ mtg= ] filter-card-faces-main-card ;
+: filter-power< ( seq n -- seq' ) '[ "power" of _ mtg< ] filter-card-faces-main-card ;
+: filter-power> ( seq n -- seq' ) '[ "power" of _ mtg> ] filter-card-faces-main-card ;
+: filter-power<= ( seq n -- seq' ) '[ "power" of _ mtg<= ] filter-card-faces-main-card ;
+: filter-power>= ( seq n -- seq' ) '[ "power" of _ mtg>= ] filter-card-faces-main-card ;
-: filter-toughness= ( seq n -- seq' ) '[ "toughness" of _ mtg= ] filter-card-faces ;
-: filter-toughness< ( seq n -- seq' ) '[ "toughness" of _ mtg< ] filter-card-faces ;
-: filter-toughness> ( seq n -- seq' ) '[ "toughness" of _ mtg> ] filter-card-faces ;
-: filter-toughness<= ( seq n -- seq' ) '[ "toughness" of _ mtg<= ] filter-card-faces ;
-: filter-toughness>= ( seq n -- seq' ) '[ "toughness" of _ mtg>= ] filter-card-faces ;
+: filter-toughness= ( seq n -- seq' ) '[ "toughness" of _ mtg= ] filter-card-faces-main-card ;
+: filter-toughness< ( seq n -- seq' ) '[ "toughness" of _ mtg< ] filter-card-faces-main-card ;
+: filter-toughness> ( seq n -- seq' ) '[ "toughness" of _ mtg> ] filter-card-faces-main-card ;
+: filter-toughness<= ( seq n -- seq' ) '[ "toughness" of _ mtg<= ] filter-card-faces-main-card ;
+: filter-toughness>= ( seq n -- seq' ) '[ "toughness" of _ mtg>= ] filter-card-faces-main-card ;
: map-props ( seq props -- seq' ) '[ _ intersect-keys ] map ;
card>image-uris download-normal-images images. ;
: normal-cards. ( seq -- ) [ normal-card. ] each ;
+: standard-cards. ( seq -- ) filter-standard normal-cards. ;
+: historic-cards. ( seq -- ) filter-historic normal-cards. ;
+: modern-cards. ( seq -- ) filter-modern normal-cards. ;
! rarity is only on main card `json` (if there are two faces)
: card-face-summary. ( json seq -- )
: cards-by-set-colors. ( seq -- ) sort-by-set-colors normal-cards. ;
-: cards-by-name ( seq name -- seq' ) filter-by-name-itext sort-by-release ;
-: cards-by-name. ( seq name -- ) cards-by-name [ "name" of ] sort-by normal-cards. ;
+: cards-by-name ( name -- seq' ) [ mtg-oracle-cards ] dip filter-by-name-itext sort-by-release ;
+: card-by-name ( name -- card )
+ [ mtg-oracle-cards ] dip >lower
+ [ '[ "name" of >lower _ = ] filter ?first ]
+ [ '[ "name" of >lower _ head? ] filter ?first ] 2bi or ;
+: cards-by-name. ( name -- ) cards-by-name normal-cards. ;
+: standard-cards-by-name. ( name -- ) cards-by-name standard-cards. ;
+: historic-cards-by-name. ( name -- ) cards-by-name historic-cards. ;
+: modern-cards-by-name. ( name -- ) cards-by-name modern-cards. ;
+
+: paren-set? ( string -- ? )
+ { [ "(" head? ] [ ")" tail? ] [ length 5 = ] } 1&& ;
+
+: remove-set-and-num ( string -- string' )
+ " " split
+ dup 2 ?lastn
+ [ paren-set? ] [ string>number ] bi* and [
+ 2 head*
+ ] when " " join ;
+
+: assoc>cards ( assoc -- seq )
+ [ card-by-name <array> ] { } assoc>map concat ;
+
+: parse-mtga-card-line ( string -- array )
+ [ blank? ] trim
+ " " split1
+ [ string>number ]
+ [ remove-set-and-num card-by-name ] bi* <array> ;
+
+: parse-mtga-cards ( strings -- seq )
+ [ parse-mtga-card-line ] map concat ;
+
+TUPLE: mtga-deck name deck sideboard section ;
+
+: <mtga-deck> ( -- mtga-deck )
+ mtga-deck new "Deck" >>section ;
+
+: <moxfield-deck> ( name deck sideboard -- deck )
+ mtga-deck new
+ swap >>sideboard
+ swap >>deck
+ swap >>name ;
+
+ERROR: unknown-mtga-deck-section section ;
+: parse-mtga-deck ( string -- mtga-deck )
+ string-lines [ [ blank? ] trim ] map harvest
+ { "About" "Deck" "Sideboard" } split*
+ [ <mtga-deck> ] dip
+ [
+ dup { "About" "Deck" "Sideboard" } intersects? [
+ first >>section
+ ] [
+ over section>> {
+ { "About" [ first "Name " ?head drop [ blank? ] trim >>name ] }
+ { "Deck" [ parse-mtga-cards >>deck ] }
+ { "Sideboard" [ parse-mtga-cards >>sideboard ] }
+ [
+ unknown-mtga-deck-section
+ ]
+ } case
+ ] if
+ ] each ;
+
+: sort-by-deck-order ( seq -- seq' )
+ [ "Land" any-type? not ] partition
+ [ sort-by-set-colors ] bi@ append ;
+
+: cards. ( seq -- ) sort-by-deck-order normal-cards. ;
+
+: sideboard. ( seq -- )
+ sideboard>> [ "Sideboard" print sort-by-deck-order normal-cards. ] when* ;
+
+GENERIC: deck. ( obj -- )
+
+M: string deck. parse-mtga-deck deck. ;
+
+M: mtga-deck deck. [ name>> ?print ] [ deck>> cards. ] bi ;
+
+M: sequence deck. cards. ;
+
+GENERIC: deck-and-sideboard. ( mtga-deck -- )
+
+M: string deck-and-sideboard. parse-mtga-deck deck-and-sideboard. ;
+
+M: mtga-deck deck-and-sideboard. [ deck. ] [ sideboard. ] bi ;
+
+M: sequence deck-and-sideboard. deck. ;
: filter-mtg-cheat-sheet ( seq -- seq' )
[
{
[ filter-instant ]
- [ filter-flash ]
- [ filter-cycling ]
- [ filter-disguise ]
- [ filter-madness ]
+ [ filter-flash-keyword ]
+ [ filter-cycling-keyword ]
+ [ filter-disguise-keyword ]
+ [ filter-madness-keyword ]
} cleave
] { } append-outputs-as sort-by-colors ;
: mtg-cheat-sheet. ( seq -- ) filter-mtg-cheat-sheet normal-cards. ;
: mtg-cheat-sheet-text. ( seq -- ) filter-mtg-cheat-sheet card-summaries. ;
+
+MEMO: get-moxfield-user ( username -- json )
+ "https://api2.moxfield.com/v2/users/%s/decks?pageNumber=1&pageSize=100" sprintf http-get-json nip ;
+
+MEMO: get-moxfield-deck ( public-id -- json )
+ "https://api2.moxfield.com/v3/decks/all/" prepend http-get-json nip ;
+
+: moxfield-board>cards ( board -- seq )
+ "cards" of values [
+ [ "quantity" of ] [ "card" of "name" of ] bi 2array
+ ] map assoc>cards ;
+
+: json>moxfield-deck ( json -- mtga-deck )
+ [ "name" of ]
+ [
+ "boards" of
+ [ "mainboard" of moxfield-board>cards ]
+ [ "sideboard" of moxfield-board>cards ] bi
+ ] bi
+ <moxfield-deck> ;
+
+: moxfield-decks-for-username ( username -- json )
+ get-moxfield-user "data" of ;
+
+: moxfield-random-deck-for-username ( username -- json )
+ moxfield-decks-for-username
+ random "publicId" of get-moxfield-deck
+ json>moxfield-deck ;
+
+: moxfield-latest-deck-for-username ( username -- json )
+ get-moxfield-user
+ "data" of ?first "publicId" of get-moxfield-deck
+ json>moxfield-deck ;
+
+: moxfield-latest-deck-for-username. ( username -- )
+ moxfield-latest-deck-for-username deck. ;
+
+: moxfield-latest-deck-and-sideboard-for-username. ( username -- )
+ moxfield-latest-deck-for-username deck-and-sideboard. ;