mirror of
https://github.com/janet-lang/janet
synced 2024-12-25 07:50:27 +00:00
547 lines
18 KiB
C
547 lines
18 KiB
C
/*
|
|
* Copyright (c) 2017 Calvin Rose
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <dst/dst.h>
|
|
#include <dst/dststl.h>
|
|
#include "compile.h"
|
|
#include "gc.h"
|
|
#include "sourcemap.h"
|
|
#include "util.h"
|
|
|
|
DstSlot dstc_quote(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
if (argn != 1) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected 1 argument");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
return dstc_cslot(argv[0]);
|
|
}
|
|
|
|
DstSlot dstc_varset(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstFopts subopts;
|
|
DstSlot ret, dest;
|
|
if (argn != 2) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected 2 arguments");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
if (!dst_checktype(argv[0], DST_SYMBOL)) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected symbol");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
dest = dstc_resolve(opts.compiler, opts.sourcemap, dst_unwrap_symbol(argv[0]));
|
|
if (!(dest.flags & DST_SLOT_MUTABLE)) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "cannot set constant");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
subopts = dstc_getindex(opts, 2);
|
|
subopts.flags = DST_FOPTS_HINT;
|
|
subopts.hint = dest;
|
|
ret = dstc_value(subopts);
|
|
dstc_copy(opts.compiler, subopts.sourcemap, dest, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Add attributes to a global def or var table */
|
|
static void handleattr(DstFopts opts, int32_t argn, const Dst *argv, DstTable *tab) {
|
|
int32_t i;
|
|
for (i = 1; i < argn - 1; i++) {
|
|
Dst attr = argv[i];
|
|
DstFopts subopts = dstc_getindex(opts, i + 1);
|
|
switch (dst_type(attr)) {
|
|
default:
|
|
dstc_cerror(opts.compiler, subopts.sourcemap, "could not add meta data to def");
|
|
return;
|
|
case DST_SYMBOL:
|
|
dst_table_put(tab, attr, dst_wrap_true());
|
|
break;
|
|
case DST_STRING:
|
|
dst_table_put(tab, dst_csymbolv("doc"), attr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DstSlot dstc_var(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstCompiler *c = opts.compiler;
|
|
DstFopts subopts;
|
|
DstSlot ret;
|
|
if (argn < 2) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected at least 2 arguments");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
if (!dst_checktype(argv[0], DST_SYMBOL)) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected symbol");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
subopts = dstc_getindex(opts, argn);
|
|
subopts.flags = opts.flags & ~DST_FOPTS_TAIL;
|
|
ret = dstc_value(subopts);
|
|
if (dst_v_last(c->scopes).flags & DST_SCOPE_TOP) {
|
|
DstCompiler *c = opts.compiler;
|
|
const Dst *sm = opts.sourcemap;
|
|
DstSlot refslot, refarrayslot;
|
|
/* Global var, generate var */
|
|
DstTable *reftab = dst_table(1);
|
|
DstArray *ref = dst_array(1);
|
|
dst_array_push(ref, dst_wrap_nil());
|
|
dst_table_put(reftab, dst_csymbolv("ref"), dst_wrap_array(ref));
|
|
handleattr(opts, argn, argv, reftab);
|
|
dst_put(opts.compiler->env, argv[0], dst_wrap_table(reftab));
|
|
refslot = dstc_cslot(dst_wrap_array(ref));
|
|
refarrayslot = refslot;
|
|
refslot.flags |= DST_SLOT_REF | DST_SLOT_NAMED | DST_SLOT_MUTABLE;
|
|
/* Generate code to set ref */
|
|
int32_t refarrayindex = dstc_preread(c, sm, 0xFF, 1, refarrayslot);
|
|
int32_t retindex = dstc_preread(c, sm, 0xFF, 2, ret);
|
|
dstc_emit(c, sm,
|
|
(retindex << 16) |
|
|
(refarrayindex << 8) |
|
|
DOP_PUT_INDEX);
|
|
dstc_postread(c, refarrayslot, refarrayindex);
|
|
dstc_postread(c, ret, retindex);
|
|
/*dstc_freeslot(c, refarrayslot);*/
|
|
ret = refslot;
|
|
} else {
|
|
/* Non root scope, bring to local slot */
|
|
if (ret.flags & DST_SLOT_NAMED ||
|
|
ret.envindex != 0 ||
|
|
ret.index < 0 ||
|
|
ret.index > 0xFF) {
|
|
/* Slot is not able to be named */
|
|
DstSlot localslot;
|
|
localslot.index = dstc_lsloti(c);
|
|
/* infer type? */
|
|
localslot.flags = DST_SLOT_NAMED | DST_SLOT_MUTABLE;
|
|
localslot.envindex = 0;
|
|
localslot.constant = dst_wrap_nil();
|
|
dstc_copy(opts.compiler, opts.sourcemap, localslot, ret);
|
|
ret = localslot;
|
|
}
|
|
dstc_nameslot(c, dst_unwrap_symbol(argv[0]), ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
DstSlot dstc_def(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstCompiler *c = opts.compiler;
|
|
const Dst *sm = opts.sourcemap;
|
|
DstFopts subopts;
|
|
DstSlot ret;
|
|
if (argn < 2) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected at least 2 arguments");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
if (!dst_checktype(argv[0], DST_SYMBOL)) {
|
|
dstc_cerror(opts.compiler, opts.sourcemap, "expected symbol");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
subopts = dstc_getindex(opts, argn);
|
|
subopts.flags &= ~DST_FOPTS_TAIL;
|
|
ret = dstc_value(subopts);
|
|
ret.flags |= DST_SLOT_NAMED;
|
|
if (dst_v_last(c->scopes).flags & DST_SCOPE_TOP) {
|
|
DstTable *tab = dst_table(2);
|
|
int32_t tableindex, valsymindex, valueindex;
|
|
DstSlot valsym = dstc_cslot(dst_csymbolv("value"));
|
|
DstSlot tabslot = dstc_cslot(dst_wrap_table(tab));
|
|
|
|
/* Add env entry to env */
|
|
handleattr(opts, argn, argv, tab);
|
|
dst_put(c->env, argv[0], dst_wrap_table(tab));
|
|
|
|
/* Put value in table when evaulated */
|
|
tableindex = dstc_preread(c, sm, 0xFF, 1, tabslot);
|
|
valsymindex = dstc_preread(c, sm, 0xFF, 2, valsym);
|
|
valueindex = dstc_preread(c, sm, 0xFF, 3, ret);
|
|
dstc_emit(c, sm,
|
|
(valueindex << 24) |
|
|
(valsymindex << 16) |
|
|
(tableindex << 8) |
|
|
DOP_PUT);
|
|
dstc_postread(c, tabslot, tableindex);
|
|
dstc_postread(c, valsym, valsymindex);
|
|
dstc_postread(c, ret, valueindex);
|
|
} else {
|
|
/* Non root scope, simple slot alias */
|
|
dstc_nameslot(c, dst_unwrap_symbol(argv[0]), ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* :condition
|
|
* ...
|
|
* jump-if-not condition :right
|
|
* :left
|
|
* ...
|
|
* jump done (only if not tail)
|
|
* :right
|
|
* ...
|
|
* :done
|
|
*/
|
|
DstSlot dstc_if(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstCompiler *c = opts.compiler;
|
|
const Dst *sm = opts.sourcemap;
|
|
int32_t labelr, labeljr, labeld, labeljd, condlocal;
|
|
DstFopts leftopts, rightopts, condopts;
|
|
DstSlot cond, left, right, target;
|
|
const int tail = opts.flags & DST_FOPTS_TAIL;
|
|
const int drop = opts.flags & DST_FOPTS_DROP;
|
|
(void) argv;
|
|
|
|
if (argn < 2 || argn > 3) {
|
|
dstc_cerror(c, sm, "expected 2 or 3 arguments to if");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
|
|
/* Get options */
|
|
condopts = dstc_getindex(opts, 1);
|
|
leftopts = dstc_getindex(opts, 2);
|
|
rightopts = dstc_getindex(opts, 3);
|
|
if (argn == 2) rightopts.sourcemap = opts.sourcemap;
|
|
if (opts.flags & DST_FOPTS_HINT) {
|
|
leftopts.flags |= DST_FOPTS_HINT;
|
|
rightopts.flags |= DST_FOPTS_HINT;
|
|
}
|
|
if (tail) {
|
|
leftopts.flags |= DST_FOPTS_TAIL;
|
|
rightopts.flags |= DST_FOPTS_TAIL;
|
|
}
|
|
if (drop) {
|
|
leftopts.flags |= DST_FOPTS_DROP;
|
|
rightopts.flags |= DST_FOPTS_DROP;
|
|
}
|
|
|
|
/* Compile condition */
|
|
cond = dstc_value(condopts);
|
|
|
|
/* Check constant condition. */
|
|
/* TODO: Use type info for more short circuits */
|
|
if ((cond.flags & DST_SLOT_CONSTANT) && !(cond.flags & DST_SLOT_REF)) {
|
|
DstFopts goodopts, badopts;
|
|
if (dst_truthy(cond.constant)) {
|
|
goodopts = leftopts;
|
|
badopts = rightopts;
|
|
} else {
|
|
goodopts = rightopts;
|
|
badopts = leftopts;
|
|
}
|
|
dstc_scope(c, 0);
|
|
target = dstc_value(goodopts);
|
|
dstc_popscope(c);
|
|
dstc_throwaway(badopts);
|
|
return target;
|
|
}
|
|
|
|
/* Set target for compilation */
|
|
target = (!drop && !tail)
|
|
? dstc_gettarget(opts)
|
|
: dstc_cslot(dst_wrap_nil());
|
|
|
|
/* Compile jump to right */
|
|
condlocal = dstc_preread(c, sm, 0xFF, 1, cond);
|
|
labeljr = dst_v_count(c->buffer);
|
|
dstc_emit(c, sm, DOP_JUMP_IF_NOT | (condlocal << 8));
|
|
dstc_postread(c, cond, condlocal);
|
|
dstc_freeslot(c, cond);
|
|
|
|
/* Condition left body */
|
|
dstc_scope(c, 0);
|
|
left = dstc_value(leftopts);
|
|
if (!drop && !tail) dstc_copy(c, sm, target, left);
|
|
dstc_popscope(c);
|
|
|
|
/* Compile jump to done */
|
|
labeljd = dst_v_count(c->buffer);
|
|
if (!tail) dstc_emit(c, sm, DOP_JUMP);
|
|
|
|
/* Compile right body */
|
|
labelr = dst_v_count(c->buffer);
|
|
dstc_scope(c, 0);
|
|
right = dstc_value(rightopts);
|
|
if (!drop && !tail) dstc_copy(c, sm, target, right);
|
|
dstc_popscope(c);
|
|
|
|
/* Write jumps - only add jump lengths if jump actually emitted */
|
|
labeld = dst_v_count(c->buffer);
|
|
c->buffer[labeljr] |= (labelr - labeljr) << 16;
|
|
if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8;
|
|
|
|
if (tail) target.flags |= DST_SLOT_RETURNED;
|
|
return target;
|
|
}
|
|
|
|
/* Compile a do form. Do forms execute their body sequentially and
|
|
* evaluate to the last expression in the body. */
|
|
DstSlot dstc_do(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
int32_t i;
|
|
DstSlot ret;
|
|
dstc_scope(opts.compiler, 0);
|
|
(void) argv;
|
|
for (i = 0; i < argn; i++) {
|
|
DstFopts subopts = dstc_getindex(opts, i + 1);
|
|
if (i != argn - 1) {
|
|
subopts.flags = DST_FOPTS_DROP;
|
|
} else if (opts.flags & DST_FOPTS_TAIL) {
|
|
subopts.flags = DST_FOPTS_TAIL;
|
|
}
|
|
ret = dstc_value(subopts);
|
|
if (i != argn - 1) {
|
|
dstc_freeslot(opts.compiler, ret);
|
|
}
|
|
}
|
|
dstc_popscope(opts.compiler);
|
|
return ret;
|
|
}
|
|
|
|
DstSlot dstc_transfer(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstCompiler *c = opts.compiler;
|
|
const Dst *sm = opts.sourcemap;
|
|
DstSlot dest, fib, val;
|
|
int32_t destindex, fibindex, valindex;
|
|
(void) argv;
|
|
if (argn > 2) {
|
|
dstc_cerror(c, sm, "expected no more than 2 arguments");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
dest = dstc_gettarget(opts);
|
|
fib = (argn > 0) ? dstc_value(dstc_getindex(opts, 1)) : dstc_cslot(dst_wrap_nil());
|
|
val = (argn > 1) ? dstc_value(dstc_getindex(opts, 2)) : dstc_cslot(dst_wrap_nil());
|
|
destindex = dstc_preread(c, sm, 0xFF, 1, dest);
|
|
fibindex = dstc_preread(c, sm, 0xFF, 2, fib);
|
|
valindex = dstc_preread(c, sm, 0xFF, 3, val);
|
|
dstc_emit(c, sm,
|
|
(valindex << 24) |
|
|
(fibindex << 16) |
|
|
(destindex << 8) |
|
|
DOP_TRANSFER);
|
|
dstc_postread(c, dest, destindex);
|
|
dstc_postread(c, fib, fibindex);
|
|
dstc_postread(c, val, valindex);
|
|
dstc_freeslot(c, fib);
|
|
dstc_freeslot(c, val);
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* :whiletop
|
|
* ...
|
|
* :condition
|
|
* jump-if-not cond :done
|
|
* ...
|
|
* jump :whiletop
|
|
* :done
|
|
*/
|
|
DstSlot dstc_while(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstCompiler *c = opts.compiler;
|
|
const Dst *sm = opts.sourcemap;
|
|
DstSlot cond;
|
|
int32_t condlocal, labelwt, labeld, labeljt, labelc, i;
|
|
int infinite = 0;
|
|
(void) argv;
|
|
|
|
if (argn < 2) {
|
|
dstc_cerror(c, sm, "expected at least 2 arguments");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
|
|
labelwt = dst_v_count(c->buffer);
|
|
|
|
/* Compile condition */
|
|
cond = dstc_value(dstc_getindex(opts, 1));
|
|
|
|
/* Check for constant condition */
|
|
if (cond.flags & DST_SLOT_CONSTANT) {
|
|
/* Loop never executes */
|
|
if (!dst_truthy(cond.constant)) {
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
/* Infinite loop */
|
|
infinite = 1;
|
|
}
|
|
|
|
dstc_scope(c, 0);
|
|
|
|
/* Infinite loop does not need to check condition */
|
|
if (!infinite) {
|
|
condlocal = dstc_preread(c, sm, 0xFF, 1, cond);
|
|
labelc = dst_v_count(c->buffer);
|
|
dstc_emit(c, sm, DOP_JUMP_IF_NOT | (condlocal << 8));
|
|
dstc_postread(c, cond, condlocal);
|
|
}
|
|
|
|
/* Compile body */
|
|
for (i = 1; i < argn; i++) {
|
|
DstFopts subopts = dstc_getindex(opts, i + 1);
|
|
subopts.flags = DST_FOPTS_DROP;
|
|
dstc_freeslot(c, dstc_value(subopts));
|
|
}
|
|
|
|
/* Compile jump to whiletop */
|
|
labeljt = dst_v_count(c->buffer);
|
|
dstc_emit(c, sm, DOP_JUMP);
|
|
|
|
/* Calculate jumps */
|
|
labeld = dst_v_count(c->buffer);
|
|
if (!infinite) c->buffer[labelc] |= (labeld - labelc) << 16;
|
|
c->buffer[labeljt] |= (labelwt - labeljt) << 8;
|
|
|
|
/* Pop scope and return nil slot */
|
|
dstc_popscope(opts.compiler);
|
|
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
|
|
/* Add a funcdef to the top most function scope */
|
|
static int32_t dstc_addfuncdef(DstCompiler *c, DstFuncDef *def) {
|
|
DstScope *scope = &dst_v_last(c->scopes);
|
|
while (scope >= c->scopes) {
|
|
if (scope->flags & DST_SCOPE_FUNCTION)
|
|
break;
|
|
scope--;
|
|
}
|
|
dst_assert(scope >= c->scopes, "could not add funcdef");
|
|
dst_v_push(scope->defs, def);
|
|
return dst_v_count(scope->defs) - 1;
|
|
}
|
|
|
|
DstSlot dstc_fn(DstFopts opts, int32_t argn, const Dst *argv) {
|
|
DstCompiler *c = opts.compiler;
|
|
const Dst *sm = opts.sourcemap;
|
|
DstFuncDef *def;
|
|
DstSlot ret;
|
|
int32_t paramcount, argi, parami, arity, localslot, defindex;
|
|
const Dst *params;
|
|
const Dst *psm;
|
|
int varargs = 0;
|
|
|
|
if (argn < 2) {
|
|
dstc_cerror(c, sm, "expected at least 2 arguments to function literal");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
|
|
/* Begin function */
|
|
dstc_scope(c, DST_SCOPE_FUNCTION);
|
|
|
|
/* Read function parameters */
|
|
parami = 0;
|
|
arity = 0;
|
|
if (dst_checktype(argv[0], DST_SYMBOL)) parami = 1;
|
|
if (parami >= argn) {
|
|
dstc_cerror(c, sm, "expected function parameters");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
if (dst_seq_view(argv[parami], ¶ms, ¶mcount)) {
|
|
psm = dst_sourcemap_index(sm, parami + 1);
|
|
int32_t i;
|
|
for (i = 0; i < paramcount; i++) {
|
|
const Dst *psmi = dst_sourcemap_index(psm, i);
|
|
if (dst_checktype(params[i], DST_SYMBOL)) {
|
|
DstSlot slot;
|
|
/* Check for varargs */
|
|
if (0 == dst_cstrcmp(dst_unwrap_symbol(params[i]), "&")) {
|
|
if (i != paramcount - 2) {
|
|
dstc_cerror(c, psmi, "variable argument symbol in unexpected location");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
varargs = 1;
|
|
arity--;
|
|
continue;
|
|
}
|
|
slot.flags = DST_SLOT_NAMED;
|
|
slot.envindex = 0;
|
|
slot.constant = dst_wrap_nil();
|
|
slot.index = dstc_lsloti(c);
|
|
dstc_nameslot(c, dst_unwrap_symbol(params[i]), slot);
|
|
arity++;
|
|
} else {
|
|
dstc_cerror(c, psmi, "expected symbol as function parameter");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
}
|
|
} else {
|
|
dstc_cerror(c, sm, "expected function parameters");
|
|
return dstc_cslot(dst_wrap_nil());
|
|
}
|
|
|
|
/* Compile function body */
|
|
for (argi = parami + 1; argi < argn; argi++) {
|
|
DstSlot s;
|
|
DstFopts subopts = dstc_getindex(opts, argi + 1);
|
|
subopts.flags = argi == (argn - 1) ? DST_FOPTS_TAIL : DST_FOPTS_DROP;
|
|
s = dstc_value(subopts);
|
|
dstc_freeslot(c, s);
|
|
}
|
|
|
|
/* Build function */
|
|
def = dstc_pop_funcdef(c);
|
|
def->arity = arity;
|
|
if (varargs) def->flags |= DST_FUNCDEF_FLAG_VARARG;
|
|
defindex = dstc_addfuncdef(c, def);
|
|
|
|
/* Ensure enough slots for vararg function. */
|
|
if (arity + varargs > def->slotcount) def->slotcount = arity + varargs;
|
|
|
|
/* Instantiate closure */
|
|
ret.flags = 0;
|
|
ret.envindex = 0;
|
|
ret.constant = dst_wrap_nil();
|
|
ret.index = dstc_lsloti(c);
|
|
|
|
localslot = ret.index > 0xF0 ? 0xF1 : ret.index;
|
|
dstc_emit(c, sm,
|
|
(defindex << 16) |
|
|
(localslot << 8) |
|
|
DOP_CLOSURE);
|
|
|
|
if (ret.index != localslot) {
|
|
dstc_emit(c, sm,
|
|
(ret.index << 16) |
|
|
(localslot << 8) |
|
|
DOP_MOVE_FAR);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Keep in lexographic order */
|
|
static const DstSpecial dstc_specials[] = {
|
|
{"def", dstc_def},
|
|
{"do", dstc_do},
|
|
{"fn", dstc_fn},
|
|
{"if", dstc_if},
|
|
{"quote", dstc_quote},
|
|
{"transfer", dstc_transfer},
|
|
{"var", dstc_var},
|
|
{"varset!", dstc_varset},
|
|
{"while", dstc_while}
|
|
};
|
|
|
|
/* Find a special */
|
|
const DstSpecial *dstc_special(const uint8_t *name) {
|
|
return dst_strbinsearch(
|
|
&dstc_specials,
|
|
sizeof(dstc_specials)/sizeof(DstSpecial),
|
|
sizeof(DstSpecial),
|
|
name);
|
|
}
|
|
|