Range.pike 4.91 KB
Newer Older
1 2 3
#pike __REAL_VERSION__
#pragma strict_types

4
//!   Generic lightweight range type.  Supports any values for lower
5 6
//!   and upper boundaries that implement @[lfun::`<()] and @[lfun::`-@()],
//!   and preferrably also cast to @expr{int@} and @expr{string@}.
7 8
//! @note
//!   Can only contain a single contiguous range.
9 10 11 12 13
//! @note
//!   The empty range must be stored as @expr{(Math.inf, -Math.inf)@}
//!   if assigned directly to @[from] and @[till].

constant is_range = 1;
14

15 16
protected typedef int|float|string|object value_type;

17
//!  The lower inclusive boundary.
18
value_type from;
19

20
//!  The upper exclusive boundary.
21
value_type till;
22

23
array(value_type) _encode() {
24 25 26
  return ({from, till});
}

27
void _decode(array(value_type) x) {
28 29 30 31
  from = x[0];
  till = x[1];
}

32 33 34 35 36 37 38
protected int __hash() {
  catch {
    return (int)from ^ (int)till;
  };
  return 0;
}

39
//! @param from
40
//!   Lower inclusive boundary for the range.  Specify no lower-boundary
41 42
//!   by filling in @expr{-Math.inf@}.
//! @param till
43
//!   Upper exclusive boundary for the range.  Specify no upper-boundary
44 45
//!   by filling in @expr{Math.inf@}.
//! @seealso
46
//!   @[Math.inf]
47
protected variant void create(value_type from, value_type till) {
48 49 50 51
  if (from >= till) {
    from = Math.inf;
    till = -Math.inf;
  }
52 53 54
  this::from = from;
  this::till = till;
}
55
protected variant void create(this_program copy) {
56 57 58 59 60 61 62
  from = copy->from;
  till = copy->till;
}
protected variant void create() {
}

//! Difference
63 64 65
protected this_program `-(this_program that) {
  this_program n = this_program(max(from, that->from),
				min(till, that->till));
66
  if (!n)
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
    return this;
  if (till == n->till) {
    n->till = n->from;
    n->from = from;
    return n;
  }
  if (from == n->from) {
    n->from = n->till;
    n->till = till;
    return n;
  }
  error("Result of range difference would not be contiguous\n");
}

//! Union
82
protected this_program `+(this_program that) {
83 84 85
  if (from != ([object]that)->till && ([object]that)->from != till
      && !(this & ([object]that)))
    error("Result of range union would not be contiguous\n");
86 87
  return this_program(min(from, that->from),
		      max(till, that->till));
88 89 90
}

//! Intersection
91 92 93
protected this_program `*(this_program that) {
  return this_program(max(from, that->from),
                      min(till, that->till));
94 95 96
}

//! Overlap: have points in common.
97 98
protected int(0..1) `&(this_program that) {
  return till > that->from && that->till > from;
99 100
}

101
//! Is adjacent to
102 103 104 105 106
//!
//! @fixme
//!   This does NOT seem like a good operator for this operation.
protected int(0..1) `|(this_program that) {
  return till == that->from || from == that->till;
107 108 109
}

//! Strictly left of
110 111
protected int(0..1) `<<(this_program that) {
  return till <= that->from;
112 113 114
}

//! Strictly right of
115 116
protected int(0..1) `>>(this_program that) {
  return from >= that->till;
117 118
}

119 120 121 122 123 124
protected int(0..1) `<(mixed that) {
  return from < ([object]that)->from
    || from == ([object]that)->from && till < ([object]that)->till;
}

protected int(0..1) `==(mixed that) {
125
  return objectp(that) && ([object]that)->is_range
126 127 128
   && from == ([object]that)->from && till == ([object]that)->till;
}

129 130
//! @returns
//!   True if range is empty.
131 132 133
//!
//! @seealso
//!   @[isempty()]
134 135 136 137 138 139
protected inline int(0..1) `!() {
  return from >= till;
}

//! @returns
//!   True if range is empty.
140 141 142
//!
//! @seealso
//!   @[`!()]
143 144 145 146 147 148 149
inline int(0..1) isempty() {
  return !this;
}

//! @param other
//!  Extends the current range to the smallest range which encompasses
//!  itself and all other given ranges.
150 151 152
//!
//! @fixme
//!   This seems like the operation that @expr{`|()@} ought to do.
153
this_program merge(this_program ... other) {
154 155
  from = [object(value_type)]min(from, @other->from);
  till = [object(value_type)]max(till, @other->till);
156 157 158 159 160
  return this;
}

//! @returns
//!  True if this range fully contains another range or element.
161
int(0..1) contains(object(this_program)|value_type other) {
162 163 164 165 166
  return objectp(other) && ([object]other)->is_range
    ? from <= ([object]other)->from && ([object]other)->till <= till
    : from <= other && other < till;
}

167 168
//! @returns
//!   Calculates the value of the interval: @expr{till - from@}.
169
//!   Returns @expr{0@} if the range is empty.
170
mixed interval() {
171
  return !this ? 0 : till - from;
172 173
}

174 175 176 177 178 179 180 181
//! @returns
//!  A string representing the range using SQL-compliant syntax.
final string sql() {
  return !this ? "empty" : sprintf("%c%s,%s)",
    from == -Math.inf ? '(' : '[', from == -Math.inf ? "" : (string)from,
    till == Math.inf ? "" : (string)till);
}

182
protected mixed cast(string to) {
183 184 185 186 187
  if (to!="string") return UNDEFINED;
  if (!this) return "[]";
  return sprintf("[%s..%s]",
                 from != -Math.inf ? (string)from : "",
                 till != Math.inf ? (string)till : "");
188 189 190 191 192 193 194 195 196
}

protected string _sprintf(int fmt, mapping(string:mixed) params) {
  switch (fmt) {
    case 'O': return sprintf("this_program( %s )", (string)this);
    case 's': return (string)this;
  }
  return sprintf(sprintf("%%*%c", fmt), params, 0);
}