diff --git a/docs/nqp-opcode.txt b/docs/nqp-opcode.txt new file mode 100644 index 0000000000..794e499191 --- /dev/null +++ b/docs/nqp-opcode.txt @@ -0,0 +1,117 @@ +General notes: + * Not really attempting to be exhaustive just yet -- only want + to get the most significant ones done in this pass. + * Avoid "set" -- use "bind" or "assign" if that's what we're really doing. + Or, if we want to keep "set", make sure it always means "bind". + * A suffix of _i, _n, or _s generally denotes an int/num/str version + of the opcode. Stick to lowercase letters here... + * ... except that bigints might be a _I suffix :-) + * Perhaps reserve _b for a boolean native type + * Might want to reserve _a and _h for "native" list/hash versions of opcodes + * Might want to reserve _o for object/instance versions of opcodes + * aim for consistency with underscores and the like + (i.e., unlike parrot, don't mix "get_foo" and "getbar") + * Use the p6-defined terms where possible (e.g., "elems" and "chars" + instead of "elements" and "length") + * Generally return an appropriate argument instead of void + * Items marked (?) mean that the opcode itself might be questionable + for inclusion in the set. + + + control opcodes: + nqp::if(test, iftrue, iffalse) + nqp::unless(test, iffalse, iftrue) + nqp::while(test, body) + nqp::loop(init, test, next, body) + nqp::throw pir::throw__P + nqp::die pir::die + nqp::exit pir::exit__vi + nqp::sleep pir::sleep__vn + (need ops to fetch caller, outer, context, etc?) + + I/O: + nqp::print pir::print + nqp::say pir::say + + arithmetic: + nqp::add_i pir::add__Iii + nqp::add_n pir::add__Nnn + ...etc... + + string: + nqp::concat pir::concat__Sss + nqp::join pir::join__SsP + nqp::substr pir::substr__Ssii + nqp::downcase pir::downcase__Ss + nqp::upcase pir::upcase__Ss + nqp::index pir::index__Issi + nqp::chr pir::chr__Si + nqp::ord pir::ord__Si + nqp::chars pir::length__Is + nqp::split pir::split__Pss + nqp::iscclass pir::iscclass__Isii (?) + nqp::findcclass pir::find_cclass__Isii (?) + nqp::findnotcclass pir::find_not_cclass__Isii (?) + + relational: + nqp::iseq_i pir::iseq__Iii + nqp::iseq_n pir::iseq__Inn + nqp::iseq_s pir::iseq__Iss + ...etc... + + aggregate: + nqp::pop pir::pop__PP + nqp::push pir::push__0PP + nqp::shift pir::shift__PP + nqp::unshift pir::unshift__0PP + nqp::splice pir::splice__0PPii + nqp::bindpos pir::set__1QiP + nqp::bindkey pir::set__1QsP + nqp::atpos pir::set__PQi + nqp::atkey pir::set__PQs + nqp::existpos pir::exists__IQi + nqp::existkey pir::exists__IQs + nqp::elems pir::elements__IP + + object: + nqp::bindattribute pir::setattribute__0PPsP + nqp::getattribute pir::getattribute__PPsP + nqp::findmethod pir::find_method__PPs (?) + nqp::null pir::null__P + nqp::isnull pir::isnull__IP + nqp::defined pir::defined__IP + nqp::?? pir::istrue__IP + nqp::clone pir::clone__PP + nqp::typecheck or ::isa pir::type_check__IPP + or ::istype + nqp::?? pir::repr_instance_of__PP (?) + nqp::unbox_i pir::repr_unbox_int__IP + nqp::unbox_n pir::repr_unbox_num__NP + nqp::unbox_s pir::repr_unbox_str__SP + nqp::box_i pir::repr_box_int__PPi + nqp::box_n pir::repr_box_num__PPn + nqp::box_s pir::repr_box_str__PPs + nqp::what or nqp::getwhat (?) + nqp::how or nqp::gethow (?) + nqp::?? pir::can__IPs (?) + + Rakudo-specific: + nqp::p6box_i pir::perl6_box_int__Pi + nqp::p6box_n pir::perl6_box_num__Pn + nqp::p6box_s pir::perl6_box_str__Ps + nqp::p6iscontainer pir::is_container__IP + nqp::?? (::p6capturepos?) pir::perl6_current_args__P + nqp::p6assign or ::p6store pir::perl6_container_store__0PP + nqp::p6bool or ::p6box_b pir::perl6_booleanize__PI + nqp::p6list_a pir::perl6_list_from_rpa__PPPP + nqp::p6iter_a pir::perl6_iter_from_rpa__PPP + + Lexical and variable ops (?) + nqp::bindlex pir::store_lex__1sP + nqp::getlex or ::lex pir::find_lex__Ps + nqp::getdynlex or ::dynlex pir::find_dynamic_lex__Ps + nqp::binddynlex pir::store_dynamic_lex__Ps + + STable ops (?) + ...??... + diff --git a/src/NQP/Actions.pm b/src/NQP/Actions.pm index c6a38d42d3..4ef09dcdfc 100644 --- a/src/NQP/Actions.pm +++ b/src/NQP/Actions.pm @@ -1162,6 +1162,17 @@ class NQP::Actions is HLL::Actions { make PAST::Val.new( :value(~$), :returns, :node($/) ); } + method term:sym($/) { + my $op := ~$; + my $args := $ ?? $[0].ast.list !! []; + my $past := PAST::Node.'map_node'(|$args, :map, :op($op), + :node($/)); + + pir::defined($past) || + $/.CURSOR.panic("Unrecognized nqp:: opcode 'nqp::$op'"); + make $past; + } + method term:sym($/) { make PAST::Op.new( :pirop('multi_dispatch_over_lexical_candidates P') diff --git a/src/NQP/Grammar.pm b/src/NQP/Grammar.pm index a2701b7f4d..7e6cca757e 100644 --- a/src/NQP/Grammar.pm +++ b/src/NQP/Grammar.pm @@ -545,6 +545,10 @@ grammar NQP::Grammar is HLL::Grammar { 'pir::const::' $=[\w+] } + token term:sym { + 'nqp::' $=[\w+] ? + } + token term:sym { '{*}' [ || <.panic: '{*} may only appear in proto'> ] diff --git a/src/PAST/NQP.pir b/src/PAST/NQP.pir index 00b892acd6..109e96e3cd 100644 --- a/src/PAST/NQP.pir +++ b/src/PAST/NQP.pir @@ -7,6 +7,10 @@ p6meta = new 'P6metaclass' base = get_hll_global ['PAST'], 'Node' p6meta.'new_class'('PAST::Want', 'parent'=>base) + + # add the nqp:: opcode map + .const 'Sub' nqpmap = 'nqpmap' + base.'map_add'('nqp', nqpmap) .end @@ -58,3 +62,227 @@ Select a single past child based on rtype. .end +=item map_add(mapid, [hash]) + +Add entries from any C arguments to the pseudo-opcode table +with identifier C. Slurpy named arguments are also added to +the hash (after all hash arguments have been processed). The +resulting pseudo-opcode table hash for C is returned. + +The hash entries being added consist of pseudo-opcode (key, spec) +pairs describing the pseudo-opcode name and node type to be +constructed. If C is a plain string, it's assumed to be +a Parrot C opcode name. (A future version may allow +strings of the form 'mapid::opcode' to reference an entry of +another map table.) + +If C is a hash, it contains +attributes used to build a PAST node. For example, the following +pseudo-code would cause the 'ifelse' pseudo-op in the 'nqp' +mapping space to to translate to a PAST::Op node with 'pasttype' +set to 'if': + + $x := hash( 'WHAT' => PAST::Op, 'pasttype'=>'if' ); + PAST::Node.map_add('nqp', 'ifelse' => $x ); + +The 'WHAT' entry of such a hash defaults to PAST::Op if not specified. + +=item map_node([args], 'map'=>mapid, 'op'=>opname, [options]) + +Create a node based on the mapping specification for C +from the pseudo-op mapping table given by C. The +C and C are combined with the mapping table +entry to produce the node to be returned. + +=cut + +.namespace ['PAST';'Node'] +.sub 'map_add' :method + .param string mapid + .param pmc args :slurpy + .param pmc opt_hash :slurpy :named + + .local pmc maptables, maphash + maptables = get_global '%!maptables' + unless null maptables goto have_maptables + maptables = new ['Hash'] + set_global '%!maptables', maptables + have_maptables: + + maphash = maptables[mapid] + unless null maphash goto have_maphash + maphash = new ['Hash'] + maptables[mapid] = maphash + have_maphash: + + args_loop: + unless args goto args_done + .local pmc hash_it + $P0 = shift args + hash_it = iter $P0 + args_merge_loop: + unless hash_it goto args_loop + $P0 = shift hash_it + $S1 = $P0.'key'() + $P1 = $P0.'value'() + maphash[$S1] = $P1 + goto args_merge_loop + args_done: + + hash_it = iter opt_hash + opts_loop: + unless hash_it goto opts_done + $P0 = shift hash_it + $S1 = $P0.'key'() + $P1 = $P0.'value'() + maphash[$S1] = $P1 + goto opts_loop + opts_done: + + .return (maphash) +.end + +.namespace ['PAST';'Node'] +.sub 'map_node' :method + .param pmc args :slurpy + .param string mapid :named('map') + .param string opcode :named('op') + .param pmc options :slurpy :named + + .local pmc maphash, ophash, past + $P0 = get_global '%!maptables' + maphash = $P0[mapid] + if null maphash goto fail + ophash = maphash[opcode] + if null ophash goto fail + + $I0 = isa ophash, ['Hash'] + if $I0 goto have_ophash + # handle case where the opspec is a simple (pirop) string + $P0 = get_hll_global ['PAST'], 'Op' + past = $P0.'new'(args :flat, 'pirop'=>ophash, options :named :flat) + .return (past) + + have_ophash: + # merge the ophash entries into options + .local pmc ophash_it, key, value + ophash_it = iter ophash + ophash_loop: + unless ophash_it goto ophash_done + $P0 = shift ophash_it + $S1 = $P0.'key'() + $P1 = $P0.'value'() + # don't overwrite an existing option + $I0 = exists options[$S1] + if $I0 goto ophash_loop + options[$S1] = $P1 + goto ophash_loop + ophash_done: + + # determine the 'what' value to use + .local pmc what + what = get_hll_global ['PAST'], 'Op' + $I0 = exists options['WHAT'] + unless $I0 goto have_what + what = options['WHAT'] + null $P0 + options['WHAT'] = $P0 + have_what: + + past = what.'new'(args :flat, options :named :flat) + .return (past) + + fail: + $P0 = new ['Undef'] + .return ($P0) +.end + + +.sub 'nqpmap' :immediate :subid('nqpmap') + .local pmc maphash + maphash = new ['Hash'] + + # I/O opcodes + maphash['print'] = 'print' + maphash['say'] = 'say' + + # arithmetic opcodes + maphash['add_i'] = 'add__Iii' + maphash['add_n'] = 'add__Nnn' + maphash['sub_i'] = 'sub__Iii' + maphash['sub_n'] = 'sub__Nnn' + maphash['mul_i'] = 'mul__Iii' + maphash['mul_n'] = 'mul__Nnn' + maphash['div_i'] = 'div__Iii' + maphash['div_n'] = 'div__Nnn' + maphash['mod_i'] = 'mod__Iii' + + # string opcodes + maphash['chars'] = 'length__Is' + maphash['concat'] = 'concat' # allow either P or S form + maphash['join'] = 'join__SsP' + maphash['split'] = 'split__Pss' + maphash['index'] = 'index__Issi' + maphash['chr'] = 'chr__Si' + maphash['ord'] = 'ord__Is' + maphash['downcase'] = 'downcase__Ss' + maphash['upcase'] = 'upcase__Ss' + + # relational opcodes + maphash['iseq_i'] = 'iseq__Iii' + maphash['isne_i'] = 'isne__Iii' + maphash['islt_i'] = 'islt__Iii' + maphash['isle_i'] = 'isle__Iii' + maphash['isgt_i'] = 'isgt__Iii' + maphash['isge_i'] = 'isge__Iii' + + maphash['iseq_n'] = 'iseq__Inn' + maphash['isne_n'] = 'isne__Inn' + maphash['islt_n'] = 'islt__Inn' + maphash['isle_n'] = 'isle__Inn' + maphash['isgt_n'] = 'isgt__Inn' + maphash['isge_n'] = 'isge__Inn' + + maphash['iseq_s'] = 'iseq__Iss' + maphash['isne_s'] = 'isne__Iss' + maphash['islt_s'] = 'islt__Iss' + maphash['isle_s'] = 'isle__Iss' + maphash['isgt_s'] = 'isgt__Iss' + maphash['isge_s'] = 'isge__Iss' + + # aggregate opcodes + maphash['elems'] = 'elements__IP' + maphash['push'] = 'push__0PP' + maphash['pop'] = 'pop__PP' + maphash['shift'] = 'shift__PP' + maphash['unshift'] = 'unshift__0PP' + maphash['splice'] = 'splice__0PPii' + maphash['atpos'] = 'set__PQi' + maphash['bindpos'] = 'set__1QiP' + maphash['existspos'] = 'exists__IQi' + maphash['deletepos'] = 'delete__0Qi' + maphash['atkey'] = 'set__PQs' + maphash['bindkey'] = 'set__1QsP' + maphash['existskey'] = 'exists__IQs' + maphash['deletekey'] = 'delete__0Qs' + + # object opcodes + maphash['unbox_i'] = 'repr_unbox_int__IP' + maphash['unbox_n'] = 'repr_unbox_num__NP' + maphash['unbox_s'] = 'repr_unbox_str__SP' + + # control opcodes + $P0 = new ['Hash'] + $P0['pasttype'] = 'if' + maphash['if'] = $P0 + + $P0 = new ['Hash'] + $P0['pasttype'] = 'unless' + maphash['unless'] = $P0 + + $P0 = new ['Hash'] + $P0['pasttype'] = 'while' + maphash['while'] = $P0 + + .return (maphash) +.end diff --git a/t/nqp/59-nqpop.t b/t/nqp/59-nqpop.t new file mode 100644 index 0000000000..8581265001 --- /dev/null +++ b/t/nqp/59-nqpop.t @@ -0,0 +1,43 @@ +#! nqp + +# Test nqp::op pseudo-functions. + +plan(25); + + +ok( nqp::add_i(5,2) == 7, 'nqp::add_i'); +ok( nqp::sub_i(5,2) == 3, 'nqp::sub_i'); +ok( nqp::mul_i(5,2) == 10, 'nqp::mul_i'); +ok( nqp::div_i(5,2) == 2, 'nqp::div_i'); + +ok( nqp::add_n(5,2) == 7, 'nqp::add_n'); +ok( nqp::sub_n(5,2) == 3, 'nqp::sub_n'); +ok( nqp::mul_n(5,2) == 10, 'nqp::mul_n'); +ok( nqp::div_n(5,2) == 2.5e0, 'nqp::div_n'); + +ok( nqp::chars('hello') == 5, 'nqp::chars'); +ok( nqp::concat('hello ', 'world') eq 'hello world', 'nqp::concat'); +ok( nqp::join(' ', ('abc', 'def', 'ghi')) eq 'abc def ghi', 'nqp::join'); +ok( nqp::index('rakudo', 'do') == 4, 'nqp::index found'); +ok( nqp::index('rakudo', 'dont') == -1, 'nqp::index not found'); +ok( nqp::chr(120) eq 'x', 'nqp::chr'); +ok( nqp::ord('xyz') eq 120, 'nqp::ord'); +ok( nqp::downcase('Hello World') eq 'hello world', 'nqp::downcase'); +ok( nqp::upcase("Don't Panic") eq "DON'T PANIC", 'nqp::upcase'); + +ok( nqp::iseq_i(2, 2) == 1, 'nqp::iseq_i'); + +my @array := ['zero', 'one', 'two']; +ok( nqp::elems(@array) == 3, 'nqp::elems'); + +ok( nqp::if(0, 'true', 'false') eq 'false', 'nqp::if(false)'); +ok( nqp::if(1, 'true', 'false') eq 'true', 'nqp::if(true)'); +ok( nqp::unless(0, 'true', 'false') eq 'true', 'nqp::unless(false)'); +ok( nqp::unless(1, 'true', 'false') eq 'false', 'nqp::unless(true)'); + +my $a := 10; + +ok( nqp::if(0, ($a++), ($a--)) == 10, 'nqp::if shortcircuit'); +ok( $a == 9, 'nqp::if shortcircuit'); + +