@effekt-lang/effekt 0.77.0 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/effekt +0 -0
- package/libraries/common/array.effekt +4 -3
- package/libraries/common/bytearray.effekt +44 -25
- package/libraries/common/char.effekt +12 -5
- package/libraries/common/effekt.effekt +10 -13
- package/libraries/common/list.effekt +22 -0
- package/libraries/common/string.effekt +28 -0
- package/libraries/common/stringbuffer.effekt +5 -5
- package/libraries/llvm/bytearray.c +14 -5
- package/libraries/llvm/forward-declare-c.ll +1 -0
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +3 -3
- package/licenses/mit license - mit-license.html +58 -50
- package/licenses/the bsd license - bsd-license.html +59 -51
- package/licenses/the mit license - mit.html +60 -52
- package/package.json +1 -1
package/bin/effekt
CHANGED
|
Binary file
|
|
@@ -63,9 +63,10 @@ extern def unsafeAllocate[T](size: Int) at global: Array[T] =
|
|
|
63
63
|
|
|
64
64
|
/// Fills a given array in-place.
|
|
65
65
|
def fill[T](arr: Array[T], filler: T): Unit = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
val n = arr.size
|
|
67
|
+
def go(i: Int): Unit =
|
|
68
|
+
if (i < n) { arr.set(i, filler); go(i + 1) }
|
|
69
|
+
go(0)
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/// Creates a new Array of size `size` filled with the value `init`
|
|
@@ -113,17 +113,35 @@ def build(size: Int) { index: Int => Byte }: ByteArray = {
|
|
|
113
113
|
arr
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/// Copies `length`-many bytes from `from` to `to`
|
|
117
|
+
/// starting at `start` (in `from`) and `offset` (in `to`).
|
|
118
|
+
/// The ranges may overlap, even within the same bytearray: the source bytes are
|
|
119
|
+
/// read before any are written. Panics if out of bounds.
|
|
120
|
+
///
|
|
121
|
+
/// O(N)
|
|
122
|
+
def copy(from: ByteArray, start: Int, to: ByteArray, offset: Int, length: Int): Unit =
|
|
123
|
+
if (length < 0 || start < 0 || offset < 0
|
|
124
|
+
|| start + length > from.size || offset + length > to.size) {
|
|
125
|
+
<{ "bytearray::copy: range out of bounds" }>
|
|
126
|
+
} else {
|
|
127
|
+
unsafeCopy(from, start, to, offset, length)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// `copy` without its bounds check.
|
|
131
|
+
/// Corrupts memory if misused; prefer `copy`.
|
|
132
|
+
extern def unsafeCopy(from: ByteArray, start: Int, to: ByteArray, offset: Int, length: Int) at global: Unit =
|
|
133
|
+
js "bytearray$copy(${from}, ${start}, ${to}, ${offset}, ${length})"
|
|
134
|
+
chez "(bytevector-copy! ${from} ${start} ${to} ${offset} ${length})"
|
|
135
|
+
llvm """
|
|
136
|
+
%z = call %Pos @c_bytearray_copy(%Pos ${from}, %Int ${start}, %Pos ${to}, %Int ${offset}, %Int ${length})
|
|
137
|
+
ret %Pos %z
|
|
138
|
+
"""
|
|
139
|
+
|
|
116
140
|
def resize(source: ByteArray, size: Int): ByteArray = {
|
|
117
141
|
val target = allocate(size)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
target.set(i, source.get(i))
|
|
122
|
-
go(i + 1)
|
|
123
|
-
} else {
|
|
124
|
-
target
|
|
125
|
-
}
|
|
126
|
-
go(0)
|
|
142
|
+
// safe: `min` establishes the bound for both ranges ~> no check is needed
|
|
143
|
+
unsafeCopy(source, 0, target, 0, min(source.size, size))
|
|
144
|
+
target
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
/// Sorts a given bytearray `arr` in-place using a counting sort,
|
|
@@ -190,6 +208,21 @@ extern def toString(arr: ByteArray) at {}: String =
|
|
|
190
208
|
chez "(utf8->string ${arr})"
|
|
191
209
|
|
|
192
210
|
extern js """
|
|
211
|
+
function bytearray$copy(src, srcOff, dst, dstOff, n) {
|
|
212
|
+
// for small `n`, it's faster to copy manually to avoid allocating `subarray`
|
|
213
|
+
if (n < 40) {
|
|
214
|
+
if (srcOff < dstOff) {
|
|
215
|
+
for (let i = n - 1; i >= 0; --i) dst[dstOff + i] = src[srcOff + i];
|
|
216
|
+
} else {
|
|
217
|
+
for (let i = 0; i < n; ++i) dst[dstOff + i] = src[srcOff + i];
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// safe: `set` clones the source region first when both share a buffer
|
|
221
|
+
dst.set(src.subarray(srcOff, srcOff + n), dstOff);
|
|
222
|
+
}
|
|
223
|
+
return $effekt.unit;
|
|
224
|
+
}
|
|
225
|
+
|
|
193
226
|
function bytearray$set(bytes, index, value) {
|
|
194
227
|
bytes[index] = value;
|
|
195
228
|
return $effekt.unit;
|
|
@@ -242,22 +275,8 @@ extern def compareByteArrayImpl(b1: ByteArray, b2: ByteArray) at {}: Int =
|
|
|
242
275
|
"""
|
|
243
276
|
chez "(bytearray$compare ${b1} ${b2})"
|
|
244
277
|
|
|
245
|
-
def compareByteArray(b1: ByteArray, b2: ByteArray): Ordering =
|
|
246
|
-
|
|
247
|
-
if (ret == 0) {
|
|
248
|
-
Equal()
|
|
249
|
-
} else if (ret < 0) {
|
|
250
|
-
Less()
|
|
251
|
-
} else { // ret > 0
|
|
252
|
-
Greater()
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
def compareStringBytes(left: String, right: String): Ordering = {
|
|
257
|
-
val l = left.fromString
|
|
258
|
-
val r = right.fromString
|
|
259
|
-
compareByteArray(l, r)
|
|
260
|
-
}
|
|
278
|
+
def compareByteArray(b1: ByteArray, b2: ByteArray): Ordering =
|
|
279
|
+
compareByteArrayImpl(b1, b2).fromInt
|
|
261
280
|
|
|
262
281
|
|
|
263
282
|
// Streaming
|
|
@@ -144,6 +144,10 @@ extern def infixGte(x: Char, y: Char) at {}: Bool =
|
|
|
144
144
|
ret %Pos %adt_boolean
|
|
145
145
|
"""
|
|
146
146
|
|
|
147
|
+
def compareChar(x: Char, y: Char): Ordering =
|
|
148
|
+
if (x == y) Equal()
|
|
149
|
+
else if (x < y) Less()
|
|
150
|
+
else Greater()
|
|
147
151
|
|
|
148
152
|
/**
|
|
149
153
|
* Determines the number of bytes needed by a codepoint
|
|
@@ -155,18 +159,21 @@ def utf8ByteCount(codepoint: Char): Int = codepoint match {
|
|
|
155
159
|
case c and c >= '\u0080' and c <= '\u07FF' => 2
|
|
156
160
|
case c and c >= '\u0800' and c <= '\uFFFF' => 3
|
|
157
161
|
case c and c >= '\u10000' and c <= '\u10FFFF' => 4
|
|
158
|
-
case c =>
|
|
162
|
+
case c => <{ "Not a valid code point" }>
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
def utf16UnitCount(codepoint: Char): Int = codepoint match {
|
|
162
166
|
case c and c >= '\u0000' and c <= '\uFFFF' => 1
|
|
163
|
-
case c and c >= '\u10000' and c <= '\u10FFFF' =>
|
|
164
|
-
case c =>
|
|
167
|
+
case c and c >= '\u10000' and c <= '\u10FFFF' => 2
|
|
168
|
+
case c => <{ "Not a valid code point" }>
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
/// How many index units `c` occupies in the unit that `length` and `unsafeCharAt`
|
|
172
|
+
/// use on this backend. Adding it to a code-point boundary gives the next character.
|
|
167
173
|
extern def charWidth(c: Char) at {}: Int =
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
js { utf16UnitCount(c) }
|
|
175
|
+
chez { 1 }
|
|
176
|
+
llvm { utf8ByteCount(c) }
|
|
170
177
|
|
|
171
178
|
|
|
172
179
|
def decodeChar(): Char / {next[Byte], stop} = {
|
|
@@ -191,20 +191,25 @@ extern def random() at io: Double =
|
|
|
191
191
|
// extern io def freshGlobal[T](t: T): Ref[T] at global = js "$effekt.fresh(${t})"
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// Comparison
|
|
195
195
|
// ========
|
|
196
196
|
|
|
197
197
|
/// Result of comparison between two objects according to some order:
|
|
198
198
|
///
|
|
199
199
|
/// - `Less()` means that the first object is *before* the first in the order,
|
|
200
|
-
/// - `Equal()` means that the two objects are the same in the order,
|
|
201
|
-
/// -
|
|
200
|
+
/// - `Equal()` means that the two objects are the same in the order, and
|
|
201
|
+
/// - `Greater()` means that the second object is *before* the first in the order.
|
|
202
202
|
type Ordering {
|
|
203
203
|
Less();
|
|
204
204
|
Equal();
|
|
205
205
|
Greater()
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
def ordering::fromInt(n: Int): Ordering =
|
|
209
|
+
if (n == 0) Equal()
|
|
210
|
+
else if (n < 0) Less()
|
|
211
|
+
else Greater()
|
|
212
|
+
|
|
208
213
|
def compareInt(n: Int, m: Int) =
|
|
209
214
|
if (n == m) Equal()
|
|
210
215
|
else if (n < m) Less()
|
|
@@ -215,16 +220,8 @@ extern def genericCompareImpl[R](x: R, y: R) at {}: Int =
|
|
|
215
220
|
|
|
216
221
|
/// Compares two values of the same type, returning an `Ordering`.
|
|
217
222
|
/// Only available on the JavaScript backend.
|
|
218
|
-
def genericCompare[R](x: R, y: R): Ordering =
|
|
219
|
-
|
|
220
|
-
if (ret == 0) {
|
|
221
|
-
Equal()
|
|
222
|
-
} else if (ret < 0) {
|
|
223
|
-
Less()
|
|
224
|
-
} else { // ret > 0
|
|
225
|
-
Greater()
|
|
226
|
-
}
|
|
227
|
-
}
|
|
223
|
+
def genericCompare[R](x: R, y: R): Ordering =
|
|
224
|
+
genericCompareImpl(x, y).fromInt
|
|
228
225
|
|
|
229
226
|
def println(o: Ordering): Unit = println(o.show)
|
|
230
227
|
|
|
@@ -820,6 +820,28 @@ def partition[A](l: List[A]) { pred: A => Bool }: (List[A], List[A]) = {
|
|
|
820
820
|
(lefts.reverse, rights.reverse)
|
|
821
821
|
}
|
|
822
822
|
|
|
823
|
+
/// List functions that we discourage to use, but should still be available
|
|
824
|
+
namespace slow {
|
|
825
|
+
/// Find the first element in a list which satisfies a given predicate.
|
|
826
|
+
/// If there is no such element, returns None.
|
|
827
|
+
///
|
|
828
|
+
/// Examples:
|
|
829
|
+
/// ```
|
|
830
|
+
/// > [].slow::find{ x => x.mod(2) == 0 }
|
|
831
|
+
/// None()
|
|
832
|
+
/// > [1, 3, 6].slow::find{ x => x.mod(2) == 0 }
|
|
833
|
+
/// Some(6)
|
|
834
|
+
/// > [2, 4, 6].slow::find{ x => x.mod(2) == 0 }
|
|
835
|
+
/// Some(2)
|
|
836
|
+
/// ```
|
|
837
|
+
///
|
|
838
|
+
/// O(N)
|
|
839
|
+
def find[A](list: List[A]){ pred: A => Bool }: Option[A] = list match {
|
|
840
|
+
case Cons(x, xs) => if (pred(x)) Some(x) else find(xs){pred}
|
|
841
|
+
case Nil() => None()
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
823
845
|
/// Utilities for sorting, see 'sortBy' for more details.
|
|
824
846
|
namespace sort {
|
|
825
847
|
/// Splits the given list into monotonic segments (so a list of lists).
|
|
@@ -210,6 +210,28 @@ namespace internal {
|
|
|
210
210
|
do emit(c.utf8ByteCount)
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
|
|
214
|
+
def compareCodePoints(left: String, right: String): Ordering = {
|
|
215
|
+
val ln = left.length
|
|
216
|
+
val rn = right.length
|
|
217
|
+
def go(i: Int, j: Int): Ordering =
|
|
218
|
+
if (i >= ln) { if (j >= rn) Equal() else Less() }
|
|
219
|
+
else if (j >= rn) Greater()
|
|
220
|
+
else {
|
|
221
|
+
val a = unsafeCharAt(left, i)
|
|
222
|
+
val b = unsafeCharAt(right, j)
|
|
223
|
+
compareChar(a, b) match {
|
|
224
|
+
case Equal() =>
|
|
225
|
+
// equal code points have equal widths, so take the step only once
|
|
226
|
+
val w = a.charWidth
|
|
227
|
+
go(i + w, j + w)
|
|
228
|
+
case Less() => Less()
|
|
229
|
+
case Greater() => Greater()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
go(0, 0)
|
|
234
|
+
}
|
|
213
235
|
}
|
|
214
236
|
|
|
215
237
|
/// Returns the number of bytes that the given `str` would take up in a UTF-8 encoding.
|
|
@@ -235,6 +257,12 @@ extern def utf8ByteLength(str: String) at {io, global}: Int =
|
|
|
235
257
|
// extern def unsafeIndexOf(str: String, sub: String) at {}: Int =
|
|
236
258
|
// js "${str}.indexOf(${sub})"
|
|
237
259
|
|
|
260
|
+
/// Compares two strings lexicographically by code point.
|
|
261
|
+
/// Identical on every backend.
|
|
262
|
+
extern def compareStringBytes(left: String, right: String) at {}: Ordering =
|
|
263
|
+
llvm { compareByteArray(fromString(left), fromString(right)) }
|
|
264
|
+
default { internal::compareCodePoints(left, right) }
|
|
265
|
+
|
|
238
266
|
def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
|
|
239
267
|
if (index < 0 || index >= length(str))
|
|
240
268
|
do raise(OutOfBounds(), "Index out of bounds: " ++ show(index) ++ " in string: '" ++ str ++ "'")
|
|
@@ -28,11 +28,11 @@ def flushableStringBuffer[A] { program: () => A / { write, flush } }: A = {
|
|
|
28
28
|
program()
|
|
29
29
|
} with write { string =>
|
|
30
30
|
val bytes = fromString(string)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
val n = bytes.size
|
|
32
|
+
ensureCapacity(n)
|
|
33
|
+
// safe: `ensureCapacity` just established the target range
|
|
34
|
+
bytearray::unsafeCopy(bytes, 0, buffer, position, n)
|
|
35
|
+
position = position + n
|
|
36
36
|
resume(())
|
|
37
37
|
} with flush {
|
|
38
38
|
val string = buffer.resize(position).toString()
|
|
@@ -56,6 +56,17 @@ uint8_t* c_bytearray_data(const struct Pos arr) {
|
|
|
56
56
|
return data;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
struct Pos c_bytearray_copy(const struct Pos source, const Int sourceOffset,
|
|
60
|
+
const struct Pos target, const Int targetOffset,
|
|
61
|
+
const Int length) {
|
|
62
|
+
// memmove, not memcpy: the ranges may overlap, and may even be the same bytearray
|
|
63
|
+
memmove(c_bytearray_data(target) + targetOffset,
|
|
64
|
+
c_bytearray_data(source) + sourceOffset, length);
|
|
65
|
+
erasePositive(source);
|
|
66
|
+
erasePositive(target);
|
|
67
|
+
return Unit;
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
struct Pos c_bytearray_construct(const uint64_t n, const uint8_t *data) {
|
|
60
71
|
struct Pos arr = c_bytearray_new(n);
|
|
61
72
|
memcpy(c_bytearray_data(arr), data, n);
|
|
@@ -134,11 +145,9 @@ struct Pos c_bytearray_concatenate(const struct Pos left, const struct Pos right
|
|
|
134
145
|
uint64_t left_size = left.tag;
|
|
135
146
|
uint64_t right_size = right.tag;
|
|
136
147
|
const struct Pos concatenated = c_bytearray_new(left_size + right_size);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
? c_bytearray_data(left)[j]
|
|
141
|
-
: c_bytearray_data(right)[j - left_size];
|
|
148
|
+
// two memcpys rather than a byte loop with a branch per byte
|
|
149
|
+
memcpy(c_bytearray_data(concatenated), c_bytearray_data(left), left_size);
|
|
150
|
+
memcpy(c_bytearray_data(concatenated) + left_size, c_bytearray_data(right), right_size);
|
|
142
151
|
|
|
143
152
|
erasePositive(left);
|
|
144
153
|
erasePositive(right);
|
|
@@ -25,6 +25,7 @@ declare %Int @c_bytearray_size(%Pos)
|
|
|
25
25
|
declare %Byte @c_bytearray_get(%Pos, %Int)
|
|
26
26
|
declare %Pos @c_bytearray_set(%Pos, %Int, %Byte)
|
|
27
27
|
|
|
28
|
+
declare %Pos @c_bytearray_copy(%Pos, %Int, %Pos, %Int, %Int)
|
|
28
29
|
declare ptr @c_bytearray_data(%Pos)
|
|
29
30
|
declare %Pos @c_bytearray_construct(i64, ptr)
|
|
30
31
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!doctype html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><title>Eclipse Public License 2.0 (EPL) | The Eclipse Foundation</title>
|
|
2
|
-
<meta property="og:title" content="Eclipse Public License 2.0 (EPL) | The Eclipse Foundation"><meta name=twitter:title content="Eclipse Public License 2.0 (EPL) | The Eclipse Foundation"><meta name=description content="The Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …"><meta property="og:description" content="The Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …"><meta name=twitter:description content="The Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …"><meta name=author content="Wayne Beaton"><link href=/favicon.ico rel=icon type=image/x-icon><meta property="og:image" content="https://www.eclipse.org/images/og-image-template_hu_cdf3b1afdf89db6f.png"><meta name=twitter:image content="https://www.eclipse.org/images/og-image-template_hu_cdf3b1afdf89db6f.png"><meta name=twitter:card content="summary"><meta name=twitter:site content="@eclipsefdn"><meta name=twitter:creator content="@eclipsefdn"><meta property="og:url" content="https://www.eclipse.org/legal/epl-2.0/"><meta property="og:type" content="website"><meta property="og:site_name" content="Eclipse Foundation"><meta name=keywords content="epl,epl-2.0,legal,foundation,eclipse,eclipse public license,license,licenses"><meta name=generator content="Hugo 0.144.2"><link rel=canonical href=https://www.eclipse.org/legal/epl-2.0/><link rel=alternate href=https://www.eclipse.org//legal/epl-2.0/index.xml type=application/rss+xml title><script>(function(e,t,n,s,o){e[s]=e[s]||[],e[s].push({"gtm.start":(new Date).getTime(),event:"gtm.js"});var a=t.getElementsByTagName(n)[0],i=t.createElement(n),r=s!="dataLayer"?"&l="+s:"";i.async=!0,i.src="https://www.googletagmanager.com/gtm.js?id="+o+r,a.parentNode.insertBefore(i,a)})(window,document,"script","dataLayer","GTM-5WLCZXC")</script><script>var tableClasses="table"</script><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel=stylesheet type=text/css> <script type="module" crossorigin src="/public/assets/legal/epl-2.0/index-
|
|
3
|
-
<link rel="modulepreload" crossorigin href="/public/assets/main-
|
|
2
|
+
<meta property="og:title" content="Eclipse Public License 2.0 (EPL) | The Eclipse Foundation"><meta name=twitter:title content="Eclipse Public License 2.0 (EPL) | The Eclipse Foundation"><meta name=description content="The Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …"><meta property="og:description" content="The Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …"><meta name=twitter:description content="The Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …"><meta name=author content="Wayne Beaton"><link href=/favicon.ico rel=icon type=image/x-icon><meta property="og:image" content="https://www.eclipse.org/images/og-image-template_hu_cdf3b1afdf89db6f.png"><meta name=twitter:image content="https://www.eclipse.org/images/og-image-template_hu_cdf3b1afdf89db6f.png"><meta name=twitter:card content="summary"><meta name=twitter:site content="@eclipsefdn"><meta name=twitter:creator content="@eclipsefdn"><meta property="og:url" content="https://www.eclipse.org/legal/epl-2.0/"><meta property="og:type" content="website"><meta property="og:site_name" content="Eclipse Foundation"><meta name=keywords content="epl,epl-2.0,legal,foundation,eclipse,eclipse public license,license,licenses"><meta name=generator content="Hugo 0.144.2"><link rel=canonical href=https://www.eclipse.org/legal/epl-2.0/><link rel=alternate href=https://www.eclipse.org//legal/epl-2.0/index.xml type=application/rss+xml title><script>(function(e,t,n,s,o){e[s]=e[s]||[],e[s].push({"gtm.start":(new Date).getTime(),event:"gtm.js"});var a=t.getElementsByTagName(n)[0],i=t.createElement(n),r=s!="dataLayer"?"&l="+s:"";i.async=!0,i.src="https://www.googletagmanager.com/gtm.js?id="+o+r,a.parentNode.insertBefore(i,a)})(window,document,"script","dataLayer","GTM-5WLCZXC")</script><script>var tableClasses="table"</script><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel=stylesheet type=text/css> <script type="module" crossorigin src="/public/assets/legal/epl-2.0/index-KKbnVOd8.js"></script>
|
|
3
|
+
<link rel="modulepreload" crossorigin href="/public/assets/main-BKIcjYwC.js">
|
|
4
4
|
<link rel="modulepreload" crossorigin href="/public/assets/get-body-content-D33PtpOf.js">
|
|
5
5
|
<link rel="modulepreload" crossorigin href="/public/assets/generate-body-content-BZFBJ1cI.js">
|
|
6
|
-
<link rel="stylesheet" crossorigin href="/public/assets/main-
|
|
6
|
+
<link rel="stylesheet" crossorigin href="/public/assets/main-BzlpBkIh.css">
|
|
7
7
|
</head><body><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5WLCZXC" height=0 width=0 style=display:none;visibility:hidden></iframe></noscript><a href=#main class=skip-link>Skip to content</a><div class=eclipsefdn-featured-story-banner data-publish-target=eclipse_org></div><header class="header-wrapper header-wrapper" id=header><div class="navigation-bar dark"><div class=container><div class=navigation-bar-inner><div class=navigation-bar-left><a class="focus-highlight focus-inverse" href=/><img class=navigation-bar-brand src=https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-foundation-white-orange.svg alt="Eclipse Foundation"></a></div><nav class="navigation-bar-nav hidden-xs hidden-sm"><ul><li><button type=button class="btn btn-link" data-target=join-us-menu>Join us</button></li><li><button type=button class="btn btn-link" data-target=what-we-do-menu>What we do</button></li><li><button type=button class="btn btn-link" data-target=resources-menu>Resources</button></li><li><button type=button class="btn btn-link" data-target=about-us-menu>About us</button></li></ul></nav><div class=navigation-bar-right><div class="navigation-bar-controls hidden-xs hidden-sm"><ul><li class=dropdown><a class="dropdown-toggle link-unstyled focus-highlight" tabindex=0 id=search-dropdown-toggle data-toggle=dropdown href=# aria-label=Search><i class="fa-solid fa-magnifying-glass" aria-hidden=true></i></a><form class="search-bar dropdown-menu dropdown-menu-right" aria-labelledby=search-dropdown-toggle action=https://www.eclipse.org/home/search/ method=get><input class=search-bar-input name=q placeholder=Search>
|
|
8
8
|
<button class="btn btn-link" type=submit>
|
|
9
9
|
<i class="fa-solid fa-arrow-right"></i></button></form></li><li class=dropdown><a class="dropdown-toggle focus-highlight" id=user-menu href=# data-toggle=dropdown aria-haspopup=true aria-expanded=false tabindex=0><i class="fa-regular fa-user" aria-hidden=true></i></a><ul class="dropdown-menu dropdown-menu-right" aria-labelledby=user-menu><li><a href=https://accounts.eclipse.org/user><i class="fa-solid fa-user margin-right-10" aria-hidden=true></i>
|