User:O11c/asm
This is a spec for some internal stuff that's better than the current stuff.
It is not expected that ordinary people will ever need to do this, but it *is* more powerful than the existing language.
I've given up on the lambda stack method - despite its purity, it simply consumes too much memory without specialized tools.
A script consists of a sequence of one-byte opcode and one-byte data, as well as ancillary tables used to assign meaning to the data byte.
There are tables for: integer constant, string constant, label or user function, builtin function, and variable name
These tables are per-function, so there should be no problem hitting the limit of 256 of each.
At runtime, there are 3 stacks: integer, string, and location (label/user function). Should there be another one for opaque pointers, such as items?
Note that the builtin functions obviously do NOT correspond directly to the ones in the scripting page.
Opcodes
op | arg | description |
---|---|---|
add | N ≥ 2 | pop top N integers, push their sum |
cat | N ≥ 2 | pop top N strings, push their concatenation. |
sub | N ≥ 2 | pop top N-1 integers, pop another, subtract the others from it, push the result |
mul | N ≥ 2 | pop top N integers, push their product |
div | N ≥ 2 | pop top N-1 integers, pop another, divide it by all the others |
mod | 0 | pop top two integers, push their remainder |
band | N ≥ 2 | pop top N integers, push their bitwise-and. |
bor | N ≥ 2 | pop top N integers, push their bitwise-or |
bxor | N ≥ 2 | pop top N integers, bitwise-xor repeatedly, push the final result. |
neg | 0 | pop an integer, push its negation |
inv | 0 | pop an integer, push its bitwise complement |
not | 0 | pop an integer, push its boolean complement |
shr | 0 or N | pop one or two integers, push unsigned right shift |
shl | 0 or N | pop one or two integers, push left shift. |
lt,le,gt,ge,eq,ne | 0 | (still squishy) pop two integers, push boolean comparison result
(still need a string version) (should I completely remove this and instead add more jumps?) |
s2i | 0 | pop top string, push converted integer |
i2s | 0 | pop top integer, push converted string |
dupi | 0 | pop top integer, push it twice
(maybe use N to take an element that's not at the top?) |
dups | 0 | pop top string, push it twice |
popi | 0 | pop top integer, discard |
pops | 0 | pop top string, discard |
jump | N | unconditional jump to "location" table entry |
jz | N | pop top integer; if zero, jump to location |
jnz | N | pop top integer; if not zero, jump to location
(maybe add jl/jg or jp/jn ?) |
bcall | function | call a entry N from the "builtin function" table. The function must determine its own arity.
Most functions take a fixed number of arguments - default arguments are added by the compiler. A few take a variable number, such as menu, and have their own way of indicating this - such as pushing the number of arguments onto the integer stack before the call. |
ret | 0 | Computed jump to top location on location stack. |
op | arg | description |
li | integer | push an integer from the integer constant table |
ls | string | push a string from the string constant table |
ll | location | push a location from the label/user function table |
lparam | name | load a pc special parameter, as given in the table |
sparam | name | store a pc special parameter, as given in the table |
lpgr | name | load a persistent player variable |
spgr | name | store a persistent player variable |
lpr | name | load a temporary player integer variable |
spr | name | store a temporary player integer variable |
lprs | name | load a temporary player string variable |
sprs | name | store a temporary player string variable |
lpa1r | name | load an account variable (#) |
spa1r | name | store an account variable (#) |
lpa2r | name | load an account variable (##) |
spa2r | name | store an account variable (##) |
lgr | name | load a global variable (whether persistent or temporary - may change) |
sgr | name | store a global variable (whether persistent or temporary - may change) |
lgrs | name | load a global string variable (whether persistent or temporary - may change) |
sgrs | name | store a global string variable (whether persistent or temporary - may change) |
Example
function DailyQuestPoints # lb gettimetick li 2 bcall gettimetick # 1 li 86400 sub spr @dq_earliest lpgr DailyQuestTime lpr @dq_earliest lt jnz .L0 lpr @dq_earliest spgr DailyQuestTime label .L0 # lb gettimetick li 2 bcall gettimetick # 1 lpgr DailyQuestTime sub lpgr BaseLevel mul li 86400 div spr @dq_increments lpgr DailyQuestTime lpr @dq_increments li 86400 mul lparam BaseLevel div add spgr DailyQuestTime lpgr DailyQuestPoints lparam BaseLevel ge jnz L_Bonus lpgr DailyQuestPoints lpr @dq_increments add spgr DailyQuestPoints lpgr DailyQuestPoints lparam BaseLevel gt jz .L1 lparam BaseLevel spgr DailyQuestPoints label .L1 label L_Bonus lpgr DailyQuestPoints lpgr DailyQuestBonus add spgr DailyQuestPoints li 0 spgr DailyQuestBonus ret function DailyQuest ucall DailyQuestPoints lparam BaseLevel lpr @dq_level lt jnz L_Low_Level lpgr DailyQuestPoints lpr @dq_cost lt jnz L_Not_Enough_Points # lb mes ls "\"If you bring me " lpr @dq_count i2s ls " " lprs @dq_friendly_name$ ls ", I will give you a reward.\"" cat 5 # or maybe drop cat and instead: li 5 bcall mes # 1 # lb menu ls "I have what you want." ll L_Trade ls "Ok, I'll get to work." ll .L0 ls "Nah, I'm not going to help you." ll .L0 li 3 bcall menu .L0 li 1 spr @dq_return j L_Exit label L_Trade # lb countitem lprs @dq_name$ bcall countitem # 1 lpr @dq_count lt jnz L_Not_Enough # lb delitem lprs @dq_name$ lpr @dq_count bcall delitem # 2 lparam Zeny lpr @dq_money add sparam Zeny lb getexp lpr @dq_exp li 0 bcall getexp # 2 lpgr DailyQuestPoints lpr @dq_cost sub spr DailyQuestPoints lpr @dq_handle_return jnz L_Exit_Good # lb mes ls "\"Thank you!\"" # maybe: li 1 # or bcall mes1 ? bcall mes # ucallsub S_SayPhrase # lb mes ls "" # li 1 bcall mes # 1 # lb mes ls "[" lpr @dq_money i2s ls " money]" cat 3 # li 1 bcall mes # 1 # lb mes ls "[" lpr @dq_exp i2s ls " experience points]" cat 3 # li 3 bcall mes # 1 label L_Exit_Good li 4 spr @dq_return j L_Exit label L_Not_Enough lpr @dq_handle_return jnz .L2 lb mes ls "\"I said " lpr @dq_count i2s ls " " lprs @dq_friendly_name$ ls "; you should learn to count.\"" cat 5 bcall mes 1 label .L2 li 3 spr @dq_return j L_Exit label L_Low_Level lpr @dq_handle_return jnz .L3 # lb mes ls "\"Hey, you should go kill some things to get stronger first.\"" # li 1 bcall mes # 1 label .L3 li 0 spr @dq_return j L_Exit label L_Not_Enough_Points # lb mes ls "\"You look exhausted, maybe you should rest a bit.\"" # li 1 bcall mes # 1 li 2 spr @dq_return j L_Exit label L_Exit li 0 spr @dq_handle_return ret function DailyQuest.S_SayPhrase lpr @dq_handle_return jz .L4 ret label .L4 lpgr DailyQuestPoints lpr @dq_cost lt jnz L_Exhausted lpgr DailyQuestPoints lparam BaseLevel gt jnz L_Over lpgr DailyQuestPoints lparam BaseLevel li 9 mul li 10 div gt jnz L_P90 lpgr DailyQuestPoints lparam BaseLevel li 7 mul li 10 div gt jnz L_P70 lpgr DailyQuestPoints lparam BaseLevel li 5 mul li 10 div gt jnz L_P50 j L_Low label L_Over # lb mes ls "\"Woah, you're bursting with power.\"" # li 1 bcall mes # 1 ret label L_P90 # lb mes ls "\"You're in a very good shape.\"" # li 1 bcall mes # 1 ret label L_P70 # lb mes ls "\"You don't seem very exhausted by my tasks.\"" # li 1 bcall mes # 1 ret label L_P50 # lb mes ls "\"Aren't you getting weary yet?\"" # li 1 bcall mes # 1 ret label L_Low # lb mes ls "\"You look a little tired.\"" # li 1 bcall mes # 1 ret label L_Exhausted # lb mes ls "\"You look exhausted, maybe you should rest a bit.\"" # li 1 bcall mes # 1 ret