/*
    Driver for SAA6588 RDS decoder

    (c) 2005 Hans J. Koch

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <asm/uaccess.h>

#include <media/saa6588.h>
#include <media/v4l2-device.h>

/* insmod options */
static unsigned int debug;
static unsigned int xtal;
static unsigned int mmbs;
static unsigned int plvl;
static unsigned int bufblocks = 100;

module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debug messages");
module_param(xtal, int, 0);
MODULE_PARM_DESC(xtal, "select oscillator frequency (0..3), default 0");
module_param(mmbs, int, 0);
MODULE_PARM_DESC(mmbs, "enable MMBS mode: 0=off (default), 1=on");
module_param(plvl, int, 0);
MODULE_PARM_DESC(plvl, "select pause level (0..3), default 0");
module_param(bufblocks, int, 0);
MODULE_PARM_DESC(bufblocks, "number of buffered blocks, default 100");

MODULE_DESCRIPTION("v4l2 driver module for SAA6588 RDS decoder");
MODULE_AUTHOR("Hans J. Koch <koch@hjk-az.de>");

MODULE_LICENSE("GPL");

/* ---------------------------------------------------------------------- */

#define UNSET       (-1U)
#define PREFIX      "saa6588: "
#define dprintk     if (debug) printk

struct saa6588 {
	struct v4l2_subdev sd;
	struct delayed_work work;
	spinlock_t lock;
	unsigned char *buffer;
	unsigned int buf_size;
	unsigned int rd_index;
	unsigned int wr_index;
	unsigned int block_count;
	unsigned char last_blocknum;
	wait_queue_head_t read_queue;
	int data_available_for_read;
	u8 sync;
};

static inline struct saa6588 *to_saa6588(struct v4l2_subdev *sd)
{
	return container_of(sd, struct saa6588, sd);
}

/* ---------------------------------------------------------------------- */

/*
 * SAA6588 defines
 */

/* Initialization and mode control byte (0w) */

/* bit 0+1 (DAC0/DAC1) */
#define cModeStandard           0x00
#define cModeFastPI             0x01
#define cModeReducedRequest     0x02
#define cModeInvalid            0x03

/* bit 2 (RBDS) */
#define cProcessingModeRDS      0x00
#define cProcessingModeRBDS     0x04

/* bit 3+4 (SYM0/SYM1) */
#define cErrCorrectionNone      0x00
#define cErrCorrection2Bits     0x08
#define cErrCorrection5Bits     0x10
#define cErrCorrectionNoneRBDS  0x18

/* bit 5 (NWSY) */
#define cSyncNormal             0x00
#define cSyncRestart            0x20

/* bit 6 (TSQD) */
#define cSigQualityDetectOFF    0x00
#define cSigQualityDetectON     0x40

/* bit 7 (SQCM) */
#define cSigQualityTriggered    0x00
#define cSigQualityContinous    0x80

/* Pause level and flywheel control byte (1w) */

/* bits 0..5 (FEB0..FEB5) */
#define cFlywheelMaxBlocksMask  0x3F
#define cFlywheelDefault        0x20

/* bits 6+7 (PL0/PL1) */
#define cPauseLevel_11mV	0x00
#define cPauseLevel_17mV        0x40
#define cPauseLevel_27mV        0x80
#define cPauseLevel_43mV        0xC0

/* Pause time/oscillator frequency/quality detector control byte (1w) */

/* bits 0..4 (SQS0..SQS4) */
#define cQualityDetectSensMask  0x1F
#define cQualityDetectDefault   0x0F

/* bit 5 (SOSC) */
#define cSelectOscFreqOFF	0x00
#define cSelectOscFreqON	0x20

/* bit 6+7 (PTF0/PTF1) */
#define cOscFreq_4332kHz	0x00
#define cOscFreq_8664kHz	0x40
#define cOscFreq_12996kHz	0x80
#define cOscFreq_17328kHz	0xC0

/* ---------------------------------------------------------------------- */

static bool block_from_buf(struct saa6588 *s, unsigned char *buf)
{
	int i;

	if (s->rd_index == s->wr_index) {
		if (debug > 2)
			dprintk(PREFIX "Read: buffer empty.\n");
		return false;
	}

	if (debug > 2) {
		dprintk(PREFIX "Read: ");
		for (i = s->rd_index; i < s->rd_index + 3; i++)
			dprintk("0x%02x ", s->buffer[i]);
	}

	memcpy(buf, &s->buffer[s->rd_index], 3);

	s->rd_index += 3;
	if (s->rd_index >= s->buf_size)
		s->rd_index = 0;
	s->block_count--;

	if (debug > 2)
		dprintk("%d blocks total.\n", s->block_count);

	return true;
}

static void read_from_buf(struct saa6588 *s, struct saa6588_command *a)
{
	unsigned char __user *buf_ptr = a->buffer;
	unsigned char buf[3];
	unsigned long flags;
	unsigned int rd_blocks;
	unsigned int i;

	a->result = 0;
	if (!a->buffer)
		return;

	while (!a->nonblocking && !s->data_available_for_read) {
		int ret = wait_event_interruptible(s->read_queue,
					     s->data_available_for_read);
		if (ret == -ERESTARTSYS) {
			a->result = -EINTR;
			return;
		}
	}

	rd_blocks = a->block_count;
	spin_lock_irqsave(&s->lock, flags);
	if (rd_blocks > s->block_count)
		rd_blocks = s->block_count;
	spin_unlock_irqrestore(&s->lock, flags);

	if (!rd_blocks)
		return;

	for (i = 0; i < rd_blocks; i++) {
		bool got_block;

		spin_lock_irqsave(&s->lock, flags);
		got_block = block_from_buf(s, buf);
		spin_unlock_irqrestore(&s->lock, flags);
		if (!got_block)
			break;
		if (copy_to_user(buf_ptr, buf, 3)) {
			a->result = -EFAULT;
			return;
		}
		buf_ptr += 3;
		a->result += 3;
	}
	spin_lock_irqsave(&s->lock, flags);
	s->data_available_for_read = (s->block_count > 0);
	spin_unlock_irqrestore(&s->lock, flags);
}

static void block_to_buf(struct saa6588 *s, unsigned char *blockbuf)
{
	unsigned int i;

	if (debug > 3)
		dprintk(PREFIX "New block: ");

	for (i = 0; i < 3; ++i) {
		if (debug > 3)
			dprintk("0x%02x ", blockbuf[i]);
		s->buffer[s->wr_index] = blockbuf[i];
		s->wr_index++;
	}

	if (s->wr_index >= s->buf_size)
		s->wr_index = 0;

	if (s->wr_index == s->rd_index) {
		s->rd_index += 3;
		if (s->rd_index >= s->buf_size)
			s->rd_index = 0;
	} else
		s->block_count++;

	if (debug > 3)
		dprintk("%d blocks total.\n", s->block_count);
}

static void saa6588_i2c_poll(struct saa6588 *s)
{
	struct i2c_client *client = v4l2_get_subdevdata(&s->sd);
	unsigned long flags;
	unsigned char tmpbuf[6];
	unsigned char blocknum;
	unsigned char tmp;

	/* Although we only need 3 bytes, we have to read at least 6.
	   SAA6588 returns garbage otherwise. */
	if (6 != i2c_master_recv(client, &tmpbuf[0], 6)) {
		if (debug > 1)
			dprintk(PREFIX "read error!\n");
		return;
	}

	s->sync = tmpbuf[0] & 0x10;
	if (!s->sync)
		return;
	blocknum = tmpbuf[0] >> 5;
	if (blocknum == s->last_blocknum) {
		if (debug > 3)
			dprintk("Saw block %d again.\n", blocknum);
		return;
	}

	s->last_blocknum = blocknum;

	/*
	   Byte order according to v4l2 specification:

	   Byte 0: Least Significant Byte of RDS Block
	   Byte 1: Most Significant Byte of RDS Block
	   Byte 2 Bit 7: Error bit. Indicates that an uncorrectable error
	   occurred during reception of this block.
	   Bit 6: Corrected bit. Indicates that an error was
	   corrected for this data block.
	   Bits 5-3: Same as bits 0-2.
	   Bits 2-0: Block number.

	   SAA6588 byte order is Status-MSB-LSB, so we have to swap the
	   first and the last of the 3 bytes block.
	 */

	tmp = tmpbuf[2];
	tmpbuf[2] = tmpbuf[0];
	tmpbuf[0] = tmp;

	/* Map 'Invalid block E' to 'Invalid Block' */
	if (blocknum == 6)
		blocknum = V4L2_RDS_BLOCK_INVALID;
	/* And if are not in mmbs mode, then 'Block E' is also mapped
	   to 'Invalid Block'. As far as I can tell MMBS is discontinued,
	   and if there is ever a need to support E blocks, then please
	   contact the linux-media mailinglist. */
	else if (!mmbs && blocknum == 5)
		blocknum = V4L2_RDS_BLOCK_INVALID;
	tmp = blocknum;
	tmp |= blocknum << 3;	/* Received offset == Offset Name (OK ?) */
	if ((tmpbuf[2] & 0x03) == 0x03)
		tmp |= V4L2_RDS_BLOCK_ERROR;	 /* uncorrectable error */
	else if ((tmpbuf[2] & 0x03) != 0x00)
		tmp |= V4L2_RDS_BLOCK_CORRECTED; /* corrected error */
	tmpbuf[2] = tmp;	/* Is this enough ? Should we also check other bits ? */

	spin_lock_irqsave(&s->lock, flags);
	block_to_buf(s, tmpbuf);
	spin_unlock_irqrestore(&s->lock, flags);
	s->data_available_for_read = 1;
	wake_up_interruptible(&s->read_queue);
}

static void saa6588_work(struct work_struct *work)
{
	struct saa6588 *s = container_of(work, struct saa6588, work.work);

	saa6588_i2c_poll(s);
	schedule_delayed_work(&s->work, msecs_to_jiffies(20));
}

static void saa6588_configure(struct saa6588 *s)
{
	struct i2c_client *client = v4l2_get_subdevdata(&s->sd);
	unsigned char buf[3];
	int rc;

	buf[0] = cSyncRestart;
	if (mmbs)
		buf[0] |= cProcessingModeRBDS;

	buf[1] = cFlywheelDefault;
	switch (plvl) {
	case 0:
		buf[1] |= cPauseLevel_11mV;
		break;
	case 1:
		buf[1] |= cPauseLevel_17mV;
		break;
	case 2:
		buf[1] |= cPauseLevel_27mV;
		break;
	case 3:
		buf[1] |= cPauseLevel_43mV;
		break;
	default:		/* nothing */
		break;
	}

	buf[2] = cQualityDetectDefault | cSelectOscFreqON;

	switch (xtal) {
	case 0:
		buf[2] |= cOscFreq_4332kHz;
		break;
	case 1:
		buf[2] |= cOscFreq_8664kHz;
		break;
	case 2:
		buf[2] |= cOscFreq_12996kHz;
		break;
	case 3:
		buf[2] |= cOscFreq_17328kHz;
		break;
	default:		/* nothing */
		break;
	}

	dprintk(PREFIX "writing: 0w=0x%02x 1w=0x%02x 2w=0x%02x\n",
		buf[0], buf[1], buf[2]);

	rc = i2c_master_send(client, buf, 3);
	if (rc != 3)
		printk(PREFIX "i2c i/o error: rc == %d (should be 3)\n", rc);
}

/* ---------------------------------------------------------------------- */

static long saa6588_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
	struct saa6588 *s = to_saa6588(sd);
	struct saa6588_command *a = arg;

	switch (cmd) {
		/* --- close() for /dev/radio --- */
	case SAA6588_CMD_CLOSE:
		s->data_available_for_read = 1;
		wake_up_interruptible(&s->read_queue);
		s->data_available_for_read = 0;
		a->result = 0;
		break;
		/* --- read() for /dev/radio --- */
	case SAA6588_CMD_READ:
		read_from_buf(s, a);
		break;
		/* --- poll() for /dev/radio --- */
	case SAA6588_CMD_POLL:
		a->result = 0;
		if (s->data_available_for_read)
			a->result |= POLLIN | POLLRDNORM;
		poll_wait(a->instance, &s->read_queue, a->event_list);
		break;

	default:
		/* nothing */
		return -ENOIOCTLCMD;
	}
	return 0;
}

static int saa6588_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
{
	struct saa6588 *s = to_saa6588(sd);

	vt->capability |= V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO;
	if (s->sync)
		vt->rxsubchans |= V4L2_TUNER_SUB_RDS;
	return 0;
}

static int saa6588_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt)
{
	struct saa6588 *s = to_saa6588(sd);

	saa6588_configure(s);
	return 0;
}

/* ----------------------------------------------------------------------- */

static const struct v4l2_subdev_core_ops saa6588_core_ops = {
	.ioctl = saa6588_ioctl,
};

static const struct v4l2_subdev_tuner_ops saa6588_tuner_ops = {
	.g_tuner = saa6588_g_tuner,
	.s_tuner = saa6588_s_tuner,
};

static const struct v4l2_subdev_ops saa6588_ops = {
	.core = &saa6588_core_ops,
	.tuner = &saa6588_tuner_ops,
};

/* ---------------------------------------------------------------------- */

static int saa6588_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct saa6588 *s;
	struct v4l2_subdev *sd;

	v4l_info(client, "saa6588 found @ 0x%x (%s)\n",
			client->addr << 1, client->adapter->name);

	s = devm_kzalloc(&client->dev, sizeof(*s), GFP_KERNEL);
	if (s == NULL)
		return -ENOMEM;

	s->buf_size = bufblocks * 3;

	s->buffer = devm_kzalloc(&client->dev, s->buf_size, GFP_KERNEL);
	if (s->buffer == NULL)
		return -ENOMEM;
	sd = &s->sd;
	v4l2_i2c_subdev_init(sd, client, &saa6588_ops);
	spin_lock_init(&s->lock);
	s->block_count = 0;
	s->wr_index = 0;
	s->rd_index = 0;
	s->last_blocknum = 0xff;
	init_waitqueue_head(&s->read_queue);
	s->data_available_for_read = 0;

	saa6588_configure(s);

	/* start polling via eventd */
	INIT_DELAYED_WORK(&s->work, saa6588_work);
	schedule_delayed_work(&s->work, 0);
	return 0;
}

static int saa6588_remove(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct saa6588 *s = to_saa6588(sd);

	v4l2_device_unregister_subdev(sd);

	cancel_delayed_work_sync(&s->work);

	return 0;
}

/* ----------------------------------------------------------------------- */

static const struct i2c_device_id saa6588_id[] = {
	{ "saa6588", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, saa6588_id);

static struct i2c_driver saa6588_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "saa6588",
	},
	.probe		= saa6588_probe,
	.remove		= saa6588_remove,
	.id_table	= saa6588_id,
};

module_i2c_driver(saa6588_driver);


/**
 * Object#toString() ref for stringify().
 */

var toString = Object.prototype.toString;

/**
 * Object#hasOwnProperty ref
 */

var hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * Array#indexOf shim.
 */

var indexOf = typeof Array.prototype.indexOf === 'function'
  ? function(arr, el) { return arr.indexOf(el); }
  : function(arr, el) {
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] === el) return i;
      }
      return -1;
    };

/**
 * Array.isArray shim.
 */

var isArray = Array.isArray || function(arr) {
  return toString.call(arr) == '[object Array]';
};

/**
 * Object.keys shim.
 */

var objectKeys = Object.keys || function(obj) {
  var ret = [];
  for (var key in obj) ret.push(key);
  return ret;
};

/**
 * Array#forEach shim.
 */

var forEach = typeof Array.prototype.forEach === 'function'
  ? function(arr, fn) { return arr.forEach(fn); }
  : function(arr, fn) {
      for (var i = 0; i < arr.length; i++) fn(arr[i]);
    };

/**
 * Array#reduce shim.
 */

var reduce = function(arr, fn, initial) {
  if (typeof arr.reduce === 'function') return arr.reduce(fn, initial);
  var res = initial;
  for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]);
  return res;
};

/**
 * Create a nullary object if possible
 */

function createObject() {
  return Object.create
    ? Object.create(null)
    : {};
}

/**
 * Cache non-integer test regexp.
 */

var isint = /^[0-9]+$/;

function promote(parent, key) {
  if (parent[key].length == 0) return parent[key] = createObject();
  var t = createObject();
  for (var i in parent[key]) {
    if (hasOwnProperty.call(parent[key], i)) {
      t[i] = parent[key][i];
    }
  }
  parent[key] = t;
  return t;
}

function parse(parts, parent, key, val) {
  var part = parts.shift();
  // end
  if (!part) {
    if (isArray(parent[key])) {
      parent[key].push(val);
    } else if ('object' == typeof parent[key]) {
      parent[key] = val;
    } else if ('undefined' == typeof parent[key]) {
      parent[key] = val;
    } else {
      parent[key] = [parent[key], val];
    }
    // array
  } else {
    var obj = parent[key] = parent[key] || [];
    if (']' == part) {
      if (isArray(obj)) {
        if ('' != val) obj.push(val);
      } else if ('object' == typeof obj) {
        obj[objectKeys(obj).length] = val;
      } else {
        obj = parent[key] = [parent[key], val];
      }
      // prop
    } else if (~indexOf(part, ']')) {
      part = part.substr(0, part.length - 1);
      if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
      parse(parts, obj, part, val);
      // key
    } else {
      if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
      parse(parts, obj, part, val);
    }
  }
}

/**
 * Merge parent key/val pair.
 */

function merge(parent, key, val){
  if (~indexOf(key, ']')) {
    var parts = key.split('[')
      , len = parts.length
      , last = len - 1;
    parse(parts, parent, 'base', val);
    // optimize
  } else {
    if (!isint.test(key) && isArray(parent.base)) {
      var t = createObject();
      for (var k in parent.base) t[k] = parent.base[k];
      parent.base = t;
    }
    set(parent.base, key, val);
  }

  return parent;
}

/**
 * Compact sparse arrays.
 */

function compact(obj) {
  if ('object' != typeof obj) return obj;

  if (isArray(obj)) {
    var ret = [];

    for (var i in obj) {
      if (hasOwnProperty.call(obj, i)) {
        ret.push(obj[i]);
      }
    }

    return ret;
  }

  for (var key in obj) {
    obj[key] = compact(obj[key]);
  }

  return obj;
}

/**
 * Restore Object.prototype.
 * see pull-request #58
 */

function restoreProto(obj) {
  if (!Object.create) return obj;
  if (isArray(obj)) return obj;
  if (obj && 'object' != typeof obj) return obj;

  for (var key in obj) {
    if (hasOwnProperty.call(obj, key)) {
      obj[key] = restoreProto(obj[key]);
    }
  }

  obj.__proto__ = Object.prototype;
  return obj;
}

/**
 * Parse the given obj.
 */

function parseObject(obj){
  var ret = { base: {} };

  forEach(objectKeys(obj), function(name){
    merge(ret, name, obj[name]);
  });

  return compact(ret.base);
}

/**
 * Parse the given str.
 */

function parseString(str){
  var ret = reduce(String(str).split('&'), function(ret, pair){
    var eql = indexOf(pair, '=')
      , brace = lastBraceInKey(pair)
      , key = pair.substr(0, brace || eql)
      , val = pair.substr(brace || eql, pair.length)
      , val = val.substr(indexOf(val, '=') + 1, val.length);

    // ?foo
    if ('' == key) key = pair, val = '';
    if ('' == key) return ret;

    return merge(ret, decode(key), decode(val));
  }, { base: createObject() }).base;

  return restoreProto(compact(ret));
}

/**
 * Parse the given query `str` or `obj`, returning an object.
 *
 * @param {String} str | {Object} obj
 * @return {Object}
 * @api public
 */

exports.parse = function(str){
  if (null == str || '' == str) return {};
  return 'object' == typeof str
    ? parseObject(str)
    : parseString(str);
};

/**
 * Turn the given `obj` into a query string
 *
 * @param {Object} obj
 * @return {String}
 * @api public
 */

var stringify = exports.stringify = function(obj, prefix) {
  if (isArray(obj)) {
    return stringifyArray(obj, prefix);
  } else if ('[object Object]' == toString.call(obj)) {
    return stringifyObject(obj, prefix);
  } else if ('string' == typeof obj) {
    return stringifyString(obj, prefix);
  } else {
    return prefix + '=' + encodeURIComponent(String(obj));
  }
};

/**
 * Stringify the given `str`.
 *
 * @param {String} str
 * @param {String} prefix
 * @return {String}
 * @api private
 */

function stringifyString(str, prefix) {
  if (!prefix) throw new TypeError('stringify expects an object');
  return prefix + '=' + encodeURIComponent(str);
}

/**
 * Stringify the given `arr`.
 *
 * @param {Array} arr
 * @param {String} prefix
 * @return {String}
 * @api private
 */

function stringifyArray(arr, prefix) {
  var ret = [];
  if (!prefix) throw new TypeError('stringify expects an object');
  for (var i = 0; i < arr.length; i++) {
    ret.push(stringify(arr[i], prefix + '[' + i + ']'));
  }
  return ret.join('&');
}

/**
 * Stringify the given `obj`.
 *
 * @param {Object} obj
 * @param {String} prefix
 * @return {String}
 * @api private
 */

function stringifyObject(obj, prefix) {
  var ret = []
    , keys = objectKeys(obj)
    , key;

  for (var i = 0, len = keys.length; i < len; ++i) {
    key = keys[i];
    if ('' == key) continue;
    if (null == obj[key]) {
      ret.push(encodeURIComponent(key) + '=');
    } else {
      ret.push(stringify(obj[key], prefix
        ? prefix + '[' + encodeURIComponent(key) + ']'
        : encodeURIComponent(key)));
    }
  }

  return ret.join('&');
}

/**
 * Set `obj`'s `key` to `val` respecting
 * the weird and wonderful syntax of a qs,
 * where "foo=bar&foo=baz" becomes an array.
 *
 * @param {Object} obj
 * @param {String} key
 * @param {String} val
 * @api private
 */

function set(obj, key, val) {
  var v = obj[key];
  if (undefined === v) {
    obj[key] = val;
  } else if (isArray(v)) {
    v.push(val);
  } else {
    obj[key] = [v, val];
  }
}

/**
 * Locate last brace in `str` within the key.
 *
 * @param {String} str
 * @return {Number}
 * @api private
 */

function lastBraceInKey(str) {
  var len = str.length
    , brace
    , c;
  for (var i = 0; i < len; ++i) {
    c = str[i];
    if (']' == c) brace = false;
    if ('[' == c) brace = true;
    if ('=' == c && !brace) return i;
  }
}

/**
 * Decode `str`.
 *
 * @param {String} str
 * @return {String}
 * @api private
 */

function decode(str) {
  try {
    return decodeURIComponent(str.replace(/\+/g, ' '));
  } catch (err) {
    return str;
  }
}


### How to use ngx-md in Angular2 with AoT compilation using ngc and rollup

The compilation process is described on the official Angular2 website here: https://angular.io/docs/ts/latest/cookbook/aot-compiler.html

#### 1) Install `ngx-md`

```bash
npm install ngx-md --save
```

#### 2) Edit Angular 2 module

Open the module file where you want to include ngx-md (most probably `app.module.ts`) and import either specific ngx-md modules by listing them in the import statement and then in the import array of the Angular 2 module

```typescript
import { NgxMdModule } from 'ngx-md';
...

@NgModule({
   ...
   imports: [NgxMdModule.forRoot(), ... ],
   ...
})
```

#### 3) Edit rollup configuration (rollup-config.js)

You have to use CommonJS rollup plugin, which you should be using anyway due to RxJS. If for some reason not, install it:

```bash
npm install rollup-plugin-commonjs --save --dev
```

Then you have to import the CommonJS plugin, include it in the plugins array and add ngx-md to the list of modules:

```javascript
...
import commonjs from 'rollup-plugin-commonjs';
...

//paths are relative to the execution path
export default {
	...
	plugins: [
		...
		commonjs({
			include: [
				'node_modules/rxjs/**',
				'node_modules/ngx-md/**'
			]
		}),
		...
	]
}
```

#### 4) Run compilation the standard way

e.g.

```bash
ngc -p tsconfig-aot.json
rollup -c rollup-config.js
```


/*
 * Copyright (c) 2018-2999 广州亚米信息科技有限公司 All rights reserved.
 *
 * https://www.gz-yami.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.admin.controller;

import javax.validation.Valid;
import java.util.Date;
import java.util.List;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.security.util.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.yami.shop.common.util.PageParam;
import com.baomidou.mybatisplus.core.metadata.IPage;

import com.yami.shop.service.HotSearchService;
import com.yami.shop.bean.model.HotSearch;

/**
 *
 * @author lgh on 2019/03/27.
 */
@RestController
@RequestMapping("/admin/hotSearch")
public class HotSearchController {

    @Autowired
    private HotSearchService hotSearchService;

	/**
	 * 分页获取
	 */
    @GetMapping("/page")
	@PreAuthorize("@pms.hasPermission('admin:hotSearch:page')")
	public ResponseEntity<IPage<HotSearch>> page(HotSearch hotSearch,PageParam<HotSearch> page){
		IPage<HotSearch> hotSearchs = hotSearchService.page(page,new LambdaQueryWrapper<HotSearch>()
			.eq(HotSearch::getShopId, SecurityUtils.getSysUser().getShopId())
			.like(StrUtil.isNotBlank(hotSearch.getContent()), HotSearch::getContent,hotSearch.getContent())
				.like(StrUtil.isNotBlank(hotSearch.getTitle()), HotSearch::getTitle,hotSearch.getTitle())
			.eq(hotSearch.getStatus()!=null, HotSearch::getStatus,hotSearch.getStatus())
		);
		return ResponseEntity.ok(hotSearchs);
	}

    /**
	 * 获取信息
	 */
	@GetMapping("/info/{id}")
	@PreAuthorize("@pms.hasPermission('admin:hotSearch:info')")
	public ResponseEntity<HotSearch> info(@PathVariable("id") Long id){
		HotSearch hotSearch = hotSearchService.getById(id);
		return ResponseEntity.ok(hotSearch);
	}

	/**
	 * 保存
	 */
	@PostMapping
	@PreAuthorize("@pms.hasPermission('admin:hotSearch:save')")
	public ResponseEntity<Void> save(@RequestBody @Valid HotSearch hotSearch){
		hotSearch.setRecDate(new Date());
		hotSearch.setShopId(SecurityUtils.getSysUser().getShopId());
		hotSearchService.save(hotSearch);
		//清除缓存
		hotSearchService.removeHotSearchDtoCacheByshopId(SecurityUtils.getSysUser().getShopId());
		return ResponseEntity.ok().build();
	}

	/**
	 * 修改
	 */
	@PutMapping
	@PreAuthorize("@pms.hasPermission('admin:hotSearch:update')")
	public ResponseEntity<Void> update(@RequestBody @Valid HotSearch hotSearch){
		hotSearchService.updateById(hotSearch);
		//清除缓存
		hotSearchService.removeHotSearchDtoCacheByshopId(SecurityUtils.getSysUser().getShopId());
		return ResponseEntity.ok().build();
	}

	/**
	 * 删除
	 */
	@DeleteMapping
	@PreAuthorize("@pms.hasPermission('admin:hotSearch:delete')")
	public ResponseEntity<Void> delete(@RequestBody List<Long> ids){
		hotSearchService.removeByIds(ids);
		//清除缓存
		hotSearchService.removeHotSearchDtoCacheByshopId(SecurityUtils.getSysUser().getShopId());
		return ResponseEntity.ok().build();
	}
}


# Copyright (C) 2006-2019 The Gregorio Project (see CONTRIBUTORS.md)
#
# This file is part of Gregorio.
#
# Gregorio is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gregorio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gregorio.  If not, see <http://www.gnu.org/licenses/>.

SUBDIRS = TeXShop

EXTRA_DIST = gprocess gabc.xml gabc.lang gabc.vim 900_gregorio.xml gregorio.png gabc-syntax.plist contrib_README.md system-setup.command system-setup.bat config-texworks.command checkSyllabation.py


{ $Id$ }
{                        ----------------------------------------------
                         CMDLineDebugger.pp  -  Debugger class for
                                                commandline debuggers
                         ----------------------------------------------

 @created(Wed Feb 28st WET 2001)
 @lastmod($Date$)
 @author(Marc Weustink <marc@@lazarus.dommelstein.net>)

 This unit contains the Commandline debugger class for external commandline
 debuggers.

 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************
}
unit CmdLineDebugger;

{$mode objfpc}
{$H+}

interface

uses
  Classes, Types, process, FileUtil, LCLProc, LazLoggerBase, UTF8Process,
  DbgIntfDebuggerBase, Forms, DebugUtils;

type

  { TCmdLineDebugger }

  TCmdLineDebugger = class(TDebuggerIntf)
  private
    FDbgProcess: TProcessUTF8;   // The process used to call the debugger
    FLineEnds: TStringDynArray;  // List of strings considered as lineends
    FOutputBuf: String;
    FReading: Boolean;       // Set if we are in the ReadLine loop
    FFlushAfterRead: Boolean;// Set if we should flush after finished reading
    FPeekOffset: Integer;    // Count the number of lines we have peeked
    FReadLineTimedOut, FReadLineWasAbortedByNested: Boolean;
    FReadLineCallStamp: Int64;
    function WaitForHandles(const AHandles: array of Integer; var ATimeOut: Integer): Integer; overload;
    function WaitForHandles(const AHandles: array of Integer): Integer; overload;
  protected
    procedure DoReadError; virtual;
    procedure DoWriteError; virtual;
    function GetDebugProcessRunning: Boolean; virtual;
    procedure ProcessWhileWaitForHandles; virtual;
    function  CreateDebugProcess(const AOptions: String): Boolean; virtual;
    procedure Flush;                                   // Flushes output buffer
    function  GetWaiting: Boolean; override;
    function  LineEndPos(const {%H-}s: string; out LineEndLen: integer): integer; virtual;
    function  ReadLine(ATimeOut: Integer = -1): String; overload;
    function  ReadLine(const APeek: Boolean; ATimeOut: Integer = -1): String; virtual; overload;
    procedure SendCmdLn(const ACommand: String); virtual; overload;
    procedure SendCmdLn(const ACommand: String; Values: array of const); overload;
    procedure SetLineEnds(ALineEnds: TStringDynArray);
    function  ReadLineTimedOut: Boolean; virtual;
    property  ReadLineWasAbortedByNested: Boolean read FReadLineWasAbortedByNested;
    procedure AbortReadLine;
  public
    constructor Create(const AExternalDebugger: String); override;
    destructor Destroy; override;
    procedure TestCmd(const ACommand: String); virtual;// For internal debugging purposes
    class function CanExternalDebugSymbolsFile: boolean; override;
  public
    property DebugProcess: TProcessUTF8 read FDbgProcess;
    property DebugProcessRunning: Boolean read GetDebugProcessRunning;
  end;

implementation

//////////////////////////////////////////////////
//       Needs to go to proper include
//          Platform dependent
//////////////////////////////////////////////////

uses
  LCLIntf,
{$IFdef MSWindows}
  Windows,
{$ENDIF}
{$IFDEF UNIX}
   Unix,BaseUnix,
{$ENDIF}
  SysUtils;

var
  DBG_CMD_ECHO, DBG_CMD_ECHO_FULL: PLazLoggerLogGroup;

{------------------------------------------------------------------------------
  Function: WaitForHandles
  Params:  AHandles:              A set of handles to wait for (max 32)
  TimeOut: Max Time in milli-secs => set to 0 if timeout occured
  Returns: BitArray of handles set, 0 when an error occoured
 ------------------------------------------------------------------------------}
function TCmdLineDebugger.WaitForHandles(const AHandles: array of Integer; var ATimeOut: Integer): Integer;
{$IFDEF UNIX}
const
  IDLE_STEP_COUNT=50;
var
  n, R, Max, Count: Integer;
  TimeOut: Integer;
  FDSWait, FDS: TFDSet;
  Step: Integer;
  t, t2, t3: QWord;
  CurCallStamp: Int64;
begin
  Result := 0;
  CurCallStamp := FReadLineCallStamp;
  Max := 0;
  Count := High(AHandles);
  if Count < 0 then Exit;
  if Count > 31 then Count := 31;

  // zero the whole bit set of handles
  FpFD_ZERO(FDS);

  // set bits for all waiting handles
  for n := 0 to Count do
  begin
    if AHandles[n] < 0 then
      continue;
    if Max < AHandles[n] + 1 then Max := AHandles[n] + 1;
    FpFD_Set(AHandles[n], FDS);
  end;
  if Max=0 then begin
    // no valid handle, so no change possible
    DebugLn('WaitForHandles: Error: no handles');
    exit;
  end;

  if ATimeOut > 0
  then t := GetTickCount64;

  // wait for all handles
  Step:=IDLE_STEP_COUNT-1;
  repeat
    FDSWait := FDS;
    TimeOut := 10;
    // Select:
    // R = -1 on error, 0 on timeout, >0 on success and is number of handles
    // FDSWait is changed, and indicates what descriptors have changed
    R := FpSelect(Max, @FDSWait, nil, nil, TimeOut);

    if CurCallStamp <> FReadLineCallStamp then
      exit;

    if (ATimeOut > 0) then begin
      t2 := GetTickCount64;
      if t2 < t
      then t3 := t2 + (High(t) - t)
      else t3 := t2 - t;
      if (t3 >= ATimeOut)
      then begin
        ATimeOut := 0;
        break;
      end
      else begin
        ATimeOut := ATimeOut - t3;
        t := t2;
      end;
    end;

    ProcessWhileWaitForHandles;
    inc(Step);
    if Step=IDLE_STEP_COUNT then begin
      Step:=0;
      Application.Idle(false);
    end;
    try
      Application.ProcessMessages;
    except
      Application.HandleException(Application);
    end;
    if Application.Terminated then Break;
  until R <> 0;

  // set bits for all changed handles
  if R > 0
  then begin
    for n := 0 to Count do
      if  (AHandles[n] >= 0)
      and (FpFD_ISSET(AHandles[n],FDSWait)=1)
      then begin
        Result := Result or 1 shl n;
        Dec(R);
        if R=0 then Break;
      end;
  end;
end;
{$ELSE linux}
{$IFdef MSWindows}
const
  IDLE_STEP_COUNT = 20;
var
  PipeHandle: Integer;
  TotalBytesAvailable: dword;
  R: LongBool;
  n: integer;
  Step: Integer;
  t, t2, t3: DWord;
  CurCallStamp: Int64;
begin
  Result := 0;
  CurCallStamp := FReadLineCallStamp;
  Step:=IDLE_STEP_COUNT-1;
  if ATimeOut > 0
  then t := GetTickCount;

  while Result=0 do
  begin
    for n:= 0 to High(AHandles) do
    begin
      PipeHandle := AHandles[n];
      R := Windows.PeekNamedPipe(PipeHandle, nil, 0, nil, @TotalBytesAvailable, nil);
      if not R then begin
        // PeekNamedPipe failed
        DebugLn('PeekNamedPipe failed, GetLastError is ', IntToStr(GetLastError));
        Exit;
      end;
      if R then begin
        // PeekNamedPipe successfull
        if (TotalBytesAvailable>0) then begin
          Result := 1 shl n;
          Break;
        end;
      end;
    end;

    if CurCallStamp <> FReadLineCallStamp then
      exit;

    if (ATimeOut > 0) then begin
      t2 := GetTickCount;
      if t2 < t
      then t3 := t2 + (High(t) - t)
      else t3 := t2 - t;
      if (t3 >= ATimeOut)
      then begin
        ATimeOut := 0;
        break;
      end
      else begin
        ATimeOut := ATimeOut - t3;
        t := t2;
      end;
    end;

    ProcessWhileWaitForHandles;
    // process messages
    inc(Step);
    if Step=IDLE_STEP_COUNT then begin
      Step:=0;
      Application.Idle(false);
    end;
    try
      Application.ProcessMessages;
    except
      Application.HandleException(Application);
    end;
    if Application.Terminated or not DebugProcessRunning then Break;
    // sleep a bit
    Sleep(10);
  end;
end;
{$ELSE win32}
begin
  DebugLn('ToDo: implement WaitForHandles for this OS');
  Result := 0;
end;
{$ENDIF win32}
{$ENDIF linux}

function TCmdLineDebugger.WaitForHandles(const AHandles: array of Integer): Integer; overload;
var
  t: Integer;
begin
  t := -1;
  Result := WaitForHandles(AHandles, t);
end;

procedure TCmdLineDebugger.DoReadError;
begin
  SetState(dsError);
end;

procedure TCmdLineDebugger.DoWriteError;
begin
  SetState(dsError);
end;

procedure TCmdLineDebugger.ProcessWhileWaitForHandles;
begin
  // nothing
end;

//////////////////////////////////////////////////

{ TCmdLineDebugger }

constructor TCmdLineDebugger.Create(const AExternalDebugger: String);
begin
  FDbgProcess := nil;
  SetLength(FLineEnds, 1);
  FLineEnds[0] := LineEnding;
  FReading := False;
  FFlushAfterRead := False;
  FPeekOffset := 0;
  inherited;
end;

function TCmdLineDebugger.CreateDebugProcess(const AOptions: String): Boolean;
begin
  Result := False;
  if FDbgProcess = nil
  then begin
    FDbgProcess := TProcessUTF8.Create(nil);
    try
      FDbgProcess.ParseCmdLine(ExternalDebugger + ' ' + AOptions);
      FDbgProcess.Options:= [poUsePipes, poNoConsole, poStdErrToOutPut, poNewProcessGroup];
      {$if defined(windows) and not defined(wince)}
      // under win9x and winMe should be created with console,
      // otherwise no break can be sent.
      if Win32MajorVersion <= 4 then
        FDbgProcess.Options:= [poUsePipes, poNewConsole, poStdErrToOutPut, poNewProcessGroup];
      {$endif windows}
      FDbgProcess.ShowWindow := swoNone;
      FDbgProcess.Environment:=DebuggerEnvironment;
    except
      FreeAndNil(FDbgProcess);
    end;
  end;
  if FDbgProcess = nil then exit;

  if not FDbgProcess.Running
  then begin
    try
      FDbgProcess.Execute;
      DebugLn('[TCmdLineDebugger] Debug PID: ', IntToStr(FDbgProcess.Handle));
      Result := FDbgProcess.Running;
    except
      on E: Exception do begin
        FOutputBuf := E.Message;
        DebugLn('Exception while executing debugger: ', FOutputBuf);
      end;
    end;
  end;
end;

destructor TCmdLineDebugger.Destroy;
begin
  if (FDbgProcess <> nil) and (FDbgProcess.Running)
  then FDbgProcess.Terminate(0); //TODO: set state ?

  inherited;

  try
    FreeAndNil(FDbgProcess);
  except
    on E: Exception do DebugLn('Exception while freeing debugger: ', E.Message);
  end;
end;

procedure TCmdLineDebugger.Flush;
begin
  if FReading
  then FFlushAfterRead := True
  else FOutputBuf := '';
end;

function TCmdLineDebugger.GetDebugProcessRunning: Boolean;
begin
  Result := (FDbgProcess <> nil) and FDbgProcess.Running;
end;

function TCmdLineDebugger.GetWaiting: Boolean;
begin
  Result := FReading;
end;

function TCmdLineDebugger.LineEndPos(const s: string; out LineEndLen: integer): integer;
var
  n, idx: Integer;
begin
  Result := 0;
  LineEndLen := 0;
  for n := Low(FLineEnds) to High(FLineEnds) do
  begin
    idx := Pos(FLineEnds[n], FOutputBuf);
    if (idx > 0) and ( (idx < Result) or (Result = 0) )
    then begin
      Result := idx;
      LineEndLen := length(FLineEnds[n]);
    end;
  end;
end;

function TCmdLineDebugger.ReadLine(ATimeOut: Integer = -1): String;
begin
  Result := ReadLine(False, ATimeOut);
end;

function TCmdLineDebugger.ReadLine(const APeek: Boolean; ATimeOut: Integer = -1): String;

  function ReadData(const AStream: TStream; var ABuffer: String): Integer;
  var
    S: String;
  begin
    SetLength(S, 1024);
    Result := AStream.Read(S[1], 1024);
    if Result > 0
    then begin
      SetLength(S, Result);
      ABuffer := ABuffer + S;
    end;
  end;

var
  WaitSet: Integer;
  {%H-}LineEndMatch: String;
  LineEndIdx, LineEndLen, PeekCount: Integer;
  CurCallStamp: Int64;
begin
//  WriteLN('[TCmdLineDebugger.GetOutput] Enter');

// TODO: get extra handles to wait for
// TODO: Fix multiple peeks
  Result := '';
  if not DebugProcessRunning then begin
    if FOutputBuf <> '' then begin
      Result := FOutputBuf;
      FOutputBuf := '';
      exit;
    end;
    DoReadError;
    exit;
  end;

  FReadLineTimedOut := False;
  FReadLineWasAbortedByNested := False;
  if FReadLineCallStamp = high(FReadLineCallStamp) then
    FReadLineCallStamp := low(FReadLineCallStamp)
  else
    inc(FReadLineCallStamp);
  CurCallStamp := FReadLineCallStamp;

  if not APeek
  then FPeekOffset := 0;
  FReading := True;
  PeekCount := 0;
  repeat
    if FOutputBuf <> ''
    then begin
      LineEndIdx := LineEndPos(FOutputBuf, LineEndLen);

      if LineEndIdx > 0
      then begin
        Dec(LineEndIdx);
        Result := Copy(FOutputBuf, 1, LineEndIdx);
        if APeek
        then begin
          if PeekCount = FPeekOffset
          then Inc(FPeekOffset)
          else begin
            Inc(PeekCount);
            Continue;
          end;
        end
        else Delete(FOutputBuf, 1, LineEndIdx + LineEndLen);

        DoDbgOutput(Result);
        Break;
      end;
    end;

    if FReadLineTimedOut
    then break;
    if FDbgProcess.Output = nil then begin
      DoReadError;
      break;
    end;

    WaitSet := WaitForHandles([FDbgProcess.Output.Handle], ATimeOut);

    if CurCallStamp <> FReadLineCallStamp then begin
      // nested call: return empty, even if data exists
      FReadLineWasAbortedByNested := True; // this is true for all outer calls too.
      break;
    end;

    if (ATimeOut = 0)
    then FReadLineTimedOut := True;

    if (WaitSet = 0) and not FReadLineTimedOut
    then begin
      SmartWriteln('[TCmdLineDebugger.Getoutput] Error waiting ');
      DoReadError;
      Break;
    end;

    if  ((WaitSet and 1) <> 0)
    and DebugProcessRunning
    and (ReadData(FDbgProcess.Output, FOutputBuf) > 0)
    then Continue; // start lineend search

(*
    if ((WaitSet and 2) <> 0) and (FTargetProcess <> nil)
    then begin
      Count := ReadData(FTargetProcess.Output, FTargetOutputBuf);
      if Count > 0
      then while True do
      begin
        Line := StripLN(GetLine(FTargetOutputBuf));
        if Line = '' then Break;
        DoOutput(Line);
      end;
    end;
*)
  {$IFDEF VerboseIDEToDo}{$message warning condition should also check end-of-file reached for process output stream}{$ENDIF}
  until not DebugProcessRunning and (Length(FOutputBuf) = 0);

  FReading := False;
  if FFlushAfterRead
  then FOutputBuf := '';
  FFlushAfterRead := False;

  if not( FReadLineTimedOut and (Result = '') ) then begin
    if ((DBG_CMD_ECHO_FULL <> nil) and (DBG_CMD_ECHO_FULL^. Enabled))
    then debugln(DBG_CMD_ECHO_FULL, '<< TCmdLineDebugger.ReadLn "',Result,'"')
    else if (length(Result) < 300)
    then debugln(DBG_CMD_ECHO, '<< TCmdLineDebugger.ReadLn "',Result,'"')
    else debugln(DBG_CMD_ECHO, ['<< TCmdLineDebugger.ReadLn "',copy(Result, 1, 200), '" ..(',length(Result)-250,').. "',copy(Result, length(Result)-99, 100),'"']);
  end;
end;

procedure TCmdLineDebugger.SendCmdLn(const ACommand: String); overload;
var
  LE: string[2];
begin
  if (DBG_CMD_ECHO_FULL <> nil) and (DBG_CMD_ECHO_FULL^.Enabled)
  then debugln(DBG_CMD_ECHO_FULL, '>> TCmdLineDebugger.SendCmdLn "',ACommand,'"')
  else debugln(DBG_CMD_ECHO,      '>> TCmdLineDebugger.SendCmdLn "',ACommand,'"');

  if DebugProcessRunning
  then begin
    DoDbgOutput('<' + ACommand + '>');
    if ACommand <> ''
    then FDbgProcess.Input.Write(ACommand[1], Length(ACommand));
    // store LineEnding in local variable, so the same statement can be used
    // for windows and *nix (1 or 2 character line ending)
    LE := LineEnding;
    FDbgProcess.Input.Write(LE[1], Length(LE));
  end
  else begin
    DebugLn('[TCmdLineDebugger.SendCmdLn] Unable to send <', ACommand, '>. No process running.');
    DoWriteError;
  end;
end;

procedure TCmdLineDebugger.SendCmdLn(const ACommand: String; Values: array of const);
begin
  SendCmdLn(Format(ACommand, Values));
end;

procedure TCmdLineDebugger.SetLineEnds(ALineEnds: TStringDynArray);
begin
  if Length(ALineEnds) = 0
  then begin
    SetLength(FLineEnds, 1);
    FLineEnds[0] := LineEnding;
  end
  else FLineEnds := ALineEnds;
end;

function TCmdLineDebugger.ReadLineTimedOut: Boolean;
begin
  Result := FReadLineTimedOut;
end;

procedure TCmdLineDebugger.AbortReadLine;
begin
  inc(FReadLineCallStamp);
end;

procedure TCmdLineDebugger.TestCmd(const ACommand: String);
begin
  SendCmdLn(ACommand);
end;

class function TCmdLineDebugger.CanExternalDebugSymbolsFile: boolean;
begin
  Result:=true;
end;

initialization
  DBG_CMD_ECHO      := DebugLogger.RegisterLogGroup('DBG_CMD_ECHO' {$IF defined(DBG_VERBOSE) or defined(DBG_CMD_ECHO)} , True {$ENDIF} );
  DBG_CMD_ECHO_FULL := DebugLogger.RegisterLogGroup('DBG_CMD_ECHO_FULL' {$IF defined(DBG_VERBOSE_FULL_DATA) or defined(DBG_CMD_ECHO_FULL)} , True {$ENDIF} );

end.


// +build amd64,!gccgo

#include "textflag.h"

// func rdtscpAsm() uint64
TEXT ·rdtscpAsm(SB),NOSPLIT,$0-8
	BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
	SHLQ $32, DX
	ADDQ DX, AX
	MOVQ AX, ret+0(FP)
	RET

// func rdtscAsm() uint64
TEXT ·rdtscAsm(SB),NOSPLIT,$0-8
	RDTSC
	SHLQ $32, DX
	ADDQ DX, AX
	MOVQ AX, ret+0(FP)
	RET

// func ·cpuidAsm(op, op2 uint32) (eax, ebx, ecx, edx uint32)
TEXT ·cpuidAsm(SB),NOSPLIT,$8-24
	MOVL  op1+0(FP), AX
	MOVL  op2+4(FP), CX
	CPUID
	MOVL  AX, eax+8(FP)
	MOVL  BX, ebx+12(FP)
	MOVL  CX, ecx+16(FP)
	MOVL  DX, edx+20(FP)
	RET


# Loki Comparators

>Comparators (in javascript and loki) are functions which accept two values and return :
>- 0 : if the first and second are considered equal
>- -1 : if the first is less than the second
>- 1 : if the first is greater than the second

## Overview
Since comparators now play a important role in customizing and fine-tuning LokiDB, this page will attempt to summarize the reasoning behind this increased structuring.

In the previous version of LokiDB (LokiJS), various components such as find ops, BinaryIndices and sorting shared a common set of helper functions serving as a common comparator. This arbitrary co-mingling inadvertently became standard as it would have been too much of a breaking change to decouple that later on (although some attempts to provide additional options were later added).

In LokiDB, to support decoupling these various components, we have established a 'ComparatorMap' which is a collection of named comparator functions.

LokiDB utilizes these comparators for :
- Unindexed sorting
- Used with [RangedIndexes](./ranged_indexes.md) (such as our provided "avl" index) for higher performance filtering.
- Can be optionally leveraged within unindexed find() operations (like $eq, $gt, $gte, $lt, $lte, $bewtween) when using our new 'ComparatorOperatorPackage' (See [operators.md](./operators.md)).

## Why would I need more than the default "js" comparator?

Out of the box, LokiDB provides some base comparators :
- "js" (default) : fastest, pure javascript with strict equality for $eq
- "abstract-js": fast, pure javascript with loose equality
- "abstract-date": can be used for comparing dates in various formats
- "loki" comparators: slower, safer for mixed datatypes in ranged indexes.  This more closely resembles the comparator functionality which existed in LokiJS.
- (possible additon) "string-insensitive" : could be used for comparing, sorting strings with case insensitivity.

Individual comparators can be selected from the above to be used for indexing, sorting, or filtering... whichever makes the most sense from a performance, functional, or type safety perspective.

## Custom, user-defined comparators
If the various 'built-in' comparators don't fit a particular use case,  users can define their own comparator functions and add to (or override) comparators within the ComparatorMap.  They can then be leveraged in various places for sorting, indexing, filtering.  This configuration is done when instancing your Loki database instance, within the constructor.

An Example of this might look like the following :
```javascript
// define comparator function
let myComparator = (a, b) => {
   if (a === b) return 0;
   if (a > b) return 1;
   return -1;
}

// pass in any number of comparators to add to or override those aleady registered
let db = new Loki("test.db", {
   comparatorMap: {
      "MyComparator": myComparator
   }
});
```

Having configured the above example, you might use them within collections, such as this simplesort() operation :
```javascript
// when instancing a new collection, you can specify this to override the default "js" unindexedSortComparator.
let coll = db.addCollection("users", {
   unindexedSortComparator: "MyComparator"
});

coll.insert(someDocuments);

// this will use the currently configured default unindexedSortComparator
let result = coll.simplesort("name");

// simplesort also allows you to override the default comparator in options
let reversed = coll.simplesort("name", {
   desc: true,
   unindexedSortComparator: "string-insensitive"
});

```

Other uses for utilizing the named comparators include defining your own [LokiOperatorPackage](./operators.md) and using within [RangedIndexes](./ranged_indexes.md).  Those topics will go into details and give code samples within their respective pages.

Transform steps such as 'find' filtering and simplesort steps will also be able to leverage these, either explicitly or via options.

## Summary
In the future, anywhere where these comparators can be leveraged, we will likely do so... to allow the ability for the user to customize and optimize LokiDB to best work with their data.

Registration of user defined comparators needs to done every time you re-instance the database.  Only the names of the comparators are persisted within your saved/serialized database.  While this is an additional responsibility, it also allows experimentation to determine the performance (for tuning).

**_Warning_** : If a user defined comparator is paired with a range index, it is important that the comparator not change over time.  Ranged indexes (typically) persist themselves as ordered represenations and future relative calculations of re-organization across inserts, updates, and removes depend on consistent comparison results.

If your custom comparator is **_not_** used by a ranged index, you are free to experiment and refine the comparison results over time to reflect and refine the functional output of various LokiDB tasks.


/*
 *************************************************************************
 * Ralink Tech Inc.
 * 5F., No.36, Taiyuan St., Jhubei City,
 * Hsinchu County 302,
 * Taiwan, R.O.C.
 *
 * (c) Copyright 2002-2010, Ralink Technology, Inc.
 *
 * This program is free software; you can redistribute it and/or modify  *
 * it under the terms of the GNU General Public License as published by  *
 * the Free Software Foundation; either version 2 of the License, or     *
 * (at your option) any later version.                                   *
 *                                                                       *
 * This program is distributed in the hope that it will be useful,       *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 * GNU General Public License for more details.                          *
 *                                                                       *
 * You should have received a copy of the GNU General Public License     *
 * along with this program; if not, write to the                         *
 * Free Software Foundation, Inc.,                                       *
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                       *
 *************************************************************************/

#include "crypt_arc4.h"

/*
========================================================================
Routine Description:
    ARC4 initialize the key block

Arguments:
    pARC4_CTX        Pointer to ARC4 CONTEXT
    Key              Cipher key, it may be 16, 24, or 32 bytes (128, 192, or 256 bits)
    KeyLength        The length of cipher key in bytes

========================================================================
*/
VOID ARC4_INIT (
    IN ARC4_CTX_STRUC *pARC4_CTX,
    IN PUCHAR pKey,
	IN UINT KeyLength)
{
    UINT BlockIndex = 0, SWAPIndex = 0, KeyIndex = 0;
    UINT8 TempValue = 0;

    /*Initialize the block value*/
    pARC4_CTX->BlockIndex1 = 0;
    pARC4_CTX->BlockIndex2 = 0;
    for (BlockIndex = 0; BlockIndex < ARC4_KEY_BLOCK_SIZE; BlockIndex++)
        pARC4_CTX->KeyBlock[BlockIndex] = (UINT8) BlockIndex;

    /*Key schedule*/
    for (BlockIndex = 0; BlockIndex < ARC4_KEY_BLOCK_SIZE; BlockIndex++)
    {
        TempValue = pARC4_CTX->KeyBlock[BlockIndex];
        KeyIndex = BlockIndex % KeyLength;
        SWAPIndex = (SWAPIndex + TempValue + pKey[KeyIndex]) & 0xff;
        pARC4_CTX->KeyBlock[BlockIndex] = pARC4_CTX->KeyBlock[SWAPIndex];
        pARC4_CTX->KeyBlock[SWAPIndex] = TempValue;
    } /* End of for */

} /* End of ARC4_INIT */

/*
========================================================================
Routine Description:
    ARC4 encryption/decryption

Arguments:
    pARC4_CTX       Pointer to ARC4 CONTEXT
    InputText       Input text
    InputTextLength The length of input text in bytes

Return Value:
    OutputBlock       Return output text
 ========================================================================
*/
VOID ARC4_Compute (
    IN ARC4_CTX_STRUC *pARC4_CTX,
    IN UINT8 InputBlock[],
    IN UINT InputBlockSize,
    OUT UINT8 OutputBlock[])
{
    UINT InputIndex = 0;
    UINT8 TempValue = 0;

    for (InputIndex = 0; InputIndex < InputBlockSize; InputIndex++)
    {
        pARC4_CTX->BlockIndex1 = (pARC4_CTX->BlockIndex1 + 1) & 0xff;
        TempValue = pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex1];
        pARC4_CTX->BlockIndex2 = (pARC4_CTX->BlockIndex2 + TempValue) & 0xff;

        pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex1] = pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex2];
        pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex2] = TempValue;

        TempValue = (TempValue + pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex1]) & 0xff;
        OutputBlock[InputIndex] = InputBlock[InputIndex]^pARC4_CTX->KeyBlock[TempValue];

    } /* End of for */
} /* End of ARC4_Compute */

/*
========================================================================
Routine Description:
    Discard the key length

Arguments:
    pARC4_CTX   Pointer to ARC4 CONTEXT
    Length      Discard the key length

========================================================================
*/
VOID ARC4_Discard_KeyLength (
    IN ARC4_CTX_STRUC *pARC4_CTX,
    IN UINT Length)
{
    UINT Index = 0;
    UINT8 TempValue = 0;

    for (Index = 0; Index < Length; Index++)
    {
        pARC4_CTX->BlockIndex1 = (pARC4_CTX->BlockIndex1 + 1) & 0xff;
        TempValue = pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex1];
        pARC4_CTX->BlockIndex2 = (pARC4_CTX->BlockIndex2 + TempValue) & 0xff;

        pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex1] = pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex2];
        pARC4_CTX->KeyBlock[pARC4_CTX->BlockIndex2] = TempValue;
    } /* End of for */

} /* End of ARC4_Discard_KeyLength */


/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.common.lucene.search.morelikethis;

import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.analysis.MockTokenizer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.mlt.MoreLikeThis;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.elasticsearch.test.ESTestCase;

import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;

public class XMoreLikeThisTests extends ESTestCase {
    private void addDoc(RandomIndexWriter writer, String[] texts) throws IOException {
        Document doc = new Document();
        for (String text : texts) {
            doc.add(newTextField("text", text, Field.Store.YES));
        }
        writer.addDocument(doc);
    }

    public void testTopN() throws Exception {
        int numDocs = 100;
        int topN = 25;

        // add series of docs with terms of decreasing df
        Directory dir = newDirectory();
        RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
        for (int i = 0; i < numDocs; i++) {
            addDoc(writer, generateStrSeq(0, i + 1));
        }
        IndexReader reader = writer.getReader();
        writer.close();

        // setup MLT query
        MoreLikeThis mlt = new MoreLikeThis(reader);
        mlt.setAnalyzer(new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false));
        mlt.setMaxQueryTerms(topN);
        mlt.setMinDocFreq(1);
        mlt.setMinTermFreq(1);
        mlt.setMinWordLen(1);
        mlt.setFieldNames(new String[]{"text"});

        // perform MLT query
        String likeText = "";
        for (String text : generateStrSeq(0, numDocs)) {
            likeText += text + " ";
        }
        BooleanQuery query = (BooleanQuery) mlt.like("text", new StringReader(likeText));

        // check best terms are topN of highest idf
        List<BooleanClause> clauses = query.clauses();
        assertEquals("Expected" + topN + "clauses only!", topN, clauses.size());

        Term[] expectedTerms = new Term[topN];
        int idx = 0;
        for (String text : generateStrSeq(numDocs - topN, topN)) {
            expectedTerms[idx++] = new Term("text", text);
        }
        for (BooleanClause clause : clauses) {
            Term term = ((TermQuery) clause.getQuery()).getTerm();
            assertTrue(Arrays.asList(expectedTerms).contains(term));
        }

        // clean up
        reader.close();
        dir.close();
    }

    private String[] generateStrSeq(int from, int size) {
        String[] generatedStrings = new String[size];
        for (int i = 0; i < generatedStrings.length; i++) {
            generatedStrings[i] = String.valueOf(from + i);
        }
        return generatedStrings;
    }

}


#
# Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.  Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#

# This file stores localized messages for the Xerces JAXP Datatype API implementation.
#
# The messages are arranged in key and value tuples in a ListResourceBundle.

BadMessageKey = Die zum Meldungsschl\u00FCssel geh\u00F6rige Fehlermeldung kann nicht gefunden werden.
FormatFailed = Beim Formatieren der folgenden Meldung ist ein interner Fehler aufgetreten:\n

FieldCannotBeNull={0} kann nicht mit "Null"-Parameter aufgerufen werden.
UnknownField={0} mit unbekanntem Feld aufgerufen:{1}
#There are two similar keys 'InvalidXMLGreogorianCalendarValue' . Suffix (year, month) has been added and are used as per the context.
InvalidXGCValue-milli=Jahr = {0}, Monat = {1}, Tag = {2}, Stunde= {3}, Minute = {4}, Sekunde = {5}, fractionalSecond = {6}, Zeitzone = {7} ist keine g\u00FCltige Darstellung eines XML-Wertes f\u00FCr einen gregorianischen Kalender.
#There are two similar keys 'InvalidXMLGreogorianCalendarValue' . Suffix (year, month) has been added and are used as per the context.
InvalidXGCValue-fractional=Jahr = {0}, Monat = {1}, Tag = {2}, Stunde= {3}, Minute = {4}, Sekunde = {5}, fractionalSecond = {6}, Zeitzone = {7} ist keine g\u00FCltige Darstellung eines XML-Wertes f\u00FCr einen gregorianischen Kalender.

InvalidXGCFields=Ung\u00FCltige Gruppe von Feldern f\u00FCr XMLGregorianCalendar festgelegt

InvalidFractional=Ung\u00FCltiger Wert {0} f\u00FCr Sekundenbruchteil.

#XGC stands for XML Gregorian Calendar
InvalidXGCRepresentation="{0}" ist keine g\u00FCltige Darstellung eines XML-Wertes f\u00FCr einen gregorianischen Kalender.

InvalidFieldValue=Ung\u00FCltiger Wert {0} f\u00FCr Feld {1}.

NegativeField= Feld {0} ist negativ

AllFieldsNull=Alle Felder (javax.xml.datatype.DatatypeConstants.Field) sind null.

TooLarge={0}-Wert "{1}" zu gro\u00DF, um von dieser Implementierung unterst\u00FCtzt zu werden


namespace Snow.ViewModels
{
    using System;
    using System.ComponentModel;

    /// <summary>
    /// Helper interface used to hide the base <see cref="Object"/>  members from the fluent API to make it much cleaner
    /// in Visual Studio intellisense.
    /// </summary>
    /// <remarks>Created by Daniel Cazzulino http://www.clariusconsulting.net/blogs/kzu/archive/2008/03/10/58301.aspx</remarks>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public interface IHideObjectMembers
    {
        /// <summary>
        /// Hides the <see cref="Equals"/> method.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        bool Equals(object obj);

        /// <summary>
        /// Hides the <see cref="GetHashCode"/> method.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        int GetHashCode();

        /// <summary>
        /// Hides the <see cref="GetType"/> method.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        Type GetType();

        /// <summary>
        /// Hides the <see cref="ToString"/> method.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        string ToString();
    }
}


/*
 * Copyright (C) 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.sites.liberation.parsers;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.gdata.data.spreadsheet.Field;

import org.w3c.dom.Element;

/**
 * Implements FieldParser to parse an html element for a {@link Field}.
 *
 * @author bsimon@google.com (Benjamin Simon)
 */
final class FieldParserImpl implements FieldParser {

  @Override
  public Field parseField(Element element) {
    checkNotNull(element);
    Field field = new Field();
    field.setIndex(element.getAttribute("title"));
    String value = element.getTextContent();
    if (value.equals("\u2713")) {
      value = "on";
    }
    field.setValue(value);
    return field;
  }
}


namespace ClassLib088
{
    public class Class071
    {
        public static string Property => "ClassLib088";
    }
}


Twig Extension for Yii 2

This extension provides a `ViewRender` that would allow you to use [Twig](http://twig.sensiolabs.org/) view template engine
with [Yii framework 2.0](http://www.yiiframework.com).

For license information check the [LICENSE](LICENSE.md)-file.

Documentation is at [docs/guide/README.md](docs/guide/README.md).

[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-twig/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-twig)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-twig/downloads.png)](https://packagist.org/packages/yiisoft/yii2-twig)
[![Build Status](https://travis-ci.com/yiisoft/yii2-twig.svg?branch=master)](https://travis-ci.com/yiisoft/yii2-twig)

Installation
------------

The preferred way to install this extension is through [composer](http://getcomposer.org/download/):

```
php composer.phar require --prefer-dist yiisoft/yii2-twig
```

Usage
-----

To use this extension, configure it as described in [documentation](docs/guide/installation.md#configuring-application).

Requirements
------------

* PHP 7.2 - 7.4


//
// disass.c - disassembly routines for Xtensa
//
// $Id: //depot/rel/Foxhill/dot.8/Xtensa/OS/hal/disass.c#1 $

// Copyright (c) 2004-2013 Tensilica Inc.
//
// 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 <xtensa/hal.h>
#include <xtensa/config/core.h>

#ifdef XCHAL_OP0_FORMAT_LENGTHS
extern const unsigned char Xthal_op0_format_lengths[16];
#endif
extern const unsigned char Xthal_byte0_format_lengths[256];

#if defined(__SPLIT__op0_format_lengths)

/*  Instruction length in bytes as function of its op0 field (first nibble):  */
#ifdef XCHAL_OP0_FORMAT_LENGTHS
const unsigned char Xthal_op0_format_lengths[16] = {
  XCHAL_OP0_FORMAT_LENGTHS
};
#endif

#elif defined(__SPLIT__byte0_format_lengths)

/*  Instruction length in bytes as function of its first byte:  */
const unsigned char Xthal_byte0_format_lengths[256] = {
  XCHAL_BYTE0_FORMAT_LENGTHS
};

#elif defined(__SPLIT__disassemble_size)

//
// Disassembly is currently not supported in xtensa hal.
//

int xthal_disassemble_size( unsigned char *instr_buf )
{
#ifdef XCHAL_OP0_FORMAT_LENGTHS
    /*  Extract op0 field of instruction (first nibble used for decoding):  */
# if XCHAL_HAVE_BE
    int op0 = ((*instr_buf >> 4) & 0xF);
# else
    int op0 = (*instr_buf & 0xF);
# endif
    /*return (op0 & 8) ? 2 : 3;*/	/* simple case only works consistently on older hardware */
    return Xthal_op0_format_lengths[op0];
#else
    return Xthal_byte0_format_lengths[*instr_buf];
#endif
}

#elif defined(__SPLIT__disassemble)

/*
 *  Note:  we make sure to avoid the use of library functions,
 *  to minimize dependencies.
 */
int xthal_disassemble(
    unsigned char *instr_buffer, /* the address of the instructions */
    void *tgt_address,		 /* where the instruction is to be */
    char *buffer,		 /* where the result goes */
    unsigned buflen,		 /* size of buffer */
    unsigned options		 /* what to display */
    )
{
#define OUTC(c)	do{ if( p < endp ) *p = c; p++; }while(0)
    int i, n;
    char *p = buffer, *endp = buffer + buflen - 1;
    /*static char *ret = " decoding not supported";*/
    static const char _hexc[16] = "0123456789ABCDEF";

    n = xthal_disassemble_size( instr_buffer );

    if( options & XTHAL_DISASM_OPT_ADDR ) {
	unsigned addr = (unsigned)tgt_address;
	for( i = 0; i < 8; i++ ) {
	    OUTC( _hexc[(addr >> 28) & 0xF] );
	    addr <<= 4;
	}
    }

    if( options & XTHAL_DISASM_OPT_OPHEX ) {
	if( p > buffer )
	    OUTC( ' ' );
	for( i = 0; i < 3; i++ ) {
	    if( i < n ) {
		OUTC( _hexc[(*instr_buffer >> 4) & 0xF] );
		OUTC( _hexc[*instr_buffer++ & 0xF] );
	    } else {
		OUTC( ' ' );
		OUTC( ' ' );
	    }
	    OUTC( ' ' );
	}
    }

    if( options & XTHAL_DISASM_OPT_OPCODE ) {
	if( p > buffer )
	    OUTC( ' ' );
	OUTC( '?' );
	OUTC( '?' );
	OUTC( '?' );
	OUTC( ' ' );
	OUTC( ' ' );
	OUTC( ' ' );
	OUTC( ' ' );
    }

    if( options & XTHAL_DISASM_OPT_PARMS ) {
	if( p > buffer )
	    OUTC( ' ' );
	OUTC( '?' );
	OUTC( '?' );
	OUTC( '?' );
    }

    if( p < endp )
	*p = 0;
    else if( buflen > 0 )
	*endp = 0;

    return p - buffer;	/* return length needed, even if longer than buflen */
}

#undef OUTC

#endif /*split*/


<?php
/**
 * Kazakh (Қазақша) specific code.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 * @ingroup Language
 */

/**
 * Kazakh (Қазақша)
 *
 * @ingroup Language
 */
// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
class LanguageKk_cyrl extends Language {
	# Convert from the nominative form of a noun to some other case
	# Invoked with {{GRAMMAR:case|word}}

	/**
	 * Cases: genitive, dative, accusative, locative, ablative, comitative + possessive forms
	 *
	 * @param string $word
	 * @param string $case
	 *
	 * @return string
	 */
	protected function convertGrammarKk_cyrl( $word, $case ) {
		global $wgGrammarForms;
		if ( isset( $wgGrammarForms['kk-kz'][$case][$word] ) ) {
			return $wgGrammarForms['kk-kz'][$case][$word];
		}
		if ( isset( $wgGrammarForms['kk-cyrl'][$case][$word] ) ) {
			return $wgGrammarForms['kk-cyrl'][$case][$word];
		}
		// Set up some constants...
		// Vowels in last syllable
		$frontVowels = [ "е", "ө", "ү", "і", "ә", "э", "я", "ё", "и" ];
		$backVowels = [ "а", "о", "ұ", "ы" ];
		$allVowels = [ "е", "ө", "ү", "і", "ә", "э", "а", "о", "ұ", "ы", "я", "ё", "и" ];
		// Preceding letters
		$Nasals = [ "м", "н", "ң" ];
		$Sonants = [ "и", "й", "л", "р", "у", "ю" ];
		$Consonants = [ "п", "ф", "к", "қ", "т", "ш", "с", "х", "ц", "ч", "щ", "б", "в", "г", "д" ];
		$Sibilants = [ "ж", "з" ];
		$Sonorants = [ "и", "й", "л", "р", "у", "ю", "м", "н", "ң", "ж", "з" ];

		// Possessives
		$firstPerson = [ "м", "ң" ]; // 1st singular, 2nd unformal
		$secondPerson = [ "з" ]; // 1st plural, 2nd formal
		$thirdPerson = [ "ы", "і" ]; // 3rd

		list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels );

		// Now convert the word
		switch ( $case ) {
			case "dc1":
			case "genitive": # ilik
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "тің";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "тың";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Nasals ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "нің";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ның";
					}
				} elseif ( in_array( $wordEnding, $Sonants ) || in_array( $wordEnding, $Sibilants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "дің";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "дың";
					}
				}
				break;
			case "dc2":
			case "dative": # barıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ке";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "қа";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ге";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ға";
					}
				}
				break;
			case "dc21":
			case "possessive dative": # täweldık + barıs
				if ( in_array( $wordEnding, $firstPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "е";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "а";
					}
				} elseif ( in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ге";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ға";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "не";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "на";
					}
				}
				break;
			case "dc3":
			case "accusative": # tabıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ті";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ты";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ні";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ны";
					}
				} elseif ( in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ді";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ды";
					}
				}
				break;
			case "dc31":
			case "possessive accusative": # täweldık + tabıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ді";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ды";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
						$word .= "н";
				}
				break;
			case "dc4":
			case "locative": # jatıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "те";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "та";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "де";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "да";
					}
				}
				break;
			case "dc41":
			case "possessive locative": # täweldık + jatıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "де";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "да";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "нде";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "нда";
					}
				}
				break;
			case "dc5":
			case "ablative": # şığıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "тен";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "тан";
					}
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Sonants )
					|| in_array( $wordEnding, $Sibilants )
				) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ден";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "дан";
					}
				} elseif ( in_array( $wordEnding, $Nasals ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "нен";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "нан";
					}
				}
				break;
			case "dc51":
			case "possessive ablative": # täweldık + şığıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "нен";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "нан";
					}
				} elseif ( in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ден";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "дан";
					}
				}
				break;
			case "dc6":
			case "comitative": # kömektes
				if ( in_array( $wordEnding, $Consonants ) ) {
						$word .= "пен";
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Nasals )
					|| in_array( $wordEnding, $Sonants )
				) {
						$word .= "мен";
				} elseif ( in_array( $wordEnding, $Sibilants ) ) {
						$word .= "бен";
				}
				break;
			case "dc61":
			case "possessive comitative": # täweldık + kömektes
				if ( in_array( $wordEnding, $Consonants ) ) {
						$word .= "пенен";
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Nasals )
					|| in_array( $wordEnding, $Sonants )
				) {
						$word .= "менен";
				} elseif ( in_array( $wordEnding, $Sibilants ) ) {
						$word .= "бенен";
				}
				break;
			default: # dc0 #nominative #ataw
		}
		return $word;
	}

	/**
	 * @param string $word
	 * @param string $case
	 * @return string
	 */
	protected function convertGrammarKk_latn( $word, $case ) {
		global $wgGrammarForms;
		if ( isset( $wgGrammarForms['kk-tr'][$case][$word] ) ) {
			return $wgGrammarForms['kk-tr'][$case][$word];
		}
		if ( isset( $wgGrammarForms['kk-latn'][$case][$word] ) ) {
			return $wgGrammarForms['kk-latn'][$case][$word];
		}
		// Set up some constants...
		// Vowels in last syllable
		$frontVowels = [ "e", "ö", "ü", "i", "ä", "é" ];
		$backVowels = [ "a", "o", "u", "ı" ];
		$allVowels = [ "e", "ö", "ü", "i", "ä", "é", "a", "o", "u", "ı" ];
		// Preceding letters
		$Nasals = [ "m", "n", "ñ" ];
		$Sonants = [ "ï", "y", "ý", "l", "r", "w" ];
		$Consonants = [ "p", "f", "k", "q", "t", "ş", "s", "x", "c", "ç", "b", "v", "g", "d" ];
		$Sibilants = [ "j", "z" ];
		$Sonorants = [ "ï", "y", "ý", "l", "r", "w", "m", "n", "ñ", "j", "z" ];

		// Possessives
		$firstPerson = [ "m", "ñ" ]; // 1st singular, 2nd unformal
		$secondPerson = [ "z" ]; // 1st plural, 2nd formal
		$thirdPerson = [ "ı", "i" ]; // 3rd

		list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels );

		// Now convert the word
		switch ( $case ) {
			case "dc1":
			case "genitive": # ilik
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "tiñ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "tıñ";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Nasals ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "niñ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "nıñ";
					}
				} elseif ( in_array( $wordEnding, $Sonants ) || in_array( $wordEnding, $Sibilants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "diñ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "dıñ";
					}
				}
				break;
			case "dc2":
			case "dative": # barıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ke";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "qa";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ge";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ğa";
					}
				}
				break;
			case "dc21":
			case "possessive dative": # täweldık + barıs
				if ( in_array( $wordEnding, $firstPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "e";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "a";
					}
				} elseif ( in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ge";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ğa";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ne";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "na";
					}
				}
				break;
			case "dc3":
			case "accusative": # tabıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ti";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "tı";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ni";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "nı";
					}
				} elseif ( in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "di";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "dı";
					}
				}
				break;
			case "dc31":
			case "possessive accusative": # täweldık + tabıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "di";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "dı";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
						$word .= "n";
				}
				break;
			case "dc4":
			case "locative": # jatıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "te";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ta";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "de";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "da";
					}
				}
				break;
			case "dc41":
			case "possessive locative": # täweldık + jatıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "de";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "da";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "nde";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "nda";
					}
				}
				break;
			case "dc5":
			case "ablative": # şığıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ten";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "tan";
					}
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Sonants )
					|| in_array( $wordEnding, $Sibilants )
				) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "den";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "dan";
					}
				} elseif ( in_array( $wordEnding, $Nasals ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "nen";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "nan";
					}
				}
				break;
			case "dc51":
			case "possessive ablative": # täweldık + şığıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "nen";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "nan";
					}
				} elseif ( in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "den";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "dan";
					}
				}
				break;
			case "dc6":
			case "comitative": # kömektes
				if ( in_array( $wordEnding, $Consonants ) ) {
						$word .= "pen";
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Nasals )
					|| in_array( $wordEnding, $Sonants )
				) {
						$word .= "men";
				} elseif ( in_array( $wordEnding, $Sibilants ) ) {
						$word .= "ben";
				}
				break;
			case "dc61":
			case "possessive comitative": # täweldık + kömektes
				if ( in_array( $wordEnding, $Consonants ) ) {
						$word .= "penen";
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Nasals )
					|| in_array( $wordEnding, $Sonants )
				) {
						$word .= "menen";
				} elseif ( in_array( $wordEnding, $Sibilants ) ) {
						$word .= "benen";
				}
				break;
			default: # dc0 #nominative #ataw
		}
		return $word;
	}

	/**
	 * @param string $word
	 * @param string $case
	 * @return string
	 */
	protected function convertGrammarKk_arab( $word, $case ) {
		global $wgGrammarForms;
		if ( isset( $wgGrammarForms['kk-cn'][$case][$word] ) ) {
			return $wgGrammarForms['kk-cn'][$case][$word];
		}
		if ( isset( $wgGrammarForms['kk-arab'][$case][$word] ) ) {
			return $wgGrammarForms['kk-arab'][$case][$word];
		}
		// Set up some constants...
		// Vowels in last syllable
		$frontVowels = [ "ە", "ٶ", "ٷ", "ٸ", "ٵ", "ە" ];
		$backVowels = [ "ا", "و", "ۇ", "ى" ];
		$allVowels = [ "ە", "ٶ", "ٷ", "ٸ", "ٵ", "ە", "ا", "و", "ۇ", "ى" ];
		// Preceding letters
		$Nasals = [ "م", "ن", "ڭ" ];
		$Sonants = [ "ي", "ي", "ل", "ر", "ۋ" ];
		$Consonants = [ "پ", "ف", "ك", "ق", "ت", "ش", "س", "ح", "تس", "چ", "ب", "ۆ", "گ", "د" ];
		$Sibilants = [ "ج", "ز" ];
		$Sonorants = [ "ي", "ي", "ل", "ر", "ۋ", "م", "ن", "ڭ", "ج", "ز" ];

		// Possessives
		$firstPerson = [ "م", "ڭ" ]; // 1st singular, 2nd unformal
		$secondPerson = [ "ز" ]; // 1st plural, 2nd formal
		$thirdPerson = [ "ى", "ٸ" ]; // 3rd

		list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels );

		// Now convert the word
		switch ( $case ) {
			case "dc1":
			case "genitive": # ilik
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "تٸڭ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "تىڭ";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Nasals ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "نٸڭ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "نىڭ";
					}
				} elseif ( in_array( $wordEnding, $Sonants ) || in_array( $wordEnding, $Sibilants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دٸڭ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دىڭ";
					}
				}
				break;
			case "dc2":
			case "dative": # barıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "كە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "قا";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "گە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "عا";
					}
				}
				break;
			case "dc21":
			case "possessive dative": # täweldık + barıs
				if ( in_array( $wordEnding, $firstPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ا";
					}
				} elseif ( in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "گە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "عا";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "نە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "نا";
					}
				}
				break;
			case "dc3":
			case "accusative": # tabıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "تٸ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "تى";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "نٸ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "نى";
					}
				} elseif ( in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دٸ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دى";
					}
				}
				break;
			case "dc31":
			case "possessive accusative": # täweldık + tabıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دٸ";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دى";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
						$word .= "ن";
				}
				break;
			case "dc4":
			case "locative": # jatıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "تە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "تا";
					}
				} elseif ( in_array( $wordEnding, $allVowels ) || in_array( $wordEnding, $Sonorants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دا";
					}
				}
				break;
			case "dc41":
			case "possessive locative": # täweldık + jatıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دا";
					}
				} elseif ( in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "ندە";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "ندا";
					}
				}
				break;
			case "dc5":
			case "ablative": # şığıs
				if ( in_array( $wordEnding, $Consonants ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "تەن";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "تان";
					}
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Sonants )
					|| in_array( $wordEnding, $Sibilants )
				) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دەن";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دان";
					}
				} elseif ( in_array( $wordEnding, $Nasals ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "نەن";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "نان";
					}
				}
				break;
			case "dc51":
			case "possessive ablative": # täweldık + şığıs
				if ( in_array( $wordEnding, $firstPerson ) || in_array( $wordEnding, $thirdPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "نەن";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "نان";
					}
				} elseif ( in_array( $wordEnding, $secondPerson ) ) {
					if ( in_array( $wordLastVowel, $frontVowels ) ) {
						$word .= "دەن";
					} elseif ( in_array( $wordLastVowel, $backVowels ) ) {
						$word .= "دان";
					}
				}
				break;
			case "dc6":
			case "comitative": # kömektes
				if ( in_array( $wordEnding, $Consonants ) ) {
						$word .= "پەن";
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Nasals )
					|| in_array( $wordEnding, $Sonants )
				) {
						$word .= "مەن";
				} elseif ( in_array( $wordEnding, $Sibilants ) ) {
						$word .= "بەن";
				}
				break;
			case "dc61":
			case "possessive comitative": # täweldık + kömektes
				if ( in_array( $wordEnding, $Consonants ) ) {
						$word .= "پەنەن";
				} elseif ( in_array( $wordEnding, $allVowels )
					|| in_array( $wordEnding, $Nasals )
					|| in_array( $wordEnding, $Sonants )
				) {
						$word .= "مەنەن";
				} elseif ( in_array( $wordEnding, $Sibilants ) ) {
						$word .= "بەنەن";
				}
				break;
			default: # dc0 #nominative #ataw
		}
		return $word;
	}

	/**
	 * @param string $word
	 * @param string[] $allVowels
	 * @return array
	 */
	private function lastLetter( $word, $allVowels ) {
		$lastLetter = [];

		// Put the word in a form we can play with since we're using UTF-8
		$ar = preg_split( '//u', parent::lc( $word ), -1, PREG_SPLIT_NO_EMPTY );

		// Here's an array with the order of the letters in the word reversed
		// so we can find a match quicker *shrug*
		$wordReversed = array_reverse( $ar );

		// Here's the last letter in the word
		$lastLetter[0] = $ar[count( $ar ) - 1];

		// Find the last vowel in the word
		$lastLetter[1] = null;
		foreach ( $wordReversed as $xvalue ) {
			foreach ( $allVowels as $yvalue ) {
				if ( strcmp( $xvalue, $yvalue ) == 0 ) {
					$lastLetter[1] = $xvalue;
					break;
				}
			}
			if ( $lastLetter[1] !== null ) {
				break;
			}
		}

		return $lastLetter;
	}
}