diff --git a/_content/tour/eng/static/js/page.js b/_content/tour/eng/static/js/page.js index 85d10967..48a0c2fa 100644 --- a/_content/tour/eng/static/js/page.js +++ b/_content/tour/eng/static/js/page.js @@ -113,7 +113,7 @@ function setLanguageOptionBasedOnUrl() { // replaceLanguageInUrl takes a URL and a new language as arguments, // and returns a new URL with the language segment replaced by the new language. function replaceLanguageInUrl(url, newLanguage) { - return url.replace(/(\/tour\/)(eng|rus)(\/)/, `$1${newLanguage}$3`); + return url.replace(/(\/tour\/)(eng|rus|gre)(\/)/, `$1${newLanguage}$3`); } window.onload = function() { diff --git a/_content/tour/eng/template/index.tmpl b/_content/tour/eng/template/index.tmpl index ed04e1c3..d141ef3e 100644 --- a/_content/tour/eng/template/index.tmpl +++ b/_content/tour/eng/template/index.tmpl @@ -71,6 +71,7 @@
diff --git a/_content/tour/grc/algorithms-bits-seven.article b/_content/tour/grc/algorithms-bits-seven.article new file mode 100644 index 00000000..ae7f871d --- /dev/null +++ b/_content/tour/grc/algorithms-bits-seven.article @@ -0,0 +1,12 @@ +Bit Operations +This section provides examples that perform bit operations. + +* Is Even Or Odd + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +This sample program shows you how to check if an integer is even or odd +using bit manipulation. + +.play algorithms/bits/iseven.go diff --git a/_content/tour/grc/algorithms-data.article b/_content/tour/grc/algorithms-data.article new file mode 100644 index 00000000..2d104b30 --- /dev/null +++ b/_content/tour/grc/algorithms-data.article @@ -0,0 +1,146 @@ +Data Structures +This section provides data struct examples. + +* Hash Map + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +This sample program implements a basic hash table. + +- hashKey(key) returns a number between 0 to len(buckets)-1 + +- We use a slice of entries as a bucket to handles cases where two or more keys + are hashed to the same bucket + +- See more at [[https://en.wikipedia.org/wiki/Hash_table][https://en.wikipedia.org/wiki/Hash_table]] + +*Diagram* + + With a hash map, data is indexed by bucket and then position + inside the bucket. + + hashKey(key) ──────────────┐ + │ + ▽ + ┌────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ + │ │ │ │ │ │ │ │ │ ◁── bucket + └────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ + │ │ + ▽ ▽ + ┌─────────────┐ ┌─────────────┐ + │ key │ value │ │ key │ value │ ◁── entry + ├─────────────┤ ├─────────────┤ + │ key │ value │ │ key │ value │ + ├─────────────┤ └─────────────┘ + │ key │ value │ + ├─────────────┤ + │ key │ value │ + ├─────────────┤ + │ key │ value │ + └─────────────┘ + +.play algorithms/data/hash_map.go + +* Linked List + +This sample program implements a basic double linked list. + +- See more at [[https://en.wikipedia.org/wiki/Linked_list][https://en.wikipedia.org/wiki/Linked_list]] + +*Diagram* + + With a linked list, values are tied together in different + order through the use of pointers. + + ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ + │ Val │ ◁─▷ │ Val │ ◁─▷ │ Val │ ◁─▷ │ Val │ ◁─▷ │ Val │ + └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ + △ △ + │ │ + ──────────────────── ───────────────────── + │ │ + │ │ + ┌───────────────┐ + │ First │ Last │ + └───────────────┘ + +.play algorithms/data/list.go + +* Queue + +This sample program implements a basic circular queue. + +- See more at [[https://en.wikipedia.org/wiki/Queue_(abstract_data_type)][https://en.wikipedia.org/wiki/Queue_(abstract_data_type)]] + +*Diagram* + + With a queue, the first value in is the first value out. + + ┌──────────────────────────────────────────┐ + ┌─────┐ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ ┌─────┐ + │ V06 │ ─▷ │ │ V05 │ ─▷ │ V04 │ ─▷ │ V03 │ ─▷ │ V02 │ │ ─▷ │ V01 │ + └─────┘ | └─────┘ └─────┘ └─────┘ └─────┘ | └─────┘ + └──────────────────────────────────────────┘ + +.play algorithms/data/queue_circular.go + +* Stack + +This sample program implements a basic stack. + +- See more at [[https://en.wikipedia.org/wiki/Stack_(abstract_data_type)][https://en.wikipedia.org/wiki/Stack_(abstract_data_type)]] + +*Diagram* + + With a stack, the first value in is the last value out. + + ┌─────┐ + │ V05 │ + └─────┘ + │ + ▽ ┌─────┐ + ┌───────────┐ ─▷ │ V04 │ + │ ┌─────┐ │ └─────┘ + │ │ V03 │ │ + │ └─────┘ │ + │ ┌─────┐ │ + │ │ V02 │ │ + │ └─────┘ │ + │ ┌─────┐ │ + │ │ V01 │ │ + │ └─────┘ │ + └───────────┘ + +.play algorithms/data/stack.go + +* Binary Tree + +This sample program implements a basic binary tree. + +- See more at [[https://en.wikipedia.org/wiki/Binary_tree][https://en.wikipedia.org/wiki/Binary_tree]] + +*Diagram* + + With a binary tree, data is indexed either to the left or right + side of the tree. With the adding of each node, the tree is + balanced. + + 0 1 2 3 4 5 6 ◁─ Insert Order + ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 65 ││ 45 ││ 35 ││ 75 ││ 85 ││ 78 ││ 95 │ + └────┘└────┘└────┘└────┘└────┘└────┘└────┘ + + ┌────┐ + │ 75 │ ◁─ Final Tree + └────┘ + / \ + ┌────┐ ┌────┐ + │ 45 │ │ 85 │ + └────┘ └────┘ + / \ / \ + ┌────┐ ┌────┐ ┌────┐ ┌────┐ + │ 35 │ │ 65 │ │ 78 │ │ 95 │ + └────┘ └────┘ └────┘ └────┘ + +.play algorithms/data/tree_binary.go diff --git a/_content/tour/grc/algorithms-fun.article b/_content/tour/grc/algorithms-fun.article new file mode 100644 index 00000000..36ff14e9 --- /dev/null +++ b/_content/tour/grc/algorithms-fun.article @@ -0,0 +1,131 @@ +Fun Problems +This section contains a set fun code challenges that showcase some of the capabilities offered by Go. + +* Sleeping Barber Problem + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +This sample program implements the sleeping barber problem. + +- See more at [[https://en.wikipedia.org/wiki/Sleeping_barber_problem][https://en.wikipedia.org/wiki/Sleeping_barber_problem]] + +There is one barber in the barber shop, one barber chair and `n` chairs for +waiting customers. If there are no customers, the barber sits down in the +barber chair and takes a nap. An arriving customer must wake the barber. +Subsequent arriving customers take a waiting chair if any are empty or +leave if all chairs are full. + +*Output:* + + Opening the shop + Barber ready to work + Customer "cust-1" entered shop + Customer "cust-1" takes a seat and waits + Barber servicing customer "cust-1" + Barber finished customer "cust-1" + Barber taking a nap + Customer "cust-2" entered shop + Customer "cust-2" takes a seat and waits + Barber servicing customer "cust-2" + Customer "cust-3" entered shop + Customer "cust-3" takes a seat and waits + Barber finished customer "cust-2" + Barber servicing customer "cust-3" + Customer "cust-4" entered shop + Customer "cust-4" takes a seat and waits + Closing the shop + Barber finished customer "cust-3" + Barber servicing customer "cust-4" + Barber finished customer "cust-4" + Shop closed + +.play algorithms/fun/barber.go + +* Frequency + +This sample programs shows you how to implement a function that can find +the frequency of a given rune that is used in a specified sentence. + +- Sequential: A linear algorithm to perform a rune count. +- Concurrent: A concurrent algorithm to perform a rune count. + +.play algorithms/fun/freq_sequential.go +.play algorithms/fun/freq_concurrent.go + +* Variable Length Quantity encoding/decoding. + +This sample program showcases how Go can be leveraged to implement variable length quantity encoding/decoding. + +- See more at [[https://en.wikipedia.org/wiki/Variable-length_code][https://en.wikipedia.org/wiki/Variable-length_code]] + +In short, the goal of this encoding is to save encode integer values in +a way that would save bytes. Only the first 7 bits of each byte is significant +(right-justified; sort of like an ASCII byte). So, if you have a 32-bit value, +you have to unpack it into a series of 7-bit bytes. Of course, you will have +a variable number of bytes depending upon your integer. To indicate which +is the last byte of the series, you leave bit #7 clear. In all of the +preceding bytes, you set bit #7. + +So, if an integer is between 0-127, it can be represented as one byte. The +largest integer allowed is 0FFFFFFF, which translates to 4 bytes variable +length. Here are examples of delta-times as 32-bit values, and the variable +length quantities that they translate to: + + NUMBER VARIABLE QUANTITY + 00000000 00 + 00000040 40 + 0000007F 7F + 00000080 81 00 + 00002000 C0 00 + 00003FFF FF 7F + 00004000 81 80 00 + 00100000 C0 80 00 + 001FFFFF FF FF 7F + 00200000 81 80 80 00 + 08000000 C0 80 80 00 + 0FFFFFFF FF FF FF 7F + +A variable-length quantity (VLQ) is a universal code that uses an arbitrary +number of binary octets (eight-bit bytes) to represent an arbitrarily large +integer. It was defined for use in the standard MIDI file format[1] to save +additional space for a resource constrained system, and is also used in the +later Extensible Music Format (XMF). A VLQ is essentially a base-128 +representation of an unsigned integer with the addition of the eighth bit +to mark continuation of bytes. See the example below. + + Int: 16384 + IntHex: 0x00004000 + IntBin: 00000000 00000000 01000000 00000000 + VLQHex: 0x81 0x80 0x00 + VLQBin: 00000000 10000001 10000000 00000000 + +Lets say I want to represent the number 3435 in VLQ. 3435 in +binary is 110101101011. We can not fit this in a byte. So we will +chop it up from the end in 7-bit blocks. + + Septet 7 6 5 4 3 2 1 + #1 1 1 0 1 0 1 1 + #2 0 0 1 1 0 1 0 + +Now we prepend all but the last with a 1-bit to indicate that an octet +follows and prepend a 0-bit to the last, signalling the final octet. + + Octet 8 7 6 5 4 3 2 1 + #1 0 1 1 0 1 0 1 1 + #2 1 0 0 1 1 0 1 0 + +Finally we concatenate them, most significant octet first, into + +Encoded: 10011010 01101011 ToHex: 0x9A 0x6B + +*Extra* *Resources:* + +- [[https://en.wikipedia.org/wiki/Variable-length_quantity][https://en.wikipedia.org/wiki/Variable-length_quantity]] +- [[https://blogs.infosupport.com/a-primer-on-vlq/][https://blogs.infosupport.com/a-primer-on-vlq/]] + +*For* *an* *excellent* *implementation* *of* *this* *algorithm* *look* *here:* + +- [[https://github.com/go-audio/midi/blob/master/varint.go][https://github.com/go-audio/midi/blob/master/varint.go]] + +.play algorithms/fun/vlq.go \ No newline at end of file diff --git a/_content/tour/grc/algorithms-numbers.article b/_content/tour/grc/algorithms-numbers.article new file mode 100644 index 00000000..d0922f1e --- /dev/null +++ b/_content/tour/grc/algorithms-numbers.article @@ -0,0 +1,31 @@ +Number Operations +This section provides examples that perform number operations. + +* Palindrome + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The sample program implements a check to see if an integer is a +palindrome or not. + +- See more at [[https://en.wikipedia.org/wiki/Palindrome][https://en.wikipedia.org/wiki/Palindrome]] + +*Diagram* + + A palindrome is a word, number, phrase, or other sequence of symbols that + reads the same backwards as forwards. + + ┌───┐┌───┐┌───┐ + │ 1 ││ 0 ││ 1 │ ────▷ Palindrome + └───┘└───┘└───┘ + + ┌───┐┌───┐┌───┐ + │ 1 ││ 2 ││ 3 │ ────▷ NO + └───┘└───┘└───┘ + + ┌───┐ + │ 5 │ ────▷ Palindrome + └───┘ + +.play algorithms/numbers/palindrome.go diff --git a/_content/tour/grc/algorithms-searches.article b/_content/tour/grc/algorithms-searches.article new file mode 100644 index 00000000..4b7f3b53 --- /dev/null +++ b/_content/tour/grc/algorithms-searches.article @@ -0,0 +1,43 @@ +Search Operations +This section provides examples that perform search operations. + +* Binary Search + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The sample program implements a function that performs an iterative +binary search against set of integers. + +- See more at [[https://en.wikipedia.org/wiki/Binary_search_algorithm][https://en.wikipedia.org/wiki/Binary_search_algorithm]] + +*Diagram* + + Binary search compares the target value to the middle element of the + array. If they are not equal, the half in which the target cannot lie + is eliminated and the search continues on the remaining half, again + taking the middle element to compare to the target value, and repeating + this until the target value is found. If the search ends with the + remaining half being empty, the target is not in the array + + ┌────┐ + │ 83 │ ◁── Target Number + └────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 04 ││ 42 ││ 80 ││ 83 ││ 95 │ ◁── Starting Array + └────┘└────┘└────┘└────┘└────┘ + ┌────┐ ◁── Middle Value + │ 80 │ ◁── Target Number Is Greater + └────┘ + ┌────┐┌────┐ + │ 83 ││ 95 │ ◁── Search This Half + └────┘└────┘ + ┌────┐ + │ 83 │ ◁── Middle Value + └────┘ + ┌────┐ + │ 83 │ ◁── Target Found / Idx 3 + └────┘ + +.play algorithms/searches/binary_iterative.go +.play algorithms/searches/binary_recursive.go diff --git a/_content/tour/grc/algorithms-slices.article b/_content/tour/grc/algorithms-slices.article new file mode 100644 index 00000000..93749d58 --- /dev/null +++ b/_content/tour/grc/algorithms-slices.article @@ -0,0 +1,39 @@ +Slice Operations +This section provides examples that perform slice operations. + +* Minimum Number + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The sample program implements a function to retrieve the minimum integer +from a slice of integers. + +*Diagram* + + ┌────┐┌────┐┌────┐ + │ 10 ││ 30 ││ 15 │ ────▷ 10 + └────┘└────┘└────┘ + + ┌────┐ + │ 25 │ ────▷ 25 + └────┘ + +.play algorithms/slices/min_number.go + +* Maximum Number + +The sample program implements a function to retrieve the maximum integer +from a slice of integers. + +*Diagram* + + ┌────┐┌────┐┌────┐ + │ 45 ││ 23 ││ 68 │ ────▷ 68 + └────┘└────┘└────┘ + + ┌────┐ + │ 78 │ ────▷ 78 + └────┘ + +.play algorithms/slices/max_number.go diff --git a/_content/tour/grc/algorithms-sorting.article b/_content/tour/grc/algorithms-sorting.article new file mode 100644 index 00000000..1d03cc7b --- /dev/null +++ b/_content/tour/grc/algorithms-sorting.article @@ -0,0 +1,188 @@ +Sort Operations +This section provides examples that perform sorting operations. + +* Bubble Sort + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The sample program implements a function that performs bubble sort +against a set of integers. + +- See more at [[https://en.wikipedia.org/wiki/Bubble_sort][https://en.wikipedia.org/wiki/Bubble_sort]] + +*Diagram* + + Bubble sort is a simple sorting algorithm that repeatedly steps through the + input list element by element, comparing the current element with the one + after it, swapping their values if needed. + + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 10 ││ 30 ││ 05 ││ 25 ││ 15 │ ◁── Starting Array + └────┘└────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 10 ││ 05 ││ 25 ││ 15 ││ 30 │ ◁── After First Iteration + └────┘└────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 05 ││ 10 ││ 15 ││ 25 ││ 30 │ ◁── After Second Iteration / Sorted + └────┘└────┘└────┘└────┘└────┘ + +.play algorithms/sorting/bubble.go + +* Insertion Sort + +The sample program implements a function that performs insertion sort +against a set of integers. + +- See more at [[https://en.wikipedia.org/wiki/Insertion_sort][https://en.wikipedia.org/wiki/Insertion_sort]] + +*Diagram* + + Insertion sort iterates the same number of times as the array + length minus one. For an array of 5 numbers, the sort will iterate + 4 times. Starting at index 1, the sort moves that number to the + left placing it in a sorted position. + + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── Starting Array + │ 10 ││ 30 ││ 05 ││ 25 ││ 15 │ ◁── Move [1]=30 to the left + └────┘└────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── After First Iteration + │ 10 ││ 30 ││ 05 ││ 25 ││ 15 │ ◁── Move [2]=05 to the left + └────┘└────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── After Second Iteration + │ 05 ││ 10 ││ 30 ││ 25 ││ 15 │ ◁── Move [3]=25 to the left + └────┘└────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── After Third Iteration + │ 05 ││ 10 ││ 25 ││ 30 ││ 15 │ ◁── Move [4]=15 to the left + └────┘└────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── After Forth Iteration + │ 05 ││ 10 ││ 15 ││ 25 ││ 30 │ ◁── Sorted + └────┘└────┘└────┘└────┘└────┘ + +.play algorithms/sorting/insertion.go + +* Heap Sort + +The sample program implements a function that performs heap sort +against a set of integers. + +- See more at [[https://en.wikipedia.org/wiki/Heapsort][https://en.wikipedia.org/wiki/Heapsort]] + +*Diagram* + +Heap sort divides its input into a sorted and an unsorted array. The +algorithm iteratively shrinks the unsorted region by extracting the largest +element from it and inserting it into the sorted array. The algorithm runs +in two phases. + + Phase I + Split the list in half and work the front half of the list, moving + the largest value we find to the front of the list and then the + second largest. + + ┌────┐┌────┐ | ┌────┐┌────┐┌────┐ ◁── Starting Array + │ 63 ││ 16 │ | │ 40 ││ 71 ││ 73 │ ◁── Check [1]=16 < [4]=73 : Swap + └────┘└────┘ | └────┘└────┘└────┘ + ┌────┐┌────┐ | ┌────┐┌────┐┌────┐ ◁── After First Iteration + │ 63 ││ 73 │ | │ 40 ││ 71 ││ 16 │ ◁── Check [0]=63 < [3]=71 : Swap + └────┘└────┘ | └────┘└────┘└────┘ + ┌────┐┌────┐ | ┌────┐┌────┐┌────┐ ◁── After Second Iteration + │ 73 ││ 71 │ | │ 40 ││ 63 ││ 16 │ ◁── Phase I Complete + └────┘└────┘ | └────┘└────┘└────┘ + + Phase II + Take the list and start moving numbers out and into a new sorted + list. Take the number in the first position and remove it to the + new list which will contain the final sort. Then move the largest + number we find once again to the front of the list. + + ┌────┐┌────┐┌────┐┌────┐┌────┐ | ◁── Starting Array + │ 73 ││ 71 ││ 40 ││ 63 ││ 16 │ | + └────┘└────┘└────┘└────┘└────┘ | + ┌────┐┌────┐┌────┐┌────┐ | ┌────┐ ◁── After First Iteration + │ 71 ││ 63 ││ 40 ││ 16 │ | │ 73 │ ◁── Move 73 out and 71 to front + └────┘└────┘└────┘└────┘ | └────┘ + ┌────┐┌────┐┌────┐ | ┌────┐┌────┐ ◁── After Second Iteration + │ 63 ││ 16 ││ 40 │ | │ 71 ││ 73 │ ◁── Move 71 out and 63 to front + └────┘└────┘└────┘ | └────┘└────┘ + ┌────┐┌────┐ | ┌────┐┌────┐┌────┐ ◁── After Third Iteration + │ 40 ││ 16 │ | │ 63 ││ 71 ││ 73 │ ◁── Move 63 out and 40 to front + └────┘└────┘ | └────┘└────┘└────┘ + ┌────┐ | ┌────┐┌────┐┌────┐┌────┐ ◁── After Forth Iteration + │ 16 │ | │ 40 ││ 63 ││ 71 ││ 73 │ ◁── Move 40 out and 16 to front + └────┘ | └────┘└────┘└────┘└────┘ + | ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── After Fifth Iteration + | │ 16 ││ 40 ││ 63 ││ 71 ││ 73 │ ◁── Move 16 out / Sorted + | └────┘└────┘└────┘└────┘└────┘ + +.play algorithms/sorting/heap.go + +* Quick Sort + +The sample program implements a function that performs quick sort +against a set of integers. + +- See more at [[https://en.wikipedia.org/wiki/Quicksort][https://en.wikipedia.org/wiki/Quicksort]] + +*Diagram* + + Quicksort is a divide-and-conquer algorithm. It works by selecting a + 'pivot' element from the array and partitioning the other elements + into two sub-arrays, according to whether they are less than or greater + than the pivot. + + ------------------------------------------------------------------- + Example 1 + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── Starting Array + │ 45 ││ 39 ││ 37 ││ 15 ││ 41 │ ◁── Pivot Value 41 + └────┘└────┘└────┘└────┘└────┘ ◁── Sort Elements 0 - 4 + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 39 ││ 37 ││ 15 ││ 41 ││ 45 │ ◁── First Sort Complete + └────┘└────┘└────┘└────┘└────┘ + + Last Two elements are sorted, focus on first three. + + ┌────┐┌────┐┌────┐ + │ 39 ││ 37 ││ 15 │ ◁── Pivot Value 15 + └────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 15 ││ 37 ││ 39 ││ 41 ││ 45 │ ◁── Sorted + └────┘└────┘└────┘└────┘└────┘ + + ------------------------------------------------------------------- + Example 2 + ┌────┐┌────┐┌────┐┌────┐┌────┐ ◁── Starting Array + │ 34 ││ 55 ││ 59 ││ 73 ││ 09 │ ◁── Pivot Value 09 + └────┘└────┘└────┘└────┘└────┘ ◁── Sort Elements 0 - 4 + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 09 ││ 55 ││ 59 ││ 73 ││ 34 │ ◁── First Sort Complete + └────┘└────┘└────┘└────┘└────┘ + + First element is sorted, focus on the last four. + + ┌────┐┌────┐┌────┐┌────┐ + │ 55 ││ 59 ││ 73 ││ 34 │ ◁── Pivot Value 34 + └────┘└────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 09 ││ 34 ││ 59 ││ 73 ││ 55 │ ◁── Second Sort Complete + └────┘└────┘└────┘└────┘└────┘ + + First two elements are sorted, focus on the last three. + + ┌────┐┌────┐┌────┐ + │ 59 ││ 73 ││ 55 │ ◁── Pivot Value 55 + └────┘└────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 09 ││ 34 ││ 55 ││ 73 ││ 59 │ ◁── Third Sort Complete + └────┘└────┘└────┘└────┘└────┘ + + First three elements are sorted, focus on the last two. + + ┌────┐┌────┐ ◁── Pivot Value 59 + │ 73 ││ 59 │ ◁── Third Sort Complete + └────┘└────┘ + ┌────┐┌────┐┌────┐┌────┐┌────┐ + │ 09 ││ 34 ││ 55 ││ 59 ││ 73 │ ◁── Sorted + └────┘└────┘└────┘└────┘└────┘ + +.play algorithms/sorting/quick.go diff --git a/_content/tour/grc/algorithms-strings.article b/_content/tour/grc/algorithms-strings.article new file mode 100644 index 00000000..f1ea0356 --- /dev/null +++ b/_content/tour/grc/algorithms-strings.article @@ -0,0 +1,67 @@ +String Operations +This section provides examples that perform string operations. + +* Permutation + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The sample program implements a check to see if a string is a +permutation or not. + +- See more at [[https://en.wikipedia.org/wiki/Permutation][https://en.wikipedia.org/wiki/Permutation]] + +*Diagram* + + The permutation of a string is the set of all strings that contains the + same characters without the order of the arrangement of the characters + mattering. + + ┌───┐┌───┐┌───┐ + │ G ││ O ││ D │ ──────┐ + └───┘└───┘└───┘ │ ────▷ Permutation + ┌───┐┌───┐┌───┐ │ + │ D ││ O ││ G │ ──────┘ + └───┘└───┘└───┘ + + ┌───┐┌───┐┌───┐ + │ G ││ O ││ D │ ──────┐ + └───┘└───┘└───┘ │ ────▷ NO + ┌───┐┌───┐ │ + │ D ││ O │ ──────┘ + └───┘└───┘ + + ┌───┐┌───┐┌───┐ + │ 1 ││ 0 ││ 0 │ ──────┐ + └───┘└───┘└───┘ │ ────▷ Permutation + ┌───┐┌───┐┌───┐ │ + │ 0 ││ 0 ││ 1 │ ──────┘ + └───┘└───┘└───┘ + +.play algorithms/strings/permutation.go + +* Palindrome + +The sample program implements a check to see if a string is a +palindrome or not. + +- See more at [[https://en.wikipedia.org/wiki/Palindrome][https://en.wikipedia.org/wiki/Palindrome]] + +*Diagram* + + A palindrome is a word, number, phrase, or other sequence of symbols that + reads the same backwards as forwards. + + ┌───┐┌───┐┌───┐ + │ B ││ O ││ B │ ────▷ Palindrome + └───┘└───┘└───┘ + + ┌───┐┌───┐┌───┐ + │ T ││ E ││ D │ ────▷ NO + └───┘└───┘└───┘ + + ┌───┐ + │ G │ ────▷ Palindrome + └───┘ + +.play algorithms/strings/palindrome.go diff --git a/_content/tour/grc/algorithms/bits/iseven.go b/_content/tour/grc/algorithms/bits/iseven.go new file mode 100644 index 00000000..707e2523 --- /dev/null +++ b/_content/tour/grc/algorithms/bits/iseven.go @@ -0,0 +1,32 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to check if an integer is even or +// odd using bit manipulation. +package main + +import ( + "fmt" +) + +func main() { + + fmt.Println(8, ":", IsEven(8)) + fmt.Println(15, ":", IsEven(15)) + fmt.Println(4, ":", IsEven(4)) +} + +// IsEven checks is an integer is even. +func IsEven(num int) bool { + + // Use the bitwise AND operator to see if the least significant + // bit (LSB) is 0. + + // Helpful source: https://catonmat.net/low-level-bit-hacks + // 0 & 1 = 0 (even number) + // 1 & 1 = 1 (odd number) + + return num&1 == 0 +} diff --git a/_content/tour/grc/algorithms/data/hash_map.go b/_content/tour/grc/algorithms/data/hash_map.go new file mode 100644 index 00000000..9200a301 --- /dev/null +++ b/_content/tour/grc/algorithms/data/hash_map.go @@ -0,0 +1,235 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a basic hash table. +package main + +import ( + "fmt" + "hash/maphash" +) + +func main() { + h := New() + + k1, v1 := "key1", 1 + k2, v2 := "key2", 2 + h.Store(k1, v1) + h.Store(k2, v2) + + v, err := h.Retrieve(k1) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("key:", k1, "value:", v) + + v1b := 11 + h.Store(k1, v1b) + + v, err = h.Retrieve(k1) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("key:", k1, "value:", v) + + if err := h.Delete(k1); err != nil { + fmt.Println(err) + return + } + + v, err = h.Retrieve(k1) + if err != nil { + fmt.Println(err) + } + + fn := func(key string, value int) bool { + fmt.Println("key:", key, "value:", value) + return true + } + h.Do(fn) +} + +// ============================================================================= + +const numBuckets = 256 + +// An entry where we store key and value in the hash. +type entry struct { + key string + value int +} + +// Hash is a simple Hash table implementation. +type Hash struct { + buckets [][]entry + hash maphash.Hash +} + +// New returns a new hash table. +func New() *Hash { + return &Hash{ + buckets: make([][]entry, numBuckets), + } +} + +// Store adds a value in the hash table based on the key. +func (h *Hash) Store(key string, value int) { + + // For the specified key, identify what bucket in + // the slice we need to store the key/value inside of. + idx := h.hashKey(key) + + // Extract a copy of the bucket from the hash table. + bucket := h.buckets[idx] + + // Iterate over the indexes for the specified bucket. + for idx := range bucket { + + // Compare the keys and if there is a match replace the + // existing entry value for the new value. + if bucket[idx].key == key { + bucket[idx].value = value + return + } + } + + // This key does not exist, so add this new value. + h.buckets[idx] = append(bucket, entry{key, value}) +} + +// Retrieve extracts a value from the hash table based on the key. +func (h *Hash) Retrieve(key string) (int, error) { + + // For the specified key, identify what bucket in + // the slice we need to store the key/value inside of. + idx := h.hashKey(key) + + // Iterate over the entries for the specified bucket. + for _, entry := range h.buckets[idx] { + + // Compare the keys and if there is a match return + // the value associated with the key. + if entry.key == key { + return entry.value, nil + } + } + + // The key was not found so return the error. + return 0, fmt.Errorf("%q not found", key) +} + +// Delete deletes an entry from the hash table. +func (h *Hash) Delete(key string) error { + + // For the specified key, identify what bucket in + // the slice we need to store the key/value inside of. + bucketIdx := h.hashKey(key) + + // Extract a copy of the bucket from the hash table. + bucket := h.buckets[bucketIdx] + + // Iterate over the entries for the specified bucket. + for entryIdx, entry := range bucket { + + // Compare the keys and if there is a match remove + // the entry from the bucket. + if entry.key == key { + + // Remove the entry based on its index position. + bucket = removeEntry(bucket, entryIdx) + + // Replace the existing bucket for the new one. + h.buckets[bucketIdx] = bucket + return nil + } + } + + // The key was not found so return the error. + return fmt.Errorf("%q not found", key) +} + +// Len return the number of elements in the hash. This function currently +// uses a linear traversal but could be improved with meta-data. +func (h *Hash) Len() int { + sum := 0 + for _, bucket := range h.buckets { + sum += len(bucket) + } + return sum +} + +// Do calls fn on each key/value. If fn return false stops the iteration. +func (h *Hash) Do(fn func(key string, value int) bool) { + for _, bucket := range h.buckets { + for _, entry := range bucket { + if ok := fn(entry.key, entry.value); !ok { + return + } + } + } +} + +// hashKey calculates the bucket index position to use +// for the specified key. +func (h *Hash) hashKey(key string) int { + + // Reset the maphash to initial state so we'll get the same + // hash value for the same key. + h.hash.Reset() + + // Write the key to the maphash to update the current state. + // We don't check error value since WriteString never fails. + h.hash.WriteString(key) + + // Ask the maphash for its current state which we will + // use to calculate the final bucket index. + n := h.hash.Sum64() + + // Use the modulus operator to return a value in the range + // of our bucket length defined by the const numBuckets. + return int(n % numBuckets) +} + +// removeEntry performs the physical act of removing an +// entry from a bucket, +func removeEntry(bucket []entry, idx int) []entry { + + // https://github.com/golang/go/wiki/SliceTricks + // Cut out the entry by taking all entries from + // infront of the index and moving them behind the + // index specified. + copy(bucket[idx:], bucket[idx+1:]) + + // Set the proper length for the new slice since + // an entry was removed. The length needs to be + // reduced by 1. + bucket = bucket[:len(bucket)-1] + + // Look to see if the current allocation for the + // bucket can be reduced due to the amount of + // entries removed from this bucket. + return reduceAllocation(bucket) +} + +// reduceAllocation looks to see if memory can be freed to +// when a bucket has lost a percent of entries. +func reduceAllocation(bucket []entry) []entry { + + // If the bucket if more than ½ full, do nothing. + if cap(bucket) < 2*len(bucket) { + return bucket + } + + // Free memory when the bucket shrinks a lot. If we don't do that, + // the underlying bucket array will stay in memory and will be in + // the biggest size the bucket ever was + newBucket := make([]entry, len(bucket)) + copy(newBucket, bucket) + return newBucket +} diff --git a/_content/tour/grc/algorithms/data/list.go b/_content/tour/grc/algorithms/data/list.go new file mode 100644 index 00000000..e45173f4 --- /dev/null +++ b/_content/tour/grc/algorithms/data/list.go @@ -0,0 +1,233 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a basic double linked list. +package main + +import ( + "fmt" + "strings" +) + +func main() { + var l List + + const nodes = 5 + for i := 0; i < nodes; i++ { + data := fmt.Sprintf("Node%d", i) + l.Add(data) + } + + f := func(n *Node) error { + fmt.Println("Data:", n.Data) + return nil + } + + l.Operate(f) + + fmt.Println("------------------") + + l.OperateReverse(f) +} + +// ============================================================================= + +// Node represents the data being stored. +type Node struct { + Data string + next *Node + prev *Node +} + +// List represents a list of nodes. +type List struct { + Count int + first *Node + last *Node +} + +// Add places a new node at the end of the list. +func (l *List) Add(data string) *Node { + + // When creating the new node, have the new node + // point to the last node in the list. + n := Node{ + Data: data, + prev: l.last, + } + + // Increment the count for the new node. + l.Count++ + + // If this is the first node, attach it. + if l.first == nil && l.last == nil { + l.first = &n + l.last = &n + return &n + } + + // Fix the fact that the last node does not point back to + // the NEW node. + l.last.next = &n + + // Fix the fact the Last pointer is not pointing to the true + // end of the list. + l.last = &n + + return &n +} + +// AddFront places a new node at the front of the list. +func (l *List) AddFront(data string) *Node { + + // When creating the new node, have the new node + // point to the first node in the list. + n := Node{ + Data: data, + next: l.first, + } + + // Increment the count for the new node. + l.Count++ + + // If this is the first node, attach it. + if l.first == nil && l.last == nil { + l.first = &n + l.last = &n + return &n + } + + // Fix the fact that the first node does not point back to + // the NEW node. + l.first.prev = &n + + // Fix the fact the First pointer is not pointing to the true + // beginning of the list. + l.first = &n + + return &n +} + +// Find traverses the list looking for the specified data. +func (l *List) Find(data string) (*Node, error) { + n := l.first + for n != nil { + if n.Data == data { + return n, nil + } + n = n.next + } + return nil, fmt.Errorf("unable to locate %q in list", data) +} + +// FindReverse traverses the list in the opposite direction +// looking for the specified data. +func (l *List) FindReverse(data string) (*Node, error) { + n := l.last + for n != nil { + if n.Data == data { + return n, nil + } + n = n.prev + } + return nil, fmt.Errorf("unable to locate %q in list", data) +} + +// Remove traverses the list looking for the specified data +// and if found, removes the node from the list. +func (l *List) Remove(data string) (*Node, error) { + n, err := l.Find(data) + if err != nil { + return nil, err + } + + // Detach the node by linking the previous node's next + // pointer to the node in front of the one being removed. + n.prev.next = n.next + n.next.prev = n.prev + l.Count-- + + return n, nil +} + +// Operate accepts a function that takes a node and calls +// the specified function for every node found. +func (l *List) Operate(f func(n *Node) error) error { + n := l.first + for n != nil { + if err := f(n); err != nil { + return err + } + n = n.next + } + return nil +} + +// OperateReverse accepts a function that takes a node and +// calls the specified function for every node found. +func (l *List) OperateReverse(f func(n *Node) error) error { + n := l.last + for n != nil { + if err := f(n); err != nil { + return err + } + n = n.prev + } + return nil +} + +// AddSort adds a node based on lexical ordering. +func (l *List) AddSort(data string) *Node { + + // If the list was empty add the data + // as the first node. + if l.first == nil { + return l.Add(data) + } + + // Traverse the list looking for placement. + n := l.first + for n != nil { + + // If this data is greater than the current node, + // keep traversing until it is less than or equal. + if strings.Compare(data, n.Data) > 0 { + n = n.next + continue + } + + // Create the new node and place it before the + // current node. + new := Node{ + Data: data, + next: n, + prev: n.prev, + } + + l.Count++ + + // If this node is now to be the first, + // fix the first pointer. + if l.first == n { + l.first = &new + } + + // If the current node points to a previous node, + // then that previous nodes next must point to the + // new node. + if n.prev != nil { + n.prev.next = &new + } + + // The current previous points must point back + // to this new node. + n.prev = &new + + return n + } + + // This must be the largest string, so add to the end. + return l.Add(data) +} diff --git a/_content/tour/grc/algorithms/data/queue_circular.go b/_content/tour/grc/algorithms/data/queue_circular.go new file mode 100644 index 00000000..06f2fbda --- /dev/null +++ b/_content/tour/grc/algorithms/data/queue_circular.go @@ -0,0 +1,158 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a basic circular queue. +package main + +import ( + "errors" + "fmt" +) + +func main() { + const items = 5 + + q, err := New(items) + if err != nil { + fmt.Println(err) + return + } + + for i := 0; i < items; i++ { + name := fmt.Sprintf("Name%d", i) + if err := q.Enqueue(Data{Name: name}); err != nil { + fmt.Println(err) + return + } + + fmt.Println("queue:", name) + } + + fmt.Println("------------------") + + f := func(d Data) error { + fmt.Println("enqueue:", d.Name) + return nil + } + + q.Operate(f) +} + +// Data represents what is being stored on the queue. +type Data struct { + Name string +} + +// Queue represents a list of data. +type Queue struct { + Count int + data []Data + front int + end int +} + +// New returns a queue with a set capacity. +func New(cap int) (*Queue, error) { + if cap <= 0 { + return nil, errors.New("invalid capacity") + } + + q := Queue{ + front: 0, + end: 0, + data: make([]Data, cap), + } + return &q, nil +} + +// Enqueue inserts data into the queue if there +// is available capacity. +func (q *Queue) Enqueue(data Data) error { + + // If the front of the queue is right behind the end or + // if the front is at the end of the capacity and the end + // is at the beginning of the capacity, the queue is full. + // F E - Enqueue (Full) | E F - Enqueue (Full) + // [A][B][C] | [A][B][C] + if q.front+1 == q.end || + q.front == len(q.data) && q.end == 0 { + return errors.New("queue at capacity") + } + + switch { + case q.front == len(q.data): + + // If we are at the end of the capacity, then + // circle back to the beginning of the capacity by + // moving the front pointer to the beginning. + q.front = 0 + q.data[q.front] = data + default: + + // Add the data to the current front position + // and then move the front pointer. + q.data[q.front] = data + q.front++ + } + + q.Count++ + + return nil +} + +// Dequeue removes data into the queue if data exists. +func (q *Queue) Dequeue() (Data, error) { + + // If the front and end are the same, the + // queue is empty + // EF - (Empty) + // [ ][ ][ ] + if q.front == q.end { + return Data{}, errors.New("queue is empty") + } + + var data Data + switch { + case q.end == len(q.data): + + // If we are at the end of the capacity, then + // circle back to the beginning of the capacity by + // moving the end pointer to the beginning. + q.end = 0 + data = q.data[q.end] + default: + + // Remove the data from the current end position + // and then move the end pointer. + data = q.data[q.end] + q.end++ + } + + q.Count-- + + return data, nil +} + +// Operate accepts a function that takes data and calls +// the specified function for every piece of data found. +func (q *Queue) Operate(f func(d Data) error) error { + end := q.end + for { + if end == q.front { + break + } + + if end == len(q.data) { + end = 0 + } + + if err := f(q.data[end]); err != nil { + return err + } + + end++ + } + return nil +} diff --git a/_content/tour/grc/algorithms/data/stack.go b/_content/tour/grc/algorithms/data/stack.go new file mode 100644 index 00000000..097cda3d --- /dev/null +++ b/_content/tour/grc/algorithms/data/stack.go @@ -0,0 +1,104 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a basic stack. +package main + +import ( + "errors" + "fmt" +) + +func main() { + const items = 5 + + var s Stack + + for i := 0; i < items; i++ { + name := fmt.Sprintf("Name%d", i) + s.Push(Data{Name: name}) + + fmt.Println("push:", name) + } + + fmt.Println("------------------") + + f := func(d Data) error { + fmt.Println("pop:", d.Name) + return nil + } + + s.Operate(f) +} + +// Data represents what is being stored on the stack. +type Data struct { + Name string +} + +// Stack represents a stack of data. +type Stack struct { + data []Data +} + +// Make allows the creation of a stack with an initial +// capacity for efficiency. Otherwise a stack can be +// used in its zero value state. +func Make(cap int) *Stack { + return &Stack{ + data: make([]Data, 0, cap), + } +} + +// Count returns the number of items in the stack. +func (s *Stack) Count() int { + return len(s.data) +} + +// Push adds data into the top of the stack. +func (s *Stack) Push(data Data) { + s.data = append(s.data, data) +} + +// Pop removes data from the top of the stack. +func (s *Stack) Pop() (Data, error) { + if len(s.data) == 0 { + return Data{}, errors.New("stack empty") + } + + // Calculate the top level index. + idx := len(s.data) - 1 + + // Copy the data from that index position. + data := s.data[idx] + + // Remove the top level index from the slice. + s.data = s.data[:idx] + + return data, nil +} + +// Peek provides the data stored on the stack based +// on the level from the bottom. A value of 0 would +// return the top piece of data. +func (s *Stack) Peek(level int) (Data, error) { + if level < 0 || level > (len(s.data)-1) { + return Data{}, errors.New("invalid level position") + } + idx := (len(s.data) - 1) - level + return s.data[idx], nil +} + +// Operate accepts a function that takes data and calls +// the specified function for every piece of data found. +// It traverses from the top down through the stack. +func (s *Stack) Operate(f func(data Data) error) error { + for i := len(s.data) - 1; i > -1; i-- { + if err := f(s.data[i]); err != nil { + return err + } + } + return nil +} diff --git a/_content/tour/grc/algorithms/data/tree_binary.go b/_content/tour/grc/algorithms/data/tree_binary.go new file mode 100644 index 00000000..437e708a --- /dev/null +++ b/_content/tour/grc/algorithms/data/tree_binary.go @@ -0,0 +1,644 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a basic binary tree. +package main + +import ( + "errors" + "fmt" + "math" +) + +func main() { + values := []Data{ + {Key: 65, Name: "Bill"}, + {Key: 45, Name: "Ale"}, + {Key: 35, Name: "Joan"}, + {Key: 75, Name: "Hanna"}, + {Key: 85, Name: "John"}, + {Key: 78, Name: "Steph"}, + {Key: 95, Name: "Sally"}, + } + + var tree Tree + for _, value := range values { + tree.Insert(value) + } + + PrettyPrint(tree) + pre := tree.PreOrder() + fmt.Println("Pre-order :", pre) + in := tree.InOrder() + fmt.Println("In-order :", in) + post := tree.PostOrder() + fmt.Println("Post-order:", post) + + fmt.Print("\n") + d35, err := tree.Find(35) + if err != nil { + fmt.Println("ERROR: Unable to find 35") + return + } + fmt.Println("found:", d35) + + d78, err := tree.Find(78) + if err != nil { + fmt.Println("ERROR: Unable to find 78") + return + } + fmt.Println("found:", d78) + + d3, err := tree.Find(3) + if err == nil { + fmt.Println("ERROR: found 3", d3) + return + } + fmt.Println("not-found: 3") + + fmt.Print("\n") + tree.Delete(75) + PrettyPrint(tree) + + tree.Delete(85) + PrettyPrint(tree) +} + +// ============================================================================= + +// Data represents the information being stored. +type Data struct { + Key int + Name string +} + +// Tree represents all values in the tree. +type Tree struct { + root *node +} + +// Insert adds a value into the tree and keeps the tree balanced. +func (t *Tree) Insert(data Data) { + t.root = t.root.insert(t, data) + + if t.root.balRatio() < -1 || t.root.balRatio() > 1 { + t.root = t.root.rebalance() + } +} + +// Find traverses the tree looking for the specified tree. +func (t *Tree) Find(key int) (Data, error) { + if t.root == nil { + return Data{}, errors.New("cannot find from an empty tree") + } + + return t.root.find(key) +} + +// Delete removes the key from the tree and keeps it balanced. +func (t *Tree) Delete(key int) error { + if t.root == nil { + return errors.New("cannot delete from an empty tree") + } + + fakeParent := &node{right: t.root} + if err := t.root.delete(key, fakeParent); err != nil { + return err + } + + if fakeParent.right == nil { + t.root = nil + } + return nil +} + +// PreOrder traversal get the root node then traversing its child +// nodes recursively. +// Use cases: copying tree, mapping prefix notation. +// +// #1 +// / \ +// #2 #5 +// / \ / \ +// #3 #4 #6 #7 +func (t *Tree) PreOrder() []Data { + order := []Data{} + f := func(n *node) { + order = append(order, n.data) + } + t.root.preOrder(f) + return order +} + +// InOrder traversal travel from the leftmost node to the rightmost nodes +// regardless of depth. +// In-order traversal gives node values in ascending order. +// +// #4 +// / \ +// #2 #6 +// / \ / \ +// #1 #3 #5 #7 +func (t *Tree) InOrder() []Data { + order := []Data{} + f := func(n *node) { + order = append(order, n.data) + } + t.root.inOrder(f) + return order +} + +// PostOrder traversal get the leftmost node then its sibling then go up to its +// parent, recursively. +// Use cases: tree deletion, mapping postfix notation. +// +// #7 +// / \ +// #3 #6 +// / \ / \ +// #1 #2 #4 #5 +func (t *Tree) PostOrder() []Data { + order := []Data{} + f := func(n *node) { + order = append(order, n.data) + } + t.root.postOrder(f) + return order +} + +// ============================================================================= + +// node represents the data stored in the tree. +type node struct { + data Data + level int + tree *Tree + left *node + right *node +} + +// height returned the level of the tree the node exists in. +// Level 1 is at the last layer of the tree. +// +// #7 -- height = 3 +// / \ +// #3 #6 -- height = 2 +// / \ / \ +// #1 #2 #4 #5 -- height = 1 +func (n *node) height() int { + if n == nil { + return 0 + } + return n.level +} + +// insert adds the node into the tree and makes sure the +// tree stays balanced. +func (n *node) insert(t *Tree, data Data) *node { + if n == nil { + return &node{data: data, level: 1, tree: t} + } + + switch { + case data.Key < n.data.Key: + n.left = n.left.insert(t, data) + + case data.Key > n.data.Key: + n.right = n.right.insert(t, data) + + default: + return n.rebalance() + } + + n.level = max(n.left.height(), n.right.height()) + 1 + return n.rebalance() +} + +// find traverses the tree looking for the specified key. +func (n *node) find(key int) (Data, error) { + if n == nil { + return Data{}, errors.New("key not found") + } + + switch { + case n.data.Key == key: + return n.data, nil + + case key < n.data.Key: + return n.left.find(key) + + default: + return n.right.find(key) + } +} + +// balRatio provides information about the balance ratio +// of the node. +func (n *node) balRatio() int { + return n.right.height() - n.left.height() +} + +// rotateLeft turns the node to the left. +// +// #3 #4 +// \ / \ +// #4 #3 #5 +// \ +// #5 +func (n *node) rotateLeft() *node { + r := n.right + n.right = r.left + r.left = n + n.level = max(n.left.height(), n.right.height()) + 1 + r.level = max(r.left.height(), r.right.height()) + 1 + return r +} + +// rotateRight turns the node to the right. +// +// #5 #4 +// / / \ +// #4 #3 #5 +// / +// #3 +func (n *node) rotateRight() *node { + l := n.left + n.left = l.right + l.right = n + n.level = max(n.left.height(), n.right.height()) + 1 + l.level = max(l.left.height(), l.right.height()) + 1 + return l +} + +// rotateLeftRight turns the node to the left and then right. +// +// #5 #5 #4 +// / / / \ +// #3 #4 #3 #5 +// \ / +// #4 #3 +func (n *node) rotateLeftRight() *node { + n.left = n.left.rotateLeft() + n = n.rotateRight() + n.level = max(n.left.height(), n.right.height()) + 1 + return n +} + +// rotateLeftRight turns the node to the left and then right. +// +// #3 #3 #4 +// \ \ / \ +// #5 #4 #3 #5 +// / \ +// #4 #5 +func (n *node) rotateRightLeft() *node { + n.right = n.right.rotateRight() + n = n.rotateLeft() + n.level = max(n.left.height(), n.right.height()) + 1 + return n +} + +// rebalance will rotate the nodes based on the ratio. +func (n *node) rebalance() *node { + switch { + case n.balRatio() < -1 && n.left.balRatio() == -1: + return n.rotateRight() + + case n.balRatio() > 1 && n.right.balRatio() == 1: + return n.rotateLeft() + + case n.balRatio() < -1 && n.left.balRatio() == 1: + return n.rotateLeftRight() + + case n.balRatio() > 1 && n.right.balRatio() == -1: + return n.rotateRightLeft() + } + return n +} + +// findMax finds the maximum element in a (sub-)tree. Its value replaces +// the value of the to-be-deleted node. Return values: the node itself and +// its parent node. +func (n *node) findMax(parent *node) (*node, *node) { + switch { + case n == nil: + return nil, parent + + case n.right == nil: + return n, parent + } + return n.right.findMax(n) +} + +// replaceNode replaces the parent’s child pointer to n with a pointer +// to the replacement node. parent must not be nil. +func (n *node) replaceNode(parent, replacement *node) error { + if n == nil { + return errors.New("replaceNode() not allowed on a nil node") + } + + switch n { + case parent.left: + parent.left = replacement + + default: + parent.right = replacement + } + + return nil +} + +// delete removes an element from the tree. It is an error to try +// deleting an element that does not exist. In order to remove an +// element properly, Delete needs to know the node’s parent node. +// Parent must not be nil. +func (n *node) delete(key int, parent *node) error { + if n == nil { + return errors.New("value to be deleted does not exist in the tree") + } + + switch { + case key < n.data.Key: + return n.left.delete(key, n) + + case key > n.data.Key: + return n.right.delete(key, n) + + default: + switch { + case n.left == nil && n.right == nil: + n.replaceNode(parent, nil) + return nil + case n.left == nil: + n.replaceNode(parent, n.right) + return nil + case n.right == nil: + n.replaceNode(parent, n.left) + return nil + } + replacement, replParent := n.left.findMax(n) + n.data = replacement.data + return replacement.delete(replacement.data.Key, replParent) + } +} + +// preOrder traverses the node by traversing the child nodes recursively. +func (n *node) preOrder(f func(*node)) { + if n != nil { + f(n) + n.left.preOrder(f) + n.right.preOrder(f) + } +} + +// inOrder traversal the node by the leftmost node to the rightmost nodes +// regardless of depth. +func (n *node) inOrder(f func(*node)) { + if n != nil { + n.left.inOrder(f) + f(n) + n.right.inOrder(f) + } +} + +// postOrder traversal the node by the leftmost node then its sibling +// then up to its parent, recursively. +func (n *node) postOrder(f func(*node)) { + if n != nil { + n.left.postOrder(f) + n.right.postOrder(f) + f(n) + } +} + +// ============================================================================= + +// max returns the larger of the two values. +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// ============================================================================= + +// PrettyPrint takes a Tree value and displays a pretty print +// version of the tree. +func PrettyPrint(t Tree) { + + // Build an index map of positions for print layout. + values := make(map[int]int) + maxIdx := buildIndexMap(values, 0, 0, t.root) + + // Calculate the total number of levels based on + // the max index provided. + var levels int + for { + pow := math.Pow(2, float64(levels)) + if maxIdx < int(pow) { + break + } + levels++ + } + levels-- + + // Capture the positional data to use. + data := generateData(levels) + + // Set the edge of the top of the tree. + for sp := 0; sp < data[0].edge; sp++ { + fmt.Print(" ") + } + fmt.Printf("%02d", values[0]) + fmt.Print("\n") + + dataIdx := 1 + for i := 1; i < len(data); i = i + 2 { + + // Set the edge of this row. + for sp := 0; sp < data[i].edge; sp++ { + fmt.Print(" ") + } + + // Draw the hashes for this row. + dataHashIdx := dataIdx + for h := 0; h < data[i].draw; h++ { + if values[dataHashIdx] != maxInt { + fmt.Printf("/") + } else { + fmt.Printf(" ") + } + for sp := 0; sp < data[i].padding; sp++ { + fmt.Print(" ") + } + if values[dataHashIdx+1] != maxInt { + fmt.Printf("\\") + } else { + fmt.Printf(" ") + } + dataHashIdx += 2 + + if data[i].gaps != 0 && data[i].gaps > h { + for sp := 0; sp < data[i].gapPad; sp++ { + fmt.Print(" ") + } + } + } + fmt.Print("\n") + + // Set the edge of the next row. + for sp := 0; sp < data[i+1].edge; sp++ { + fmt.Print(" ") + } + + // Draw the numbers for this row. + for n := 0; n < data[i+1].draw; n++ { + if values[dataIdx] != maxInt { + fmt.Printf("%02d", values[dataIdx]) + } else { + fmt.Printf(" ") + } + for sp := 0; sp < data[i+1].padding; sp++ { + fmt.Print(" ") + } + if values[dataIdx+1] != maxInt { + fmt.Printf("%02d", values[dataIdx+1]) + } else { + fmt.Printf(" ") + } + dataIdx += 2 + + if data[i+1].gaps != 0 && data[i+1].gaps > n { + for sp := 0; sp < data[i+1].gapPad; sp++ { + fmt.Print(" ") + } + } + } + fmt.Print("\n") + } + + fmt.Print("\n") +} + +const maxInt = int(^uint(0) >> 1) + +// buildIndex traverses the tree and generates a map of index positions +// for each node in the tree for printing. +// +// 40 +// / \ +// 05 80 +// / \ / \ +// 02 25 65 98 +// +// values{0:40, 1:05, 2:80, 3:02, 4:25, 5:65, 6:98} +func buildIndexMap(values map[int]int, idx int, maxIdx int, n *node) int { + + // We need to keep track of the highest index position used + // to help calculate tree depth. + if idx > maxIdx { + maxIdx = idx + } + + // We have reached the end of a branch. Use the maxInt to mark + // no value in that position. + if n == nil { + values[idx] = maxInt + return maxIdx + } + + // Save the value of this node in the map at the + // calculated index position. + values[idx] = n.data.Key + + // Check if there are still nodes to check down the left + // branch. When we move down the tree, the next index doubles. + if n.left != nil { + nextidx := 2*idx + 1 + maxIdx = buildIndexMap(values, nextidx, maxIdx, n.left) + } + + // Check if there are still nodes to check down the right + // branch. When we move down the tree, the next index doubles. + nextidx := 2*idx + 2 + maxIdx = buildIndexMap(values, nextidx, maxIdx, n.right) + + // We need to set missing indexes in the map to maxInt. + // So they are ignored in the printing of the map. + if idx == 0 { + for i := 0; i < maxIdx; i++ { + if _, ok := values[i]; !ok { + values[i] = maxInt + } + } + } + + return maxIdx +} + +// pos provides positional data for printing a tree. +type pos struct { + edge int + draw int + padding int + gaps int + gapPad int +} + +// generateData generates all the positional data needed to display +// nodes at different levels. +func generateData(level int) []pos { + totalData := (level * 2) - 1 + data := make([]pos, totalData) + edge := 1 + draw := level - 2 + padding := 0 + gapPad := 2 + + for i := totalData - 1; i >= 0; i = i - 2 { + + // Generate starting edge positions. + data[i].edge = int(math.Pow(2, float64(edge))) + if i > 0 { + data[i-1].edge = data[i].edge + 1 + } + edge++ + + // Generate draw information. + if draw > 0 { + data[i].draw = int(math.Pow(2, float64(draw))) + data[i-1].draw = data[i].draw + } else { + data[i].draw = 1 + if i > 0 { + data[i-1].draw = 1 + } + } + draw-- + + // Generate padding information. + padding += data[i].edge + data[i].padding = padding + if i > 0 { + data[i-1].padding = padding + } + + // Generate gaps information. + data[i].gaps = data[i].draw - 1 + if i > 0 { + data[i-1].gaps = data[i].gaps + } + + // Generate gap padding information. + if i > 2 { + data[i-1].gapPad = int(math.Pow(2, float64(gapPad))) + data[i].gapPad = data[i-1].gapPad - 2 + } + gapPad++ + } + + return data +} diff --git a/_content/tour/grc/algorithms/fun/barber.go b/_content/tour/grc/algorithms/fun/barber.go new file mode 100644 index 00000000..f02d7298 --- /dev/null +++ b/_content/tour/grc/algorithms/fun/barber.go @@ -0,0 +1,142 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to implement the sleeping barber +// problem. +package main + +import ( + "errors" + "fmt" + "math/rand" + "sync" + "sync/atomic" + "time" +) + +func main() { + const maxChairs = 3 + + shop := OpenShop(maxChairs) + defer shop.Close() + + // Close the shop in 50 milliseconds. + t := time.NewTimer(50 * time.Millisecond) + <-t.C +} + +// ============================================================================= + +var ( + // ErrShopClosed is returned when the shop is closed. + ErrShopClosed = errors.New("shop closed") + + // ErrNoChair is returned when all the chairs are occupied. + ErrNoChair = errors.New("no chair available") +) + +// customer represents a customer to be serviced. +type customer struct { + name string +} + +// Shop represents the barber's shop which contains chairs for customers +// that customers can occupy and the barber can service. The shop can +// be closed for business. +type Shop struct { + open int32 // Determines if the shop is open for business. + chairs chan customer // The set of chairs customers wait in. + wgClose sync.WaitGroup // Provides support for closing the shop. +} + +// OpenShop creates a new shop for business and gets the barber working. +func OpenShop(maxChairs int) *Shop { + fmt.Println("Opening the shop") + + s := Shop{ + chairs: make(chan customer, maxChairs), + } + atomic.StoreInt32(&s.open, 1) + + // This is the barber and they will service customers. + + s.wgClose.Add(1) + go func() { + defer s.wgClose.Done() + + fmt.Println("Barber ready to work") + + for cust := range s.chairs { + s.serviceCustomer(cust) + } + }() + + // Start creating customers who enter the shop. + + go func() { + var id int64 + + for { + // Wait some random time for the next customer to enter. + time.Sleep(time.Duration(rand.Intn(75)) * time.Millisecond) + + name := fmt.Sprintf("cust-%d", atomic.AddInt64(&id, 1)) + if err := s.newCustomer(name); err != nil { + if err == ErrShopClosed { + break + } + } + } + }() + + return &s +} + +// Close prevents any new customers from entering the shop and waits for +// the barber to finish all existing customers. +func (s *Shop) Close() { + fmt.Println("Closing the shop") + defer fmt.Println("Shop closed") + + // Mark the shop closed. + atomic.StoreInt32(&s.open, 0) + + // Wait for the barber to finish with the existing customers. + close(s.chairs) + s.wgClose.Wait() +} + +// ============================================================================= + +func (s *Shop) serviceCustomer(cust customer) { + fmt.Printf("Barber servicing customer %q\n", cust.name) + + time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond) + + fmt.Printf("Barber finished customer %q\n", cust.name) + + if len(s.chairs) == 0 && atomic.LoadInt32(&s.open) == 1 { + fmt.Println("Barber taking a nap") + } +} + +func (s *Shop) newCustomer(name string) error { + if atomic.LoadInt32(&s.open) == 0 { + fmt.Printf("Customer %q leaves, shop closed\n", name) + return ErrShopClosed + } + + fmt.Printf("Customer %q entered shop\n", name) + + select { + case s.chairs <- customer{name: name}: + fmt.Printf("Customer %q takes a seat and waits\n", name) + + default: + fmt.Printf("Customer %q leaves, no seat\n", name) + } + + return nil +} diff --git a/_content/tour/grc/algorithms/fun/freq_concurrent.go b/_content/tour/grc/algorithms/fun/freq_concurrent.go new file mode 100644 index 00000000..3b91df41 --- /dev/null +++ b/_content/tour/grc/algorithms/fun/freq_concurrent.go @@ -0,0 +1,84 @@ +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample concurrent program shows you how to implement a function +// that can find the frequency a given rune is used in a specified sentence. +package main + +import ( + "fmt" + "runtime" +) + +func main() { + sentence := `The quick brown fox jumps over the lazy dog Stay hungry. + Stay foolish Keep going. Be all in Boldness be my friend Screw it, + let's do it My life is my message Leave no stone unturned Dream big. + Pray bigger` + + print(concurrent(sentence)) +} + +func concurrent(text string) map[rune]int { + m := make(map[rune]int) // Map with final result + g := runtime.GOMAXPROCS(0) // Number of goroutines + l := len(text) // Number of bytes to process + b := l / g // Number of buckets, one per goroutine + + // Receives the result of each bucket processed + // by a goroutine. + ch := make(chan map[rune]int, g) + + // Create g number of goroutines. + + for i := 0; i < g; i++ { + str := i * b // Starting idx position of bucket + end := str + b // Ending idx position of bucket + if i == g-1 { // The last bucket gets ant remaining bytes + end = end + (l - end) + } + + go func() { + m := make(map[rune]int) + + defer func() { + ch <- m + }() + + // This G processes its bucket sequentially. + for _, r := range text[str:end] { + m[r]++ + } + }() + } + + // Wait for the results of each bucket to come + // in and process them into the final map. + + for i := 0; i < g; i++ { + result := <-ch + for rk, rv := range result { + m[rk] = m[rk] + rv + } + } + + return m +} + +func print(m map[rune]int) { + var cols int + + for r := 65; r < 65+26; r++ { + v := m[rune(r)] + fmt.Printf("%q:%d, ", rune(r), v) + + v = m[rune(r+32)] + fmt.Printf("%q:%d, ", rune(r+32), v) + + cols++ + if cols == 5 { + fmt.Print("\n") + cols = 0 + } + } +} diff --git a/_content/tour/grc/algorithms/fun/freq_sequential.go b/_content/tour/grc/algorithms/fun/freq_sequential.go new file mode 100644 index 00000000..66483efc --- /dev/null +++ b/_content/tour/grc/algorithms/fun/freq_sequential.go @@ -0,0 +1,49 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to implement a function that can +// find the frequency a given rune is used in a specified sentence. +package main + +import ( + "fmt" +) + +func main() { + sentence := `The quick brown fox jumps over the lazy dog Stay hungry. + Stay foolish Keep going. Be all in Boldness be my friend Screw it, + let's do it My life is my message Leave no stone unturned Dream big. + Pray bigger` + + print(sequential(sentence)) +} + +func sequential(text string) map[rune]int { + m := make(map[rune]int) + + for _, r := range text { + m[r]++ + } + + return m +} + +func print(m map[rune]int) { + var cols int + + for r := 65; r < 65+26; r++ { + v := m[rune(r)] + fmt.Printf("%q:%d, ", rune(r), v) + + v = m[rune(r+32)] + fmt.Printf("%q:%d, ", rune(r+32), v) + + cols++ + if cols == 5 { + fmt.Print("\n") + cols = 0 + } + } +} diff --git a/_content/tour/grc/algorithms/fun/vlq.go b/_content/tour/grc/algorithms/fun/vlq.go new file mode 100644 index 00000000..98418e56 --- /dev/null +++ b/_content/tour/grc/algorithms/fun/vlq.go @@ -0,0 +1,130 @@ +//go:build OMIT + +package main + +import ( + "fmt" + "math" + "math/bits" +) + +func main() { + inputs := [][]byte{ + []byte{0x7F}, + []byte{0x81, 0x00}, + []byte{0xC0, 0x00}, + []byte{0xFF, 0x7F}, + []byte{0x81, 0x80, 0x00}, + []byte{0xFF, 0xFF, 0x7F}, + []byte{0x81, 0x80, 0x80, 0x00}, + []byte{0xC0, 0x80, 0x80, 0x00}, + []byte{0xFF, 0xFF, 0xFF, 0x7F}, + []byte{0x82, 0x00}, + []byte{0x81, 0x10}, + } + + for _, input := range inputs { + decoded, err := DecodeVarint(input) + if err != nil { + fmt.Println(err) + return + } + + encoded := EncodeVarint(decoded) + fmt.Printf("input 0x%x, decoded: %d, encoded: 0x%x\n", input, decoded, encoded) + } +} + +// DecodeVarint takes a variable length VLQ based integer and +// decodes it into a 32 bit integer. +func DecodeVarint(input []byte) (uint32, error) { + const lastBitSet = 0x80 // 1000 0000 + + var d uint32 + var bitPos int + + for i := len(input) - 1; i >= 0; i-- { + n := uint8(input[i]) + + // Process the first 7 bits and ignore the 8th. + for checkBit := 0; checkBit < 7; checkBit++ { + + // Rotate the last bit off and move it to the back. + // Before: 0000 0001 + // After: 1000 0000 + n = bits.RotateLeft8(n, -1) + + // Calculate based on only those 1 bits that were rotated. + // Convert the bitPos to base 10. + if n >= lastBitSet { + switch { + case bitPos == 0: + d++ + default: + base10 := math.Pow(2, float64(bitPos)) + d += uint32(base10) + } + } + + // Move the bit position. + bitPos++ + } + } + + return d, nil +} + +// EncodeVarint takes a 32 bit integer and encodes it into +// a variable length VLQ based integer. +func EncodeVarint(n uint32) []byte { + const maxBytes = 4 + const eightBitSet = 0x80 // 1000 0000 + const lastBitSet = 0x80000000 // 1000 0000 0000 0000 + + encoded := make([]byte, maxBytes) + + for bytePos := maxBytes - 1; bytePos >= 0; bytePos-- { + var d uint8 + + // Process the next 7 bits. + for checkBit := 0; checkBit < 7; checkBit++ { + + // Rotate the last bit off and move it to the back. + // Before: 0000 0000 0000 0001 + // After: 1000 0000 0000 0000 + n = bits.RotateLeft32(n, -1) + + // Calculate based on only those 1 bits that were + // rotated. Convert the bit position to base 10. + if n >= lastBitSet { + switch { + case checkBit == 0: + d++ + default: + base10 := math.Pow(2, float64(checkBit)) + d += uint8(base10) + } + } + } + + // These values need the 8th bit to be set as 1. + if bytePos < 3 { + d += eightBitSet + } + + // Store the value in reserve order. + encoded[bytePos] = d + } + + // Remove leading zero values by finding values that only + // have their eight bit set. + for bytePos, b := range encoded { + if b == eightBitSet { + continue + } + encoded = encoded[bytePos:] + break + } + + return encoded +} diff --git a/_content/tour/grc/algorithms/numbers/palindrome.go b/_content/tour/grc/algorithms/numbers/palindrome.go new file mode 100644 index 00000000..ac547561 --- /dev/null +++ b/_content/tour/grc/algorithms/numbers/palindrome.go @@ -0,0 +1,76 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to determine if an integer is a +// palindrome or not. +package main + +import "fmt" + +func main() { + tt := []int{-1, 1, 9, 10, 1001, 125} + + for _, input := range tt { + success := IsPalindrome(input) + + switch success { + case true: + fmt.Printf("%d is a palindrome\n", input) + + case false: + fmt.Printf("%d is NOT a palindrome\n", input) + } + } +} + +// IsPalindrome checks if a integer is a Palindrome. +func IsPalindrome(input int) bool { + + // A negative integer is not a palindrome. + if input < 0 { + return false + } + + // An integer that is only one digit in length is a palindrome. + if input >= 0 && input < 10 { + return true + } + + // Reverse the digits in the integer. + rev := Reverse(input) + + return input == rev +} + +// Reverse takes the specified integer and reverses it. +func Reverse(num int) int { + + // Construct result to its zero value. + var result int + + // Loop until num is zero. + for num != 0 { + + // Perform a modulus operation to get the last digit from the value set in num. + // https://www.geeksforgeeks.org/find-first-last-digits-number/ + // Ex. For num = 125, last = 5 + last := num % 10 + + // Multiple the current result by 10 to shift the digits in + // the current result to the left. + // Ex. For result = 5, result = 50 + result = result * 10 + + // Add the digit we took from the end of num to the result. + // Ex. For result = 21 and last = 5, result = 215 + result += last + + // Remove the digit we just reversed from num. + // Ex. For num = 125, num = 12 + num = num / 10 + } + + return result +} diff --git a/_content/tour/grc/algorithms/searches/binary_iterative.go b/_content/tour/grc/algorithms/searches/binary_iterative.go new file mode 100644 index 00000000..50540300 --- /dev/null +++ b/_content/tour/grc/algorithms/searches/binary_iterative.go @@ -0,0 +1,63 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a binary search using an +// iterative approach. +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + numbers := []int{4, 42, 80, 83, 121, 137, 169, 182, 185, 180} + find := rand.Intn(10) + + fmt.Println("Numbers:", numbers) + fmt.Println("Find. :", numbers[find]) + + idx, err := binarySearchIterative(numbers, numbers[find]) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("Found : Idx", idx) +} + +func binarySearchIterative(sortedList []int, target int) (int, error) { + var leftIdx int + rightIdx := len(sortedList) - 1 + + // Loop until we find the target or searched the list. + for leftIdx <= rightIdx { + + // Calculate the middle index of the list. + mid := (leftIdx + rightIdx) / 2 + + // Capture the value to check. + value := sortedList[mid] + + switch { + + // Check if we found the target. + case value == target: + return mid, nil + + // If the value is greater than the target, cut the list + // by moving the rightIdx into the list. + case value > target: + rightIdx = mid - 1 + + // If the value is less than the target, cut the list + // by moving the leftIdx into the list. + case value < target: + leftIdx = mid + 1 + } + } + + return -1, fmt.Errorf("target not found") +} diff --git a/_content/tour/grc/algorithms/searches/binary_recursive.go b/_content/tour/grc/algorithms/searches/binary_recursive.go new file mode 100644 index 00000000..ddfeedee --- /dev/null +++ b/_content/tour/grc/algorithms/searches/binary_recursive.go @@ -0,0 +1,68 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a binary search using a +// recursive approach. +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + numbers := []int{4, 42, 80, 83, 121, 137, 169, 182, 185, 180} + find := rand.Intn(10) + + fmt.Println("Numbers:", numbers) + fmt.Println("Find. :", numbers[find]) + + idx, err := binarySearchRecursive(numbers, numbers[find], 0, len(numbers)) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("Found : Idx", idx) +} + +func binarySearchRecursive(sortedList []int, target int, leftIdx int, rightIdx int) (int, error) { + + // Calculate the middle index of the list. + midIdx := (leftIdx + rightIdx) / 2 + + // Check until leftIdx is smaller or equal with rightIdx. + if leftIdx <= rightIdx { + + switch { + + // Check if we found the target. + case sortedList[midIdx] == target: + return midIdx, nil + + // If the value is greater than the target, cut the list + // by moving the rightIdx into the list. + case sortedList[midIdx] > target: + return binarySearchRecursive(sortedList, target, leftIdx, midIdx-1) + + // If the value is less than the target, cut the list + // by moving the leftIdx into the list. + case sortedList[midIdx] < target: + return binarySearchRecursive(sortedList, target, midIdx+1, rightIdx) + } + } + + return -1, fmt.Errorf("target not found") +} + +func generateList(totalNumbers int) []int { + numbers := make([]int, totalNumbers) + + for i := 0; i < totalNumbers; i++ { + numbers[i] = rand.Intn(totalNumbers * 20) + } + + return numbers +} diff --git a/_content/tour/grc/algorithms/slices/max_number.go b/_content/tour/grc/algorithms/slices/max_number.go new file mode 100644 index 00000000..bca9466e --- /dev/null +++ b/_content/tour/grc/algorithms/slices/max_number.go @@ -0,0 +1,66 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to retrieve the maximum integer +// from a slice of integers. +package main + +import "fmt" + +func main() { + tt := []struct { + input []int + expected int + }{ + {[]int{}, 0}, + {nil, 0}, + {[]int{10}, 10}, + {[]int{20, 30, 10, 50}, 50}, + {[]int{30, 50, 10}, 50}, + } + + for _, test := range tt { + value, err := Max(test.input) + if err != nil { + fmt.Println(err) + continue + } + + fmt.Printf("Input: %d, Value: %d, Expected: %d, Match: %v\n", + test.input, + value, + test.expected, + value == test.expected, + ) + } +} + +// Max returns the maximum integer in the slice. +func Max(n []int) (int, error) { + + // First check there are numbers in the collection. + if len(n) == 0 { + return 0, fmt.Errorf("slice %#v has no elements", n) + } + + // If the length of the slice is 1 then return the + // integer at index 0. + if len(n) == 1 { + return n[0], nil + } + + // Save the first value as current max and then loop over + // the slice of integers looking for a larger number. + max := n[0] + for _, num := range n[1:] { + + // If num is greater than max, assign max to num. + if num > max { + max = num + } + } + + return max, nil +} diff --git a/_content/tour/grc/algorithms/slices/min_number.go b/_content/tour/grc/algorithms/slices/min_number.go new file mode 100644 index 00000000..62b9d21f --- /dev/null +++ b/_content/tour/grc/algorithms/slices/min_number.go @@ -0,0 +1,66 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to retrieve the minimum integer +// from a slice of integers. +package main + +import "fmt" + +func main() { + tt := []struct { + input []int + expected int + }{ + {[]int{}, 0}, + {nil, 0}, + {[]int{10}, 10}, + {[]int{20, 30, 10, 50}, 10}, + {[]int{30, 50, 10}, 10}, + } + + for _, test := range tt { + value, err := Min(test.input) + if err != nil { + fmt.Println(err) + continue + } + + fmt.Printf("Input: %d, Value: %d, Expected: %d, Match: %v\n", + test.input, + value, + test.expected, + value == test.expected, + ) + } +} + +// Min returns the minimum integer in the slice. +func Min(n []int) (int, error) { + + // First check there are numbers in the collection. + if len(n) == 0 { + return 0, fmt.Errorf("slice %#v has no elements", n) + } + + // If the length of the slice is 1 then return the + // integer at index 0. + if len(n) == 1 { + return n[0], nil + } + + // Save the first value as current min and then loop over + // the slice of integers looking for a smaller number. + min := n[0] + for _, num := range n[1:] { + + // If num is less than min. Assign min to num. + if num < min { + min = num + } + } + + return min, nil +} diff --git a/_content/tour/grc/algorithms/sorting/bubble.go b/_content/tour/grc/algorithms/sorting/bubble.go new file mode 100644 index 00000000..202f148c --- /dev/null +++ b/_content/tour/grc/algorithms/sorting/bubble.go @@ -0,0 +1,64 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a bubble sort. +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + numbers := generateList(10) + fmt.Println("Before:", numbers) + + bubbleSort(numbers) + fmt.Println("Sequential:", numbers) +} + +func bubbleSort(numbers []int) { + n := len(numbers) + + for i := 0; i < n; i++ { + if !sweep(numbers, i) { + return + } + } +} + +func sweep(numbers []int, currentPass int) bool { + var idx int + var swap bool + + idxNext := idx + 1 + n := len(numbers) + + for idxNext < (n - currentPass) { + a := numbers[idx] + b := numbers[idxNext] + + if a > b { + numbers[idx] = b + numbers[idxNext] = a + swap = true + } + + idx++ + idxNext = idx + 1 + } + + return swap +} + +func generateList(totalNumbers int) []int { + numbers := make([]int, totalNumbers) + + for i := 0; i < totalNumbers; i++ { + numbers[i] = rand.Intn(totalNumbers * 20) + } + + return numbers +} diff --git a/_content/tour/grc/algorithms/sorting/heap.go b/_content/tour/grc/algorithms/sorting/heap.go new file mode 100644 index 00000000..a08a9748 --- /dev/null +++ b/_content/tour/grc/algorithms/sorting/heap.go @@ -0,0 +1,96 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a heap sort. +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + numbers := generateList(10) + fmt.Println("Before:", numbers) + + heapSort(numbers) + fmt.Println("Sequential:", numbers) +} + +func heapSort(numbers []int) []int { + + // Split the list in half and work the front half of the list, moving + // the largest value we find to the front of the list and then the + // second largest. + + for index := (len(numbers) / 2) - 1; index >= 0; index-- { + numbers = moveLargest(numbers, len(numbers), index) + } + + // Take the list and start moving numbers out and into a new sorted + // list. Take the number in the first position and remove it to the + // new list which will contain the final sort. Then move the largest + // number we find once again to the front of the list. + + size := len(numbers) + for index := size - 1; index >= 1; index-- { + numbers[0], numbers[index] = numbers[index], numbers[0] + size-- + numbers = moveLargest(numbers, size, 0) + } + + return numbers +} + +// moveLargest starts at the index positions specified in the list and attempts +// to move the largest number it can find to that position in the list. +func moveLargest(numbers []int, size int, index int) []int { + + // Calculate the index deviation so numbers in the list can be + // compared and swapped if needed. + // index 0: cmpIdx1: 1 cmpIdx2: 2 index 5: cmpIdx1: 11 cmpIdx2: 12 + // index 1: cmpIdx1: 3 cmpIdx2: 4 index 6: cmpIdx1: 13 cmpIdx2: 14 + // index 2: cmpIdx1: 5 cmpIdx2: 6 index 7: cmpIdx1: 15 cmpIdx2: 16 + // index 3: cmpIdx1: 7 cmpIdx2: 8 index 8: cmpIdx1: 17 cmpIdx2: 19 + // index 4: cmpIdx1: 9 cmpIdx2: 10 index 9: cmpIdx1: 19 cmpIdx2: 20 + cmpIdx1, cmpIdx2 := 2*index+1, 2*index+2 + + // Save the specified index as the index with the current largest value. + largestValueIdx := index + + // Check if the value at the first deviation index is greater than + // the value at the current largest index. If so, save that + // index position. + if cmpIdx1 < size && numbers[cmpIdx1] > numbers[largestValueIdx] { + largestValueIdx = cmpIdx1 + } + + // Check the second deviation index is within bounds and is greater + // than the value at the current largest index. If so, save that + // index position. + if cmpIdx2 < size && numbers[cmpIdx2] > numbers[largestValueIdx] { + largestValueIdx = cmpIdx2 + } + + // If we found a larger value than the value at the specified index, swap + // those numbers and then recurse to find more numbers to swap from that + // point in the list. + if largestValueIdx != index { + numbers[index], numbers[largestValueIdx] = numbers[largestValueIdx], numbers[index] + numbers = moveLargest(numbers, size, largestValueIdx) + } + + return numbers +} + +func generateList(totalNumbers int) []int { + numbers := make([]int, totalNumbers) + + for i := 0; i < totalNumbers; i++ { + numbers[i] = rand.Intn(totalNumbers * 20) + } + + return numbers +} diff --git a/_content/tour/grc/algorithms/sorting/insertion.go b/_content/tour/grc/algorithms/sorting/insertion.go new file mode 100644 index 00000000..665a4a75 --- /dev/null +++ b/_content/tour/grc/algorithms/sorting/insertion.go @@ -0,0 +1,60 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a insertion sort. +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + numbers := generateList(10) + fmt.Println("Before:", numbers) + + insertionSort(numbers) + fmt.Println("Sequential:", numbers) +} + +func insertionSort(numbers []int) { + var n = len(numbers) + + // Walk through the numbers from left to right. Through + // each outer loop iteration we move values from right + // to left inside the array when they are larger than + // the value that preceed it. + + for i := 1; i < n; i++ { + j := i + + // For the given starting i index position, look + // for smaller values to move left down the numbers list. + + for j > 0 { + + // Is the value on the left larger than the + // right. If true, swap the two values. + + if numbers[j-1] > numbers[j] { + numbers[j-1], numbers[j] = numbers[j], numbers[j-1] + } + + // Walk through the item from right to left. + + j-- + } + } +} + +func generateList(totalNumbers int) []int { + numbers := make([]int, totalNumbers) + + for i := 0; i < totalNumbers; i++ { + numbers[i] = rand.Intn(totalNumbers * 20) + } + + return numbers +} diff --git a/_content/tour/grc/algorithms/sorting/quick.go b/_content/tour/grc/algorithms/sorting/quick.go new file mode 100644 index 00000000..03c57b04 --- /dev/null +++ b/_content/tour/grc/algorithms/sorting/quick.go @@ -0,0 +1,66 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to write a quick sort. +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + numbers := generateList(5) + fmt.Println("Before:", numbers) + + QuickSort(numbers) + fmt.Println("Sequential:", numbers) +} + +func QuickSort(numbers []int) []int { + return quickSort(numbers, 0, len(numbers)-1) +} + +func quickSort(numbers []int, leftIdx, rightIdx int) []int { + switch { + case leftIdx > rightIdx: + return numbers + + // Divides array into two partitions. + case leftIdx < rightIdx: + numbers, pivotIdx := partition(numbers, leftIdx, rightIdx) + + quickSort(numbers, leftIdx, pivotIdx-1) + quickSort(numbers, pivotIdx+1, rightIdx) + } + + return numbers +} + +// partition it takes a portion of an array then sort it. +func partition(numbers []int, leftIdx, rightIdx int) ([]int, int) { + pivot := numbers[rightIdx] + + for smallest := leftIdx; smallest < rightIdx; smallest++ { + if numbers[smallest] < pivot { + numbers[smallest], numbers[leftIdx] = numbers[leftIdx], numbers[smallest] + leftIdx++ + } + } + + numbers[leftIdx], numbers[rightIdx] = numbers[rightIdx], numbers[leftIdx] + + return numbers, leftIdx +} + +func generateList(totalNumbers int) []int { + numbers := make([]int, totalNumbers) + + for i := 0; i < totalNumbers; i++ { + numbers[i] = rand.Intn(totalNumbers * 20) + } + + return numbers +} diff --git a/_content/tour/grc/algorithms/strings/palindrome.go b/_content/tour/grc/algorithms/strings/palindrome.go new file mode 100644 index 00000000..b8f39f21 --- /dev/null +++ b/_content/tour/grc/algorithms/strings/palindrome.go @@ -0,0 +1,51 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to determine if a string is a +// palindrome or not. +package main + +import "fmt" + +func main() { + tt := []string{"", "G", "bob", "otto", "汉字汉", "test"} + + for _, input := range tt { + success := IsPalindrome(input) + + switch success { + case true: + fmt.Printf("%q is a palindrome\n", input) + + case false: + fmt.Printf("%q is NOT a palindrome\n", input) + } + } +} + +// ============================================================================= + +// IsPalindrome checks if a string is a Palindrome. +func IsPalindrome(input string) bool { + + // If the input string is empty or as a length of 1 return true. + if input == "" || len(input) == 1 { + return true + } + + // Convert the input string into slice of runes for processing. + // A rune represent a code point in the UTF-8 character set. + runes := []rune(input) + + // Run over runes forward and backward comparing runes. + // If runes[i] != runes[len(runes)-i-1] then it's not a palindrome. + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + if runes[i] != runes[j] { + return false + } + } + + return true +} diff --git a/_content/tour/grc/algorithms/strings/permutation.go b/_content/tour/grc/algorithms/strings/permutation.go new file mode 100644 index 00000000..a75f6189 --- /dev/null +++ b/_content/tour/grc/algorithms/strings/permutation.go @@ -0,0 +1,68 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program shows you how to determine if a string is a +// permutation or not. +package main + +import ( + "fmt" + "sort" +) + +func main() { + tt := []struct { + input1 string + input2 string + }{ + {"", ""}, + {"god", "dog"}, + {"god", "do"}, + {"1001", "0110"}, + } + + for _, test := range tt { + success := IsPermutation(test.input1, test.input2) + + switch success { + case true: + fmt.Printf("%q and %q is a permutation\n", test.input1, test.input2) + + case false: + fmt.Printf("%q and %q is NOT a permutation\n", test.input1, test.input2) + } + } +} + +// ============================================================================= + +// RuneSlice a custom type of a slice of runes. +type RuneSlice []rune + +// For sorting an RuneSlice. +func (p RuneSlice) Len() int { return len(p) } +func (p RuneSlice) Less(i, j int) bool { return p[i] < p[j] } +func (p RuneSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// IsPermutation check if two strings are permutations. +func IsPermutation(str1, str2 string) bool { + + // If the length are not equal they cannot be permutation. + if len(str1) != len(str2) { + return false + } + + // Convert each string into a collection of runes. + s1 := []rune(str1) + s2 := []rune(str2) + + // Sort each collection of runes. + sort.Sort(RuneSlice(s1)) + sort.Sort(RuneSlice(s2)) + + // Convert the collection of runes back to a string + // and compare. + return string(s1) == string(s2) +} diff --git a/_content/tour/grc/arrays.article b/_content/tour/grc/arrays.article new file mode 100644 index 00000000..3b646023 --- /dev/null +++ b/_content/tour/grc/arrays.article @@ -0,0 +1,437 @@ +Πίνακες +Οι πίνακες είναι μια ξεχωριστή δομή δεδομένων στην Go που επιτρέπει την εκχώρηση συνεχόμενων τμημάτων μνήμης σταθερού μεγέθους. + +* Πίνακες + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Παρακολουθήστε το Video]] +- Εαν Χρειάζεστε Οικονομική Συνδρομή, Χρησιμοποιείστε το σχετικό [[https://www.ardanlabs.com/scholarship/][Έγγραφο Υποτροφίας]] + +Οι πίνακες είναι μια ξεχωριστή δομή δεδομένων στην Go που επιτρέπει την εκχώρηση συνεχόμενων τμημάτων μνήμης +σταθερού μεγέθους. Οι πίνακες έχουν κάποια ειδικά χαρακτηριστικά στην Go σχετικά με το πως δηλώνονται και +πως αντιμετωπίζονται ως τύποι. + +** Επισκόπηση Κώδικα + +- *Παράδειγμα* *1:* Δήλωση, ανάθεση αρχικής τιμής και διαδοχική προσπέλαση +- *Παράδειγμα* *2:* Πίνακες διαφορετικού τύπου +- *Παράδειγμα* *3:* Εκχωρήσεις συνεχούς μνήμης +- *Παράδειγμα* *4:* Μηχανισμός range + +.play arrays/example1.go +.play arrays/example2.go +.play arrays/example3.go +.play arrays/example4.go + +** Δηλώνοντας και Εκχωρώντας Αρχικές Τιμές + +Δηλώνοντας ένα πίνακα πέντε συμβολοσειρών που λαμβάνει αρχική κατάσταση την κατάσταση μηδενικής τιμής. + + var strings [5]string + +Μια συμβολοσειρά είναι μια αμετάβλητη δομή δεδομένων δύο λέξεων, που αναπαριστά ένα δείκτη διεύθυνσης +σε ένα υποστηρικτικό πίνακα από byte και τον συνολικό αριθμό byte στον υποστηρικτικό πίνακα. Καθώς +αυτός ο πίνακας παίρνει την κατάσταση μηδενικής τιμής, κάθε στοιχείο παίρνει και αυτό με την σειρά του +την κατάσταση μηδενικής τιμής. Αυτό σημαίνει ότι κάθε συμβολοσειρά έχει την πρώτη λέξη ίση με nil και την +δεύτερη λέξη ίση με το 0. + +.image /tour/grc/static/img/a1.png + +** Εκχωρήσεις Συμβολοσειρών + +Τι συμβαίνει όταν μια συμβολοσειρά εκχωρείται σε μια άλλη συμβολοσειρά; + + strings[0] = "Apple" + +Όταν μια συμβολοσειρά εκχωρείται σε μια άλλη συμβολοσειρά, αντιγράφεται η τιμή των δύο λέξεων, +με αποτέλεσμα δύο διαφορετικές συμβολοσειρές να μοιράζονται τον ίδιο υποστηρικτικό πίνακα. + +.image /tour/grc/static/img/a2.png + +Το κόστος αντιγραφής μιας συμβολοσειράς είναι το ίδιο, ανεξάρτητα από το μέγεθος της συμβολοσειράς, +καθώς πρόκειται για αντιγραφή δύο λέξεων. + +** Διαδοχική Προσπέλαση Συλλογών + +Η Go παρέχει δύο διαφορετικές σημειολογίες για την διαδοχική προσπέλαση μιας συλλογής. Είναι δυνατόν +να γίνει η διαδοχική προσπέλαση με σημειολογία τιμής ή με σημειολογία δείκτη διεύθυνσης μνήμης. + + // Διαδοχική Προσπέλαση με Σημειολογία Τιμής + for i, fruit := range strings { + println(i, fruit) + } + + + // Διαδοχική Προσπέλαση με Σημειολογία Δείκτη Διεύθυνσης + for i := range strings { + println(i, strings[i]) + } + +Όταν χρησημοποιείται διαδοχική προσπέλαση με σημειολογία τιμής συμβαίνουν δύο πράγματα. +Πρώτα, η συλλογή αντιγράφεται και η διαδοχική προσπέλαση γινεται στο αντίγραφο. Στην +περίτπωση ενός πίνακα, το αντίγραφο μπορεί να κοστίζει να γίνει, καθώς πρόκειται για αντιγραφή +ολόκληρου του πίνακα. Στην περίπτωση ενός δυναμικού πίνακα (slice) δεν υπάρχει πραγματικό κόστος, καθώς +αντιγράφεται μόνο η τιμή του εσωτερικού δυναμικού πίνακα και όχι ο υποστηρικτικός πίνακας. Στην +συνέχεια, η διαδοχική προσπέλαση λαμβάνει ένα αντίγραφο κάθε στοιχείου. + +Όταν χρησημοποιείται διαδοχική προσπέλαση με σημειολογία δείκτη διεύθυνσης μνήμης, η διαδοχική +προσπέλαση γίενται επί της αρχικής συλλογής και η πρόσβαση κάθε στοιχείου της συλλογής γίνεται άμεσα. + +** Διαδοχική Προσπέλαση με Σημειολογία Τιμής + +Παρουσιάζεται ο ακόλουθος κώδικας και το αποτέλεσμα αυτού. + + strings := [5]string{"Apple", "Orange", "Banana", "Grape", "Plum"} + for i, fruit := range strings { + println(i, fruit) + } + +Αποτέλεσμα: + + 0 Apple + 1 Orange + 2 Banana + 3 Grape + 4 Plum + +Η μεταβλητή strings είναι ένας πίνακας 5 συμβολοσειρών. Η διαδοχική προσπέλαση γίνεται +σε κάθε συμβολοσειρά της συλλογής και παρουσιάζεται ο δείκτης θέσης και η τιμή της συμβολοσειράς. +Εφόσον πρόκειται για διαδοχική προσπέλαση με σημειολογία τιμής, το range της for επισκέπτεται το +δικό της ρηχό αντίγραφο του πίνακα και σε κάθε επανάληψη η μεταβλητή fruit είναι ένα αντίγραφο +κάθε συμβολοσειράς (της δομής δεδομένων των δύο λέξεων). + +Παρατηρεί κανείς πως η μεταβλητή fruit περνάει στην συνάρτηση εκτύπωσης με την χρήσης σημειολογίας +τιμής. Επίσης η συνάρτηση εκτύπωσης λαμβάνει το δικό της αντίγραφο της τιμής της συμβολοσειράς. Εως ότου +η συμβολοσειρά έχει περάσει στην συνάρτηση εκτύπωσης, υπάρχουν 4 αντίγραφα της τιμής της συμβολοσειράς +(ο πίνακας, το ρηχό αντίγραφο, η μεταβλητή fruit και το αντίγραφο της συνάρτησης εκτύπωσης). Όλα αυτά τα +4 αντίγραφα μοιράζονται τον ίδιο υποστηρηκτικό πίνακα από byte. + +.image /tour/grc/static/img/a3.png + +Είναι σημαντική η δημιουργία αντιγράφων της τιμής της συμβολοσειράς επειδή κάτι τέτοιο αποτρέπει την τιμή της +συμβολοσειράς να διαφύγει στον σωρό. Με αυτό τον τρόπο, αποκλείονται αναποτελεσματικές εκχωρήσεις μνήμης στον +σωρό. + +** Διαδοχική Προσπέλαση με Σημειολογία Δείκτη Διευθύνσεων + +Με βάση τον παρακάτω κώδικα και το αποτέλεσμα από την εκτέλεση του: + + strings := [5]string{"Apple", "Orange", "Banana", "Grape", "Plum"} + for i := range strings { + println(i, strings[i]) + } + +Αποτέλεσμα: + + 0 Apple + 1 Orange + 2 Banana + 3 Grape + 4 Plum + +Ακόμα μια φορά, η μεταβλητή strings είναι ένας πίνακας 5 συμβολοσειρών. Η διαδοχική προσπέλαση +επισκέφτεται κάθε συμβολοσειρά στην συλλογή και παρουσιάζει τον δείκτη θέσης και την τιμή της συμβολοσειράς. +Καθώς η διαδοχική προσπέλαση χρησιμοποιεί σημειολογία δείκτη διεύθυσνης, το range της for επισκέπτεται +τον πίνακα strings απευθείας και σε κάθε προσπέλαση, η τιμή συμβολοσειράς, για κάθε δείκτη τοποθεσίας, +περνάει απευθείας στην κλήση παρουσίασης. + +** Διαφορετικοί Τύποι Πινάκων + +Είναι ενδιαφέρον να δει κανείς τι σφάλμα παρέχει ο μεταγλωττιστής όταν γίνεται ανάθεση +πινάκων ίδιου τύπου αλλά με διαφορετικό μήκος. + + var five [5]int + four := [4]int{10, 20, 30, 40} + + five = four + +Σφάλμα Μεταγλωττιστή: + + cannot use four (type [4]int) as type [5]int in assignment + +Εδώ δηλώνονται δύο πίνακες 5 και 4 ακεραίων. Ο πρώτος λαμβάνει ως αρχική κατάσταση την κατάσταση μηδενικής τιμής. + +Στην συνέχεια γίνεται απόπειρα να ανατεθεί ο δεύτερος στον πρώτο και ο μεταγλωττιστής ενημερώνει, "δεν είναι δυνατόν +να χρησιμοποιηθεί ο four (τύπος [4]int) ως τύπος [5]int στην ανάθεση". + +Είναι σημαντικό να ξεκαθαριστεί τι ακριβώς αναφέρει ο μεταγλωττιστής. Αυτό που αναφέρει είναι ότι, +ένας πίνακας 4 ακεραίων και ένας πίνακας 5 ακεραίων, αναπαριστούν δεδομένα διαφορετικών τύπων. +Το μέγεθος ενός πίνακα είναι μέρος της πληροφορίας που αφορά τον τύπο του. Στην Go, το μέγεθος ενός πίνακα +πρέπει να είναι γνωστό, ήδη από το στάδιο της μεταγλώττισης. + +** Κατασκευή Συνεχόμενων Τμημάτων Μνήμης + +Προκειμένου να αποδειχθεί ότι ένας πίνακας παρέχει μια συνεχόμενη διάταξη μνήμης: + + five := [5]string{"Annie", "Betty", "Charley", "Doug", "Bill"} + + for i, v := range five { + fmt.Printf("Value[%s]\tAddress[%p] IndexAddr[%p]\n", + v, &v, &five[i]) + } + +Αποτέλεσμα: + + Value[Annie] Address[0xc000010250] IndexAddr[0xc000052180] + Value[Betty] Address[0xc000010250] IndexAddr[0xc000052190] + Value[Charley] Address[0xc000010250] IndexAddr[0xc0000521a0] + Value[Doug] Address[0xc000010250] IndexAddr[0xc0000521b0] + Value[Bill] Address[0xc000010250] IndexAddr[0xc0000521c0] + +Παραπάνω δηλώνεται ένας πίνακας 5 συμβολοσειρών κα λαμβάνει ως αρχική τιμή κάποιες συμβολοσειρές. +Στην συνέχεια χρησιμοποιείται διαδοχική προσπέλαση με σημειολογία τιμής προκειμένου +να παρουσιαστούν πληροφορίες για κάθε συμβολοσειρά. Το αποτέλεσμα δείχνει κάθε ξεχωριστή +τιμή συμβολοσειράς, την διεύθυνση της μεταβλητής `v` και την διεύθυνση κάθε στοιχείου στον +πίνακα. + +Είναι προφανές πως ο πίνακας αποτελείται από ένα συνεχόμενο τμήμα μνήμης και πως μια συμβολοσειρά +είναι μια δομή δεδομένων με δύο λέξεις ή 16 byte σε αρχιτεκτονική 64 bit. Η διεύθυνση για +κάθε στοιχείο απέχει κατά 16 byte. + +Το γεγονός ότι η μεταβλητή `v` έχει την ίδια διεύθυνση σε κάθε διαδοχική προσπέλαση, ενισχύει την +πεποίθηση ότι η `v` είναι μια τοπική μεταβλητή τύπου συμβολοσειράς, που περιέχει ένα αντίγραφο κάθε +τιμής συμβολοσειράς κατά την διάρκεια των διαδοχικών προσπέλάσεων. + +** Η Ταχεία Μνήμη της CPU + +Υπάρχουν πολλές μηχανικές διαφορές μεταξύ επεξεργαστών και των σχεδιασμών τους. Σε +αυτό το μέρος, θα γίνει αναφορά σε υψηλό επίπεδο αφαίρεσης, σχετικά με τους επεξεργαστές και +την σημειολογία, που είναι σχετικά ίδια μεταξύ τους. Αυτή η σημειολογική κατανόηση θα παρέχει ένα +καλό νοητικό υπόδειγμα σχετικά με το πως λειτουργεί ο επεξεργαστής και την συμπάθεια που κανείς +μπορεί να προσφέρει. + +Κάθε πυρήνας εντός του επεξεργαστή έχει την δική του τοπική μνήμη (L1 και L2) και υπάρχει +και μια κοινή περιοχή μνήμης (L3) η οποία χρησιμοποιείται για την αποθήκευση/προσπέλαση δεδομένων και οδηγιών. Τα φυσικά +νήματα εκτέλεσης σε κάθε πυρήνα μπορούν να έχουν πρόσβαση στην τοπική τους L1 και L2 μνήμη. Τα δεδομένα από την L3 ή από την +κύρια μνήμη πρέπει να αντιγραφούν πρώτα στις L1 και L2 για να μπορούν να προσπελαστούν. + +.image /tour/grc/static/img/a4.png + +Το κόστος καθυστέρησης πρόσβασης σε δεδομένα που υπάρχουν σε διαφορετικές περιοχές μνήμης αλλάζει +από το χαμηλότερο στο υψηλότερο: L1 -> L2 -> L3 -> κύρια μνήμη. Όπως έχει πει ο Scott Meyers, "Αν η απόδοση +μετράει, τότε η συνολική διαθέσιμη μνήμη είναι το συνολικό μέγεθος των τοπικά διαθέσιμων μνημών. Η κύρια +μνήμη είναι τόσο αργή στην προσπέλαση ώστε, πρακτικά μιλώντας, είναι σαν να μην υπάρχει." + +Η απόδοση σήμερα εξαρτάται από το πόσο αποτελεσματικά διακινούνται τα δεδομένα στο υλικό. Αν κάθε +μεμωνομένο κομμάτι δεδομένων που χρειάζεται το υλικό (σε κάθε δεδομένη στιγμή) υπάρχει μόνο στην κύρια μνήμη, τα +προγράμματα θα τρέχουν πιο αργά, συγκριτικά με προγράμματα, των οποίων τα δεδομένα είναι ήδη παρόντα στην L1 ή στην L2. + + 3GHz(3 κύκλοι/ns) * 4 οδηγίες ανά κύκλο = 12 οδηγίες ανά ns! + + 1 ns .............................................. 1 ns .............. 12 οδηγίες (μονάδες) + 1 µs .......................................... 1,000 ns .......... 12,000 οδηγίες (εκατοντάδες) + 1 ms ...................................... 1,000,000 ns ...... 12,000,000 οδηγίες (εκατομμύρια) + 1 s ................................... 1,000,000,000 ns .. 12,000,000,000 οδηγίες (δισεκατομμύρια) + + Τιμές Καθυστέρησης από την Βιομηχανία + Τιμή αναφοράς L1 .................... 0.5 ns .............. 6 οδηγίες + Τιμή αναφοράς L2 ...................... 7 ns .............. 84 οδηγίες + Τιμή αναφοράς Κύριας μνήμης .................... 100 ns ............ 1200 οδηγίες + +Πως γράφει κανείς κώδικα που εγγυάται ότι, τα δεδομένα που είναι απαραίτητα για την +εκτέλεση μιας οδηγίας, είναι πάντα παρόντα στην L1 η στην L2? Είναι απαραίτητο να γράφει +κανείς κώδικα που είναι μηχανικά συμπαθητικός με τον μηχανισμό που εξασφαλίζει στον επεξεργαστή +τα δεδομένα (prefetcher). Ο μηχανισμός αυτός, προσπαθεί να προβλέψει τι δεδομένα θα χρειαστούν, πριν ακόμα οι οδηγίες +αναζητήσουν τα δεδομένα, ώστε να βρίσκονται ήδη στην μνήμη L1 ή L2. + +Υπάρχουν διαφορετικοί βαθμοί πιστότητας στην προσπέλαση μνήμης, ανάλογα με το που συμβαίνει αυτή +η προπέλαση. Ο κώδικας μπορεί να διαβάσει/γράψει ένα byte μνήμης, ως την μικρότερη δυνατή προσπέλασιμη +μονάδα μνήμης. Όμως, από την μεριά των συστημάτων αποθήκευσης, η πιστότητα είναι 64 byte. Αυτό το μέρος +της μνήμης των 64 byte αποκαλείται μια σειρά μνήμης ταχείας προσπέλασης (cache line). + +Ο μηχανισμός που προβλέπει τι δεδομένα θα χρειαστούν στον επεξεργαστή, δουλεύει πιο αποτελεσματικά +όταν οι οδηγίες που εκτελούνται, δημιουργούν προβλέψιμα μοτίβα προσπέλασης στην μνήμη. Ένας τρόπος για να +δημιουργηθεί ένα προβλέψιμο μοτίβο προσπέλασης στην μνήμη είναι η δημιουργία ενός σηνεχόμενου τμήματος μνήμης +και στην συνέχεια η πραγματοποίηση μιας διαδοχικής προσπέλαση αυτής της μνήμης, κάνοντας μια γραμμική διέλευση +με προβλέψιμο βήμα. + +Ο πίνακας είναι η πιο σημαντική δομή δεδομένων στο υλικό επειδή υποστηρίζει προβλέψιμα μοτίβα προσπέλασης. Όμως +ο δυναμικός πίνακας (slice) είναι η πιο σημαντική δομή δεδομένων στην Go. Οι δυναμικοί πίνακες στην Go χρησιμοποιούν +έναν πίνακα από κάτω. + +Όταν κανείς κατασκευάσει έναν πίνακα, κάθε στοιχείο του πίνακα βρίσκεται σε ίδια απόσταση από το επόμενο ή από +το προηγούμενο στοιχείο. Καθώς επισκέπτεται διαδοχικά κανείς ένα πίνακα, διατρέχει κάθε σειρά μνήμης καθώς και κάθε +άλλη συνδεδεμένη σειρά μνήμης, με προβλέψιμο βήμα. Ο μηχανισμός που εξασφαλίζει δεδομένα στον επεξεργαστή θα αντιληφθεί +αυτό το προβλέψιμο μοτίβο πρόσβασης δεδομένων και θα ξεκινήσει να φέρνει τα δεδομένα στον επεξεργαστή, μειώνοντας έτσι +τα κόστη από την καθυστέρηση εξαιτίας της ανάγκης πρόσβασης στα δεδομένα. + +Φανταστείτε ότι έχετε ένα μεγάλο τετραγωνικό πίνακα μνήμης και μια συνδεδεμένη λίστα, τα στοιχεία της οποίας ταιριάζουν σε +πλήθος, με τα στοιχεία του πίνακα. Αν κανείς εκτελέσει μια διέλευση, στην συνδεδεμένη λίστα και στην συνέχεια επισκεφθεί τον +πίνακα και στις δύο κατευθύνσεις (Στηλών και Γράμμών), πως θα σχετίζονται οι δύο διελεύσεις, από την άποψη της απόδοσης; + + func RowTraverse() int { + var ctr int + for row := 0; row < rows; row++ { + for col := 0; col < cols; col++ { + if matrix[row][col] == 0xFF { + ctr++ + } + } + } + return ctr + } + +Η προσπέλαση των γραμμών θα έχει την καλύτερη απόδοση, επειδή διατρέχει την μνήμη, από την μία σειρά μνήμης +στην επόμενη συνδεδεμένη σειρά μνήμης, κάτι που δημιουργεί ένα προβλέψιμο μοτίβο πρόσβασης. Οι σειρές μνήμης μπορούν +να αντιγραφούν από τον μηχανισμό παροχής δεδομένων στον επεξεργαστή, στην L1 ή στην L2, πριν ακόμα τα δεδομένα χρειαστούν. + + func ColumnTraverse() int { + var ctr int + for col := 0; col < cols; col++ { + for row := 0; row < rows; row++ { + if matrix[row][col] == 0xFF { + ctr++ + } + } + } + return ctr + } + +Η διέλευση ανά στήλη είναι η χειρότερη, κατά μια τάξη μεγέθους, επειδή αυτό το μοτίβο πρόσβασης +διατρέχει τα όρια των σελίδων μνήμης του λειτουργικού (OS pages) σε κάθε προσπέλαση μνήμης που πραγματοποιεί. +Αυτός ο τρόπος διέλευσης δεν μπορεί να δημιουργήσει προβλέψιμα μοτίβα για τις σειρές μνήμης, τις οποίες +εκμεταλεύεται ο μηχανισμός τροφοδότησης με δεδομένα του επεξεργαστή και ουσιαστικά η διαδικασία μετατρέπεται +σε πρόσβαση μνήμης με τυχαίο τρόπο. + + func LinkedListTraverse() int { + var ctr int + d := list + for d != nil { + if d.v == 0xFF { + ctr++ + } + d = d.p + } + return ctr + } + +Η συνδεδεμένη λίστα είναι δύο φορές πιο αργή, από την διέλευση επί των γραμμών, κυρίως επειδή υπάρχουν αστοχίες +στην ανάγνωση των γραμμών μνήμης, όμως υπάρχουν και λιγότερες αστοχίες της TLB (Translation Lookaside Buffer). Ένα +σύνολο των συνδεδεμένων στοιχείων της λίστας υπάρχουν εντός των ίδιων σελίδων μνήμης του λειτουργικού. + + BenchmarkLinkListTraverse-16 128 28738407 ns/λειτουργία + BenchmarkColumnTraverse-16 30 126878630 ns/λειτουργία + BenchmarkRowTraverse-16 310 11060883 ns/λειτουργία + +** Πλησιέστερη Ενδιάμεση Μνήμη Μεταφοράς Διευθύνσεων (Translation Lookaside Buffer - TLB) + +Κάθε πρόγραμμα που εκτελείται, αποκτά μια πλήρη απεικόνιση της εικονικής μνήμης από το +λειτουργικό, έτσι αυτό το πρόγραμμα πιστεύει ότι έχει διαθέσιμη όλη την φυσική μνήμη στο μηχάνημα. Όμως, +η φυσική μνήμη χρειάζεται να διαμοιράζεται σε όλα τα προγράμματα που εκτελούνται. Το λειτουργικό σύστημα +μοιράζεται την φυσική μνήμη, χωρίζοντας την σε σελίδες και αντιστοιχίζοντας τις σελίδες στην εικονική +μνήμη, για κάθε πρόγραμμα που εκτελείται. Κάθε λειτουργικό μπορεί να αποφασίσει για το μέγεθος μιας σελίδας, +όμως τα 4k, τα 8k, τα 16k είναι λογικά και κοινώς αποδεκτά μεγέθη. + +Η TLB είναι μια μικρή μνήμη, μέσα στον επεξεργαστή, που βοηθάει στον περιορισμό της +καθυστέρησης, από την μετάφραση μιας εικονικής διεύθυνσης μνήμης, σε μια φυσική διεύθυνση +μνήμης, εντός του πλαισίου μιας σελίδας του λειτουργικού και της σχετικής θέσης, εντός της +σελίδας. Μια αστοχία στην εσωτερική μνήμη TLB μπορεί να προκαλέσει μεγάλες καθυστερήσεις, +επειδή, τώρα, το υλικό πρέπει να περιμένει ώστε το λειτουργικό να ελέγξει τον πίνακα των σελίδων +που διατηρεί, προκειμένου να εντοπίσει την σωστή σελίδα για την συγκεκριμένη εικονική διεύθυνση +μνήμης. Αν το πρόγραμμα τρέχει σε ένα εικονικό μηχάνημα (όπως συμβαίνει στο cloud) τότε πρέπει +να ελεγχθεί πρώτα ο πίνακας των σελίδων του εικονικού μηχανήματος. + +Υπενθύμιση όσων αναφέρθηκαν παραπάνω: + +Η συνδεδεμένη λίστα είναι δύο φορές πιο αργή από την διέλευση κατά τις γραμμές, κυρίως διότι +υπάρχουν κάποιες αστοχίες γραμμών μνήμης (cache line misses) όμως υπάρχουν λιγότερες αστοχίες στην TLB (επεξήγηση αργότερα). +Η πλειονότητα των στοιχείων της συνδεδεμένης λίστας υπάρχουν εντός των σελίδων μνήμης του λειτουργικού. + +Η συνδεδεμένη λίστα είναι τάξεις μεγέθους γρηγορότερη από την διέλευση κατά στήλες, +εξατίας της πρόσβασης στην TLB. Ακόμα και με τις αστοχίες στις γραμμές μνήμης που συμβαίνουν +στην διέλευση της συνδεδεμένης λίστας, επειδή η πλειονότητα της μνήμης για ένα σύνολο στοιχείων της +θα βρίσκεται εντός της ίδιας σελίδας μνήμης του λειτουργικού, οι καθυστερήσεις της TLB δεν +επηρεάζουν την απόδοση. Γι' αυτό, για προγράμματα που χρησιμοποιούν μεγάλη ποσόστητα μνήμης, όπως +εφαρμογές που βασίζονται στην επεξεργασία DNA, κανείς θα προτιμούσε να χρησιμοποιήσει μια διανομή +Linux που είναι διαμορφωμένη με μεγέθη σελίδας μνήμης του λειτουργικού της τάξης του ενός ή των δύο megabyte. + +Με όλα τα παραπάνω, φαίνεται ότι έχει σημασία ένας σχεδιασμός προσανατολισμένος προς τα δεδομένα. Η συγγραφή ενός +αποτελεσματικού αλγόριθμου πρέπει να λάβει υπόψη τον τρόπο πρόσβασης στα δεδομένα. Κανείς οφείλει να θυμάται ότι +η αποδοτικότητα σήμερα, αφορά την αποτελεσματικότητα με την οποία είναι δυνατόν να μεταφερθούν τα δεδομένα στον +επεξεργαστή. + +- [[https://youtu.be/WDIkqP4JbkE?t=1129][Ταχείες Μνήμες της CPU και Γιατί σε Ενδιαφέρουν (18:50-20:30)]] - Scott Meyers +- [[https://youtu.be/WDIkqP4JbkE?t=2676][Ταχείες Μνήμες της CPU και Γιατί σε Ενδιαφέρουν (44:36-45:40)]] - Scott Meyers +- [[https://youtu.be/jEG4Qyo_4Bc?t=266][Απόδοση Μέσω Ορθής Χρήσης της Ταχείας Μνήμης (4:25-5:48)]] - Damian Gryski + +** Σημειώσεις σχετικά με την Ταχεία Μνήμη της CPU + +.html arrays/array_list.html + +** Πρόσθετα Διαγράμματα + +*Υστερήσεις* *Καθορισμένες* *από* *την* *Βιομηχανία* + + Ταχεία μνήμη L1 ............................................... 0.5 ns ............ 6 οδηγίες + Αστοχία κλάδου ................................................... 5 ns ............ 60 οδηγίες + Ταχεία μνήμη L2 ................................................. 7 ns ............ 84 οδηγίες + Αμοιβαίος αποκλεισμός κλείδωμα/ξεκλείδωμα ....................... 25 ns ........... 300 οδηγίες + Κύρια μνήμη .................................................... 100 ns .......... 1200 οδηγίες + Συμπίεση 1K byte με το Zippy ................................. 3,000 ns (3 µs) .... 36k οδηγίες + Αποστολή 2K byte σε δίκτυο 1 Gbps ........................... 20,000 ns (20 µs) .. 240k οδηγίες + Τυχαία ανάγνωση SSD ........................................ 150,000 ns (150 µs) ..1.8M οδηγίες + Ανάγνωση 1 MB διαδοχικά από μνήμη ......................... 250,000 ns (250 µs) ....3M οδηγίες + Κυκλική διαδρομή εντός datacenter .......................... 500,000 ns (0.5 ms) ....6M οδηγίες + Ανάγνωση 1 MB διαδοχικά από SSD ......................... 1,000,000 ns (1 ms) ......2M οδηγίες + Αναζήτηση σε δίσκο ...................................... 10,000,000 ns (10 ms) .....0M οδηγίες + Ανάγνωση 1 MB διαδοχικά από δίσκο ....................... 20,000,000 ns (20 ms) ....40M οδηγίες + Αποστολή πακέτου απο Καλιφόρνια->Ολλανδία->Καλιφόρνια .. 150,000,000 ns (150 ms) ....8B οδηγίες + +*Εικόνα* *Καθυστέρησης* *Ταχείας* *Μνήμης* + +.image /tour/grc/static/img/cache_latencies_graph.png + +** Πρόσθετα Αναγνώσματα + +*Ταχεία* *Μνήμη* *CPU* */* *Μνήμη* + +- [[https://www.youtube.com/watch?v=WDIkqP4JbkE][Ταχείες Μνήμες της CPU και Γιατί σε Ενδιαφέρουν - Video]] - Scott Meyers +- [[https://www.youtube.com/watch?v=OFgxAFdxYAQ][Ταχύρυθμο Μάθημα για το Σύγχρονο Υλικό - Video]] - Cliff Click +- [[http://frankdenneman.nl/2016/07/06/introduction-2016-numa-deep-dive-series/][Σειρά Βαθειάς Κατανόησης NUMA]] - Frank Denneman +- [[http://www.aristeia.com/TalkNotes/codedive-CPUCachesHandouts.pdf][Ταχείες Μνήμες της CPU και Γιατί σε Ενδιαφέρουν]] - Scott Meyers +- [[https://www.youtube.com/watch?v=MC1EKLQ2Wmg][Απομυθοποιώντας το Σύγχρονο Υλικό για την Απόκτηση 'Μηχανικής Συμπάθειας']] - Martin Thompson +- [[http://www.akkadia.org/drepper/cpumemory.pdf][Αυτό που Πρέπει να Γνωρίζει κάθε Προγραμματιστής για την Μνήμη]] - Ulrich Drepper +- [[http://www.extremetech.com/extreme/188776-how-l1-and-l2-cpu-caches-work-and-why-theyre-an-essential-part-of-modern-chips][Πως Λειτουργούν οι Ταχείες Μνήμες της CPU και Γιατί]] - Joel Hruska +- [[http://www.lighterra.com/papers/modernmicroprocessors][Σύγχρονοι Μικροεπεξεργαστές Ένας Οδηγός 90 Λεπτών]] - Jason Robert Carey Patterson +- [[http://lwn.net/Articles/252125][Μνήμη μέρος 2: Ταχείες Μνήμες της CPU]] - Ulrich Drepper +- [[http://www.gotw.ca/publications/concurrency-ddj.htm][Το Δωράν Γεύμα δεν Υφίσταται Πια]] - Herb Sutter +- [[https://m.youtube.com/watch?feature=youtu.be&v=QBu2Ae8-8LM][Υπολογιστές των Data Center: Σύγχρονες Προκλήσεις στον Σχεδιασμό CPU]] - Dick Sites +- [[https://en.wikipedia.org/wiki/Wirth%27s_law][Ο Νόμος του Wirth]] - Wikipedia +- [[http://www.drdobbs.com/parallel/eliminate-false-sharing/217500206][Εξάλειψη Εσφαλμένου Διαμοιρασμού]] - Herb Sutter +- [[http://www.ilikebigbits.com/2014_04_21_myth_of_ram_1.html][Ο Μύθος της Ram]] - Emil Ernerfeldt +- [[https://www.infoq.com/presentations/hardware-transactional-memory][Κατανοώντας την Μνήμη Υλικού σχετικά με Ταυτόχρονες Συναλλαγές]] - Gil Gene +- [[https://youtu.be/jEG4Qyo_4Bc?t=266][Αποτελεσματικότητα Μέσω Φιλικής Χρήσης της Ταχείας Μνήμης της CPU (4:25-5:48)]] - Damian Gryski +- [[https://www.youtube.com/watch?v=2EWejmkKlxs][Φτάνοντας Πουθενά Γρηγορότερα]] - Chandler Carruth + +*Σχεδιασμός* *Επικεντρωμένος* *στα* *Δεδομένα* + +- [[https://www.youtube.com/watch?v=rX0ItVEVjHc][Σχεδιασμός Επικεντρωμένος στα Δεδομένα και η C++]] - Mike Acton +- [[https://www.youtube.com/watch?v=fHNmRkzxHWs][Αποτελεσματικότητα με Αλγόριθμους, Απόδοση με Δομές Δεδομένων]] - Chandler Carruth +- [[https://www.youtube.com/watch?v=LrVi9LHP8Bk][Δαμάζοντας το Θηρίο της Απόδοσης]] - Klaus Iglberger +- [[http://harmful.cat-v.org/software/OO_programming/_pdf/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf][Παγίδες του OOP]] - Tony Albrecht +- [[https://www.youtube.com/watch?v=YQs6IC-vgmo][Γιατί κανείς πρέπει να αποφεύγει τις συνδεδεμένες λίστες]] - Bjarne Stroustrup +- [[http://gamesfromwithin.com/data-oriented-design][Σχεδιασμός Προσανατολισμένος στα Δεδομένα (Ή Γιατί Ενδέχεται να Πυροβολεί Κανείς Το Πόδι του Με τον OOP)]] - Noel +- [[https://www.quora.com/Was-object-oriented-programming-a-failure][Ήταν ο αντικειμενοστραφής προγραμματισμός μια αποτυχία;]] - Quora + +** Σημειώσεις + +- Αν κανείς δεν κατανοεί τα δεδομένα, δεν κατανοεί ούτε το πρόβλημα. +- Αν κανείς δεν κατανοεί το κόστος επίλυσης του προβλήματος, δεν μπορεί ούτε να επιχειρηματολογήσει σχετικά με το πρόβλημα. +- Αν κανείς δεν κατανοεί το υλικό, δεν μπορεί να επιχειρηματολογήσει σχετικά με το κόστος επίλυσης του προβλήματος. +- Οι πίνακες είναι δομές δεδομένων σταθερού μήκους που δεν μεταβάλλονται. +- Πίνακες διαφορετικών μεγεθών θεωρούνται ότι είναι διαφορετικών τύπων. +- Η μνήμη εκχωρείται σε συνεχόμενα τμήματα. +- Η Go παρέχει έλεγχο στην χωρική τοπικότητα. + +* Ασκήσεις + +Χρησιμοποιείστε το παρόν πρώτυπο ως αφετηρία για να ολοκληρώσετε τις ασκήσεις. Παρέχεται μια πιθανή λύση. + +** Άσκηση 1 + +Δηλώστε ένα πίνακα 5 συμβολοσειρών με κάθε στοιχείο του πίνακα να λαμβάνει σαν αρχική κατάσταση την κατάσταση +μηδενικής τιμής. Δηλώστε ένα δεύτερο πίνακα 5 συμβολοσειρών και δώστε αρχικές τιμές στα στοιχεία του με ρητές +τιμές συμβολοσειράς. Εκχωρείστε τον δεύτερο πίνακα στον πρώτο και παρουσιάστε τα αποτελέσματα του πρώτου πίνακα. +Παρουσιάστε την τιμή συμβολοσειράς και την διεύθυνση μνήμης κάθε στοιχείου. + +.play arrays/exercise1.go +.play arrays/answer1.go diff --git a/_content/tour/grc/arrays/answer1.go b/_content/tour/grc/arrays/answer1.go new file mode 100644 index 00000000..7b58b1de --- /dev/null +++ b/_content/tour/grc/arrays/answer1.go @@ -0,0 +1,30 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare an array of 5 strings with each element initialized to its zero value. +// +// Declare a second array of 5 strings and initialize this array with literal string +// values. Assign the second array to the first and display the results of the first array. +// Display the string value and address of each element. +package main + +import "fmt" + +func main() { + + // Declare string arrays to hold names. + var names [5]string + + // Declare an array pre-populated with friend's names. + friends := [5]string{"Joe", "Ed", "Jim", "Erick", "Bill"} + + // Assign the array of friends to the names array. + names = friends + + // Display each string value and address index in names. + for i, name := range names { + fmt.Println(name, &names[i]) + } +} diff --git a/_content/tour/grc/arrays/array_list.html b/_content/tour/grc/arrays/array_list.html new file mode 100644 index 00000000..472ebc05 --- /dev/null +++ b/_content/tour/grc/arrays/array_list.html @@ -0,0 +1,38 @@ + diff --git a/_content/tour/grc/arrays/example1.go b/_content/tour/grc/arrays/example1.go new file mode 100644 index 00000000..769f190e --- /dev/null +++ b/_content/tour/grc/arrays/example1.go @@ -0,0 +1,36 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare and iterate over +// arrays of different types. +package main + +import "fmt" + +func main() { + + // Declare an array of five strings that is initialized + // to its zero value. + var fruits [5]string + fruits[0] = "Apple" + fruits[1] = "Orange" + fruits[2] = "Banana" + fruits[3] = "Grape" + fruits[4] = "Plum" + + // Iterate over the array of strings. + for i, fruit := range fruits { + fmt.Println(i, fruit) + } + + // Declare an array of 4 integers that is initialized + // with some values. + numbers := [4]int{10, 20, 30, 40} + + // Iterate over the array of numbers. + for i := 0; i < len(numbers); i++ { + fmt.Println(i, numbers[i]) + } +} diff --git a/_content/tour/grc/arrays/example2.go b/_content/tour/grc/arrays/example2.go new file mode 100644 index 00000000..ce6e4299 --- /dev/null +++ b/_content/tour/grc/arrays/example2.go @@ -0,0 +1,29 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how arrays of different sizes are +// not of the same type. +package main + +import "fmt" + +func main() { + + // Declare an array of 5 integers that is initialized + // to its zero value. + var five [5]int + + // Declare an array of 4 integers that is initialized + // with some values. + four := [4]int{10, 20, 30, 40} + + // Assign one array to the other + five = four + + // ./example2.go:21: cannot use four (type [4]int) as type [5]int in assignment + + fmt.Println(four) + fmt.Println(five) +} diff --git a/_content/tour/grc/arrays/example3.go b/_content/tour/grc/arrays/example3.go new file mode 100644 index 00000000..8e922a1f --- /dev/null +++ b/_content/tour/grc/arrays/example3.go @@ -0,0 +1,22 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the behavior of the for range and +// how memory for an array is contiguous. +package main + +import "fmt" + +func main() { + + // Declare an array of 5 strings initialized with values. + friends := [5]string{"Annie", "Betty", "Charley", "Doug", "Edward"} + + // Iterate over the array displaying the value and + // address of each element. + for i, v := range friends { + fmt.Printf("Value[%s]\tAddress[%p] IndexAddr[%p]\n", v, &v, &friends[i]) + } +} diff --git a/_content/tour/grc/arrays/example4.go b/_content/tour/grc/arrays/example4.go new file mode 100644 index 00000000..ac89b5b2 --- /dev/null +++ b/_content/tour/grc/arrays/example4.go @@ -0,0 +1,49 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the for range has both value and pointer semantics. +package main + +import "fmt" + +func main() { + + // Using the pointer semantic form of the for range. + friends := [5]string{"Annie", "Betty", "Charley", "Doug", "Edward"} + fmt.Printf("Bfr[%s] : ", friends[1]) + + for i := range friends { + friends[1] = "Jack" + + if i == 1 { + fmt.Printf("Aft[%s]\n", friends[1]) + } + } + + // Using the value semantic form of the for range. + friends = [5]string{"Annie", "Betty", "Charley", "Doug", "Edward"} + fmt.Printf("Bfr[%s] : ", friends[1]) + + for i, v := range friends { + friends[1] = "Jack" + + if i == 1 { + fmt.Printf("v[%s]\n", v) + } + } + + // Using the value semantic form of the for range but with pointer + // semantic access. DON'T DO THIS. + friends = [5]string{"Annie", "Betty", "Charley", "Doug", "Edward"} + fmt.Printf("Bfr[%s] : ", friends[1]) + + for i, v := range &friends { + friends[1] = "Jack" + + if i == 1 { + fmt.Printf("v[%s]\n", v) + } + } +} diff --git a/_content/tour/grc/arrays/exercise1.go b/_content/tour/grc/arrays/exercise1.go new file mode 100644 index 00000000..614f29bb --- /dev/null +++ b/_content/tour/grc/arrays/exercise1.go @@ -0,0 +1,25 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare an array of 5 strings with each element initialized to its zero value. +// +// Declare a second array of 5 strings and initialize this array with literal string +// values. Assign the second array to the first and display the results of the first array. +// Display the string value and address of each element. +package main + +// Add imports. + +func main() { + + // Declare an array of 5 strings set to its zero value. + + // Declare an array of 5 strings and pre-populate it with names. + + // Assign the populated array to the array of zero values. + + // Iterate over the first array declared. + // Display the string value and address of each element. +} diff --git a/_content/tour/grc/channels.article b/_content/tour/grc/channels.article new file mode 100644 index 00000000..4e4e9275 --- /dev/null +++ b/_content/tour/grc/channels.article @@ -0,0 +1,201 @@ +Channels +Channels allow goroutines to communicate with each other through the use of signaling semantics. + +* Channels + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +It’s important to think of a channel not as a data structure, but as a mechanic for +signaling. This goes in line with the idea that you send and receive from a channel, +not read and write. If the problem in front of you can’t be solved with signaling, +if the word signaling is not coming out of your mouth, you need to question the use of +channels. + +** Code Review + +- *Example* *1:* Wait for result +- *Example* *2:* Fan out +- *Example* *3:* Wait for task +- *Example* *4:* Pooling +- *Example* *5:* Fan out semaphore +- *Example* *6:* Bounded work pooling +- *Example* *7:* Drop +- *Example* *8:* Cancellation +- *Example* *9:* Retry timeout +- *Example* *10:* Channel cancellation + +.play channels/example1.go +.play channels/example2.go +.play channels/example3.go +.play channels/example4.go +.play channels/example5.go +.play channels/example6.go +.play channels/example7.go +.play channels/example8.go +.play channels/example9.go +.play channels/example10.go + +** Channel Mechanics + +The cost of having the guarantee at the signaling level is unknown latency. The +sender won’t know how long they need to wait for the receiver to accept the signal. +Having to wait for the receiver creates blocking latency. In this case, unknown +amounts of blocking latency. The sender has to wait, for an unknown amount of time, +until the receiver becomes available to receive the signal. + +Waiting for the receiver means mechanically, the receive operation happens before +the send. With channels, the receive happens nanoseconds before, but it’s before. +This means the receiver takes the signal and then walks away, allowing the sender +to now move on with a guarantee. + +What if the process can’t wait for an unknown amount of time? What if that kind of +latency won’t work? Then the guarantee can’t be at the signaling level, it needs +to be outside of it. The mechanics behind this working is that the send now happens +before the receive. The sender can perform the signal without needing the receiver +to be available. So the sender gets to walk away and not wait. Eventually, you hope, +the receiver shows up and takes the signal. + +This is reducing latency cost on the send, but it’s creating uncertainty about signals +being received and therefore knowing if there are problems upstream with receivers. +This can create the process to accept work that never gets started or finished. It +could eventually cause massive back pressure and systems to crash. + +The second thing to focus on is, do you need to send data with the signal? If the signal +requires the transmission of data, then the signaling is a 1 to 1 between Goroutines. +If a new Goroutine needs to receive the signal as well, a second signal must be sent. + +If data doesn’t need to be transmitted with the signal, then the signal can be a 1 to +1 or 1 to many between Goroutines. Signaling without data is primarily used for +cancellation or shutdowns. It’s done by closing the channel. + +The third thing to focus on is channel state. A channel can be in 1 of 3 states. + +A channel can be in a nil state by constructing the channel to its zero value state. +Sends and receives against channels in this state will block. This is good for situations +where you want to implement short term stoppages of work. + +A channel can be in an open state by using the built-in function make. Sends and +receives against channels in this state will work under the following conditions: + +*Unbuffered* *Channels:* + +- Guarantees at the signaling level with the receive happening before send. Sending +and receiving Goroutines need to come together in the same space and time for a +signal to be processed. + +*Buffered* *Channels:* + +- Guarantees outside of the signaling level with the send happening before the +receive. If the buffer is not full, sends can complete else they block. If the +buffer is not empty, receives can complete else they block. + +A channel can be in a closed state by using the built-in function close. You don’t +need to close a channel to release memory, this is for changing the state. Sending +on a closed channel will cause a panic, however receiving on a closed channel +will return immediately. + +With all this information, you can focus on channel patterns. The focus on signaling +is important. The idea is, if you need a guarantee at the signaling level or not, +based on latency concerns. If you need to transmit data with the signal or not, based +on handling cancellations or not. You want to convert the syntax to these semantics. + +** Design Guidelines + +- Learn about the [[https://github.com/ardanlabs/gotraining/blob/master/topics/go/#channel-design][design guidelines]] for channels. + +** Diagrams + +*Guarantee* *Of* *Delivery* + +The `Guarantee Of Delivery` is based on one question: “Do I need a guarantee that +the signal sent by a particular goroutine has been received?” + +.image /tour/grc/static/img/guarantee_of_delivery.png + +*Signaling* *With* *Or* *Without* *Data* + +When you are going to signal `with` data, there are three channel configuration +options you can choose depending on the type of `guarantee` you need. + +.image /tour/grc/static/img/signaling_with_data.png + +Signaling without data serves the main purpose of cancellation. It allows one +goroutine to signal another goroutine to cancel what they are doing and move on. +Cancellation can be implemented using both `unbuffered` and `buffered` channels. + +.image /tour/grc/static/img/signaling_without_data.png + +*State* + +The behavior of a channel is directly influenced by its current `State`. The +state of a channel can be `nil`, `open` or `closed`. + +.image /tour/grc/static/img/state.png + +** Extra Reading + +- [[https://www.ardanlabs.com/blog/2017/10/the-behavior-of-channels.html][The Behavior Of Channels]] - William Kennedy +- [[https://golang.org/ref/mem#tmp_7][Channel Communication]] +- [[http://blog.golang.org/share-memory-by-communicating][Share Memory By Communicating]] - Andrew Gerrand +- [[https://www.ardanlabs.com/blog/2014/02/the-nature-of-channels-in-go.html][The Nature Of Channels In Go]] - William Kennedy +- [[http://matt-welsh.blogspot.com/2010/07/retrospective-on-seda.html][A Retrospective on SEDA]] - Matt Welsh +- [[https://www.youtube.com/watch?v=KBZlN0izeiY][Understanding Channels]] - Kavya Joshi + +** Buffer Bloat - 2011 + +Be careful about using large buffers with the idea of reducing latency. + +- Large buffers prevent timely notification of back pressure. +- They defeat your ability to reduce back pressure in a timely matter. +- They can increase latency not reduce it. +- Use buffered channels to provide a way of maintaining continuity. +- Don't use them just for performance. +- Use them to handle well defined bursts of data. +- Use them to deal with speed of light issues between handoffs. + +*Videos* + +- [[https://www.youtube.com/watch?v=qbIozKVz73g][Bufferbloat: Dark Buffers in the Internet]] +- [[http://www.bufferbloat.net/projects/cerowrt/wiki/Bloat-videos][Buffer Bloat Videos]] + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Write a program where two goroutines pass an integer back and forth ten times. +Display when each goroutine receives the integer. Increment the integer with each +pass. Once the integer equals ten, terminate the program cleanly. + +.play channels/exercise1.go +.play channels/answer1.go + +** Exercise 2 + +Write a program that uses a fan out pattern to generate 100 random numbers concurrently. +Have each goroutine generate a single random number and return that number to the main +goroutine over a buffered channel. Set the size of the buffer channel so no send ever +blocks. Don't allocate more buffers than you need. Have the main goroutine display each +random number it receives and then terminate the program. + +.play channels/exercise2.go +.play channels/answer2.go + +** Exercise 3 + +Write a program that generates up to 100 random numbers concurrently. Do not send +all 100 values so the number of sends/receives is unknown. + +.play channels/exercise3.go +.play channels/answer3.go + +** Exercise 4 + +Write a program that generates up to 100 random numbers concurrently using a worker +pool. Reject even values. Instruct the workers to shutdown with 100 odd numbers have +been collected. + +.play channels/exercise4.go +.play channels/answer4.go diff --git a/_content/tour/grc/channels/answer1.go b/_content/tour/grc/channels/answer1.go new file mode 100644 index 00000000..a7e90eec --- /dev/null +++ b/_content/tour/grc/channels/answer1.go @@ -0,0 +1,72 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program where two goroutines pass an integer back and forth +// ten times. Display when each goroutine receives the integer. Increment +// the integer with each pass. Once the integer equals ten, terminate +// the program cleanly. +package main + +import ( + "fmt" + "sync" +) + +func main() { + + // Create an unbuffered channel. + share := make(chan int) + + // Create the WaitGroup and add a count + // of two, one for each goroutine. + var wg sync.WaitGroup + wg.Add(2) + + // Launch two goroutines. + go func() { + goroutine("Bill", share) + wg.Done() + }() + + go func() { + goroutine("Joan", share) + wg.Done() + }() + + // Start the sharing. + share <- 1 + + // Wait for the program to finish. + wg.Wait() +} + +// goroutine simulates sharing a value. +func goroutine(name string, share chan int) { + for { + + // Wait to receive a value. + value, ok := <-share + if !ok { + + // If the channel was closed, return. + fmt.Printf("Goroutine %s Down\n", name) + return + } + + // Display the value. + fmt.Printf("Goroutine %s Inc %d\n", name, value) + + // Terminate when the value is 10. + if value == 10 { + close(share) + fmt.Printf("Goroutine %s Down\n", name) + return + } + + // Increment the value and send it + // over the channel. + share <- (value + 1) + } +} diff --git a/_content/tour/grc/channels/answer2.go b/_content/tour/grc/channels/answer2.go new file mode 100644 index 00000000..0da03cc7 --- /dev/null +++ b/_content/tour/grc/channels/answer2.go @@ -0,0 +1,53 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program that uses a fan out pattern to generate 100 random numbers +// concurrently. Have each goroutine generate a single random number and return +// that number to the main goroutine over a buffered channel. Set the size of +// the buffer channel so no send every blocks. Don't allocate more buffers than +// you need. Have the main goroutine display each random number is receives and +// then terminate the program. +package main + +import ( + "fmt" + "math/rand" +) + +const ( + goroutines = 100 +) + +func main() { + + // Create the buffer channel with a buffer for + // each goroutine to be created. + values := make(chan int, goroutines) + + // Iterate and launch each goroutine. + for gr := 0; gr < goroutines; gr++ { + + // Create an anonymous function for each goroutine that + // generates a random number and sends it on the channel. + go func() { + values <- rand.Intn(1000) + }() + } + + // Create a variable to be used to track received messages. + // Set the value to the number of goroutines created. + wait := goroutines + + // Iterate receiving each value until they are all received. + // Store them in a slice of ints. + var nums []int + for wait > 0 { + nums = append(nums, <-values) + wait-- + } + + // Print the values in our slice. + fmt.Println(nums) +} diff --git a/_content/tour/grc/channels/answer3.go b/_content/tour/grc/channels/answer3.go new file mode 100644 index 00000000..344fed78 --- /dev/null +++ b/_content/tour/grc/channels/answer3.go @@ -0,0 +1,69 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program that uses goroutines to generate up to 100 random numbers. +// Do not send values that are divisible by 2. Have the main goroutine receive +// values and add them to a slice. +package main + +import ( + "fmt" + "math/rand" + "sync" +) + +const ( + goroutines = 100 +) + +func main() { + + // Create the channel for sharing results. + values := make(chan int) + + // Create a sync.WaitGroup to monitor the Goroutine pool. Add the count. + var wg sync.WaitGroup + wg.Add(goroutines) + + // Iterate and launch each goroutine. + for gr := 0; gr < goroutines; gr++ { + + // Create an anonymous function for each goroutine. + go func() { + + // Ensure the waitgroup is decremented when this function returns. + defer wg.Done() + + // Generate a random number up to 1000. + n := rand.Intn(1000) + + // Return early if the number is divisible by 2. n%2 == 0 + if n%2 == 0 { + return + } + + // Send the odd values through the channel. + values <- n + }() + } + + // Create a goroutine that waits for the other goroutines to finish then + // closes the channel. + go func() { + wg.Wait() + close(values) + }() + + // Receive from the channel until it is closed. + // Store values in a slice of ints. + var nums []int + for n := range values { + nums = append(nums, n) + } + + // Print the values in our slice. + fmt.Printf("Result count: %d\n", len(nums)) + fmt.Println(nums) +} diff --git a/_content/tour/grc/channels/answer4.go b/_content/tour/grc/channels/answer4.go new file mode 100644 index 00000000..45f14f66 --- /dev/null +++ b/_content/tour/grc/channels/answer4.go @@ -0,0 +1,90 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program that creates a fixed set of workers to generate random +// numbers. Discard any number divisible by 2. Continue receiving until 100 +// numbers are received. Tell the workers to shut down before terminating. +package main + +import ( + "fmt" + "math/rand" + "runtime" + "sync" +) + +func main() { + + // Create the channel for sharing results. + values := make(chan int) + + // Create a channel "shutdown" to tell goroutines when to terminate. + shutdown := make(chan struct{}) + + // Define the size of the worker pool. Use runtime.GOMAXPROCS(0) to size the pool based on number of processors. + poolSize := runtime.GOMAXPROCS(0) + + // Create a sync.WaitGroup to monitor the Goroutine pool. Add the count. + var wg sync.WaitGroup + wg.Add(poolSize) + + // Create a fixed size pool of goroutines to generate random numbers. + for i := 0; i < poolSize; i++ { + go func(id int) { + + // Start an infinite loop. + for { + + // Generate a random number up to 1000. + n := rand.Intn(1000) + + // Use a select to either send the number or receive the shutdown signal. + select { + + // In one case send the random number. + case values <- n: + fmt.Printf("Worker %d sent %d\n", id, n) + + // In another case receive from the shutdown channel. + case <-shutdown: + fmt.Printf("Worker %d shutting down\n", id) + wg.Done() + return + } + } + }(i) + } + + // Create a slice to hold the random numbers. + var nums []int + for i := range values { + + // continue the loop if the value was even. + if i%2 == 0 { + fmt.Println("Discarding", i) + continue + } + + // Store the odd number. + fmt.Println("Keeping", i) + nums = append(nums, i) + + // break the loop once we have 100 results. + if len(nums) == 100 { + break + } + } + + // Send the shutdown signal by closing the channel. + fmt.Println("Receiver sending shutdown signal") + close(shutdown) + + // Wait for the Goroutines to finish. + wg.Wait() + + // Print the values in our slice. + fmt.Printf("Result count: %d\n", len(nums)) + fmt.Println(nums) +} diff --git a/_content/tour/grc/channels/example1.go b/_content/tour/grc/channels/example1.go new file mode 100644 index 00000000..e9695f7f --- /dev/null +++ b/_content/tour/grc/channels/example1.go @@ -0,0 +1,35 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the wait for result channel pattern. +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + waitForResult() +} + +// waitForResult: In this pattern, the parent goroutine waits for the child +// goroutine to finish some work to signal the result. +func waitForResult() { + ch := make(chan string) + + go func() { + time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond) + ch <- "data" + fmt.Println("child : sent signal") + }() + + d := <-ch + fmt.Println("parent : recv'd signal :", d) + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example10.go b/_content/tour/grc/channels/example10.go new file mode 100644 index 00000000..e680c74a --- /dev/null +++ b/_content/tour/grc/channels/example10.go @@ -0,0 +1,53 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the channel cancellation channel pattern. +package main + +import ( + "context" + "net/http" +) + +func main() { + stop := make(chan struct{}) + + channelCancellation(stop) +} + +// channelCancellation shows how you can take an existing channel being +// used for cancellation and convert that into using a context where +// a context is needed. +func channelCancellation(stop <-chan struct{}) { + + // Create a cancel context for handling the stop signal. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // If a signal is received on the stop channel, cancel the + // context. This will propagate the cancel into the p.Run + // function below. + go func() { + select { + case <-stop: + cancel() + case <-ctx.Done(): + } + }() + + // Imagine a function that is performing an I/O operation that is + // cancellable. + func(ctx context.Context) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.ardanlabs.com/blog/index.xml", nil) + if err != nil { + return err + } + _, err = http.DefaultClient.Do(req) + if err != nil { + return err + } + return nil + }(ctx) +} diff --git a/_content/tour/grc/channels/example2.go b/_content/tour/grc/channels/example2.go new file mode 100644 index 00000000..1353107c --- /dev/null +++ b/_content/tour/grc/channels/example2.go @@ -0,0 +1,42 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the fan out channel pattern. +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + fanOut() +} + +// fanOut: In this pattern, the parent goroutine creates 2000 child goroutines +// and waits for them to signal their results. +func fanOut() { + children := 2000 + ch := make(chan string, children) + + for c := 0; c < children; c++ { + go func(child int) { + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + ch <- "data" + fmt.Println("child : sent signal :", child) + }(c) + } + + for children > 0 { + d := <-ch + children-- + fmt.Println(d) + fmt.Println("parent : recv'd signal :", children) + } + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example3.go b/_content/tour/grc/channels/example3.go new file mode 100644 index 00000000..5fc1c432 --- /dev/null +++ b/_content/tour/grc/channels/example3.go @@ -0,0 +1,35 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the wait for task channel pattern. +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + waitForTask() +} + +// waitForTask: In this pattern, the parent goroutine sends a signal to a +// child goroutine waiting to be told what to do. +func waitForTask() { + ch := make(chan string) + + go func() { + d := <-ch + fmt.Println("child : recv'd signal :", d) + }() + + time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond) + ch <- "data" + fmt.Println("parent : sent signal") + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example4.go b/_content/tour/grc/channels/example4.go new file mode 100644 index 00000000..8aeaed97 --- /dev/null +++ b/_content/tour/grc/channels/example4.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the pooling channel pattern. +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + pooling() +} + +// pooling: In this pattern, the parent goroutine signals 100 pieces of work +// to a pool of child goroutines waiting for work to perform. +func pooling() { + ch := make(chan string) + + g := runtime.GOMAXPROCS(0) + for c := 0; c < g; c++ { + go func(child int) { + for d := range ch { + fmt.Printf("child %d : recv'd signal : %s\n", child, d) + } + fmt.Printf("child %d : recv'd shutdown signal\n", child) + }(c) + } + + const work = 100 + for w := 0; w < work; w++ { + ch <- "data" + fmt.Println("parent : sent signal :", w) + } + + close(ch) + fmt.Println("parent : sent shutdown signal") + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example5.go b/_content/tour/grc/channels/example5.go new file mode 100644 index 00000000..dc646a6b --- /dev/null +++ b/_content/tour/grc/channels/example5.go @@ -0,0 +1,51 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the fan out semaphore channel pattern. +package main + +import ( + "fmt" + "math/rand" + "runtime" + "time" +) + +func main() { + fanOutSem() +} + +// fanOutSem: In this pattern, a semaphore is added to the fan out pattern +// to restrict the number of child goroutines that can be schedule to run. +func fanOutSem() { + children := 2000 + ch := make(chan string, children) + + g := runtime.GOMAXPROCS(0) + sem := make(chan bool, g) + + for c := 0; c < children; c++ { + go func(child int) { + sem <- true + { + t := time.Duration(rand.Intn(200)) * time.Millisecond + time.Sleep(t) + ch <- "data" + fmt.Println("child : sent signal :", child) + } + <-sem + }(c) + } + + for children > 0 { + d := <-ch + children-- + fmt.Println(d) + fmt.Println("parent : recv'd signal :", children) + } + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example6.go b/_content/tour/grc/channels/example6.go new file mode 100644 index 00000000..38adb03c --- /dev/null +++ b/_content/tour/grc/channels/example6.go @@ -0,0 +1,52 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the bounded work pooling channel pattern. +package main + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +func main() { + boundedWorkPooling() +} + +// boundedWorkPooling: In this pattern, a pool of child goroutines is created +// to service a fixed amount of work. The parent goroutine iterates over all +// work, signalling that into the pool. Once all the work has been signaled, +// then the channel is closed, the channel is flushed, and the child +// goroutines terminate. +func boundedWorkPooling() { + work := []string{"paper", "paper", "paper", "paper", 2000: "paper"} + + g := runtime.GOMAXPROCS(0) + var wg sync.WaitGroup + wg.Add(g) + + ch := make(chan string, g) + + for c := 0; c < g; c++ { + go func(child int) { + defer wg.Done() + for wrk := range ch { + fmt.Printf("child %d : recv'd signal : %s\n", child, wrk) + } + fmt.Printf("child %d : recv'd shutdown signal\n", child) + }(c) + } + + for _, wrk := range work { + ch <- wrk + } + close(ch) + wg.Wait() + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example7.go b/_content/tour/grc/channels/example7.go new file mode 100644 index 00000000..d521219d --- /dev/null +++ b/_content/tour/grc/channels/example7.go @@ -0,0 +1,46 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the drop channel pattern. +package main + +import ( + "fmt" + "time" +) + +func main() { + drop() +} + +// drop: In this pattern, the parent goroutine signals 2000 pieces of work to +// a single child goroutine that can't handle all the work. If the parent +// performs a send and the child is not ready, that work is discarded and dropped. +func drop() { + const cap = 100 + ch := make(chan string, cap) + + go func() { + for p := range ch { + fmt.Println("child : recv'd signal :", p) + } + }() + + const work = 2000 + for w := 0; w < work; w++ { + select { + case ch <- "data": + fmt.Println("parent : sent signal :", w) + default: + fmt.Println("parent : dropped data :", w) + } + } + + close(ch) + fmt.Println("parent : sent shutdown signal") + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example8.go b/_content/tour/grc/channels/example8.go new file mode 100644 index 00000000..6eced8ac --- /dev/null +++ b/_content/tour/grc/channels/example8.go @@ -0,0 +1,46 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the cancellation channel pattern. +package main + +import ( + "context" + "fmt" + "math/rand" + "time" +) + +func main() { + cancellation() +} + +// cancellation: In this pattern, the parent goroutine creates a child +// goroutine to perform some work. The parent goroutine is only willing to +// wait 150 milliseconds for that work to be completed. After 150 milliseconds +// the parent goroutine walks away. +func cancellation() { + duration := 150 * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + ch := make(chan string, 1) + + go func() { + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + ch <- "data" + }() + + select { + case d := <-ch: + fmt.Println("work complete", d) + + case <-ctx.Done(): + fmt.Println("work cancelled") + } + + time.Sleep(time.Second) + fmt.Println("-------------------------------------------------") +} diff --git a/_content/tour/grc/channels/example9.go b/_content/tour/grc/channels/example9.go new file mode 100644 index 00000000..ee7459f6 --- /dev/null +++ b/_content/tour/grc/channels/example9.go @@ -0,0 +1,53 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This sample program demonstrates the retry timeout channel pattern. +package main + +import ( + "context" + "errors" + "fmt" + "time" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + retryTimeout(ctx, time.Second, func(ctx context.Context) error { return errors.New("always fail") }) +} + +// retryTimeout: You need to validate if something can be done with no error +// but it may take time before this is true. You set a retry interval to create +// a delay before you retry the call and you use the context to set a timeout. +func retryTimeout(ctx context.Context, retryInterval time.Duration, check func(ctx context.Context) error) { + + for { + fmt.Println("perform user check call") + if err := check(ctx); err == nil { + fmt.Println("work finished successfully") + return + } + + fmt.Println("check if timeout has expired") + if ctx.Err() != nil { + fmt.Println("time expired 1 :", ctx.Err()) + return + } + + fmt.Printf("wait %s before trying again\n", retryInterval) + t := time.NewTimer(retryInterval) + + select { + case <-ctx.Done(): + fmt.Println("timed expired 2 :", ctx.Err()) + t.Stop() + return + case <-t.C: + fmt.Println("retry again") + } + } +} diff --git a/_content/tour/grc/channels/exercise1.go b/_content/tour/grc/channels/exercise1.go new file mode 100644 index 00000000..3bd8d916 --- /dev/null +++ b/_content/tour/grc/channels/exercise1.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program where two goroutines pass an integer back and forth +// ten times. Display when each goroutine receives the integer. Increment +// the integer with each pass. Once the integer equals ten, terminate +// the program cleanly. +package main + +// Add imports. + +func main() { + + // Create an unbuffered channel. + + // Create the WaitGroup and add a count + // of two, one for each goroutine. + + // Launch the goroutine and handle Done. + + // Launch the goroutine and handle Done. + + // Send a value to start the counting. + + // Wait for the program to finish. +} + +// goroutine simulates sharing a value. +func goroutine( /* parameters */ ) { + for { + + // Wait for the value to be sent. + // If the channel was closed, return. + + // Display the value. + + // Terminate when the value is 10. + + // Increment the value and send it + // over the channel. + } +} diff --git a/_content/tour/grc/channels/exercise2.go b/_content/tour/grc/channels/exercise2.go new file mode 100644 index 00000000..595fc82f --- /dev/null +++ b/_content/tour/grc/channels/exercise2.go @@ -0,0 +1,41 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program that uses a fan out pattern to generate 100 random numbers +// concurrently. Have each goroutine generate a single random number and return +// that number to the main goroutine over a buffered channel. Set the size of +// the buffered channel so no send ever blocks. Don't allocate more capacity +// than you need. Have the main goroutine store each random number it receives +// in a slice. Print the slice values then terminate the program. +package main + +// Add imports. + +// Declare constant for number of goroutines. + +func init() { + // Seed the random number generator. +} + +func main() { + + // Create the buffered channel with room for + // each goroutine to be created. + + // Iterate and launch each goroutine. + { + + // Create an anonymous function for each goroutine that + // generates a random number and sends it on the channel. + } + + // Create a variable to be used to track received messages. + // Set the value to the number of goroutines created. + + // Iterate receiving each value until they are all received. + // Store them in a slice of ints. + + // Print the values in our slice. +} diff --git a/_content/tour/grc/channels/exercise3.go b/_content/tour/grc/channels/exercise3.go new file mode 100644 index 00000000..6214a61a --- /dev/null +++ b/_content/tour/grc/channels/exercise3.go @@ -0,0 +1,47 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program that uses goroutines to generate up to 100 random numbers. +// Do not send values that are divisible by 2. Have the main goroutine receive +// values and add them to a slice. +package main + +// Declare constant for number of goroutines. +const goroutines = 100 + +func init() { + // Seed the random number generator. +} + +func main() { + + // Create the channel for sharing results. + + // Create a sync.WaitGroup to monitor the Goroutine pool. Add the count. + + // Iterate and launch each goroutine. + { + + // Create an anonymous function for each goroutine. + { + + // Ensure the waitgroup is decremented when this function returns. + + // Generate a random number up to 1000. + + // Return early if the number is even. (n%2 == 0) + + // Send the odd values through the channel. + } + } + + // Create a goroutine that waits for the other goroutines to finish then + // closes the channel. + + // Receive from the channel until it is closed. + // Store values in a slice of ints. + + // Print the values in our slice. +} diff --git a/_content/tour/grc/channels/exercise4.go b/_content/tour/grc/channels/exercise4.go new file mode 100644 index 00000000..3ef64f3d --- /dev/null +++ b/_content/tour/grc/channels/exercise4.go @@ -0,0 +1,61 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Write a program that creates a fixed set of workers to generate random +// numbers. Discard any number divisible by 2. Continue receiving until 100 +// numbers are received. Tell the workers to shut down before terminating. +package main + +// Add imports. + +func main() { + + // Create the channel for sharing results. + + // Create a channel "shutdown" to tell goroutines when to terminate. + + // Define the size of the worker pool. Use runtime.GOMAXPROCS(0) to size the pool based on number of processors. + + // Create a sync.WaitGroup to monitor the Goroutine pool. Add the count. + + // Create a fixed size pool of goroutines to generate random numbers. + { + { + + // Start an infinite loop. + { + + // Generate a random number up to 1000. + + // Use a select to either send the number or receive the shutdown signal. + { + + // In one case send the random number. + + // In another case receive from the shutdown channel. + + } + } + } + } + + // Create a slice to hold the random numbers. + + // Receive from the values channel with range. + { + + // continue the loop if the value was even. + + // Store the odd number. + + // break the loop once we have 100 results. + } + + // Send the shutdown signal by closing the shutdown channel. + + // Wait for the Goroutines to finish. + + // Print the values in our slice. +} diff --git a/_content/tour/grc/composition-assertions.article b/_content/tour/grc/composition-assertions.article new file mode 100644 index 00000000..62f3ada2 --- /dev/null +++ b/_content/tour/grc/composition-assertions.article @@ -0,0 +1,140 @@ +Type Conversions And Assertions +Learn how type conversations and assertions work. + +* Type Conversions And Assertions + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +A type conversion allows the data of one type to convert to another type. A type +assertion allows you to ask the question if there a value of the given type stored +inside an interface. + +** Code Review + +- *Example* *1:* Interface Conversions +- *Example* *2:* Runtime Type Assertions +- *Example* *3:* Behavior Changes + +.play composition/assertions/example1.go +.play composition/assertions/example2.go +.play composition/assertions/example3.go + +** Implicit Interface Conversions + +As you saw in the last example, An interface value of one type can be passed for a +different interface type if the concrete value stored inside the interface implements +both behaviors. This could be considered an implicit interface conversion, but it’s +better to think about how concrete data is being moved through interfaces in a +decoupled state. + + type Mover interface { + Move() + } + + type Locker interface { + Lock() + Unlock() + } + + type MoveLocker interface { + Mover + Locker + } + +Given these three interfaces, where MoveLocker is the composition of Mover and Locker. + + type bike struct{} + + func (bike) Move() { + fmt.Println("Moving the bike") + } + + func (bike) Lock() { + fmt.Println("Locking the bike") + } + + func (bike) Unlock() { + fmt.Println("Unlocking the bike") + } + +And given this concrete type bike that implements all three interfaces. What can you do? + + var ml MoveLocker + var m Mover + +You can construct a value of type MoveLocker and Mover to its zero value state. +These are interface values that are truly valueless. + + ml = bike{} + +Then you can construct a value of type bike to its zero value state and assign a +copy to the MoveLocker variable ml. This is possible because a bike implements +all three behaviors, and the compiler can see that the implementation exists. + + m = ml + +You can then assign the MoveLocker variable ml to the Mover variable m. This is possible +because I’m not assigning the interface value ml but the concrete value stored inside +of ml which is a bike value. The compiler knows that any concrete value stored inside +of ml must also implement the Mover interface. + +This assignment however is not valid. + + ml = m + +Output: + + cannot use m (type Mover) as type MoveLocker in assignment: + Mover does not implement MoveLocker (missing Lock method) + +You can’t assign the Mover variable m back to the MoverLocker variable ml because the +compiler can only guarantee that the concrete value stored inside of m knows how to Move. +It doesn’t know at compile time if the concrete value also knows how to Lock and Unlock. + +** Type Assertion Mechanics + +A type assertion allows you at runtime to ask a question, is there a value of the +given type stored inside an interface. You see that with the m.(bike) syntax. + + b := m.(bike) + ml = b + +In this case, you am asking if there is a bike value stored inside of m at the moment +the code is executed. If there is, then the variable b is given a copy of the bike +value stored. Then the copy can be copied inside of the ml interface variable. + +If there isn’t a bike value stored inside of the interface value, then the program panics. +you want this if there absolutely should have been a bike value stored. What if there is a +chance there isn’t and that is valid? Then you need the second form of the type assertion. + + b, ok := m.(bike) + +In this form, if ok is true, there is a bike value stored inside of the interface. If +ok is false, then there isn’t and the program does not panic. The variable b however +is still of type bike, but it is set to its zero value state. + + func main() { + mvs := []fmt.Stringer{ + Car{}, + Cloud{}, + } + + for i := 0; i < 10; i++ { + rn := rand.Intn(2) + + if v, is := mvs[rn].(Cloud); is { + fmt.Println("Got Lucky:", v) + continue + } + + fmt.Println("Got Unlucky") + } + } + +Assuming the program does declare two types named Car and Cloud that each implement +the fmt.Stringer interface, you can construct a collection that allows you to store a +value of both Car and Cloud. Then 10 times, you randomly choose a number from 0 to 1, +and perform a type assertion to see if the value at that random index contains a +Cloud value. Since it’s possible it’s not of type Cloud, the second form of the type +assertion is critical here. diff --git a/_content/tour/grc/composition-decoupling.article b/_content/tour/grc/composition-decoupling.article new file mode 100644 index 00000000..7930f06a --- /dev/null +++ b/_content/tour/grc/composition-decoupling.article @@ -0,0 +1,402 @@ +Decoupling +Composition goes beyond the mechanics of type embedding and is more than just a paradigm. + +* Decoupling + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The best way to take advantage of embedding is through the compositional design +pattern. It's the key for maintaining stability in your software by having the +ability to adapt to the data and transformation changes that are coming. + +** Code Review + +- *Example* *1:* Struct Composition +- *Example* *2:* Decoupling With Interface +- *Example* *3:* Interface Composition +- *Example* *4:* Decoupling With Interface Composition +- *Example* *5:* Remove Interface Pollution +- *Example* *6:* More Precise API + +.play composition/decoupling/example1.go +.play composition/decoupling/example2.go +.play composition/decoupling/example3.go +.play composition/decoupling/example4.go +.play composition/decoupling/example5.go +.play composition/decoupling/example6.go + +** Decoupling Mechanics + +The idea is to compose larger types from smaller types and focus on the composition +of behavior. + + type Xenia struct { + Host string + Timeout time.Duration + } + + func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + case 5: + return errors.New("Error reading data from Xenia") + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } + } + +The Xenia type represents a system that you need to pull data from. The implementation +is not important. What is important is that the method Pull can succeed, fail, or +not have any data to pull. + + type Pillar struct { + Host string + Timeout time.Duration + } + + func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil + } + +The Pillar type represents a system that you need to store data into. What is +important again is that the method Store can succeed or fail. + +These two types represent a primitive layer of code that provides the base behavior +required to solve the business problem of pulling data out of Xenia and storing +that data into Pillar. + + func Pull(x *Xenia, data []Data) (int, error) { + for i := range data { + if err := x.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil + } + + func Store(p *Pillar, data []Data) (int, error) { + for i := range data { + if err := p.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil + } + +The next layer of code is represented by these two functions, Pull and Store. They +build on the primitive layer of code by accepting a collection of data values to +pull or store in the respective systems. These functions focus on the concrete types +of Xenia and Pillar since those are the systems the program needs to work with at +this time. + + func Copy(sys *System, batch int) error { + data := make([]Data, batch) + + for { + i, err := Pull(&sys.Xenia, data) + if i > 0 { + if _, err := Store(&sys.Pillar, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } + } + +The Copy function builds on top of the Pull and Store functions to move all the +data that is pending for each run. If you notice the first parameter to Copy, it’s +a type called System. + + type System struct { + Xenia + Pillar + } + +The initial idea of the System type is to compose a system that knows how to Pull +and Store. In this case, composing the ability to Pull and Store from Xenia and Pillar. + + func main() { + sys := System{ + Xenia: Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Pillar: Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } + } + +Finally, the main function can be written to construct a Xenia and Pillar within +the composition of a System. Then the System can be passed to the Copy function +and data can begin to flow between the two systems. + +With all this code, you now have my first draft of a concrete solution to a concrete +problem. + +** Decoupling With Interfaces + +The next step is to understand what could change in the program. In this case, what +can change is the systems themselves. Today it’s Xenia and Pillar, tomorrow it could +be Alice and Bob. With this knowledge, you want to decouple the existing concrete +solution from this change. To do that, you want to change the concrete functions to +be polymorphic functions. + + func Pull(p Puller, data []Data) (int, error) { + for i := range data { + if err := p.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil + } + + func Store(s Storer, data []Data) (int, error) { + for i := range data { + if err := s.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil + } + +Currently, the Pull function accepts a Xenia value and the Store function accepts +a Pillar value. In the end, it wasn’t Xenia and Pillar that was important, what’s +important is a concrete value that knows how to Pull and Store. You can change these +concrete functions to be polymorphic by asking for data based on what it can do +instead of what it is. + + type Puller interface { + Pull(d *Data) error + } + + type Storer interface { + Store(d *Data) error + } + +These two interfaces describe what concrete data must do and it’s these types that +are replaced in the declaration of the Pull and Store functions. Now these functions +are polymorphic. When Alice and Bob are declared and implemented as a Puller and a +Storer, they can be passed into the functions. + +You am not done yet. The Copy function needs to be polymorphic as well. + + func Copy(ps PullStorer, batch int) error { + data := make([]Data, batch) + + for { + i, err := Pull(ps, data) + if i > 0 { + if _, err := Store(ps, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } + } + +The Copy function is no longer asking for a System value, but any concrete value +that knows how to both Pull and Store. + + type PullStorer interface { + Puller + Storer + } + +The PullStorer interface is declared through the use of composition. It’s composed +of the Puller and Storer interfaces. Work towards composing larger interfaces +from smaller ones. + +Notice how the PullStorer variable is now being passed into the Pull and Store +functions. How is this possible when the type information is different? + + // func Pull(p Puller, data []Data) (int, error) { + i, err := Pull(ps, data) + + // func Store(s Storer, data []Data) (int, error) { + if _, err := Store(ps, data[:i]); err != nil { + +You always need to remember, you am never passing an interface value around my program +since they don’t exist and are valueless. you can only pass concrete data. So the +concrete data stored inside of the interface ps variable is what’s being passed +to Pull and Store. Isn’t it true, the concrete value stored inside of ps must +know how to Pull and Store? + +.image /tour/grc/static/img/comp1.png + +Since a System is composed from a Xenia and Pillar, System implements the PullStorer +interface. With these changes, you can now create new concrete types that implement +the PullStorer interface. + + type System1 struct { + Xenia + Pillar + } + + type System2 struct { + Alice + Bob + } + + type System3 struct { + Xenia + Bob + } + + type System4 struct { + Alice + Pillar + } + +When you think about this more, declaring different System types for all the possible +combinations is not realistic. This will work, but the maintenance nightmare requires +a better solution. + +** Interface Composition + +What if you decided to compose my concrete system type from two interface types? + + type System struct { + Puller + Storer + } + +This is an interesting solution. This would allow the application to inject the +concrete Puller or Storer into the system at application startup. + + func main() { + sys := System{ + Puller: &Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Storer: &Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } + } + +This one system type implements the PullStorer interface for all possible +combinations of concrete types. + +.image /tour/grc/static/img/comp2.png + +With this change, the application is fully decoupled from changes to a new system +that may come online over time. + +** Precision Review + +The next question to ask is, are the polymorphic functions as precise as they +otherwise could be? This is a part of the engineering process that can’t be skipped. +The answer is no, two changes can be made. + + func Copy(sys *System, batch int) error { + +The Copy function doesn’t need to be polymorphic anymore since there will only be a +single System type. The PullStorer interface type can be removed from the program. +Remember, you moved the polymorphism inside the type when you used composition with the +interface types. + + func Copy(p Puller, s Storer, batch int) error { + +This is another change that can be made to the Copy function. This change makes the +function more precise and polymorphic again. Now the function is asking for exactly +what it needs based on what the concrete data can do. + +.image /tour/grc/static/img/comp3.png + +With that change the System struct type can be removed from the program as well. + + func main() { + x := Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + } + + p := Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + } + + if err := Copy(&x, &p, 3); err != io.EOF { + fmt.Println(err) + } + } + +By removing the PullStorer and System types, the program simplifies. The main +function can focus on constructing the concrete Puller and Storer values necessary +for that moving data. The type system and APIs are more precise. +This idea of precision comes from Edsger W. Dijkstra + +"The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise". - Edsger W. Dijkstra + +** Notes + +- This is much more than the mechanics of type embedding. +- Declare types and implement workflows with composition in mind. +- Understand the problem you are trying to solve first. This means understanding the data. +- The goal is to reduce and minimize cascading changes across your software. +- Interfaces provide the highest form of composition. +- Don't group types by a common DNA but by a common behavior. +- Everyone can work together when we focus on what we do and not what we are. + +** Quotes + +"A good API is not just easy to use but also hard to misuse." - JBD + +"You can always embed, but you cannot decompose big interfaces once they are out there. Keep interfaces small." - JBD + +"Don't design with interfaces, discover them." - Rob Pike + +"Duplication is far cheaper than the wrong abstraction. - Sandi Metz + +** Design Guidelines + +Learn about the [[https://github.com/ardanlabs/gotraining/blob/master/topics/go/#interface-and-composition-design][design guidelines]] for composition. + +** Extra Reading + +- [[https://programmingisterrible.com/post/176657481103/repeat-yourself-do-more-than-one-thing-and][Repeat yourself, do more than one thing, and rewrite everything]] - tef +- [[https://golang.org/doc/effective_go.html#embedding][Embedding]] +- [[https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html][Methods, Interfaces and Embedding]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2015/09/composition-with-go.html][Composition In Go]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2016/10/reducing-type-hierarchies.html][Reducing Type Hierarchies]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2016/10/avoid-interface-pollution.html][Avoid Interface Pollution]] - William Kennedy + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Using the template, declare a set of concrete types that implement the set of predefined +interface types. Then create values of these types and use them to complete a set of +predefined tasks. + +.play composition/decoupling/exercise1.go +.play composition/decoupling/answer1.go diff --git a/_content/tour/grc/composition-grouping.article b/_content/tour/grc/composition-grouping.article new file mode 100644 index 00000000..1cb7f9d8 --- /dev/null +++ b/_content/tour/grc/composition-grouping.article @@ -0,0 +1,228 @@ +Grouping With Types +It’s important to remember that in Go the concepts of sub-typing or sub-classing really don't exist and these design patterns should be avoided. + +* Grouping With Types + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +It’s important to remember that in Go the concepts of sub-typing or sub-classing +really don't exist and these design patterns should be avoided. + +** Code Review + +- *Example* *1:* Grouping By State +- *Example* *2:* Grouping By Behavior + +.play composition/grouping/example1.go +.play composition/grouping/example2.go + +** Grouping Different Types of Data + +It’s important to remember that in Go the concepts of sub-typing or sub-classing +really don't exist and these design patterns should be avoided. + +The following is an anti-pattern you shouldn’t follow or implement. + + type Animal struct { + Name string + IsMammal bool + } + +The Animal type is being declared as a base type that tries to define data that is +common to all animals. You also attempt to provide some common behavior to an +animal as well. + + func (a *Animal) Speak() { + fmt.Println("UGH!", + "My name is", a.Name, ", it is", a.IsMammal, "I am a mammal") + } + +Most animals have the ability to speak in one way or the other. However, trying to +apply this common behavior to just an animal doesn’t make any sense. At this +point, You have no idea what sound this animal makes, so you write UGH. + + type Dog struct { + Animal + PackFactor int + } + +Now the real problems begin. I’m attempting to use embedding to make a Dog +everything an Animal is plus more. On the surface this will seem to work, but there +will be problems. With that being said, a Dog does have a specific way they speak. + + func (d *Dog) Speak() { + fmt.Println("Woof!", + "My name is", d.Name, + ", it is", d.IsMammal, + "I am a mammal with a pack factor of", d.PackFactor) + } + +In the implementation of the Speak method, you can change out UGH for Woof. This is +specific to how a dog speaks. + + type Cat struct { + Animal + ClimbFactor int + } + +If I’m going to have a Dog that represents an Animal, then you have to have a Cat. +Using embedding, a Cat is everything an Animal is plus more. + + func (c *Cat) Speak() { + fmt.Println("Meow!", + "My name is", c.Name, + ", it is", c.IsMammal, + "I am a mammal with a climb factor of", c.ClimbFactor) + } + +In the implementation of the Speak method, you can change out UGH for Meow. This +is specific to how a cat speaks. + +Everything seems fine and it looks like embedding is providing the same +functionality as inheritance does in other languages. Then you try to go ahead and +group dogs and cats by the fact they have a common DNA of being an Animal. + + animals := []Animal{ + Dog{ + Animal: Animal{ + Name: "Fido", + IsMammal: true, + }, + PackFactor: 5, + }, + + Cat{ + Animal: Animal{ + Name: "Milo", + IsMammal: true, + }, + ClimbFactor: 4, + }, + } + + for _, animal := range animals { + animal.Speak() + } + +When you try to do this, the compiler complains that a Dog and Cat are not an Animal +and this is true. Embedding isn’t the same as inheritance and this is the pattern I +need to stay away from. A Dog is a Dog, a Cat a Cat, and an Animal an Animal. I +can’t pass Dog’s and Cat’s around as if they are Animals because they are not. + +This kind of mechanic is also not very flexible. It requires configuration by the +developer and unless you have access to the code, you can’t make configuration changes +over time. + +If this is not how we can construct a collection of Dog’s and Cat’s, how can we do +this in Go? It’s not about grouping through common DNA, it’s about grouping +through common behavior. Behavior is the key. + + type Speaker interface { + Speak() + } + +If you use an interface, then you can define the common method set of behavior that I +want to group different types of data against. + + speakers := []Speaker{ + &Dog{ + Animal: Animal{ + Name: "Fido", + IsMammal: true, + }, + PackFactor: 5, + }, + &Cat{ + Animal: Animal{ + Name: "Milo", + IsMammal: true, + }, + ClimbFactor: 4, + }, + } + + for _, speaker := range speakers { + speaker.Speak() + } + +In the new code, you can now group Dogs and Cats together based on their common +set of behavior, which is the fact that Dogs and Cats can speak. + +In fact, the Animal type is really type pollution because declaring a type just to +share a set of common states is a smell and should be avoided. + + type Dog struct { + Name string + IsMammal bool + PackFactor int + } + + type Cat struct { + Name string + IsMammal bool + ClimbFactor int + } + +In this particular case, you would rather see the Animal type removed and the fields +copied and pasted into the Dog and Cat types. Later you will have notes about better +patterns that eliminate these scenarios from happening. + +Here are the code smells from the original code: + +- The Animal type provides an abstraction layer of reusable state. +- The program never needs to create or solely use a value of Animal type. +- The implementation of the Speak method for the Animal type is generalized. +- The Speak method for the Animal type is never going to be called. + +Guidelines around declaring types: + +- Declare types that represent something new or unique. +- Don't create aliases just for readability. +- Validate that a value of any type is created or used on its own. +- Embed types not because you need the state, but because we need the behavior. +- If you are not thinking about behavior, You're locking yourself into the design that you can’t grow in the future without cascading code changes. +- Question types that are aliases or abstractions for an existing type. +- Question types whose sole purpose is to share a common set of states. + +** Don’t Design With Interfaces + +Unfortunately, too many developers attempt to solve problems in the abstract first. +They focus on interfaces right away and this leads to interface pollution. As a +developer, you exist in one of two modes: a programmer and then an engineer. + +When you are programming, you are focused on getting a piece of code to work. Trying +to solve the problem and break down walls. Prove that my initial ideas work. That is +all you care about. This programming should be done in the concrete and is never +production ready. + +Once you have a prototype of code that solves the problem, you need to switch to +engineering mode. You need to focus on how to write the code at a micro-level for +data semantics and readability, then at a macro-level for mental models and +maintainability. You also need to focus on errors and failure states. + +This work is done in a cycle of refactoring. Refactoring for readability, efficiency, +abstraction, and for testability. Abstracting is only one of a few refactors that need +to be performed. This works best when you start with a piece of concrete code and +then DISCOVER the interfaces that are needed. Don’t apply abstractions unless they +are absolutely necessary. + +Every problem you solve with code is a data problem requiring me to write data +transformations. If you don’t understand the data, you don’t understand the problem. If you +don’t understand the problem, you can’t write any code. Starting with a concrete +solution that is based on the concrete data structures is critical. As Rob Pike said, + +"Data dominates. If you've chosen the right data structures and organized things +well, the algorithms will almost always be self-evident". - Rob Pike + +When is abstraction necessary? When you see a place in the code where the data +could change and you want to minimize the cascading code effects that would result. I +might use abstraction to help make code testable, but you should try to avoid this if +possible. The best testable functions are functions that take raw data in and send +raw data out. It shouldn’t matter where the data is coming from or going. + +In the end, start with a concrete solution to every problem. Even if the bulk of that +is just programming. Then discover the interfaces that are absolutely required for +the code today. + +"Don’t design with interfaces, discover them". - Rob Pike diff --git a/_content/tour/grc/composition-mocking.article b/_content/tour/grc/composition-mocking.article new file mode 100644 index 00000000..3067c3ca --- /dev/null +++ b/_content/tour/grc/composition-mocking.article @@ -0,0 +1,97 @@ +Mocking +Since the compiler can perform static code analysis to determine if a concrete value implements an interface, the developer declaring the concrete type doesn’t need to provide interfaces as well. + +* Mocking + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The best way to take advantage of embedding is through the compositional design +pattern. The idea is to compose larger types from smaller types and focus on the +composition of behavior. + +** Code Review + +- *Example* *1:* Mocking + +.play composition/mocking/example1.go + +** Interface Ownership + +One thing that is different about Go from other languages is the idea of convention +over configuration. This really shows itself with how Go handles interface compliance. +Because the compiler can perform static code analysis to determine if a concrete value +implements an interface, the developer declaring the concrete type doesn’t need to +provide interfaces as well. + + package pubsub + + type PubSub struct { + host string + } + + func New(host string) *PubSub { + return &PubSub{ + host: host, + } + } + + func (ps *PubSub) Publish(key string, v interface{}) error { + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil + } + + func (ps *PubSub) Subscribe(key string) error { + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil + } + +You've just implemented a new API that provides a concrete implementation for publish +and subscribe. There are no interfaces being provided because this API does not +need one. This is a single concrete implementation. + +What if the application developer wanting to use this new API needs an interface +because they have the need to mock this implementation during tests? In Go, that +developer can declare the interface and the compiler can identify the compliance. + + package main + + type publisher interface { + Publish(key string, v interface{}) error + Subscribe(key string) error + } + + type mock struct{} + + func (m *mock) Publish(key string, v interface{}) error { + // ADD MY MOCK FOR THE PUBLISH CALL. + return nil + } + + func (m *mock) Subscribe(key string) error { + // ADD MY MOCK FOR THE SUBSCRIBE CALL. + return nil + } + +This code in the main package is declaring an interface. This interface represents +the API that the application is using from the pubsub package. The developer has +implemented their own pubsub implementation for testing. The key here is that this +application developer doesn’t use any concrete implementation directly, but decouples +themselves through their own interface. + + func main() { + pubs := []publisher{ + pubsub.New("localhost"), + &mock{}, + } + + for _, p := range pubs { + p.Publish("key", "value") + p.Subscribe("key") + } + } + +To provide an example, the main function constructs a collection that is initialized +with the pubsub implementation and the mock implementation. The publisher interface +allows this. Then a for range loop is implemented to show how the application code +is abstracted from any concrete implementation. diff --git a/_content/tour/grc/composition-pollution.article b/_content/tour/grc/composition-pollution.article new file mode 100644 index 00000000..bcafe097 --- /dev/null +++ b/_content/tour/grc/composition-pollution.article @@ -0,0 +1,86 @@ +Interface Pollution +Interface pollution comes from the fact that people are designing software with interfaces instead of discovering them. + +* Interface Pollution + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Interface pollution comes from the fact that people are designing software with +interfaces instead of discovering them. + +** Code Review + +- *Example* *1:* Create Interface Pollution +- *Example* *2:* Remove Interface Pollution + +.play composition/pollution/example1.go +.play composition/pollution/example2.go + +** Interface Pollution + +Interface pollution comes from the fact that people are designing software with +interfaces instead of discovering them. You should design a concrete solution to the +problem first. Then you can discover where the program needs to be polymorphic, if +at all. + +These are things I’ve heard from other developers. + +"I’m using interfaces because we have to use interfaces". + +No. We don't have to use interfaces. We use interfaces when it’s practical and +reasonable to do so. There is a cost of using interfaces: a level of indirection +and allocation when we store concrete values inside of them. Unless the cost of +the allocation is worth what I’m gaining by decoupling, you shouldn't be using interfaces. + +"I need to be able to test my code so I need to use interfaces". + +No. you must design my API for the user first, not my test. If the API is not testable, +You should question if it’s usable. There are different layers of API’s as well. The +lower level unexported API’s can and should focus on testability. The higher level +exported API’s need to focus on usability. + +Functions that accept raw data in and return raw data out are the most testable. +Separate the data transformation from where the data comes from and where it is +going. This is a refactoring exercise you need to perform during the engineering +coding cycle. + +Below is an example that creates interface pollution by improperly using an +interface when one is not needed. + + type Server interface { + Start() error + Stop() error + Wait() error + } + +The Server interface defines a contract for TCP servers. The problem here is I +don’t need a contract, you need an implementation. There will only be one implementation +as well, especially since you are the one implementing it. You do not need someone else +to implement this for you. + +Plus, this interface is based on a noun and not a verb. Concrete types are nouns +since they represent the concrete problem. Interfaces describe the behavior and +Server is not behavior. + +Here are some ways to identify interface pollution: + +- A package declares an interface that matches the entire API of its own concrete type. +- The interfaces are exported but the concrete types implementing the interface are unexported. +- The factory function for the concrete type returns the interface value with the unexported concrete type value inside. +- The interface can be removed and nothing changes for the user of the API. +- The interface is not decoupling the API from change. + +Guidelines around interface pollution: + +Use an interface: + +- When users of the API need to provide an implementation detail. +- When APIs have multiple implementations that need to be maintained. +- When parts of the APIs that can change have been identified and require decoupling. + +Question an interface: + +- When its only purpose is for writing testable API’s (write usable APIs first). +- When it’s not providing support for the API to decouple from change. +- When it's not clear how the interface makes the code better. diff --git a/_content/tour/grc/composition/assertions/example1.go b/_content/tour/grc/composition/assertions/example1.go new file mode 100644 index 00000000..5d1191cb --- /dev/null +++ b/_content/tour/grc/composition/assertions/example1.go @@ -0,0 +1,85 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating when implicit interface conversions +// are provided by the compiler. +package main + +import "fmt" + +// Mover provides support for moving things. +type Mover interface { + Move() +} + +// Locker provides support for locking and unlocking things. +type Locker interface { + Lock() + Unlock() +} + +// MoveLocker provides support for moving and locking things. +type MoveLocker interface { + Mover + Locker +} + +// bike represents a concrete type for the example. +type bike struct{} + +// Move can change the position of a bike. +func (bike) Move() { + fmt.Println("Moving the bike") +} + +// Lock prevents a bike from moving. +func (bike) Lock() { + fmt.Println("Locking the bike") +} + +// Unlock allows a bike to be moved. +func (bike) Unlock() { + fmt.Println("Unlocking the bike") +} + +func main() { + + // Declare variables of the MoveLocker and Mover interfaces set to their + // zero value. + var ml MoveLocker + var m Mover + + // Create a value of type bike and assign the value to the MoveLocker + // interface value. + ml = bike{} + + // An interface value of type MoveLocker can be implicitly converted into + // a value of type Mover. They both declare a method named move. + m = ml + + // prog.go:68: cannot use m (type Mover) as type MoveLocker in assignment: + // Mover does not implement MoveLocker (missing Lock method) + ml = m + + // Interface type Mover does not declare methods named lock and unlock. + // Therefore, the compiler can't perform an implicit conversion to assign + // a value of interface type Mover to an interface value of type MoveLocker. + // It is irrelevant that the concrete type value of type bike that is stored + // inside of the Mover interface value implements the MoveLocker interface. + + // We can perform a type assertion at runtime to support the assignment. + + // Perform a type assertion against the Mover interface value to access + // a COPY of the concrete type value of type bike that was stored inside + // of it. Then assign the COPY of the concrete type to the MoveLocker + // interface. + b := m.(bike) + ml = b + + // It's important to note that the type assertion syntax provides a way + // to state what type of value is stored inside the interface. This is + // more powerful from a language and readability standpoint, than using + // a casting syntax, like in other languages. +} diff --git a/_content/tour/grc/composition/assertions/example2.go b/_content/tour/grc/composition/assertions/example2.go new file mode 100644 index 00000000..abb3a5b6 --- /dev/null +++ b/_content/tour/grc/composition/assertions/example2.go @@ -0,0 +1,54 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating that type assertions are a runtime and +// not compile time construct. +package main + +import ( + "fmt" + "math/rand" +) + +// car represents something you drive. +type car struct{} + +// String implements the fmt.Stringer interface. +func (car) String() string { + return "Vroom!" +} + +// cloud represents somewhere you store information. +type cloud struct{} + +// String implements the fmt.Stringer interface. +func (cloud) String() string { + return "Big Data!" +} + +func main() { + + // Create a slice of the Stringer interface values. + mvs := []fmt.Stringer{ + car{}, + cloud{}, + } + + // Let's run this experiment ten times. + for i := 0; i < 10; i++ { + + // Choose a random number from 0 to 1. + rn := rand.Intn(2) + + // Perform a type assertion that we have a concrete type + // of cloud in the interface value we randomly chose. + if v, is := mvs[rn].(cloud); is { + fmt.Println("Got Lucky:", v) + continue + } + + fmt.Println("Got Unlucky") + } +} diff --git a/_content/tour/grc/composition/assertions/example3.go b/_content/tour/grc/composition/assertions/example3.go new file mode 100644 index 00000000..b834c53c --- /dev/null +++ b/_content/tour/grc/composition/assertions/example3.go @@ -0,0 +1,33 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how method sets can affect behavior. +package main + +import "fmt" + +// user defines a user in the system. +type user struct { + name string + email string +} + +// String implements the fmt.Stringer interface. +func (u *user) String() string { + return fmt.Sprintf("My name is %q and my email is %q", u.name, u.email) +} + +func main() { + + // Create a value of type user. + u := user{ + name: "Bill", + email: "bill@ardanlabs.com", + } + + // Display the values. + fmt.Println(u) + fmt.Println(&u) +} diff --git a/_content/tour/grc/composition/decoupling/answer1.go b/_content/tour/grc/composition/decoupling/answer1.go new file mode 100644 index 00000000..9a22a001 --- /dev/null +++ b/_content/tour/grc/composition/decoupling/answer1.go @@ -0,0 +1,154 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Using the template, declare a set of concrete types that implement the set +// of predefined interface types. Then create values of these types and use +// them to complete a set of predefined tasks. +package main + +// Add import(s). +import "fmt" + +// administrator represents a person or other entity capable of administering +// hardware and software infrastructure. +type administrator interface { + administrate(system string) +} + +// developer represents a person or other entity capable of writing software. +type developer interface { + develop(system string) +} + +// ============================================================================= + +// adminlist represents a group of administrators. +type adminlist struct { + list []administrator +} + +// Enqueue adds an administrator to the adminlist. +func (l *adminlist) Enqueue(a administrator) { + l.list = append(l.list, a) +} + +// Dequeue removes an administrator from the adminlist. +func (l *adminlist) Dequeue() administrator { + a := l.list[0] + l.list = l.list[1:] + return a +} + +// ============================================================================= + +// devlist represents a group of developers. +type devlist struct { + list []developer +} + +// Enqueue adds a developer to the devlist. +func (l *devlist) Enqueue(d developer) { + l.list = append(l.list, d) +} + +// Dequeue removes a developer from the devlist. +func (l *devlist) Dequeue() developer { + d := l.list[0] + l.list = l.list[1:] + return d +} + +// ============================================================================= + +// Declare a concrete type named sysadmin with a name field of type string. +type sysadmin struct { + name string +} + +// Declare a method named administrate for the sysadmin type, implementing the +// administrator interface. administrate should print out the name of the +// sysadmin, as well as the system they are administering. +func (s *sysadmin) administrate(system string) { + fmt.Println(s.name, "is administering", system) +} + +// Declare a concrete type named programmer with a name field of type string. +type programmer struct { + name string +} + +// Declare a method named develop for the programmer type, implementing the +// developer interface. develop should print out the name of the +// programmer, as well as the system they are coding. +func (p *programmer) develop(system string) { + fmt.Println(p.name, "is developing", system) +} + +// Declare a concrete type named company. Declare it as the composition of +// the administrator and developer interface types. +type company struct { + administrator + developer +} + +// ============================================================================= + +func main() { + + // Create a variable named admins of type adminlist. + var admins adminlist + + // Create a variable named devs of type devlist. + var devs devlist + + // Enqueue a new sysadmin onto admins. + admins.Enqueue(&sysadmin{"John"}) + + // Enqueue two new programmers onto devs. + devs.Enqueue(&programmer{"Mary"}) + devs.Enqueue(&programmer{"Steve"}) + + // Create a variable named cmp of type company, and initialize it by + // hiring (dequeuing) an administrator from admins and a developer from devs. + cmp := company{ + administrator: admins.Dequeue(), + developer: devs.Dequeue(), + } + + // Enqueue the company value on both lists since the company implements + // each interface. + admins.Enqueue(&cmp) + devs.Enqueue(&cmp) + + // A set of tasks for administrators and developers to perform. + tasks := []struct { + needsAdmin bool + system string + }{ + {needsAdmin: false, system: "xenia"}, + {needsAdmin: true, system: "pillar"}, + {needsAdmin: false, system: "omega"}, + } + + // Iterate over tasks. + for _, task := range tasks { + + // Check if the task needs an administrator else use a developer. + if task.needsAdmin { + + // Dequeue an administrator value from the admins list and + // call the administrate method. + adm := admins.Dequeue() + adm.administrate(task.system) + + continue + } + + // Dequeue a developer value from the devs list and + // call the develop method. + dev := devs.Dequeue() + dev.develop(task.system) + } +} diff --git a/_content/tour/grc/composition/decoupling/example1.go b/_content/tour/grc/composition/decoupling/example1.go new file mode 100644 index 00000000..c073ca94 --- /dev/null +++ b/_content/tour/grc/composition/decoupling/example1.go @@ -0,0 +1,125 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating struct composition. +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "time" +) + +// Data is the structure of the data we are copying. +type Data struct { + Line string +} + +// ============================================================================= + +// Xenia is a system we need to pull data from. +type Xenia struct { + Host string + Timeout time.Duration +} + +// Pull knows how to pull data out of Xenia. +func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + + case 5: + return errors.New("error reading data from Xenia") + + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } +} + +// Pillar is a system we need to store data into. +type Pillar struct { + Host string + Timeout time.Duration +} + +// Store knows how to store data into Pillar. +func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil +} + +// ============================================================================= + +// System wraps Xenia and Pillar together into a single system. +type System struct { + Xenia + Pillar +} + +// ============================================================================= + +// pull knows how to pull bulks of data from Xenia. +func pull(x *Xenia, data []Data) (int, error) { + for i := range data { + if err := x.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// store knows how to store bulks of data into Pillar. +func store(p *Pillar, data []Data) (int, error) { + for i := range data { + if err := p.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// Copy knows how to pull and store data from the System. +func Copy(sys *System, batch int) error { + data := make([]Data, batch) + + for { + i, err := pull(&sys.Xenia, data) + if i > 0 { + if _, err := store(&sys.Pillar, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } +} + +// ============================================================================= + +func main() { + sys := System{ + Xenia: Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Pillar: Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } +} diff --git a/_content/tour/grc/composition/decoupling/example2.go b/_content/tour/grc/composition/decoupling/example2.go new file mode 100644 index 00000000..3c3fa12f --- /dev/null +++ b/_content/tour/grc/composition/decoupling/example2.go @@ -0,0 +1,137 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating decoupling with interfaces. +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "time" +) + +// Data is the structure of the data we are copying. +type Data struct { + Line string +} + +// ============================================================================= + +// Puller declares behavior for pulling data. +type Puller interface { + Pull(d *Data) error +} + +// Storer declares behavior for storing data. +type Storer interface { + Store(d *Data) error +} + +// ============================================================================= + +// Xenia is a system we need to pull data from. +type Xenia struct { + Host string + Timeout time.Duration +} + +// Pull knows how to pull data out of Xenia. +func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + + case 5: + return errors.New("error reading data from Xenia") + + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } +} + +// Pillar is a system we need to store data into. +type Pillar struct { + Host string + Timeout time.Duration +} + +// Store knows how to store data into Pillar. +func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil +} + +// ============================================================================= + +// System wraps Xenia and Pillar together into a single system. +type System struct { + Xenia + Pillar +} + +// ============================================================================= + +// pull knows how to pull bulks of data from any Puller. +func pull(p Puller, data []Data) (int, error) { + for i := range data { + if err := p.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// store knows how to store bulks of data from any Storer. +func store(s Storer, data []Data) (int, error) { + for i := range data { + if err := s.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// Copy knows how to pull and store data from the System. +func Copy(sys *System, batch int) error { + data := make([]Data, batch) + + for { + i, err := pull(&sys.Xenia, data) + if i > 0 { + if _, err := store(&sys.Pillar, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } +} + +// ============================================================================= + +func main() { + sys := System{ + Xenia: Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Pillar: Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } +} diff --git a/_content/tour/grc/composition/decoupling/example3.go b/_content/tour/grc/composition/decoupling/example3.go new file mode 100644 index 00000000..e7e0961a --- /dev/null +++ b/_content/tour/grc/composition/decoupling/example3.go @@ -0,0 +1,143 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating interface composition. +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "time" +) + +// Data is the structure of the data we are copying. +type Data struct { + Line string +} + +// ============================================================================= + +// Puller declares behavior for pulling data. +type Puller interface { + Pull(d *Data) error +} + +// Storer declares behavior for storing data. +type Storer interface { + Store(d *Data) error +} + +// PullStorer declares behavior for both pulling and storing. +type PullStorer interface { + Puller + Storer +} + +// ============================================================================= + +// Xenia is a system we need to pull data from. +type Xenia struct { + Host string + Timeout time.Duration +} + +// Pull knows how to pull data out of Xenia. +func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + + case 5: + return errors.New("error reading data from Xenia") + + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } +} + +// Pillar is a system we need to store data into. +type Pillar struct { + Host string + Timeout time.Duration +} + +// Store knows how to store data into Pillar. +func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil +} + +// ============================================================================= + +// System wraps Xenia and Pillar together into a single system. +type System struct { + Xenia + Pillar +} + +// ============================================================================= + +// pull knows how to pull bulks of data from any Puller. +func pull(p Puller, data []Data) (int, error) { + for i := range data { + if err := p.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// store knows how to store bulks of data from any Storer. +func store(s Storer, data []Data) (int, error) { + for i := range data { + if err := s.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// Copy knows how to pull and store data from any System. +func Copy(ps PullStorer, batch int) error { + data := make([]Data, batch) + + for { + i, err := pull(ps, data) + if i > 0 { + if _, err := store(ps, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } +} + +// ============================================================================= + +func main() { + sys := System{ + Xenia: Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Pillar: Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } +} diff --git a/_content/tour/grc/composition/decoupling/example4.go b/_content/tour/grc/composition/decoupling/example4.go new file mode 100644 index 00000000..a26aa901 --- /dev/null +++ b/_content/tour/grc/composition/decoupling/example4.go @@ -0,0 +1,143 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating decoupling with interface composition. +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "time" +) + +// Data is the structure of the data we are copying. +type Data struct { + Line string +} + +// ============================================================================= + +// Puller declares behavior for pulling data. +type Puller interface { + Pull(d *Data) error +} + +// Storer declares behavior for storing data. +type Storer interface { + Store(d *Data) error +} + +// PullStorer declares behavior for both pulling and storing. +type PullStorer interface { + Puller + Storer +} + +// ============================================================================= + +// Xenia is a system we need to pull data from. +type Xenia struct { + Host string + Timeout time.Duration +} + +// Pull knows how to pull data out of Xenia. +func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + + case 5: + return errors.New("error reading data from Xenia") + + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } +} + +// Pillar is a system we need to store data into. +type Pillar struct { + Host string + Timeout time.Duration +} + +// Store knows how to store data into Pillar. +func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil +} + +// ============================================================================= + +// System wraps Pullers and Stores together into a single system. +type System struct { + Puller + Storer +} + +// ============================================================================= + +// pull knows how to pull bulks of data from any Puller. +func pull(p Puller, data []Data) (int, error) { + for i := range data { + if err := p.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// store knows how to store bulks of data from any Storer. +func store(s Storer, data []Data) (int, error) { + for i := range data { + if err := s.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// Copy knows how to pull and store data from any System. +func Copy(ps PullStorer, batch int) error { + data := make([]Data, batch) + + for { + i, err := pull(ps, data) + if i > 0 { + if _, err := store(ps, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } +} + +// ============================================================================= + +func main() { + sys := System{ + Puller: &Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Storer: &Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } +} diff --git a/_content/tour/grc/composition/decoupling/example5.go b/_content/tour/grc/composition/decoupling/example5.go new file mode 100644 index 00000000..37d79fcc --- /dev/null +++ b/_content/tour/grc/composition/decoupling/example5.go @@ -0,0 +1,137 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating removing interface pollution. +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "time" +) + +// Data is the structure of the data we are copying. +type Data struct { + Line string +} + +// ============================================================================= + +// Puller declares behavior for pulling data. +type Puller interface { + Pull(d *Data) error +} + +// Storer declares behavior for storing data. +type Storer interface { + Store(d *Data) error +} + +// ============================================================================= + +// Xenia is a system we need to pull data from. +type Xenia struct { + Host string + Timeout time.Duration +} + +// Pull knows how to pull data out of Xenia. +func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + + case 5: + return errors.New("error reading data from Xenia") + + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } +} + +// Pillar is a system we need to store data into. +type Pillar struct { + Host string + Timeout time.Duration +} + +// Store knows how to store data into Pillar. +func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil +} + +// ============================================================================= + +// System wraps Pullers and Stores together into a single system. +type System struct { + Puller + Storer +} + +// ============================================================================= + +// pull knows how to pull bulks of data from any Puller. +func pull(p Puller, data []Data) (int, error) { + for i := range data { + if err := p.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// store knows how to store bulks of data from any Storer. +func store(s Storer, data []Data) (int, error) { + for i := range data { + if err := s.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// Copy knows how to pull and store data from any System. +func Copy(sys *System, batch int) error { + data := make([]Data, batch) + + for { + i, err := pull(sys, data) + if i > 0 { + if _, err := store(sys, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } +} + +// ============================================================================= + +func main() { + sys := System{ + Puller: &Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + }, + Storer: &Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + }, + } + + if err := Copy(&sys, 3); err != io.EOF { + fmt.Println(err) + } +} diff --git a/_content/tour/grc/composition/decoupling/example6.go b/_content/tour/grc/composition/decoupling/example6.go new file mode 100644 index 00000000..a006aa2c --- /dev/null +++ b/_content/tour/grc/composition/decoupling/example6.go @@ -0,0 +1,128 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program demonstrating being more precise with API design. +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "time" +) + +// Data is the structure of the data we are copying. +type Data struct { + Line string +} + +// ============================================================================= + +// Puller declares behavior for pulling data. +type Puller interface { + Pull(d *Data) error +} + +// Storer declares behavior for storing data. +type Storer interface { + Store(d *Data) error +} + +// ============================================================================= + +// Xenia is a system we need to pull data from. +type Xenia struct { + Host string + Timeout time.Duration +} + +// Pull knows how to pull data out of Xenia. +func (*Xenia) Pull(d *Data) error { + switch rand.Intn(10) { + case 1, 9: + return io.EOF + + case 5: + return errors.New("error reading data from Xenia") + + default: + d.Line = "Data" + fmt.Println("In:", d.Line) + return nil + } +} + +// Pillar is a system we need to store data into. +type Pillar struct { + Host string + Timeout time.Duration +} + +// Store knows how to store data into Pillar. +func (*Pillar) Store(d *Data) error { + fmt.Println("Out:", d.Line) + return nil +} + +// ============================================================================= + +// pull knows how to pull bulks of data from any Puller. +func pull(p Puller, data []Data) (int, error) { + for i := range data { + if err := p.Pull(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// store knows how to store bulks of data from any Storer. +func store(s Storer, data []Data) (int, error) { + for i := range data { + if err := s.Store(&data[i]); err != nil { + return i, err + } + } + + return len(data), nil +} + +// Copy knows how to pull and store data from any System. +func Copy(p Puller, s Storer, batch int) error { + data := make([]Data, batch) + + for { + i, err := pull(p, data) + if i > 0 { + if _, err := store(s, data[:i]); err != nil { + return err + } + } + + if err != nil { + return err + } + } +} + +// ============================================================================= + +func main() { + x := Xenia{ + Host: "localhost:8000", + Timeout: time.Second, + } + + p := Pillar{ + Host: "localhost:9000", + Timeout: time.Second, + } + + if err := Copy(&x, &p, 3); err != io.EOF { + fmt.Println(err) + } +} diff --git a/_content/tour/grc/composition/decoupling/exercise1.go b/_content/tour/grc/composition/decoupling/exercise1.go new file mode 100644 index 00000000..db2f3a41 --- /dev/null +++ b/_content/tour/grc/composition/decoupling/exercise1.go @@ -0,0 +1,122 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Using the template, declare a set of concrete types that implement the set +// of predefined interface types. Then create values of these types and use +// them to complete a set of predefined tasks. +package main + +// Add import(s). + +// administrator represents a person or other entity capable of administering +// hardware and software infrastructure. +type administrator interface { + administrate(system string) +} + +// developer represents a person or other entity capable of writing software. +type developer interface { + develop(system string) +} + +// ============================================================================= + +// adminlist represents a group of administrators. +type adminlist struct { + list []administrator +} + +// Enqueue adds an administrator to the adminlist. +func (l *adminlist) Enqueue(a administrator) { + l.list = append(l.list, a) +} + +// Dequeue removes an administrator from the adminlist. +func (l *adminlist) Dequeue() administrator { + a := l.list[0] + l.list = l.list[1:] + return a +} + +// ============================================================================= + +// devlist represents a group of developers. +type devlist struct { + list []developer +} + +// Enqueue adds a developer to the devlist. +func (l *devlist) Enqueue(d developer) { + l.list = append(l.list, d) +} + +// Dequeue removes a developer from the devlist. +func (l *devlist) Dequeue() developer { + d := l.list[0] + l.list = l.list[1:] + return d +} + +// ============================================================================= + +// Declare a concrete type named sysadmin with a name field of type string. + +// Declare a method named administrate for the sysadmin type, implementing the +// administrator interface. administrate should print out the name of the +// sysadmin, as well as the system they are administering. + +// Declare a concrete type named programmer with a name field of type string. + +// Declare a method named develop for the programmer type, implementing the +// developer interface. develop should print out the name of the +// programmer, as well as the system they are coding. + +// Declare a concrete type named company. Declare it as the composition of +// the administrator and developer interface types. + +// ============================================================================= + +func main() { + + // Create a variable named admins of type adminlist. + + // Create a variable named devs of type devlist. + + // Enqueue a new sysadmin onto admins. + + // Enqueue two new programmers onto devs. + + // Create a variable named cmp of type company, and initialize it by + // hiring (dequeuing) an administrator from admins and a developer from devs. + + // Enqueue the company value on both lists since the company implements + // each interface. + + // A set of tasks for administrators and developers to perform. + tasks := []struct { + needsAdmin bool + system string + }{ + {needsAdmin: false, system: "xenia"}, + {needsAdmin: true, system: "pillar"}, + {needsAdmin: false, system: "omega"}, + } + + // Iterate over tasks. + for _, task := range tasks { + + // Check if the task needs an administrator else use a developer. + if { + + // Dequeue an administrator value from the admins list and + // call the administrate method. + + continue + } + + // Dequeue a developer value from the devs list and + // call the develop method. + } +} diff --git a/_content/tour/grc/composition/grouping/example1.go b/_content/tour/grc/composition/grouping/example1.go new file mode 100644 index 00000000..474b91ca --- /dev/null +++ b/_content/tour/grc/composition/grouping/example1.go @@ -0,0 +1,95 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This is an example of using type hierarchies with a OOP pattern. +// This is not something we want to do in Go. Go does not have the +// concept of sub-typing. All types are their own and the concepts of +// base and derived types do not exist in Go. This pattern does not +// provide a good design principle in a Go program. +package main + +import "fmt" + +// Animal contains all the base fields for animals. +type Animal struct { + Name string + IsMammal bool +} + +// Speak provides generic behavior for all animals and +// how they speak. +func (a *Animal) Speak() { + fmt.Printf( + "UGH! My name is %s, it is %t I am a mammal\n", + a.Name, + a.IsMammal, + ) +} + +// Dog contains everything an Animal is but specific +// attributes that only a Dog has. +type Dog struct { + Animal + PackFactor int +} + +// Speak knows how to speak like a dog. +func (d *Dog) Speak() { + fmt.Printf( + "Woof! My name is %s, it is %t I am a mammal with a pack factor of %d.\n", + d.Name, + d.IsMammal, + d.PackFactor, + ) +} + +// Cat contains everything an Animal is but specific +// attributes that only a Cat has. +type Cat struct { + Animal + ClimbFactor int +} + +// Speak knows how to speak like a cat. +func (c *Cat) Speak() { + fmt.Printf( + "Meow! My name is %s, it is %t I am a mammal with a climb factor of %d.\n", + c.Name, + c.IsMammal, + c.ClimbFactor, + ) +} + +func main() { + + // Create a list of Animals that know how to speak. + animals := []Animal{ + + // Create a Dog by initializing its Animal parts + // and then its specific Dog attributes. + Dog{ + Animal: Animal{ + Name: "Fido", + IsMammal: true, + }, + PackFactor: 5, + }, + + // Create a Cat by initializing its Animal parts + // and then its specific Cat attributes. + Cat{ + Animal: Animal{ + Name: "Milo", + IsMammal: true, + }, + ClimbFactor: 4, + }, + } + + // Have the Animals speak. + for _, animal := range animals { + animal.Speak() + } +} diff --git a/_content/tour/grc/composition/grouping/example2.go b/_content/tour/grc/composition/grouping/example2.go new file mode 100644 index 00000000..3aad8e35 --- /dev/null +++ b/_content/tour/grc/composition/grouping/example2.go @@ -0,0 +1,85 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This is an example of using composition and interfaces. This is +// something we want to do in Go. We will group common types by +// their behavior and not by their state. This pattern does +// provide a good design principle in a Go program. +package main + +import "fmt" + +// Speaker provide a common behavior for all concrete types +// to follow if they want to be a part of this group. This +// is a contract for these concrete types to follow. +type Speaker interface { + Speak() +} + +// Dog contains everything a Dog needs. +type Dog struct { + Name string + IsMammal bool + PackFactor int +} + +// Speak knows how to speak like a dog. +// This makes a Dog now part of a group of concrete +// types that know how to speak. +func (d *Dog) Speak() { + fmt.Printf( + "Woof! My name is %s, it is %t I am a mammal with a pack factor of %d.\n", + d.Name, + d.IsMammal, + d.PackFactor, + ) +} + +// Cat contains everything a Cat needs. +type Cat struct { + Name string + IsMammal bool + ClimbFactor int +} + +// Speak knows how to speak like a cat. +// This makes a Cat now part of a group of concrete +// types that know how to speak. +func (c *Cat) Speak() { + fmt.Printf( + "Meow! My name is %s, it is %t I am a mammal with a climb factor of %d.\n", + c.Name, + c.IsMammal, + c.ClimbFactor, + ) +} + +func main() { + + // Create a list of Animals that know how to speak. + speakers := []Speaker{ + + // Create a Dog by initializing its Animal parts + // and then its specific Dog attributes. + &Dog{ + Name: "Fido", + IsMammal: true, + PackFactor: 5, + }, + + // Create a Cat by initializing its Animal parts + // and then its specific Cat attributes. + &Cat{ + Name: "Milo", + IsMammal: true, + ClimbFactor: 4, + }, + } + + // Have the Animals speak. + for _, spkr := range speakers { + spkr.Speak() + } +} diff --git a/_content/tour/grc/composition/mocking/example1.go b/_content/tour/grc/composition/mocking/example1.go new file mode 100644 index 00000000..8be5dd98 --- /dev/null +++ b/_content/tour/grc/composition/mocking/example1.go @@ -0,0 +1,105 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how you can personally mock concrete types when +// you need to for your own packages or tests. +package main + +import ( + "play.ground/pubsub" +) + +// publisher is an interface to allow this package to mock the pubsub +// package support. +type publisher interface { + Publish(key string, v interface{}) error + Subscribe(key string) error +} + +// ============================================================================= + +// mock is a concrete type to help support the mocking of the pubsub package. +type mock struct{} + +// Publish implements the publisher interface for the mock. +func (m *mock) Publish(key string, v interface{}) error { + + // ADD YOUR MOCK FOR THE PUBLISH CALL. + return nil +} + +// Subscribe implements the publisher interface for the mock. +func (m *mock) Subscribe(key string) error { + + // ADD YOUR MOCK FOR THE SUBSCRIBE CALL. + return nil +} + +// ============================================================================= + +func main() { + + // Create a slice of publisher interface values. Assign + // the address of a pubsub.PubSub value and the address of + // a mock value. + pubs := []publisher{ + pubsub.New("localhost"), + &mock{}, + } + + // Range over the interface value to see how the publisher + // interface provides the level of decoupling the user needs. + // The pubsub package did not need to provide the interface type. + for _, p := range pubs { + p.Publish("key", "value") + p.Subscribe("key") + } +} + +// ----------------------------------------------------------------------------- +-- pubsub/pubsub.go -- + +// Package pubsub simulates a package that provides publication/subscription +// type services. +package pubsub + +// PubSub provides access to a queue system. +type PubSub struct { + host string + + // PRETEND THERE ARE MORE FIELDS. +} + +// New creates a pubsub value for use. +func New(host string) *PubSub { + ps := PubSub{ + host: host, + } + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + + return &ps +} + +// Publish sends the data for the specified key. +func (ps *PubSub) Publish(key string, v interface{}) error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +// Subscribe sets up an request to receive messages for the specified key. +func (ps *PubSub) Subscribe(key string) error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/composition/pollution/example1.go b/_content/tour/grc/composition/pollution/example1.go new file mode 100644 index 00000000..a6af6feb --- /dev/null +++ b/_content/tour/grc/composition/pollution/example1.go @@ -0,0 +1,73 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This is an example that creates interface pollution +// by improperly using an interface when one is not needed. +package main + +// Server defines a contract for tcp servers. +type Server interface { + Start() error + Stop() error + Wait() error +} + +// server is our Server implementation. +type server struct { + host string + + // PRETEND THERE ARE MORE FIELDS. +} + +// NewServer returns an interface value of type Server +// with a server implementation. +func NewServer(host string) Server { + + // SMELL - Storing an unexported type pointer in the interface. + return &server{host} +} + +// Start allows the server to begin to accept requests. +func (s *server) Start() error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +// Stop shuts the server down. +func (s *server) Stop() error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +// Wait prevents the server from accepting new connections. +func (s *server) Wait() error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +func main() { + + // Create a new Server. + srv := NewServer("localhost") + + // Use the API. + srv.Start() + srv.Stop() + srv.Wait() +} + +// ============================================================================= + +// NOTES: + +// Smells: +// * The package declares an interface that matches the entire API of its own concrete type. +// * The interface is exported but the concrete type is unexported. +// * The factory function returns the interface value with the unexported concrete type value inside. +// * The interface can be removed and nothing changes for the user of the API. +// * The interface is not decoupling the API from change. diff --git a/_content/tour/grc/composition/pollution/example2.go b/_content/tour/grc/composition/pollution/example2.go new file mode 100644 index 00000000..f1492800 --- /dev/null +++ b/_content/tour/grc/composition/pollution/example2.go @@ -0,0 +1,69 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This is an example that removes the interface pollution by +// removing the interface and using the concrete type directly. +package main + +// Server is our Server implementation. +type Server struct { + host string + + // PRETEND THERE ARE MORE FIELDS. +} + +// NewServer returns an interface value of type Server +// with a server implementation. +func NewServer(host string) *Server { + + // SMELL - Storing an unexported type pointer in the interface. + return &Server{host} +} + +// Start allows the server to begin to accept requests. +func (s *Server) Start() error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +// Stop shuts the server down. +func (s *Server) Stop() error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +// Wait prevents the server from accepting new connections. +func (s *Server) Wait() error { + + // PRETEND THERE IS A SPECIFIC IMPLEMENTATION. + return nil +} + +func main() { + + // Create a new Server. + srv := NewServer("localhost") + + // Use the API. + srv.Start() + srv.Stop() + srv.Wait() +} + +// ============================================================================= + +// NOTES: + +// Here are some guidelines around interface pollution: +// * Use an interface: +// * When users of the API need to provide an implementation detail. +// * When API’s have multiple implementations that need to be maintained. +// * When parts of the API that can change have been identified and require decoupling. +// * Question an interface: +// * When its only purpose is for writing testable API’s (write usable API’s first). +// * When it’s not providing support for the API to decouple from change. +// * When it's not clear how the interface makes the code better. diff --git a/_content/tour/grc/constants.article b/_content/tour/grc/constants.article new file mode 100644 index 00000000..6c88d5a4 --- /dev/null +++ b/_content/tour/grc/constants.article @@ -0,0 +1,179 @@ +Σταθερές +Ένα από τα περισσότερο μοναδικά χαρακτηριστικά της Go είναι ο τρόπος με τον οποίο η γλώσσα υλοποιεί τις σταθερές. + +* Σταθερές + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Παρακολουθήστε το Video]] +- Εαν Χρειάζεστε Οικονομική Συνδρομή, Χρησιμοποιείστε το σχετικό [[https://www.ardanlabs.com/scholarship/][Έγγραφο Υποτροφίας]] + +Ένα από τα πιο μοναδικά χαρακτηριστικά της Go είναι ο τρόπος με τον οποίο η γλώσσα υλοποιεί τις σταθερές. +Οι κανόνες για τις σταθερές στο έγγραφο προδιαγραφών της γλώσσας είναι μοναδικοί στην Go. Παρέχουν +την ευελιξία που χρειάζεται η Go προκειμένου να κάνει τον κώδικα που γράφει κάποιος αναγνώσιμο και εύληπτο ενώ ταυτόχρονα +να διατηρείται η ασφάλεια που παρέχουν οι τύποι. + +Οι σταθερές μπορούν να έχουν τύπο ή να μην έχουν. Όταν μια σταθερά δεν έχει τύπο, θεωρείται ότι +είναι ενός είδους. Σταθερές ενός είδους μπορούν να μετατραπούν σιωπηρά από τον μεταγλωττιστή. Όλα +αυτά συμβαίνουν στο στάδιο της μεταγλώττισης και όχι στο στάδιο της εκτέλεσης. + +** Ανασκόπιση Κώδικα + +- *Παράδειγμα* *1:* Δηλώσεις και εκώριση αρχικών τιμών σε σταθερές +- *Παράδειγμα* *2:* Παράλληλο σύστημα τύπων (Είδη) (δεν υπάρχει) +- *Παράδειγμα* *3:* iota +- *Παράδειγμα* *4:* Σιωπηρή μετατροπή + +.play constants/example1.go +.play constants/example2.go +.play constants/example3.go +.play constants/example4.go + + const ui = 12345 // είδος: ακέραιος + const uf = 3.141592 // είδος: κινητής υποδιαστολής + +Οι αριθμητικές σταθερές χωρίς τύπο έχουν ακρίβεια 256 bit, όπως αναφέρεται στις προδιαγραφές. +Βασίζονται σε ένα είδος. + + const ti int = 12345 // type: int + const tf float64 = 3.141592 // type: float64 + +Οι σταθερές με τύπο χρησιμοποιούν και αυτές το σύστημα τύπων των σταθερών, όμως η ακρίβειά τους είναι περιορισμένη. + + const myUint8 uint8 = 1000 // Σφάλμα Μεταγλωττιστή: η σταθερά 1000 υπερχειλίζει έναν uint8 +Αυτό δεν λειτουργεί επειδή ο αριθμός 1000 είναι πολύ μεγάλος προκειμένου να αποθηκευτεί σε έναν uint8. + + var answer = 3 * 0.333 // float64 = KindFloat(3) * KindFloat(0.333) + +Η αριθμητική σταθερών υποστηρίζει την χρήση διαφορετικών ειδών σταθερών. Η προαγωγή ενός είδους σε άλλο +χρησιμοποιείται προκειμένου να γίνει η διαχείριση αυτών των διαφορετικών σεναρίων. Όλα αυτά συμβαίνουν σιωπηρά. Η μεταβλητη +answer σε αυτό το παράδειγμα θα είναι τύπου float64 και θα αναπαριστά την τιμή 0.999 με ακρίβεια +64 bit. + + const third = 1 / 3.0 // KindFloat = KindFloat(1) / KindFloat(3.0) + +Η σταθερά third θα είναι είδους float και θα αναπαριστά την τιμή 1/3 με ακρίβεια 256 bit. + + const zero = 1 / 3 // KindInt = KindInt(1) / KindInt(3) + +Η σταθερά zero θα είναι είδους ακεραίου και θα ισούται με 0 καθώς η ακέραια διαίρεση δεν +έχει υπόλοιπο. + + const one int8 = 1 + const two = 2 * one // int8(2) * int8(1) + +Αυτό είναι ένα παράδειγμα αριθμητικής μεταξύ σταθερών για σταθερές με και χωρίς τύπο. +Σε αυτή την περίπτωση, μια σταθερά ενός τύπου προάγεται αντί μιας σταθεράς είδους. Η σταθερά +two θα έχει τύπο int8 και τιμή ίση με 2. + + const maxInt = 9223372036854775807 + +Αυτή είναι η μέγιστη ακέραια τιμή για έναν ακέραιο 64 bit. + + const bigger = 9223372036854775808543522345 + +Η σταθερά bigger είναι μια πολύ μεγαλύτερη τιμή από έναν ακέραιο 64 bit, όμως μπορεί να +αποθηκευτεί σε μια σταθερά είδους int καθώς οι σταθερές είδους int δεν περιορίζονται σε +ακρίβεια 64 bit. + + const bigger int64 = 9223372036854775808543522345 + +Σφάλμα μεταγλωττιστή: + + constant 9223372036854775808543522345 overflows int64 + +Όμως, αν η σταθερά bigger ήταν μια στεθερά τύπου int64, αυτό δεν θα μεταγλωττιζόταν. + +** IOTA + +Το IOTA προσφέρει υποστήριξη για τον ορισμό διαδοχικών ακέραιων σταθερών. Είναι πιθανόν το +όνομα να προέρχεται από την ακέραια συνάρτηση ⍳ από την γλώσσα προγραμματισμού APL. Στην APL, +η συνάρτηση ⍳ (η οποία αντιπροσωπεύεται από το ένατο γράμμα του Ελληνικού αλφάβητου, το γιώτα) +χρησιμοποιείται προκειμένου να δημιουργήσει κανείς έναν πίνακα με βάση το μηδέν, διαδοχικών αυξόντων ακεραίων +συγκεκριμένου μήκους. + + const ( + A1 = iota // 0 : Ξεκινάει από το 0 + B1 = iota // 1 : Αυξάνει κατά 1 + C1 = iota // 2 : Αυξάνει κατά 1 + ) + fmt.Println(A1, B1, C1) + +Αποτέλεσμα: + + 0 1 2 + +Η λέξη-κλειδί iota λειτουργεί εντός μιας ενότητας σταθερών και ξεκινάει με την τιμή 0. Μετά, +για κάθε διαδοχική σταθερά που ορίζεται στην ενότητα, το iota αυξάνει κατά 1. + + const ( + A2 = iota // 0 : Ξεκινάει από το 0 + B2 // 1 : Αυξάνει κατά 1 + C2 // 2 : Αυξάνει κατά 1 + ) + fmt.Println(A2, B2, C2) + +Αποτέλεσμα: + + 0 1 2 + +Δεν χρειάζεται να επαναλαμβάνει κανείς την χρήση της λέξης-κλειδί iota. Η διαδοχική φύση των +ακέραιων σταθερών εννοείται απο την στιγμή που γίνεται η χρήση της. + + const ( + A3 = iota + 1 // 1 : 0 + 1 + B3 // 2 : 1 + 1 + C3 // 3 : 2 + 1 + ) + fmt.Println(A3, B3, C3) + +Αποτέλεσμα: + + 1 2 3 + +Αν κανείς δεν θέλει να εφαρμόσει κάποιο μαθηματικό μοτίβο, είναι δυνατόν να πραγματοποιήσει κάποιες πράξεις και +οι πράξεις επαναεφαρμόζονται με μια αυξανόμενη τιμή του iota. + + const ( + Ldate= 1 << iota // 1 : Μετατόπισε το 1 αριστερά 0 φορές. 0000 0001 + Ltime // 2 : Μετατόπισε το 1 αριστερά 1 φορά. 0000 0010 + Lmicroseconds // 4 : Μετατόπισε το 1 αριστερά 2 φορές. 0000 0100 + Llongfile // 8 : Μετατόπισε το 1 αριστερά 3 φορές. 0000 1000 + Lshortfile // 16 : Μετατόπισε το 1 αριστερά 4 φορές. 0001 0000 + LUTC // 32 : Μετατόπισε το 1 αριστερά 5 φορές. 0010 0000 + ) + + fmt.Println(Ldate, Ltime, Lmicroseconds, Llongfile, Lshortfile, LUTC) + +Αποτέλεσμα: + + 1 2 4 8 16 32 + +Μπορεί κανείς να χρησιμποιήσει αυτό το χαρακτηριστικό, ακριβώς όπως το πακέτο Log το χρησιμοποιεί, προκειμένου να ορίζει επισημάνσεις (flags). +Σε αυτή την περίπτωση, πράξεις με bit χρησιμοποιούνται με αυξανόμενες τιμές του iota προκειμένου να υπολογιστούν οι τιμές των επισημάνσεων. + +** Σημειώσεις + +- Οι σταθερές δεν είναι μεταβλητές. +- Υπάρχουν μόνο κατά την μεταγλώττιση. +- Σταθερές χωρίς τύπο μπορούν να μετατραπούν σιωπηρά ενώ οι σταθερές με τύπο και οι μεταβλητές, δεν μπορούν. +- Κανείς μπορεί να σκεφτεί τις σταθερές σαν να έχουν Είδος και όχι Τύπο. +- Χρειάζεται να γνωρίζει κανείς σχετικά με ρητές και σιωπηρές μετατροπές. +- Η ισχύς των σταθερών και η χρήση τους στη προεγκατεστημένη βιβλιοθήκη είναι εμφανής. + +** Πρόσθετα Αναγνώσματα + +- [[https://golang.org/ref/spec#Constants][Προδιαγραφές Σταθερών]] +- [[https://blog.golang.org/constants][Σταθερές]] - Rob Pike +- [[https://www.ardanlabs.com/blog/2014/04/introduction-to-numeric-constants-in-go.html][Εισαγωγή στις Αριθμητικές Σταθερές στην Go]] - William Kennedy + +* Ασκήσεις + +Χρησιμοποιήστε το παρόν πρότυπο ως σημείο αναφοράς προκειμένου να ολοκληρώσετε τις ασκήσεις. Σας παρέχεται μια πιθανή λύση. + +** Άσκηση 1 + +*Μέρος* *Α:* Δηλώστε μια σταθερά χωρίς τύπο και μια με τύπο και παρουσιάστε τις τιμές τους. + +*Μέρος* *Β:* Διαιρέστε δύο ρητές σταθερές σε μια μεταβλητή με τύπο και παρουσιάστε τη τιμή. + +.play constants/exercise1.go +.play constants/answer1.go diff --git a/_content/tour/grc/constants/answer1.go b/_content/tour/grc/constants/answer1.go new file mode 100644 index 00000000..e06f507a --- /dev/null +++ b/_content/tour/grc/constants/answer1.go @@ -0,0 +1,30 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare an untyped and typed constant and display their values. +// +// Multiply two literal constants into a typed variable and display the value. +package main + +import "fmt" + +const ( + // server is the IP address for connecting. + server = "124.53.24.123" + + // port is the port to make that connection. + port int16 = 9000 +) + +func main() { + + // Display the server information. + fmt.Println(server) + fmt.Println(port) + + // Calculate the number of minutes in 5320 seconds. + minutes := 5320 / 60.0 + fmt.Println(minutes) +} diff --git a/_content/tour/grc/constants/example1.go b/_content/tour/grc/constants/example1.go new file mode 100644 index 00000000..2f101314 --- /dev/null +++ b/_content/tour/grc/constants/example1.go @@ -0,0 +1,50 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare constants and their +// implementation in Go. +package main + +import "fmt" + +func main() { + + // Constants live within the compiler. + // They have a parallel type system. + // Compiler can perform implicit conversions of untyped constants. + + // Untyped Constants. + const ui = 12345 // kind: integer + const uf = 3.141592 // kind: floating-point + + // Typed Constants still use the constant type system but their precision + // is restricted. + const ti int = 12345 // type: int + const tf float64 = 3.141592 // type: float64 + + // ./constants.go:XX: constant 1000 overflows uint8 + // const myUint8 uint8 = 1000 + + // Constant arithmetic supports different kinds. + // Kind Promotion is used to determine kind in these scenarios. + + // Variable answer will of type float64. + var answer = 3 * 0.333 // KindFloat(3) * KindFloat(0.333) + fmt.Println(answer) + + // Constant third will be of kind floating point. + const third = 1 / 3.0 // KindFloat(1) / KindFloat(3.0) + fmt.Println(third) + + // Constant zero will be of kind integer. + const zero = 1 / 3 // KindInt(1) / KindInt(3) + fmt.Println(zero) + + // This is an example of constant arithmetic between typed and + // untyped constants. Must have like types to perform math. + const one int8 = 1 + const two = 2 * one // int8(2) * int8(1) + fmt.Println(two) +} diff --git a/_content/tour/grc/constants/example2.go b/_content/tour/grc/constants/example2.go new file mode 100644 index 00000000..2fbe36ca --- /dev/null +++ b/_content/tour/grc/constants/example2.go @@ -0,0 +1,24 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how constants do have a parallel type system. +package main + +import "fmt" + +const ( + // Max integer value on 64 bit architecture. + maxInt = 9223372036854775807 + + // Much larger value than int64. + bigger = 9223372036854775808543522345 + + // Will NOT compile + // biggerInt int64 = 9223372036854775808543522345 +) + +func main() { + fmt.Println("Will Compile") +} diff --git a/_content/tour/grc/constants/example3.go b/_content/tour/grc/constants/example3.go new file mode 100644 index 00000000..23f311b4 --- /dev/null +++ b/_content/tour/grc/constants/example3.go @@ -0,0 +1,47 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how iota works. +package main + +import "fmt" + +func main() { + + const ( + A1 = iota // 0 : Start at 0 + B1 = iota // 1 : Increment by 1 + C1 = iota // 2 : Increment by 1 + ) + + fmt.Println("1:", A1, B1, C1) + + const ( + A2 = iota // 0 : Start at 0 + B2 // 1 : Increment by 1 + C2 // 2 : Increment by 1 + ) + + fmt.Println("2:", A2, B2, C2) + + const ( + A3 = iota + 1 // 1 : Start at 0 + 1 + B3 // 2 : Increment by 1 + C3 // 3 : Increment by 1 + ) + + fmt.Println("3:", A3, B3, C3) + + const ( + Ldate = 1 << iota // 1 : Shift 1 to the left 0. 0000 0001 + Ltime // 2 : Shift 1 to the left 1. 0000 0010 + Lmicroseconds // 4 : Shift 1 to the left 2. 0000 0100 + Llongfile // 8 : Shift 1 to the left 3. 0000 1000 + Lshortfile // 16 : Shift 1 to the left 4. 0001 0000 + LUTC // 32 : Shift 1 to the left 5. 0010 0000 + ) + + fmt.Println("Log:", Ldate, Ltime, Lmicroseconds, Llongfile, Lshortfile, LUTC) +} diff --git a/_content/tour/grc/constants/example4.go b/_content/tour/grc/constants/example4.go new file mode 100644 index 00000000..1b735f9c --- /dev/null +++ b/_content/tour/grc/constants/example4.go @@ -0,0 +1,61 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +/* +// A Duration represents the elapsed time between two instants as +// an int64 nanosecond count. The representation limits the largest +// representable duration to approximately 290 years. + +type Duration int64 + +// Common durations. There is no definition for units of Day or larger +// to avoid confusion across daylight savings time zone transitions. + +const ( + Nanosecond Duration = 1 + Microsecond = 1000 * Nanosecond + Millisecond = 1000 * Microsecond + Second = 1000 * Millisecond + Minute = 60 * Second + Hour = 60 * Minute +) + +// Add returns the time t+d. +func (t Time) Add(d Duration) Time +*/ + +// Sample program to show how literal, constant and variables work +// within the scope of implicit conversion. +package main + +import ( + "fmt" + "time" +) + +func main() { + + // Use the time package to get the current date/time. + now := time.Now() + + // Subtract 5 nanoseconds from now using a literal constant. + literal := now.Add(-5) + + // Subtract 5 seconds from now using a declared constant. + const timeout = 5 * time.Second // time.Duration(5) * time.Duration(1000000000) + constant := now.Add(-timeout) + + // Subtract 5 nanoseconds from now using a variable of type int64. + minusFive := int64(-5) + variable := now.Add(minusFive) + + // example4.go:50: cannot use minusFive (type int64) as type time.Duration in argument to now.Add + + // Display the values. + fmt.Printf("Now : %v\n", now) + fmt.Printf("Literal : %v\n", literal) + fmt.Printf("Constant: %v\n", constant) + fmt.Printf("Variable: %v\n", variable) +} diff --git a/_content/tour/grc/constants/exercise1.go b/_content/tour/grc/constants/exercise1.go new file mode 100644 index 00000000..cb1391c0 --- /dev/null +++ b/_content/tour/grc/constants/exercise1.go @@ -0,0 +1,27 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare an untyped and typed constant and display their values. +// +// Multiply two literal constants into a typed variable and display the value. +package main + +// Add imports. + +const ( +// Declare a constant named server of kind string and assign a value. + +// Declare a constant named port of type integer and assign a value. +) + +func main() { + + // Display the value of both server and port. + + // Divide a constant of kind integer and kind floating point and + // assign the result to a variable. + + // Display the value of the variable. +} diff --git a/_content/tour/grc/content_test.go b/_content/tour/grc/content_test.go new file mode 100644 index 00000000..6c7b22d4 --- /dev/null +++ b/_content/tour/grc/content_test.go @@ -0,0 +1,89 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package content + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + // Keep github.com/ardanlabs/gotour/external/tour/wc in our go.mod require list for use during test. + _ "github.com/ardanlabs/gotour/external/tour/wc" +) + +// Test that all the .go files inside the content file build +// and execute (without checking for output correctness). +// Files that contain the string "// +build no-build" are not built. +// Files that contain the string "// +build no-run" are not executed. +func TestContent(t *testing.T) { + if _, err := exec.LookPath("go"); err != nil { + t.Skipf("skipping because 'go' executable not available: %v", err) + } + + scratch, err := ioutil.TempDir("", "tour-content-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(scratch) + + err = filepath.Walk(".", func(path string, fi os.FileInfo, err error) error { + if filepath.Ext(path) != ".go" { + return nil + } + if filepath.Base(path) == "content_test.go" { + return nil + } + t.Run(path, func(t *testing.T) { + t.Parallel() + if err := testSnippet(t, filepath.ToSlash(path), scratch); err != nil { + t.Errorf("%v: %v", path, err) + } + }) + return nil + }) + if err != nil { + t.Error(err) + } +} + +func testSnippet(t *testing.T, path, scratch string) error { + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + build := string(bytes.SplitN(b, []byte{'\n'}, 2)[0]) + if !strings.HasPrefix(build, "// +build ") { + return errors.New("first line is not a +build comment") + } + if !strings.Contains(build, "OMIT") { + return errors.New(`+build comment does not contain "OMIT"`) + } + + if strings.Contains(build, "no-build") { + return nil + } + bin := filepath.Join(scratch, filepath.Base(path)+".exe") + out, err := exec.Command("go", "build", "-o", bin, path).CombinedOutput() + if err != nil { + return fmt.Errorf("build error: %v\noutput:\n%s", err, out) + } + defer os.Remove(bin) + + if strings.Contains(build, "no-run") { + return nil + } + out, err = exec.Command(bin).CombinedOutput() + if err != nil { + return fmt.Errorf("%v\nOutput:\n%s", err, out) + } + return nil +} diff --git a/_content/tour/grc/context.article b/_content/tour/grc/context.article new file mode 100644 index 00000000..4def50fc --- /dev/null +++ b/_content/tour/grc/context.article @@ -0,0 +1,524 @@ +Context Package +The package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. + +* Context Package + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +The package context defines the Context type, which carries deadlines, cancellation +signals, and other request-scoped values across API boundaries and between processes. + +** Code Review + +"Context values are for request-scoped data that passes through programs in a +distributed system. + +- *Example* *1:* Store / Retrieve Values +- *Example* *2:* WithCancel +- *Example* *3:* WithDeadline +- *Example* *4:* WithTimeout +- *Example* *5:* Request/Response +- *Example* *6:* Cancellation + +.play context/example1.go +.play context/example2.go +.play context/example3.go +.play context/example4.go +.play context/example5.go +.play context/example6.go + +** Context Semantics + +The Go programming language has the built-in keyword go to create goroutines, but +has no keywords or direct support for terminating goroutines. In a real world service, +the ability to time-out and terminate goroutines is critical for maintaining the health +and operation of a service. No request or task can be allowed to run forever so +identifying and managing latency is a responsibility every programmer has. + +A solution provided by the Go team to solve this problem is the Context package. +It was written and introduced by Sameer Ajmani back in 2014 at the Gotham Go +conference. He also wrote a blog post for the Go blog. + +Slide Deck: [[https://talks.golang.org/2014/gotham-context.slide#1][https://talks.golang.org/2014/gotham-context.slide#1]] + +Blog Post: [[https://blog.golang.org/context][https://blog.golang.org/context]] + +Through this published work and conversations with Sameer over the years, +a set of semantics have evolved. + +*Incoming* *requests* *to* *a* *server* *should* *create* *a* *Context* + +The time to create a Context is always as early as possible in the processing of a +request or task. Working with Context early in the development cycle will force you +to design API’s to take a Context as the first parameter. Even if you are not 100% +sure a function needs a Context, it’s easier to remove the Context from a few +functions than try to add Context later. + + 75 // Handle is our mechanism for mounting Handlers for a given HTTP verb and path + 76 // pair, this makes for really easy, convenient routing. + 77 func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) { + ... + 85 // The function to execute for each request. + 86 h := func(w http.ResponseWriter, r *http.Request, params map[string]string) { + 87 ctx, span := trace.StartSpan(r.Context(), "internal.platform.web") + 88 defer span.End() + ... + 106 // Add this handler for the specified verb and route. + 107 a.TreeMux.Handle(verb, path, h) + 108 } + +You see code taken from the service project we teach at Ardan Labs. Line 86 defines +a handler function that is bound to all routes as shown on line 107. It’s this +function that starts to process any incoming requests. On line 87, a span is created +for the request which takes as its first parameter a Context. This is the first +time in the service code a Context is needed. + +What’s great here is that the http.Request value already contains a Context. This +was added in version 1.7 of Go. This means the code doesn’t need to manually create +a top-level Context. If we were using version 1.8 of Go, then you would need to +create an empty Context before the call to StartSpan by using the context.Background +function. + + https://golang.org/pkg/context/#Background + + 87 ctx := context.Background() + 88 ctx, span := trace.StartSpan(ctx, "internal.platform.web") + 89 defer span.End() + +This is what the code would have to look like in version 1.8 of Go. As it’s described +in the package documentation, + +Background returns a non-nil, empty Context. It’s never canceled, has no values, +and has no deadline. It is typically used by the main function, initialization, +and tests, and as the top-level Context for incoming requests. + +It’s an idiom in Go to use the variable name ctx for all Context values. Since a +Context is an interface, no pointer semantics should be used. + + https://golang.org/pkg/context/#Context + + type Context interface { + Deadline() (deadline time.Time, ok bool) + Done() <-chan struct{} + Err() error + Value(key interface{}) interface{} + } + +Every function that accepts a Context should get its own copy of the interface value. + +*Outgoing* *calls* *to* *servers* *should* *accept* *a* *Context* + +The idea behind this semantic is that higher level calls need to tell lower level +calls how long they are willing to wait. A great example of this is with the http +package and the version 1.7 changes made to the Do method to respect timeouts on +a request. + + 01 package main + 02 + 03 import ( + 04 "context" + 05 "io" + 06 "log" + 07 "net/http" + 08 "os" + 09 "time" + 10 ) + 11 + 12 func main() { + 13 + 14 // Create a new request. + 15 req, err := http.NewRequest("GET", "https://www.ardanlabs.com/blog/post/index.xml", nil) + 16 if err != nil { + 17 log.Println("ERROR:", err) + 18 return + 19 } + 20 + 21 // Create a context with a timeout of 50 milliseconds. + 22 ctx, cancel := context.WithTimeout(req.Context(), 50*time.Millisecond) + 23 defer cancel() + 24 + 25 // Bind the new context into the request. + 26 req = req.WithContext(ctx) + 27 + 28 // Make the web call and return any error. Do will handle the + 29 // context level timeout. + 30 resp, err := http.DefaultClient.Do(req) + 31 if err != nil { + 32 log.Println("ERROR:", err) + 33 return + 34 } + 35 + 36 // Close the response body on the return. + 37 defer resp.Body.Close() + 38 + 39 // Write the response to stdout. + 40 io.Copy(os.Stdout, resp.Body) + 41 } + +This program issues a request for the Ardan rss blog feed with a timeout of 50 +milliseconds. On lines 15-19, the request is created to make a GET call against +the provided URL. Lines 22-23 create a Context with a 50 millisecond timeout. A +new API added to the Request value back in version 1.7 is the WithContext method. +This method allows the Request value’s Context field to be updated. On line 26, +that is exactly what the code is doing. + +On line 30, the actual request is made using the Do method from the http package’s +DefaultClient value. The Do method will respect the timeout value of 50 milliseconds +that is now set inside the Context within the Request value. What you are seeing is the +code (higher level function) telling the Do method (lower level function) how long +we're willing to wait for the Do operation to be completed. + +*Do* *not* *store* *Contexts* *inside* *a* *struct* *type* + +Instead, pass a Context explicitly to each function that needs it. Essentially, any +function that is performing I/O should accept a Context value as it’s first parameter +and respect any timeout or deadline configured by the caller. In the case of Request, +there was backwards compatibility issues to consider. So instead of changing the API’s, +the mechanic shown in the last section was implemented. + +There are exceptions to every rule. However, within the scope of this post and any +API’s from the standard library that take a Context, the idiom is to have the first +parameter accept the Context value. + +.image /tour/grc/static/img/context_figure1.png + +The figure shows an example from the net package where the first parameter of each +method takes a Context as the first parameter and uses the ctx variable name idiom. + +*The* *chain* *of* *function* *calls* *between* *them* *must* *propagate* *the* *Context* + +This is an important rule since a Context is request or task based. You want the +Context and any changes made to it during the processing of the request or task +to be propagated and respected. + + 23 // List returns all the existing users in the system. + 24 func (u *User) List(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { + 25 ctx, span := trace.StartSpan(ctx, "handlers.User.List") + 26 defer span.End() + 27 + 28 users, err := user.List(ctx, u.db) + 29 if err != nil { + 30 return err + 31 } + 32 + 33 return web.Respond(ctx, w, users, http.StatusOK) + 34 } + +In this listing you see a handler function called List which is executed when a +user makes an HTTP request for this endpoint. The handler accepts as its first +parameter a Context, since it’s part of a request and will perform I/O. You can +see on lines 25, 28 and 33 that the same Context value is propagated down the +call stack. + +A new Context value is not created since this function requires no changes to it. +If a new top-level Context value would be created by this function, any existing +Context information from a higher-level call associated with this request would +be lost. This is not what you want. + + 33 // List retrieves a list of existing users from the database. + 34 func List(ctx context.Context, db *sqlx.DB) ([]User, error) { + 35 ctx, span := trace.StartSpan(ctx, "internal.user.List") + 36 defer span.End() + 37 + 38 users := []User{} + 39 const q = `SELECT * FROM users` + 40 + 41 if err := db.SelectContext(ctx, &users, q); err != nil { + 42 return nil, errors.Wrap(err, "selecting users") + 43 } + 44 + 45 return users, nil + 46 } + +You see the declaration of the List method that was called on line 28. Once again +this method accepts a Context as its first parameter. This value is then propagated +down the call stack once again on lines 35 and 41. Since line 41 is a database call, +that function should be respecting any timeout information set in the Context from +any caller above. + +*Replace* *a* *Context* *using* *WithCancel,* *WithDeadline,* *WithTimeout,* *or* *WithValue* + +Because each function can add/modify the Context for their specific needs, and +those changes should not affect any function that was called before it, the Context +uses value semantics. This means any change to a Context value creates a new Context +value that is then propagated forward. + + 01 func main() { + 02 + 03 // Set a duration. + 04 duration := 150 * time.Millisecond + 05 + 06 // Create a context that is both manually cancellable and will signal + 07 // cancel at the specified duration. + 08 ctx, cancel := context.WithTimeout(context.Background(), duration) + 09 defer cancel() + 10 + 11 // Create a channel to receive a signal that work is done. + 12 ch := make(chan data, 1) + 13 + 14 // Ask the goroutine to do some work for us. + 15 go func() { + 16 + 17 // Simulate work. + 18 time.Sleep(50 * time.Millisecond) + 19 + 20 // Report the work is done. + 21 ch <- data{"123"} + 22 }() + 23 + 24 // Wait for the work to finish. If it takes too long, move on. + 25 select { + 26 case d := <-ch: + 27 fmt.Println("work complete", d) + 28 + 29 case <-ctx.Done(): + 30 fmt.Println("work cancelled") + 31 } + 32 } + +This is a small program that shows the value semantic nature of the WithTimeout +function. On line 08, the call to WithTimeout returns a new Context value and a +cancel function. Since the function call requires a parent Context, the code uses +the Background function to create a top-level empty Context. This is what the +Background function is for. + +Moving forward the Context value created by the WithTimeout function is used. If +any future functions in the call chain need their own specific timeout or deadline, +they should also use the appropriate With function and this new Context value as +the parent. + +It’s critically important that any cancel function returned from a With function +is executed before that function returns. This is why the idiom is to use the defer +keyword right after the With call, as you see on line 26. Not doing this will cause +memory leaks in your program. + +*When* *a* *Context* *is* *canceled,* *all* *Contexts* *derived* *from* *it* *are* *also* *canceled* + +The use of value semantics for the Context API means each new Context value is +given everything the parent Context has plus any new changes. This means if a +parent Context is cancelled, all children derived by that parent Context are +cancelled as well. + + 01 func main() { + 02 + 03 // Create a Context that can be cancelled. + 04 ctx, cancel := context.WithCancel(context.Background()) + 05 defer cancel() + 06 + 07 // Use the Waitgroup for orchestration. + 08 var wg sync.WaitGroup + 09 wg.Add(10) + 10 + 11 // Create ten goroutines that will derive a Context from + 12 // the one created above. + 13 for i := 0; i < 10; i++ { + 14 go func(id int) { + 15 defer wg.Done() + 16 + 17 // Derive a new Context for this goroutine from the Context + 18 // owned by the main function. + 19 ctx := context.WithValue(ctx, key, id) + 20 + 21 // Wait until the Context is cancelled. + 22 <-ctx.Done() + 23 fmt.Println("Cancelled:", id) + 24 }(i) + 25 } + 26 + 27 // Cancel the Context and any derived Context's as well. + 28 cancel() + 29 wg.Wait() + 30 } + +This program creates a Context value that can be cancelled on line 04. Then on +lines 13-25, ten goroutines are created. Each goroutine places their unique id +inside their own Context value on line 19. The call to WithValue is passed the +main function’s Context value as its parent. Then on line 22, each goroutine waits +until their Context is cancelled. + +On line 28, the main goroutine cancels its Context value and then waits on line 29 +for all ten of the goroutines to receive the signal before shutting down the program. +Once the cancel function is called, all ten goroutines on line 41 will become +unblocked and print that they have been cancelled. One call to cancel to cancel +them all. + +This also shows that the same Context may be passed to functions running in different +goroutines. A Context is safe for simultaneous use by multiple goroutines. + +Do not pass a nil Context, even if a function permits it. Pass a TODO context if +you are unsure about which Context to use. One of my favorite parts of the Context +package is the TODO function. I am a firm believer that a programmer is always +drafting code. This is no different than a writer who is drafting versions of an +article. You never know everything as you write code, but hopefully you know enough +to move things along. In the end, you are constantly learning, refactoring and +testing along the way. + +There have been many times when I knew I needed a Context but was unsure where it +would come from. I knew I was not responsible for creating the top-level Context +so using the Background function was out of the question. I needed a temporary +top-level Context until I figured out where the actual Context was coming from. +This is when you should use the TODO function over the Background function. + +*Use* *Context* *values* *only* *for* *request-scoped* *data* + +Don't use the Context for passing optional parameters to functions. This might be +the most important semantic of all. Do not use the Context value to pass data into +a function when that data is required by the function to execute its code successfully. +In other words, a function should be able to execute its logic with an empty Context +value. In cases where a function requires information to be in the Context, if that +information is missing, the program should fail and signal the application to shutdown. + +A classic example of the misuse of passing data into a function call using Context +is with database connections. As a general rule, you want to follow this order when +moving data around your program. + +Pass the data as a function parameter This is the clearest way to move data around +the program without hiding it. + +Pass the data through the receiver If the function that needs the data can’t have +its signature altered, then use a method and pass the data through the receiver. + +Quick example of using a receiver + +Request handlers are a classic example of the second rule. Since a handler function +is bound to a specific declaration, the handler signature can’t be altered. + + 23 // List returns all the existing users in the system. + 24 func (u *User) List(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { + 25 ctx, span := trace.StartSpan(ctx, "handlers.User.List") + 26 defer span.End() + 27 + 28 users, err := user.List(ctx, u.db) + 29 if err != nil { + 30 return err + 31 } + 32 + 33 return web.Respond(ctx, w, users, http.StatusOK) + 34 } + +Here you see the List handler method from the service project. The signature of +these methods are bound to the what the web framework defined and they can’t be +altered. However, to make the business call on line 28, a database connection is +required. This code finds the connection pool not from the Context value that is +passed in, but from the receiver. + + 15 // User represents the User API method handler set. + 16 type User struct { + 17 db *sqlx.DB + 18 authenticator *auth.Authenticator + 19 + 20 // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. + 21 } + +You see the declaration of the receiver type. Anything that a request handler needs +is defined as fields. This allows for information to not be hidden and for the business +layer to function with an empty Context value. + + 14 // API constructs an http.Handler with all application routes defined. + 15 func API(shutdown chan os.Signal, log *log.Logger, db *sqlx.DB, authenticator *auth.Authenticator) http.Handler { + 16 + ... + 26 // Register user management and authentication endpoints. + 27 u := User{ + 28 db: db, + 29 authenticator: authenticator, + 30 } + 31 + 32 app.Handle("GET", "/v1/users", u.List) + +This code constructs a User value and then binds the List method into the route. +Once again, since the signature of a handler function is unchangeable, using a +receiver and methods is the next best way to pass data without it being hidden. + +*Debugging* *or* *tracing* *data* *is* *safe* *to* *pass* *in* *a* *Context* + +Data that can be stored and received from a Context value is debug and tracing information. + + 23 // Values represent state for each request. + 24 type Values struct { + 25 TraceID string + 26 Now time.Time + 27 StatusCode int + 28 } + +Here is a declaration of a type that is constructed and stored inside each Context +value created for a new request. The three fields provide tracing and debugging +information for the request. This information is gathered as the request progresses. + + 75 // Handle is our mechanism for mounting Handlers for a given HTTP verb and path + 76 // pair, this makes for really easy, convenient routing. + 77 func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) { + 78 + ... + 79 // The function to execute for each request. + 80 h := func(w http.ResponseWriter, r *http.Request, params map[string]string) { + … + 84 // Set the context with the required values to + 85 // process the request. + 86 v := Values{ + 87 TraceID: span.SpanContext().TraceID.String(), + 88 Now: time.Now(), + 89 } + 90 ctx = context.WithValue(ctx, KeyValues, &v) + +See how the Values type is constructed on line 86 and then stored inside the Context +on line 90. It’s the logging middleware that needs most of this information. + + 20 // Create the handler that will be attached in the middleware chain. + 21 h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { + ... + 25 // If the context is missing this value, request the service + 26 // to be shutdown gracefully. + 27 v, ok := ctx.Value(web.KeyValues).(*web.Values) + 28 if !ok { + 29 return web.NewShutdownError("web value missing from context") + 30 } + ... + 34 log.Printf("%s : (%d) : %s %s -> %s (%s)", + 35 v.TraceID, v.StatusCode, + 36 r.Method, r.URL.Path, + 37 r.RemoteAddr, time.Since(v.Now), + 38 ) + +The consequence of passing information through the Context is shown in the code on +lines 27-30. The code is attempting to retrieve the Values data from the Context +and checking if the data was there. If the data is not there, then a major integrity +issue exists and the service needs to shutdown. This is done in the service code by +sending a special error value back up through the application. + +If you are passing database connections or user information into your business +layer using a Context, you have two problems: + +- You need to be checking for integrity and you need a mechanism to shutdown the service quickly. +- Testing and debugging becomes much harder and more complicated. You are walking away from better clarity and readability in your code. + +** Notes + +- Incoming requests to a server should create a Context. +- Outgoing calls to servers should accept a Context. +- The chain of function calls between them must propagate the Context. +- Replace a Context using WithCancel, WithDeadline, WithTimeout, or WithValue. +- When a Context is canceled, all Contexts derived from it are also canceled. +- Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. +- Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use. +- Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. +- The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines. + +** Extra Reading + +- [[https://www.ardanlabs.com/blog/2019/09/context-package-semantics-in-go.html][Context Package Semantics In Go]] - William Kennedy +- [[https://golang.org/pkg/context][Package context]] - Go Team +- [[https://blog.golang.org/context][Go Concurrency Patterns: Context]] - Sameer Ajmani +- [[https://rakyll.org/leakingctx/][Using contexts to avoid leaking goroutines]] - JBD + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Use the template and follow the directions. You will be writing a web handler that performs a mock database call but will timeout based on a context if the call takes too long. You will also save state into the context. + +.play context/exercise1.go +.play context/answer1.go diff --git a/_content/tour/grc/context/answer1.go b/_content/tour/grc/context/answer1.go new file mode 100644 index 00000000..8291bcf3 --- /dev/null +++ b/_content/tour/grc/context/answer1.go @@ -0,0 +1,116 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program that implements a simple web service using the +// context to handle timeouts and pass context into the request. +package main + +import ( + "context" + "encoding/json" + "log" + "net/http" + "time" +) + +// The key type is unexported to prevent collisions with context keys defined in +// other packages. +type key int + +// userIPkey is the context key for the user IP address. Its value of zero is +// arbitrary. If this package defined other context keys, they would have +// different integer values. +const userIPKey key = 0 + +// User defines a user in the system. +type User struct { + Name string + Email string +} + +func main() { + routes() + + log.Println("listener : Started : Listening on: http://localhost:4000") + http.ListenAndServe(":4000", nil) +} + +// routes sets the routes for the web service. +func routes() { + http.HandleFunc("/user", findUser) +} + +// findUser makes a database call to find a user. +func findUser(rw http.ResponseWriter, r *http.Request) { + + // Create a context that timeouts in fifty milliseconds. + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + // Save the user ip address in the context. This call returns + // a new context we now need to use. The original context is + // the parent context for this new child context. + ctx = context.WithValue(ctx, userIPKey, r.RemoteAddr) + + // Create a goroutine to make the database call. Use the channel + // to get the user back. + ch := make(chan *User, 1) + go func() { + + // Get the ip address from the context for logging. + if ip, ok := ctx.Value(userIPKey).(string); ok { + log.Println("Start DB for IP", ip) + } + + // Make the database call and return the value + // back on the channel. + ch <- readDatabase() + log.Println("DB goroutine terminated") + }() + + // Wait for the database call to finish or the timeout. + select { + case u := <-ch: + + // Respond with the user. + sendResponse(rw, u, http.StatusOK) + log.Println("Sent StatusOK") + return + + case <-ctx.Done(): + + // If you have the ability to cancel the database + // operation the goroutine is performing do that now. + // In this example we can't. + + // Respond with the error. + e := struct{ Error string }{ctx.Err().Error()} + sendResponse(rw, e, http.StatusRequestTimeout) + log.Println("Sent StatusRequestTimeout") + return + } +} + +// readDatabase performs a pretend database call with +// a second of latency. +func readDatabase() *User { + u := User{ + Name: "Bill", + Email: "bill@ardanlabs.com", + } + + // Create 100 milliseconds of latency. + time.Sleep(100 * time.Millisecond) + + return &u +} + +// sendResponse marshals the provided value into json and returns +// that back to the caller. +func sendResponse(rw http.ResponseWriter, v interface{}, statusCode int) { + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(statusCode) + json.NewEncoder(rw).Encode(v) +} diff --git a/_content/tour/grc/context/example1.go b/_content/tour/grc/context/example1.go new file mode 100644 index 00000000..9a19c931 --- /dev/null +++ b/_content/tour/grc/context/example1.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to store and retrieve +// values from a context. +package main + +import ( + "context" + "fmt" +) + +// TraceID is represents the trace id. +type TraceID string + +// TraceIDKey is the type of value to use for the key. The key is +// type specific and only values of the same type will match. +type TraceIDKey int + +func main() { + + // Create a traceID for this request. + traceID := TraceID("f47ac10b-58cc-0372-8567-0e02b2c3d479") + + // Declare a key with the value of zero of type userKey. + const traceIDKey TraceIDKey = 0 + + // Store the traceID value inside the context with a value of + // zero for the key type. + ctx := context.WithValue(context.Background(), traceIDKey, traceID) + + // Retrieve that traceID value from the Context value bag. + if uuid, ok := ctx.Value(traceIDKey).(TraceID); ok { + fmt.Println("TraceID:", uuid) + } + + // Retrieve that traceID value from the Context value bag not + // using the proper key type. + if _, ok := ctx.Value(0).(TraceID); !ok { + fmt.Println("TraceID Not Found") + } +} diff --git a/_content/tour/grc/context/example2.go b/_content/tour/grc/context/example2.go new file mode 100644 index 00000000..f90eee31 --- /dev/null +++ b/_content/tour/grc/context/example2.go @@ -0,0 +1,43 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use the WithCancel function. +package main + +import ( + "context" + "fmt" + "time" +) + +func main() { + + // Create a context that is cancellable only manually. + // The cancel function must be called regardless of the outcome. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Ask the goroutine to do some work for us. + go func() { + + // Wait for the work to finish. If it takes too long move on. + select { + case <-time.After(100 * time.Millisecond): + fmt.Println("moving on") + + case <-ctx.Done(): + fmt.Println("work complete") + } + }() + + // Simulate work. + time.Sleep(50 * time.Millisecond) + + // Report the work is done. + cancel() + + // Just hold the program to see the output. + time.Sleep(time.Second) +} diff --git a/_content/tour/grc/context/example3.go b/_content/tour/grc/context/example3.go new file mode 100644 index 00000000..b8a1f32c --- /dev/null +++ b/_content/tour/grc/context/example3.go @@ -0,0 +1,50 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use the WithDeadline function. +package main + +import ( + "context" + "fmt" + "time" +) + +type data struct { + UserID string +} + +func main() { + + // Set a deadline. + deadline := time.Now().Add(150 * time.Millisecond) + + // Create a context that is both manually cancellable and will signal + // a cancel at the specified date/time. + ctx, cancel := context.WithDeadline(context.Background(), deadline) + defer cancel() + + // Create a channel to received a signal that work is done. + ch := make(chan data, 1) + + // Ask the goroutine to do some work for us. + go func() { + + // Simulate work. + time.Sleep(200 * time.Millisecond) + + // Report the work is done. + ch <- data{"123"} + }() + + // Wait for the work to finish. If it takes too long move on. + select { + case d := <-ch: + fmt.Println("work complete", d) + + case <-ctx.Done(): + fmt.Println("work cancelled") + } +} diff --git a/_content/tour/grc/context/example4.go b/_content/tour/grc/context/example4.go new file mode 100644 index 00000000..7d646798 --- /dev/null +++ b/_content/tour/grc/context/example4.go @@ -0,0 +1,51 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use the WithTimeout function +// of the Context package. +package main + +import ( + "context" + "fmt" + "time" +) + +type data struct { + UserID string +} + +func main() { + + // Set a duration. + duration := 150 * time.Millisecond + + // Create a context that is both manually cancellable and will signal + // a cancel at the specified duration. + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + // Create a channel to received a signal that work is done. + ch := make(chan data, 1) + + // Ask the goroutine to do some work for us. + go func() { + + // Simulate work. + time.Sleep(50 * time.Millisecond) + + // Report the work is done. + ch <- data{"123"} + }() + + // Wait for the work to finish. If it takes too long move on. + select { + case d := <-ch: + fmt.Println("work complete", d) + + case <-ctx.Done(): + fmt.Println("work cancelled") + } +} diff --git a/_content/tour/grc/context/example5.go b/_content/tour/grc/context/example5.go new file mode 100644 index 00000000..cf819766 --- /dev/null +++ b/_content/tour/grc/context/example5.go @@ -0,0 +1,48 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program that implements a web request with a context that is +// used to timeout the request if it takes too long. +package main + +import ( + "context" + "io" + "log" + "net/http" + "os" + "time" +) + +func main() { + + // Create a new request. + req, err := http.NewRequest("GET", "https://www.ardanlabs.com/blog/post/index.xml", nil) + if err != nil { + log.Println("ERROR:", err) + return + } + + // Create a context with a timeout of 50 milliseconds. + ctx, cancel := context.WithTimeout(req.Context(), 50*time.Millisecond) + defer cancel() + + // Bind the new context into the request. + req = req.WithContext(ctx) + + // Make the web call and return any error. Do will handle the + // context level timeout. + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Println("ERROR:", err) + return + } + + // Close the response body on the return. + defer resp.Body.Close() + + // Write the response to stdout. + io.Copy(os.Stdout, resp.Body) +} diff --git a/_content/tour/grc/context/example6.go b/_content/tour/grc/context/example6.go new file mode 100644 index 00000000..70ec2f67 --- /dev/null +++ b/_content/tour/grc/context/example6.go @@ -0,0 +1,51 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show when a Context is canceled, all Contexts +// derived from it are also canceled. +package main + +import ( + "context" + "fmt" + "sync" +) + +// Need a key type. +type myKey int + +// Need a key value. +const key myKey = 0 + +func main() { + + // Create a Context that can be cancelled. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Use the Waitgroup for orchestration. + var wg sync.WaitGroup + wg.Add(10) + + // Create ten goroutines that will derive a Context from + // the one created above. + for i := 0; i < 10; i++ { + go func(id int) { + defer wg.Done() + + // Derive a new Context for this goroutine from the Context + // owned by the main function. + ctx := context.WithValue(ctx, key, id) + + // Wait until the Context is cancelled. + <-ctx.Done() + fmt.Println("Cancelled:", id) + }(i) + } + + // Cancel the Context and any derived Context's as well. + cancel() + wg.Wait() +} diff --git a/_content/tour/grc/context/exercise1.go b/_content/tour/grc/context/exercise1.go new file mode 100644 index 00000000..d7225c6c --- /dev/null +++ b/_content/tour/grc/context/exercise1.go @@ -0,0 +1,110 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Use the template and follow the directions. You will be writing a web handler +// that performs a mock database call but will timeout based on a context if the call +// takes too long. You will also save state into the context. +package main + +// Add imports. + +// Declare a new type named `key` that is based on an int. + +// Declare a constant named `userIPKey` of type `key` set to +// the value of 0. + +// Declare a struct type named `User` with two `string` based +// fields named `Name` and `Email`. + +func main() { + routes() + + log.Println("listener : Started : Listening on: http://localhost:4000") + http.ListenAndServe(":4000", nil) +} + +// routes sets the routes for the web service. +func routes() { + http.HandleFunc("/user", findUser) +} + +// Implement the findUser function to leverage the context for +// both timeouts and state. +func findUser(rw http.ResponseWriter, r *http.Request) { + + // Create a context that timeouts in fifty milliseconds. + + // Defer the call to cancel. + + // Save the `r.RemoteAddr` value in the context using `userIPKey` + // as the key. This call returns a new context so replace the + // current `ctx` value with this new one. The original context is + // the parent context for this new child context. + + // Create a channel with a buffer size of 1 that works with + // pointers of type `User` + + // Use this goroutine to make the database call. Use the channel + // to get the user back. + go func() { + + // Get the `r.RemoteAddr` value from the context and log + // the value you get back. + + // Call the `readDatabase` function provided below and + // send the returned `User` pointer on the channel. + + // Log that the goroutine is terminating. + }() + + // Wait for the database call to finish or the timeout. + select { + + // Add a case to wait on the channel for the `User` pointer. + + // Call the `sendResponse` function provided below to + // send the `User` to the caller. Use `http.StatusOK` + // as the status code. + + // Log we sent the response with a StatusOk + + return + + // Add a case to wait on the `ctx.Done()` channel. + + // Use this struct value for the error response. + e := struct{ Error string }{ctx.Err().Error()} + + // Call the `sendResponse` function provided below to + // send the error to the caller. Use `http.StatusRequestTimeout` + // as the status code. + + // Log we sent the response with a StatusRequestTimeout + + return + } +} + +// readDatabase performs a pretend database call with +// a second of latency. +func readDatabase() *User { + u := User{ + Name: "Bill", + Email: "bill@ardanlabs.com", + } + + // Create 100 milliseconds of latency. + time.Sleep(100 * time.Millisecond) + + return &u +} + +// sendResponse marshals the provided value into json and returns +// that back to the caller. +func sendResponse(rw http.ResponseWriter, v interface{}, statusCode int) { + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(statusCode) + json.NewEncoder(rw).Encode(v) +} diff --git a/_content/tour/grc/data_race.article b/_content/tour/grc/data_race.article new file mode 100644 index 00000000..8391d934 --- /dev/null +++ b/_content/tour/grc/data_race.article @@ -0,0 +1,565 @@ +Data Races +A data race is when two or more goroutines attempt to read and write to the same resource at the same time. + +* Data Races + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +A data race is when two or more Goroutines are trying to access the same memory +location at the same time where at least one Goroutine is performing a write. When +this happens it is impossible to predict the result. These types of bugs are difficult +to find because they cause issues that always appear random. + +These ~8 minutes from Scott Meyers is great to listen to here: + +[[https://youtu.be/WDIkqP4JbkE?t=1809][CPU Caches and Why You Care 30:09-38:30]] + +** Code Review + +- *Example* *1:* Data Race +- *Example* *2:* Atomic Increments +- *Example* *3:* Mutex +- *Example* *4:* Read/Write Mutex +- *Example* *5:* Map Data Race +- *Example* *1:* Interface Based Race Condition + +.play data_race/example1.go +.play data_race/example2.go +.play data_race/example3.go +.play data_race/example4.go +.play data_race/example5.go +.play data_race/example6.go + +** Data Race Example + +This is a great example of a data race and how they can be hidden for years and +eventually show up at odd times and cause data corruption. + +var counter int + + func main() { + const grs = 2 + + var wg sync.WaitGroup + wg.Add(grs) + + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + value := counter + value++ + counter = value + } + wg.Done() + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) + } + +This program creates two Goroutines that each access the same integer variable, +incrementing the variable twice. The Goroutine performs a read, modify, and write +operation against the shared state manually. + + var counter int + + func main() { + . . . + + go func() { + for i := 0; i < 2; i++ { + value := counter + value++ + counter = value + } + wg.Done() + }() + + . . . + } + +You can see the access to the shared state inside the for loop. When you build and run +this program you get the right answer of 4 each and every time. + + $ ./example1 + Final Counter: 4 + + $ ./example1 + Final Counter: 4 + + $ ./example1 + Final Counter: 4 + +How is this working? + + G1 Shared State: 0 G2 + ---------------------------------------------------------------------------- + Read: 0 + Modify: 1 + Write: 1 Shared State: 1 + Context Switch + Read: 1 + Modify: 2 + Shared State: 2 Write: 2 + Context Switch + Read: 2 + Modify: 3 + Write: 3 Shared State: 3 + Terminate + Read: 3 + Modify: 4 + Shared State: 4 Write: 4 + Terminate + ---------------------------------------------------------------------------- + +The read, modify and write operations are happening uninterrupted. Just because I +am getting the right answer doesn’t mean there isn’t a problem. What happens if +you add a log statement in the middle of the read, modify, and write operation? + + var counter int + + func main() { + . . . + + go func() { + for i := 0; i < 2; i++ { + value := counter + value++ + log.Println("logging") <-- Add Logging Here + counter = value + } + wg.Done() + }() + + . . . + } + +If you run this program you no longer get the same result of 4, now you get the answer of 2. + + $ ./example1 + Final Counter: 2 + + $ ./example1 + Final Counter: 2 + + $ ./example1 + Final Counter: 2 + + +What is happening? You are running into a data race bug that did exist before, but +wasn’t happening. The call to log is now causing the scheduler to make a context +switch between the two Goroutines at a bad time. + + G1 Shared State: 0 G2 + ---------------------------------------------------------------------------- + Read: 0 + Modify: 1 + Context Switch + Read: 0 + Modify: 1 + Context Switch + Write: 1 Shared State: 1 + Read: 1 + Modify: 2 + Context Switch + Shared State: 1 Write: 1 + Read: 1 + Modify: 2 + Context Switch + Write: 2 Shared State: 2 + Terminate + Shared State: 2 Write: 2 + Terminate + ---------------------------------------------------------------------------- + +After the modify operation a context switch is taking place. The three operations +are no longer uninterrupted and Goroutine 2 ends up with its local value being wrong +by the time it completes the write operation. You are very lucky this is happening every +time and you can see it. But normally a data race like this happens "randomly" and is +impossible to know about until it’s too late. Luckily Go has a race detector to help +find data races. + +** Race Detection + +There are several ways to engage the race detector. You can use it with the run, build +and test command. If you use it with the build command, you have to remember to run the +program. They say an instrumented binary can slow my program down by ~20%. + + $ go build -race + $ ./example1 + +The -race flag is how to instrument the build with the race detector. You will +probably use it more with "go test", but for this example you are instrumenting the +binary and then running it. + + 2021/02/01 17:30:52 logging + 2021/02/01 17:30:52 logging + 2021/02/01 17:30:52 logging + ================== + WARNING: DATA RACE + Write at 0x000001278d88 by goroutine 8: + main.main.func1() + /data_race/example1/example1.go:41 +0xa6 + + Previous read at 0x000001278d88 by goroutine 7: + main.main.func1() + /data_race/example1/example1.go:38 +0x4a + + Goroutine 8 (running) created at: + main.main() + /data_race/example1/example1.go:36 +0xaf + + Goroutine 7 (finished) created at: + main.main() + /data_race/example1/example1.go:36 +0xaf + ================== + 2021/02/01 17:30:52 logging + Final Counter: 2 + Found 1 data race(s) + +You can see a race was detected when running the program. This would happen with or +without the log statement inserted. When a race is detected, the program panics and +provides this trace. The trace shows where there was unsynchronized access to the +same shared state where at least one access was a write. + +In this trace, a Goroutine performed a write at address 0x000001278d88 on line 41, +and there was an unsynchronized read at the same address by another Goroutine on +line 38. Both Goroutines were created on line 36. + + 36 go func() { + 37 for i := 0; i < 2; i++ { + 38 value := counter + 39 value++ + 40 log.Println("logging") + 41 counter = value + 42 } + 43 wg.Done() + 44 }() + +You can clearly see the unsynchronized read and write. As a side note, the plus plus +operation on line 39 would also be a data race if the code was accessing the counter +variable. The plus plus operation is a read, modify, and write operation underneath +and the operating system could easily context switch in the middle of that. + +So how can you fix the code to make sure that you remove the data race? There are two +tools you can use, atomic instructions and mutexes. + +** Atomics + +Atomics provide synchronization at the hardware level. Because of this, it’s limited +to words and half-words of data. So they’re great for counters or fast switching +mechanics. The WaitGroup API’s use atomics. + +What changes do you need to make to apply atomics to the code? + + var counter int32 <-- CHANGED + + func main() { + const grs = 2 + + var wg sync.WaitGroup + wg.Add(grs) + + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + atomic.AddInt32(&counter, 1) <-- CHANGED + } + wg.Done() + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) + } + +You only need to do a couple things. First, change the counter variable to be a +precision based integer. You can see that at the top of the code listing. The atomic +functions only work with precision based integers. Second, remove the manually +read, modify, and write code for one call to atomic.AddInt32. That one call handles +it all. + +All of the functions associated with the atomic package take the address to the +shared state to be synchronized. Synchronization only happens at the address level. +So different Goroutines calling the same function, but at a different address, won’t +be synchronized. + +The API for atomics looks like this: + + func AddInt32(addr *int32, delta int32) (new int32) + func AddInt64(addr *int64, delta int64) (new int64) + func AddUint32(addr *uint32, delta uint32) (new uint32) + func AddUint64(addr *uint64, delta uint64) (new uint64) + func AddUintptr(addr *uintptr, delta uintptr) (new uintptr) + + func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) + func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) + func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool) + func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) + func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) + func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) + + func LoadInt32(addr *int32) (val int32) + func LoadInt64(addr *int64) (val int64) + func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) + func LoadUint32(addr *uint32) (val uint32) + func LoadUint64(addr *uint64) (val uint64) + func LoadUintptr(addr *uintptr) (val uintptr) + + func StoreInt32(addr *int32, val int32) + func StoreInt64(addr *int64, val int64) + func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer) + func StoreUint32(addr *uint32, val uint32) + func StoreUint64(addr *uint64, val uint64) + func StoreUintptr(addr *uintptr, val uintptr) + + func SwapInt32(addr *int32, new int32) (old int32) + func SwapInt64(addr *int64, new int64) (old int64) + func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer) + func SwapUint32(addr *uint32, new uint32) (old uint32) + func SwapUint64(addr *uint64, new uint64) (old uint64) + func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) + + type Value + func (v *Value) Load() (x interface{}) + func (v *Value) Store(x interface{}) + +You can see that the first parameter is always the address to a precision based +integer or pointer. There is also a type named Value that provides a synchronous +value with a small API. + +** Mutexes + +What if you wanted to keep the three lines of code you had. Then atomics aren’t going to +work. What you need then is a mutex. A mutex lets me box a group of code so only one +Goroutine at a time can execute that code. + + var counter int + + func main() { + const grs = 2 + + var wg sync.WaitGroup + wg.Add(grs) + + var mu sync.Mutex <-- CHANGED + + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + mu.Lock() <-- CHANGED + { + value := counter + value++ + counter = value + } + mu.Unlock() <-- CHANGED + } + wg.Done() + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) + } + +There are several changes to this code from the original. You added the construction +of the mu variable to be a mutex set to its zero value. Then inside the for loop, +you added calls to Lock and Unlock with an artificial code block. Inside the code block +you have the code that needs to be synchronized. The code block is used for readability. + +With this code in place, the scheduler will only allow one Goroutine to enter the +code block at a time. It’s important to understand that a mutex is not a queue. +The first Goroutine that calls Lock isn’t necessarily the first Goroutine who gets +the Lock. There is a fairness based algorithm but this is done on purpose so people +don’t use mutexes as queues. + +It’s important to remember the Lock creates back pressure, so the longer it takes to +get from the Lock to the Unlock, the more chance of Goroutines waiting for their turn. +If you forget to call Unlock, then all Goroutines waiting will deadlock. This is why it’s +critical that the call to Lock and Unlock happen in the same function. Make sure I’m +doing the bare minimum synchronization you need in the code block, but at least the +minimum. + +This is very bad code where someone is trying to get in and out of the Lock so quickly +they actually lose the synchronization and the race detector can’t even discover the +problem. + + var counter int + + func main() { + const grs = 2 + + var wg sync.WaitGroup + wg.Add(grs) + + var mu sync.Mutex + + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + var value int + mu.Lock() <-- Bad Use Of Mutex + { + value = counter + } + mu.Unlock() + + value++ + + mu.Lock() <-- Bad Use Of Mutex + { + counter = value + } + mu.Unlock() + } + wg.Done() + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) + } + +As a general guideline, if you see a call to Lock from the same mutex twice in the same +function, stop the code review. There is probably a mistake or over complication. In +this case the calls to read and write are being synchronized, however, two Goroutines +can end up at the value++ line of code with the same value. The data race still exists +and the race detector is helpless in finding it. + +** Read/Write Mutexes + +There is a second type of mutex called a read/write mutex. It allows me to separate +the locks around reads and writes. This is important since reading data doesn’t pose +a threat unless a Goroutine is attempting to write at the same time. So this type of +mutex allows multiple Goroutines to read the same memory at the same time. As soon +as a write lock is requested, the reads are no longer issued, the write takes place, +the reads can start again. + + package main + + import ( + "fmt" + "math/rand" + "sync" + "time" + ) + + var data []string + var rwMutex sync.RWMutex + + func main() { + var wg sync.WaitGroup + wg.Add(1) + + go func() { + for i := 0; i < 10; i++ { + writer(i) + } + wg.Done() + }() + + for i := 0; i < 8; i++ { + go func(id int) { + for { + reader(id) + } + }(i) + } + + wg.Wait() + fmt.Println("Program Complete") + } + + func writer(i int) { + rwMutex.Lock() + { + time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) + fmt.Println("****> : Performing Write") + data = append(data, fmt.Sprintf("String: %d", i)) + } + rwMutex.Unlock() + } + + func reader(id int) { + rwMutex.RLock() + { + time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) + fmt.Printf("%d : Performing Read : Length[%d]\n", id, len(data)) + } + rwMutex.RUnlock() + } + +You can see the use of a read/write mutex where there are 8 Goroutines reading the +length of a slice within a 10 millisecond delay of each other, and 1 Goroutine waking +up within 100 milliseconds to append a value (write) to the slice. + +The key is the implementation of the writer and reader functions. Notice how you use +Lock for the writer and RLock for the reader. One of the biggest mistakes you can +make with this is mixing up the Unlock calls with the wrong version. Having a Lock +with a RUnlock will never end well. + + 7 : Performing Read : Length[0] + 5 : Performing Read : Length[0] + 0 : Performing Read : Length[0] + 3 : Performing Read : Length[0] + 7 : Performing Read : Length[0] + 2 : Performing Read : Length[0] + 1 : Performing Read : Length[0] + ****> : Performing Write + 0 : Performing Read : Length[1] + 5 : Performing Read : Length[1] + 3 : Performing Read : Length[1] + 6 : Performing Read : Length[1] + 7 : Performing Read : Length[1] + 4 : Performing Read : Length[1] + 1 : Performing Read : Length[1] + 2 : Performing Read : Length[1] + ****> : Performing Write + 7 : Performing Read : Length[2] + 1 : Performing Read : Length[2] + 3 : Performing Read : Length[2] + +The output shows how multiple Goroutines are reading at the same time, but all +the reading stops when the write takes place. + +** Notes + +- Goroutines need to be coordinated and synchronized. +- When two or more goroutines attempt to access the same resource, we have a data race. +- Atomic functions and mutexes can provide the support we need. + +** Cache Coherency and False Sharing + +This content is provided by Scott Meyers from his talk in 2014 at Dive: + +[[https://youtu.be/WDIkqP4JbkE?t=1809][CPU Caches and Why You Care (30:09-38:30)]] +[[https://github.com/ardanlabs/gotraining/blob/master/topics/go/testing/benchmarks/falseshare/README.md][Code Example]] + +.image /tour/grc/static/img/figure1_data_race.png + +** Cache Coherency and False Sharing Notes + +- Thread memory access matters. +- If your algorithm is not scaling look for false sharing problems. + +** Extra Reading + +- [[http://www.drdobbs.com/parallel/eliminate-false-sharing/217500206][Eliminate False Sharing]] - Herb Sutter +- [[https://golang.org/ref/mem][The Go Memory Model]] +- [[http://blog.golang.org/race-detector][Introducing the Go Race Detector]] - Dmitry Vyukov and Andrew Gerrand +- [[https://www.ardanlabs.com/blog/2013/09/detecting-race-conditions-with-go.html][Detecting Race Conditions With Go]] - William Kennedy +- [[https://golang.org/doc/articles/race_detector.html][Data Race Detector]] + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Given the following program, use the race detector to find and correct the data race. + +.play data_race/exercise1.go +.play data_race/answer1.go diff --git a/_content/tour/grc/data_race/answer1.go b/_content/tour/grc/data_race/answer1.go new file mode 100644 index 00000000..1d961d29 --- /dev/null +++ b/_content/tour/grc/data_race/answer1.go @@ -0,0 +1,60 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Answer for exercise 1 of Race Conditions. +package main + +import ( + "fmt" + "math/rand" + "sync" +) + +// numbers maintains a set of random numbers. +var numbers []int + +// mutex will help protect the slice. +var mutex sync.Mutex + +// main is the entry point for the application. +func main() { + // Number of goroutines to use. + const grs = 3 + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(grs) + + // Create three goroutines to generate random numbers. + for i := 0; i < grs; i++ { + go func() { + random(10) + wg.Done() + }() + } + + // Wait for all the goroutines to finish. + wg.Wait() + + // Display the set of random numbers. + for i, number := range numbers { + fmt.Println(i, number) + } +} + +// random generates random numbers and stores them into a slice. +func random(amount int) { + // Generate as many random numbers as specified. + for i := 0; i < amount; i++ { + n := rand.Intn(100) + + // Protect this append to keep access safe. + mutex.Lock() + { + numbers = append(numbers, n) + } + mutex.Unlock() + } +} diff --git a/_content/tour/grc/data_race/example1.go b/_content/tour/grc/data_race/example1.go new file mode 100644 index 00000000..76429ef1 --- /dev/null +++ b/_content/tour/grc/data_race/example1.go @@ -0,0 +1,75 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// YOU NEED TO RUN THIS EXAMPLE OUTSIDE OF THE TOUR +// go build -race or go run main.go -race + +// Sample program to show how to create race conditions in +// our programs. We don't want to do this. +package main + +import ( + "fmt" + "sync" +) + +// counter is a variable incremented by all goroutines. +var counter int + +func main() { + + // Number of goroutines to use. + const grs = 2 + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(grs) + + // Create two goroutines. + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + + // Capture the value of Counter. + value := counter + + // Increment our local value of Counter. + value++ + + // Store the value back into Counter. + counter = value + } + + wg.Done() + }() + } + + // Wait for the goroutines to finish. + wg.Wait() + fmt.Println("Final Counter:", counter) +} + +/* +================== +WARNING: DATA RACE +Read at 0x0000011a5118 by goroutine 7: + main.main.func1() + example1.go:34 +0x4e + +Previous write at 0x0000011a5118 by goroutine 6: + main.main.func1() + example1.go:40 +0x6d + +Goroutine 7 (running) created at: + main.main() + example1.go:44 +0xc3 + +Goroutine 6 (finished) created at: + main.main() + example1.go:44 +0xc3 +================== +Final Counter: 4 +Found 1 data race(s) +*/ diff --git a/_content/tour/grc/data_race/example2.go b/_content/tour/grc/data_race/example2.go new file mode 100644 index 00000000..3edc2078 --- /dev/null +++ b/_content/tour/grc/data_race/example2.go @@ -0,0 +1,47 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// YOU NEED TO RUN THIS EXAMPLE OUTSIDE OF THE TOUR +// go build -race or go run main.go -race + +// Sample program to show how to use the atomic package to +// provide safe access to numeric types. +package main + +import ( + "fmt" + "sync" + "sync/atomic" +) + +// counter is a variable incremented by all goroutines. +var counter int64 + +func main() { + + // Number of goroutines to use. + const grs = 2 + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(grs) + + // Create two goroutines. + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + atomic.AddInt64(&counter, 1) + } + + wg.Done() + }() + } + + // Wait for the goroutines to finish. + wg.Wait() + + // Display the final value. + fmt.Println("Final Counter:", counter) +} diff --git a/_content/tour/grc/data_race/example3.go b/_content/tour/grc/data_race/example3.go new file mode 100644 index 00000000..7cf44534 --- /dev/null +++ b/_content/tour/grc/data_race/example3.go @@ -0,0 +1,58 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use a mutex to define critical +// sections of code that need synchronous access. +package main + +import ( + "fmt" + "sync" +) + +// counter is a variable incremented by all goroutines. +var counter int + +// mutex is used to define a critical section of code. +var mutex sync.Mutex + +func main() { + + // Number of goroutines to use. + const grs = 2 + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(grs) + + // Create two goroutines. + for g := 0; g < grs; g++ { + go func() { + for i := 0; i < 2; i++ { + + // Only allow one goroutine through this critical section at a time. + mutex.Lock() + { + // Capture the value of counter. + value := counter + + // Increment our local value of counter. + value++ + + // Store the value back into counter. + counter = value + } + mutex.Unlock() + // Release the lock and allow any waiting goroutine through. + } + + wg.Done() + }() + } + + // Wait for the goroutines to finish. + wg.Wait() + fmt.Printf("Final Counter: %d\n", counter) +} diff --git a/_content/tour/grc/data_race/example4.go b/_content/tour/grc/data_race/example4.go new file mode 100644 index 00000000..218304c8 --- /dev/null +++ b/_content/tour/grc/data_race/example4.go @@ -0,0 +1,92 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use a read/write mutex to define critical +// sections of code that needs synchronous access. +package main + +import ( + "fmt" + "math/rand" + "sync" + "sync/atomic" + "time" +) + +// data is a slice that will be shared. +var data []string + +// rwMutex is used to define a critical section of code. +var rwMutex sync.RWMutex + +// Number of reads occurring at ay given time. +var readCount int64 + +func main() { + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(1) + + // Create a writer goroutine. + go func() { + for i := 0; i < 10; i++ { + writer(i) + } + wg.Done() + }() + + // Create eight reader goroutines. + for i := 0; i < 8; i++ { + go func(id int) { + for { + reader(id) + } + }(i) + } + + // Wait for the write goroutine to finish. + wg.Wait() + fmt.Println("Program Complete") +} + +// writer adds a new string to the slice in random intervals. +func writer(i int) { + + // Only allow one goroutine to read/write to the slice at a time. + rwMutex.Lock() + { + // Capture the current read count. + // Keep this safe though we can due without this call. + rc := atomic.LoadInt64(&readCount) + + // Perform some work since we have a full lock. + time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) + fmt.Printf("****> : Performing Write : RCount[%d]\n", rc) + data = append(data, fmt.Sprintf("String: %d", i)) + } + rwMutex.Unlock() + // Release the lock. +} + +// reader wakes up and iterates over the data slice. +func reader(id int) { + + // Any goroutine can read when no write operation is taking place. + rwMutex.RLock() + { + // Increment the read count value by 1. + rc := atomic.AddInt64(&readCount, 1) + + // Perform some read work and display values. + time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) + fmt.Printf("%d : Performing Read : Length[%d] RCount[%d]\n", id, len(data), rc) + + // Decrement the read count value by 1. + atomic.AddInt64(&readCount, -1) + } + rwMutex.RUnlock() + // Release the read lock. +} diff --git a/_content/tour/grc/data_race/example5.go b/_content/tour/grc/data_race/example5.go new file mode 100644 index 00000000..dd7cb0fc --- /dev/null +++ b/_content/tour/grc/data_race/example5.go @@ -0,0 +1,38 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how maps are not safe for concurrent use by default. +// The runtime will detect concurrent writes and panic. +package main + +import ( + "fmt" + "sync" +) + +// scores holds values incremented by multiple goroutines. +var scores = make(map[string]int) + +func main() { + var wg sync.WaitGroup + wg.Add(2) + + go func() { + for i := 0; i < 1000; i++ { + scores["A"]++ + } + wg.Done() + }() + + go func() { + for i := 0; i < 1000; i++ { + scores["B"]++ + } + wg.Done() + }() + + wg.Wait() + fmt.Println("Final scores:", scores) +} diff --git a/_content/tour/grc/data_race/example6.go b/_content/tour/grc/data_race/example6.go new file mode 100644 index 00000000..61243b57 --- /dev/null +++ b/_content/tour/grc/data_race/example6.go @@ -0,0 +1,90 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show a more complicated race condition using +// an interface value. This produces a read to an interface value after +// a partial write. +package main + +import ( + "fmt" + "os" + "sync" +) + +// Speaker allows for speaking behavior. +type Speaker interface { + Speak() bool +} + +// Ben is a person who can speak. +type Ben struct { + name string +} + +// Speak allows Ben to say hello. It returns false if the method is +// called through the interface value after a partial write. +func (b *Ben) Speak() bool { + if b.name != "Ben" { + fmt.Printf("Ben says, \"Hello my name is %s\"\n", b.name) + return false + } + + return true +} + +// Jerry is a person who can speak. +type Jerry struct { + name string +} + +// Speak allows Jerry to say hello. It returns false if the method is +// called through the interface value after a partial write. +func (j *Jerry) Speak() bool { + if j.name != "Jerry" { + fmt.Printf("Jerry says, \"Hello my name is %s\"\n", j.name) + return false + } + + return true +} + +func main() { + + // Create values of type Ben and Jerry. + ben := Ben{"Ben"} + jerry := Jerry{"Jerry"} + + // Assign the pointer to the Ben value to the interface value. + person := Speaker(&ben) + + // Have a goroutine constantly assign the pointer of + // the Ben value to the interface and then Speak. + go func() { + for { + person = &ben + if !person.Speak() { + os.Exit(1) + } + } + }() + + // Have a goroutine constantly assign the pointer of + // the Jerry value to the interface and then Speak. + go func() { + for { + person = &jerry + if !person.Speak() { + os.Exit(1) + } + } + }() + + // Just hold main from returning. The data race will + // cause the program to exit. + var wg sync.WaitGroup + wg.Add(1) + wg.Wait() +} diff --git a/_content/tour/grc/data_race/exercise1.go b/_content/tour/grc/data_race/exercise1.go new file mode 100644 index 00000000..0404b00a --- /dev/null +++ b/_content/tour/grc/data_race/exercise1.go @@ -0,0 +1,52 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Fix the race condition in this program. +package main + +import ( + "fmt" + "math/rand" + "sync" +) + +// numbers maintains a set of random numbers. +var numbers []int + +func main() { + + // Number of goroutines to use. + const grs = 3 + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(grs) + + // Create three goroutines to generate random numbers. + for i := 0; i < grs; i++ { + go func() { + random(10) + wg.Done() + }() + } + + // Wait for all the goroutines to finish. + wg.Wait() + + // Display the set of random numbers. + for i, number := range numbers { + fmt.Println(i, number) + } +} + +// random generates random numbers and stores them into a slice. +func random(amount int) { + + // Generate as many random numbers as specified. + for i := 0; i < amount; i++ { + n := rand.Intn(100) + numbers = append(numbers, n) + } +} diff --git a/_content/tour/grc/embedding.article b/_content/tour/grc/embedding.article new file mode 100644 index 00000000..b462988d --- /dev/null +++ b/_content/tour/grc/embedding.article @@ -0,0 +1,204 @@ +Embedding +Embedding types provide the final piece of sharing and reusing state and behavior between types. + +* Embedding + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Embedding types provide the final piece of sharing and reusing state and behavior +between types. Through the use of inner type promotion, an inner type's fields and +methods can be directly accessed by references of the outer type. + +** Code Review + +- *Example* *1:* Declaring Fields +- *Example* *2:* Embedding types +- *Example* *3:* Embedded types and interfaces +- *Example* *4:* Outer and inner type interface implementations + +.play embedding/example1.go +.play embedding/example2.go +.play embedding/example3.go +.play embedding/example4.go + +** Embedding Mechanics + +This first example does not show embedding, just the declaration of two struct +types working together as a field from one type to the other. + + type user struct { + name string + email string + } + + type admin struct { + person user // NOT Embedding + level string + } + +This is embedding. + + type user struct { + name string + email string + } + + type admin struct { + user // Value Semantic Embedding + level string + } + +The person field is removed and just the type name is left. You can also embed a +type using pointer semantics. + + type user struct { + name string + email string + } + + type admin struct { + *user // Pointer Semantic Embedding + level string + } + +In this case, a pointer of the type is embedded. In either case, accessing the +embedded value is done through the use of the type’s name. + +The best way to think about embedding is to view the user type as an inner type and +admin as an outer type. It’s this inner/outer type relationship that is magical +because with embedding, everything related to the inner type (both fields and methods) +can be promoted up to the outer type. + + type user struct { + name string + email string + } + + func (u *user) notify() { + fmt.Printf("Sending user email To %s<%s>\n", + u.name, + u.email) + } + + type admin struct { + *user // Pointer Semantic Embedding + level string + } + + func main() { + ad := admin{ + user: &user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + ad.user.notify() + ad.notify() // Outer type promotion + } + +Output: + + Sending user email To john smith + Sending user email To john smith + +Once you add a method named notify for the user type and then a small main function. +You can see the output is the same whether you call the notify method through the inner +pointer value directly or through the outer type value. The notify method declared for +the user type is accessible directly by the admin type value. + +Though this looks like inheritance, you must be careful. This is not about reusing +state, but about promoting behavior. + + type notifier interface { + notify() + } + + func sendNotification(n notifier) { + n.notify() + } + +Now you add an interface and a polymorphic function that accepts any concrete value +that implements the full method set of behavior defined by the notifier interface. +Which is just a method named notify. + +Because of embedding and promotion, values of type admin now implement the notifier +interface. + + func main() { + ad := admin{ + user: &user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + sendNotification(&ad) + } + +Output: + + Sending user email To john smith + +You can send the address of the admin value into the polymorphic function since +embedding promotes the notify behavior up to the admin type. + + type admin struct { + *user // Pointer Semantic Embedding + level string + } + + func (a *admin) notify() { + fmt.Printf("Sending admin Email To %s<%s>\n", + a.name, + a.email) + } + +When the outer type implements a method already implemented by the inner type, +the promotion doesn’t take place. + + func main() { + ad := admin{ + user: &user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + sendNotification(&ad) + } + +Output: + + Sending admin email To john smith + +You can see the outer type’s method is now being executed. + +** Notes + +- Embedding types allow us to share state or behavior between types. +- The inner type never loses its identity. +- This is not inheritance. +- Through promotion, inner type fields and methods can be accessed through the outer type. +- The outer type can override the inner type's behavior. + +** Extra Reading + +- [[https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html][Methods, Interfaces and Embedded Types in Go]] - William Kennedy +- [[https://rakyll.org/typesystem/][Embedding is not inheritance]] - JBD + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Edit the code from the template. Add a new type CachingFeed which embeds Feed and +overrides the Fetch method. + +.play embedding/exercise1.go +.play embedding/answer1.go diff --git a/_content/tour/grc/embedding/answer1.go b/_content/tour/grc/embedding/answer1.go new file mode 100644 index 00000000..ba0b659b --- /dev/null +++ b/_content/tour/grc/embedding/answer1.go @@ -0,0 +1,107 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how you can use embedding to reuse behavior from +// another type and override specific methods. +package main + +import ( + "fmt" + "log" + "time" +) + +// Document is the core data model we are working with. +type Document struct { + Key string + Title string +} + +// ================================================== + +// Feed is a type that knows how to fetch Documents. +type Feed struct{} + +// Count tells how many documents are in the feed. +func (f *Feed) Count() int { + return 42 +} + +// Fetch simulates looking up the document specified by key. It is slow. +func (f *Feed) Fetch(key string) (Document, error) { + time.Sleep(time.Second) + + doc := Document{ + Key: key, + Title: "Title for " + key, + } + return doc, nil +} + +// ================================================== + +// CachingFeed keeps a local copy of Documents that have already been +// retrieved. It embeds Feed to get the Fetch and Count behavior but +// "overrides" Fetch to have its cache. +type CachingFeed struct { + docs map[string]Document + *Feed +} + +// NewCachingFeed initializes a CachingFeed for use. +func NewCachingFeed(f *Feed) *CachingFeed { + return &CachingFeed{ + docs: make(map[string]Document), + Feed: f, + } +} + +// Fetch calls the embedded type's Fetch method if the key is not cached. +func (cf *CachingFeed) Fetch(key string) (Document, error) { + if doc, ok := cf.docs[key]; ok { + return doc, nil + } + + doc, err := cf.Feed.Fetch(key) + if err != nil { + return Document{}, err + } + + cf.docs[key] = doc + return doc, nil +} + +// ================================================== + +// FetchCounter is the behavior we depend on for our process function. +type FetchCounter interface { + Fetch(key string) (Document, error) + Count() int +} + +func process(fc FetchCounter) { + fmt.Printf("There are %d documents\n", fc.Count()) + + keys := []string{"a", "a", "a", "b", "b", "b"} + + for _, key := range keys { + doc, err := fc.Fetch(key) + if err != nil { + log.Printf("Could not fetch %s : %v", key, err) + return + } + + fmt.Printf("%s : %v\n", key, doc) + } +} + +func main() { + fmt.Println("Using Feed directly") + process(&Feed{}) + + fmt.Println("Using CachingFeed") + c := NewCachingFeed(&Feed{}) + process(c) +} diff --git a/_content/tour/grc/embedding/example1.go b/_content/tour/grc/embedding/example1.go new file mode 100644 index 00000000..427a8f76 --- /dev/null +++ b/_content/tour/grc/embedding/example1.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how what we are doing is NOT embedding +// a type but just using a type as a field. +package main + +import "fmt" + +// user defines a user in the program. +type user struct { + name string + email string +} + +// notify implements a method notifies users +// of different events. +func (u *user) notify() { + fmt.Printf("Sending user email To %s<%s>\n", + u.name, + u.email) +} + +// admin represents an admin user with privileges. +type admin struct { + person user // NOT Embedding + level string +} + +func main() { + + // Create an admin user. + ad := admin{ + person: user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + // We can access fields methods. + ad.person.notify() +} diff --git a/_content/tour/grc/embedding/example2.go b/_content/tour/grc/embedding/example2.go new file mode 100644 index 00000000..37104874 --- /dev/null +++ b/_content/tour/grc/embedding/example2.go @@ -0,0 +1,48 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to embed a type into another type and +// the relationship between the inner and outer type. +package main + +import "fmt" + +// user defines a user in the program. +type user struct { + name string + email string +} + +// notify implements a method notifies users +// of different events. +func (u *user) notify() { + fmt.Printf("Sending user email To %s<%s>\n", + u.name, + u.email) +} + +// admin represents an admin user with privileges. +type admin struct { + user // Embedded Type + level string +} + +func main() { + + // Create an admin user. + ad := admin{ + user: user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + // We can access the inner type's method directly. + ad.user.notify() + + // The inner type's method is promoted. + ad.notify() +} diff --git a/_content/tour/grc/embedding/example3.go b/_content/tour/grc/embedding/example3.go new file mode 100644 index 00000000..daff1743 --- /dev/null +++ b/_content/tour/grc/embedding/example3.go @@ -0,0 +1,58 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how embedded types work with interfaces. +package main + +import "fmt" + +// notifier is an interface that defined notification +// type behavior. +type notifier interface { + notify() +} + +// user defines a user in the program. +type user struct { + name string + email string +} + +// notify implements a method notifies users +// of different events. +func (u *user) notify() { + fmt.Printf("Sending user email To %s<%s>\n", + u.name, + u.email) +} + +// admin represents an admin user with privileges. +type admin struct { + user + level string +} + +func main() { + + // Create an admin user. + ad := admin{ + user: user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + // Send the admin user a notification. + // The embedded inner type's implementation of the + // interface is "promoted" to the outer type. + sendNotification(&ad) +} + +// sendNotification accepts values that implement the notifier +// interface and sends notifications. +func sendNotification(n notifier) { + n.notify() +} diff --git a/_content/tour/grc/embedding/example4.go b/_content/tour/grc/embedding/example4.go new file mode 100644 index 00000000..bc4127a8 --- /dev/null +++ b/_content/tour/grc/embedding/example4.go @@ -0,0 +1,73 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show what happens when the outer and inner +// type implement the same interface. +package main + +import "fmt" + +// notifier is an interface that defined notification +// type behavior. +type notifier interface { + notify() +} + +// user defines a user in the program. +type user struct { + name string + email string +} + +// notify implements a method notifies users +// of different events. +func (u *user) notify() { + fmt.Printf("Sending user email To %s<%s>\n", + u.name, + u.email) +} + +// admin represents an admin user with privileges. +type admin struct { + user + level string +} + +// notify implements a method notifies admins +// of different events. +func (a *admin) notify() { + fmt.Printf("Sending admin Email To %s<%s>\n", + a.name, + a.email) +} + +func main() { + + // Create an admin user. + ad := admin{ + user: user{ + name: "john smith", + email: "john@yahoo.com", + }, + level: "super", + } + + // Send the admin user a notification. + // The embedded inner type's implementation of the + // interface is NOT "promoted" to the outer type. + sendNotification(&ad) + + // We can access the inner type's method directly. + ad.user.notify() + + // The inner type's method is NOT promoted. + ad.notify() +} + +// sendNotification accepts values that implement the notifier +// interface and sends notifications. +func sendNotification(n notifier) { + n.notify() +} diff --git a/_content/tour/grc/embedding/exercise1.go b/_content/tour/grc/embedding/exercise1.go new file mode 100644 index 00000000..a557be23 --- /dev/null +++ b/_content/tour/grc/embedding/exercise1.go @@ -0,0 +1,106 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This program defines a type Feed with two methods: Count and Fetch. Create a +// new type CachingFeed that embeds *Feed but overrides the Fetch method. +// +// The CachingFeed type should have a map of Documents to limit the number of +// calls to Feed.Fetch. +package main + +import ( + "fmt" + "log" + "time" +) + +// Document is the core data model we are working with. +type Document struct { + Key string + Title string +} + +// ================================================== + +// Feed is a type that knows how to fetch Documents. +type Feed struct{} + +// Count tells how many documents are in the feed. +func (f *Feed) Count() int { + return 42 +} + +// Fetch simulates looking up the document specified by key. It is slow. +func (f *Feed) Fetch(key string) (Document, error) { + time.Sleep(time.Second) + + doc := Document{ + Key: key, + Title: "Title for " + key, + } + return doc, nil +} + +// ================================================== + +// FetchCounter is the behavior we depend on for our process function. +type FetchCounter interface { + Fetch(key string) (Document, error) + Count() int +} + +func process(fc FetchCounter) { + fmt.Printf("There are %d documents\n", fc.Count()) + + keys := []string{"a", "a", "a", "b", "b", "b"} + + for _, key := range keys { + doc, err := fc.Fetch(key) + if err != nil { + log.Printf("Could not fetch %s : %v", key, err) + return + } + + fmt.Printf("%s : %v\n", key, doc) + } +} + +// ================================================== + +// CachingFeed keeps a local copy of Documents that have already been +// retrieved. It embeds Feed to get the Fetch and Count behavior but +// "overrides" Fetch to have its cache. +type CachingFeed struct { + // TODO embed *Feed and add a field for a map[string]Document. +} + +// NewCachingFeed initializes a CachingFeed for use. +func NewCachingFeed(f *Feed) *CachingFeed { + + // TODO create a CachingFeed with an initialized map and embedded feed. + // Return its address. + + return nil // Remove this line. +} + +// Fetch calls the embedded type's Fetch method if the key is not cached. +func (cf *CachingFeed) Fetch(key string) (Document, error) { + + // TODO implement this method. Check the map field for the specified key and + // return it if found. If it's not found, call the embedded types Fetch + // method. Store the result in the map before returning it. + + return Document{}, nil // Remove this line. +} + +// ================================================== + +func main() { + fmt.Println("Using Feed directly") + process(&Feed{}) + + // Call process again with your CachingFeed. + //fmt.Println("Using CachingFeed") +} diff --git a/_content/tour/grc/error-handling.article b/_content/tour/grc/error-handling.article new file mode 100644 index 00000000..58a94f1c --- /dev/null +++ b/_content/tour/grc/error-handling.article @@ -0,0 +1,449 @@ +Error Handling +Error handling is critical for making your programs reliable, trustworthy and respectful to those who depend on them. + +* Error Handling Design + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Integrity matters and it’s a big part of the engineering process. At the heart of +integrity is error handling. When it comes to Go, error handling is not an exception +to be handled later or somewhere else in the code. It’s a part of the main path and +needs to be a main focus. + +Developers have the responsibility to return enough context about any error so a +user can make an informed decision about how to proceed. Handling an error is +about three things: logging the error, not propagating the error any further, and +determining if the Goroutine/program needs to be terminated. + +In Go, errors are just values so they can be anything you need them to be. They +can maintain any state or behavior. + +** Code Review + +- *Example* *1:* Default Error Values +- *Example* *2:* Error Variables +- *Example* *3:* Type As Context +- *Example* *4:* Behavior As Context +- *Example* *5:* Find The Bug +- *Example* *6:* Wrapping Errors With stdlib + +.play error-handling/example1.go +.play error-handling/example2.go +.play error-handling/example3.go +.play error-handling/example4.go +.play error-handling/example5.go +.play error-handling/example6.go + +** Error Handling Basics + +The error interface is built into the language. + + // http://golang.org/pkg/builtin/#error + type error interface { + Error() string + } + +This is why it appears to be an unexported identifier. Any concrete value that +implements this interface can be used as an error value. + +One important aspect of Go is that error handling is done in a decoupled state +through this interface. A key reason for this is because error handling is an +aspect of my application that is more susceptible to change and improvement. +This interface is the type Go applications must use as the return type for error +handling. + + // http://golang.org/src/pkg/errors/errors.go + type errorString struct { + s string + } + + // http://golang.org/src/pkg/errors/errors.go + func (e *errorString) Error() string { + return e.s + } + +This is the most commonly used error value in Go programs. It’s declared in the +errors package from the standard library. Notice how the type is unexported and +it has one unexported field which is a string. You can also see how pointer semantics +are used to implement the error interface. This means only addresses to values +of this type can be shared and stored inside the interface. The method just returned +the error string. + +It’s important to remember, the implementation of the Error method serves the +purpose of implementing the interface and for logging. If any user needs to parse +the string returned from this method, You have failed to provide the user the +right amount of context to make an informed decision. + + // http://golang.org/src/pkg/errors/errors.go + func New(text string) error { + return &errorString{text} + } + +The New function is how an error using the concrete type errorString is constructed. +Notice how the function returns the error using the error interface. Also notice +how pointer semantics are being used. + + func main() { + if err := webCall(); err != nil { + fmt.Println(err) + return + } + fmt.Println("Life is good") + } + + func webCall() error { + return New("bad request") + } + +Context is everything with errors. Each error must provide enough context to allow +the caller to make an informed decision about the state of the goroutine/application. +In this example, the webCall function returns an error with the message Bad Request. +In the main function, a call is made to webCall and then a check is made to see +if an error has occurred with the call. + + if err := webCall(); err != nil { + fmt.Println(err) + return + } + +The key to the check is err != nil. What this condition is asking is, is there a +concrete value stored inside the err interface value. When the interface value is +storing a concrete value, there is an error. In this case, the context is literally +just the fact that a concrete value exists, it’s not important what the concrete +value is. + +What if it’s important to know what error value exists inside the err interface +variable? Then error variables are a good option. + + var ( + ErrBadRequest = errors.New("Bad Request") + ErrPageMoved = errors.New("Page Moved") + ) + +Error variables provide a mechanic to identify what specific error is being returned. +They have an idiom of starting with the prefix Err and are based on the concrete type +errorString from the errors package. + + func webCall(b bool) error { + if b { + return ErrBadRequest + } + return ErrPageMoved + } + +In this new version of webCall, the function returns one or the other error variable. +This allows the caller to determine which error took place. + + func main() { + if err := webCall(true); err != nil { + switch err { + case ErrBadRequest: + fmt.Println("Bad Request Occurred") + return + + case ErrPageMoved: + fmt.Println("The Page moved") + return + + default: + fmt.Println(err) + return + } + } + + fmt.Println("Life is good") + } + +In the application after the call to webCall is made, a check can be performed to +see if there is a concrete value stored inside the err interface variable. If there +is, then a switch statement is used to determine which error it was by comparing err +to the different error variables. + +In this case, the context of the error is based on which error variable was returned. +What if an error variable is not enough context? What if some special state needs +to be checked, like with networking errors? In these cases, a custom concrete error +type is the answer. + + type UnmarshalTypeError struct { + Value string + Type reflect.Type + } + + func (e *UnmarshalTypeError) Error() string { + return "json: cannot unmarshal " + e.Value + + " into Go value of type " + e.Type.String() + } + +This is a custom concrete error type implemented in the json package. Notice the +name has a suffix of Error in the naming of the type. Also notice the use of +pointer semantics for the implementation of the error interface. Once again the +implementation is for logging and should display information about all the fields +being captured. + + type InvalidUnmarshalError struct { + Type reflect.Type + } + + func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "json: Unmarshal(nil)" + } + if e.Type.Kind() != reflect.Ptr { + return "json: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "json: Unmarshal(nil " + e.Type.String() + ")" + } + +This is a second custom concrete error type found in the json package. The implementation +of the Error method is a bit more complex, but once again just for logging and +using pointer semantics. + + func Unmarshal(data []byte, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + return &UnmarshalTypeError{"string", reflect.TypeOf(v)} + } + +Here is a portion of the Unmarshal function. Notice how it constructs the concrete +error values in the return, passing them back to the caller through the error interface. +Pointer semantic construction is being used because pointer semantics were used in +the declaration of the Error method. + +The context of the error here is more about the type of error stored inside the +error interface. There needs to be a way to determine that. + + func main() { + var u user + err := Unmarshal([]byte(`{"name":"bill"}`), u) + if err != nil { + switch e := err.(type) { + case *UnmarshalTypeError: + fmt.Printf("UnmarshalTypeError: Value[%s] Type[%v]\n", + e.Value, e.Type) + case *InvalidUnmarshalError: + fmt.Printf("InvalidUnmarshalError: Type[%v]\n", e.Type) + default: + fmt.Println(err) + } + return + } + fmt.Println("Name:", u.Name) + } + +A generic type assertion within the scope of the switch statement is how you can +write code to test what type of value is being stored inside the err interface value. +Type is the context here and now you can test and take action with access to all +the states of the error. + +However, this poses one problem. I’m no longer decoupled from the concrete error +value. This means if the concrete error value is changed, my code can break. The +beautiful part of using an interface for error handling is being decoupled from +breaking changes. + +If the concrete error value has a method set, then you can use an interface for +the type check. As an example, the net package has many concrete error types that +implement different methods. One common method is called Temporary. This method +allows the user to test if the networking error is critical or just something +that can recover on its own. + + type temporary interface { + Temporary() bool + } + + func (c *client) BehaviorAsContext() { + for { + line, err := c.reader.ReadString('\n') + if err != nil { + switch e := err.(type) { + case temporary: + if !e.Temporary() { + log.Println("Temporary: Client leaving chat") + return + } + default: + if err == io.EOF { + log.Println("EOF: Client leaving chat") + return + } + log.Println("read-routine", err) + } + } + fmt.Println(line) + } + } + +In this code, the call to ReadString could fail with an error from the net package. +In this case, an interface is declared that represents the common behavior a given +concrete error value could implement. Then with a generic type assertion, you test +if that behavior exists and you can call into it. The best part, you stay in a +decoupled state with my error handling. + +** Always Use The Error Interface + +One mistake Go developers can make is when they use the concrete error type and +not the error interface for the return type for handling errors. If you were to +do this, bad things could happen. + + type customError struct{} + + func (c *customError) Error() string { + return "Find the bug." + } + + func fail() ([]byte, *customError) { + return nil, nil + } + + func main() { + var err error + if _, err = fail(); err != nil { + log.Fatal("Why did this fail?") + } + log.Println("No Error") + } + +Output: + + Why did this fail? + +Why does this code think there is an error when the fail function returns nil for +the error? It’s because the fail function is using the concrete error type and not +the error interface. In this case, there is a nil pointer of type customError stored +inside the err variable. That is not the same as a nil interface value of type error. + +** Handling Errors + +Handling errors is more of a macro level engineering conversation. In my world, error +handling means the error stops with the function handling the error, the error is +logged with full context, and the error is checked for its severity. Based on the +severity and ability to recover, a decision to recover, move on, or shutdown is made. + +One problem is that not all functions can handle an error. One reason could be because +not all functions are allowed to log. What happens when an error is being passed back +up the call stack and can’t be handled by the function receiving it? An error needs to +be wrapped in context so the function that eventually handles it, can properly do so. + + package main + + import ( + "errors" + "fmt" + ) + + type AppError struct { + State int + } + + func (ae *AppError) Error() string { + return fmt.Sprintf("App Error, State: %d", ae.State) + } + + func IsAppError(err error) bool { + var ae *AppError + return errors.As(err, &ae) + } + + func GetAppError(err error) *AppError { + var ae *AppError + if !errors.As(err, &ae) { + return nil + } + return ae + } + + func main() { + if err := firstCall(10); err != nil { + + // Check if the error is an AppError. + if IsAppError(err) { + ae := GetAppError(err) + fmt.Printf("Is AppError, State: %d\n", ae.State) + } + + fmt.Print("\n********************************\n\n") + + // Display the error using the implementation of + // the error interface. + fmt.Printf("%v\n", err) + } + } + + func firstCall(i int) error { + if err := secondCall(i); err != nil { + return fmt.Errorf("secondCall(%d) : %w", i, err) + } + return nil + } + + func secondCall(i int) error { + return &AppError{99} + } + +Output: + + Is AppError, State: 99 + + ******************************** + + secondCall(10) : App Error, State: 99 + +** Notes + +- Use the default error value for static and simple formatted messages. +- Create and return error variables to help the caller identify specific errors. +- Create custom error types when the context of the error is more complex. +- Error Values in Go aren't special, they are just values like any other, and so you have the entire language at your disposal. + +** Quotes + +"Systems cannot be developed assuming that human beings will be able to write millions +of lines of code without making mistakes, and debugging alone is not an efficient way +to develop reliable systems." - Al Aho (inventor of AWK) + +** Extra Reading + +- [[https://go.dev/blog/error-handling-and-go][Error handling and Go]] +- [[https://go.dev/blog/go1.13-errors][Working with Errors in Go 1.13]] +- [[https://www.ardanlabs.com/blog/2014/10/error-handling-in-go-part-i.html][Error Handling In Go, Part I]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2014/11/error-handling-in-go-part-ii.html][Error Handling In Go, Part II]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html][Design Philosophy On Logging]] - William Kennedy +- [[https://clipperhouse.com/bugs-are-a-failure-of-prediction/][Bugs are a failure of prediction]] - Matt Sherman +- [[https://dave.cheney.net/2014/12/24/inspecting-errors][Inspecting errors]] - Dave Cheney +- [[https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully][Don’t just check errors, handle them gracefully]] - Dave Cheney +- [[https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package][Stack traces and the errors package]] - Dave Cheney +- [[https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html][Error handling in Upspin]] - Rob Pike +- [[https://rauljordan.com/why-go-error-handling-is-awesome/][Why Go's Error Handling is Awesome]] - Raul Jordan + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Create two error variables, one called ErrInvalidValue and the other called +ErrAmountTooLarge. Provide the static message for each variable. Then write a +function called checkAmount that accepts a float64 type value and returns an +error value. Check the value for zero and if it is, return the ErrInvalidValue. +Check the value for greater than $1,000 and if it is, return the ErrAmountTooLarge. +Write a main function to call the checkAmount function and check the return error +value. Display a proper message to the screen. + +.play error-handling/exercise1.go +.play error-handling/answer1.go + +** Exercise 2 + +Create a custom error type called appError that contains three fields, err error, +message string and code int. Implement the error interface providing your own message +using these three fields. Implement a second method named temporary that returns +false when the value of the code field is 9. Write a function called checkFlag that +accepts a bool value. If the value is false, return a pointer of your custom error +type initialized as you like. If the value is true, return a default error. Write a +main function to call the checkFlag function and check the error using the temporary +interface. + +.play error-handling/exercise2.go +.play error-handling/answer2.go diff --git a/_content/tour/grc/error-handling/answer1.go b/_content/tour/grc/error-handling/answer1.go new file mode 100644 index 00000000..85d8a248 --- /dev/null +++ b/_content/tour/grc/error-handling/answer1.go @@ -0,0 +1,69 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create two error variables, one called ErrInvalidValue and the other +// called ErrAmountTooLarge. Provide the static message for each variable. +// Then write a function called checkAmount that accepts a float64 type value +// and returns an error value. Check the value for zero and if it is, return +// the ErrInvalidValue. Check the value for greater than $1,000 and if it is, +// return the ErrAmountTooLarge. Write a main function to call the checkAmount +// function and check the return error value. Display a proper message to the screen. +package main + +import ( + "errors" + "fmt" +) + +var ( + // ErrInvalidValue indicates the value is invalid. + ErrInvalidValue = errors.New("Invalid Value") + + // ErrAmountTooLarge indicates the value beyond the upper bound. + ErrAmountTooLarge = errors.New("Amount To Large") +) + +func main() { + + // Call the function and get the error. + if err := checkAmount(0); err != nil { + switch err { + + // Check if the error is an ErrInvalidValue. + case ErrInvalidValue: + fmt.Println("Value provided is not valid.") + return + + // Check if the error is an ErrAmountTooLarge. + case ErrAmountTooLarge: + fmt.Println("Value provided is too large.") + return + + // Handle the default error. + default: + fmt.Println(err) + return + } + } + + // Display everything is good. + fmt.Println("Everything checks out.") +} + +// checkAmount validates the value passed in. +func checkAmount(f float64) error { + switch { + + // Is the parameter equal to zero. + case f == 0: + return ErrInvalidValue + + // Is the parameter greater than 1000. + case f > 1000: + return ErrAmountTooLarge + } + + return nil +} diff --git a/_content/tour/grc/error-handling/answer2.go b/_content/tour/grc/error-handling/answer2.go new file mode 100644 index 00000000..10dad9ec --- /dev/null +++ b/_content/tour/grc/error-handling/answer2.go @@ -0,0 +1,67 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create a custom error type called appError that contains three fields, err error, +// message string and code int. Implement the error interface providing your own message +// using these three fields. Implement a second method named temporary that returns false +// when the value of the code field is 9. Write a function called checkFlag that accepts +// a bool value. If the value is false, return a pointer of your custom error type +// initialized as you like. If the value is true, return a default error. Write a main +// function to call the checkFlag function and check the error using the temporary +// interface. +package main + +import ( + "errors" + "fmt" +) + +// appError is a custom error type for the program. +type appError struct { + err error + message string + code int +} + +// Error implements the error interface for appError. +func (a *appError) Error() string { + return fmt.Sprintf("App Error[%s] Message[%s] Code[%d]", a.err, a.message, a.code) +} + +// Temporary implements behavior about the error. +func (a *appError) Temporary() bool { + return (a.code != 9) +} + +// temporary is used to test the error we receive. +type temporary interface { + Temporary() bool +} + +func main() { + if err := checkFlag(false); err != nil { + switch e := err.(type) { + case temporary: + fmt.Println(err) + if !e.Temporary() { + fmt.Println("Critical Error!") + } + default: + fmt.Println(err) + } + } +} + +// checkFlag returns one of two errors based on the value of the parameter. +func checkFlag(t bool) error { + + // If the parameter is false return an appError. + if !t { + return &appError{errors.New("Flag False"), "The Flag was false", 9} + } + + // Return a default error. + return errors.New("Flag True") +} diff --git a/_content/tour/grc/error-handling/example1.go b/_content/tour/grc/error-handling/example1.go new file mode 100644 index 00000000..ce52f2d6 --- /dev/null +++ b/_content/tour/grc/error-handling/example1.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the default error type is implemented. +package main + +import "fmt" + +// http://golang.org/pkg/builtin/#error +type error interface { + Error() string +} + +// http://golang.org/src/pkg/errors/errors.go +type errorString struct { + s string +} + +// http://golang.org/src/pkg/errors/errors.go +func (e *errorString) Error() string { + return e.s +} + +// http://golang.org/src/pkg/errors/errors.go +// New returns an error that formats as the given text. +func New(text string) error { + return &errorString{text} +} + +func main() { + if err := webCall(); err != nil { + fmt.Println(err) + return + } + + fmt.Println("Life is good") +} + +// webCall performs a web operation. +func webCall() error { + return New("Bad Request") +} diff --git a/_content/tour/grc/error-handling/example2.go b/_content/tour/grc/error-handling/example2.go new file mode 100644 index 00000000..3ac959ac --- /dev/null +++ b/_content/tour/grc/error-handling/example2.go @@ -0,0 +1,50 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use error variables to help the +// caller determine the exact error being returned. +package main + +import ( + "errors" + "fmt" +) + +var ( + // ErrBadRequest is returned when there are problems with the request. + ErrBadRequest = errors.New("Bad Request") + + // ErrPageMoved is returned when a 301/302 is returned. + ErrPageMoved = errors.New("Page Moved") +) + +func main() { + if err := webCall(true); err != nil { + switch err { + case ErrBadRequest: + fmt.Println("Bad Request Occurred") + return + + case ErrPageMoved: + fmt.Println("The Page moved") + return + + default: + fmt.Println(err) + return + } + } + + fmt.Println("Life is good") +} + +// webCall performs a web operation. +func webCall(b bool) error { + if b { + return ErrBadRequest + } + + return ErrPageMoved +} diff --git a/_content/tour/grc/error-handling/example3.go b/_content/tour/grc/error-handling/example3.go new file mode 100644 index 00000000..46fd5b93 --- /dev/null +++ b/_content/tour/grc/error-handling/example3.go @@ -0,0 +1,77 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// http://golang.org/src/pkg/encoding/json/decode.go +// Sample program to show how to implement a custom error type +// based on the json package in the standard library. +package main + +import ( + "fmt" + "reflect" +) + +// An UnmarshalTypeError describes a JSON value that was +// not appropriate for a value of a specific Go type. +type UnmarshalTypeError struct { + Value string // description of JSON value + Type reflect.Type // type of Go value it could not be assigned to +} + +// Error implements the error interface. +func (e *UnmarshalTypeError) Error() string { + return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() +} + +// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. +// (The argument to Unmarshal must be a non-nil pointer.) +type InvalidUnmarshalError struct { + Type reflect.Type +} + +// Error implements the error interface. +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "json: Unmarshal(nil)" + } + + if e.Type.Kind() != reflect.Ptr { + return "json: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "json: Unmarshal(nil " + e.Type.String() + ")" +} + +// user is a type for use in the Unmarshal call. +type user struct { + Name int +} + +func main() { + var u user + err := Unmarshal([]byte(`{"name":"bill"}`), u) // Run with a value and pointer. + if err != nil { + switch e := err.(type) { + case *UnmarshalTypeError: + fmt.Printf("UnmarshalTypeError: Value[%s] Type[%v]\n", e.Value, e.Type) + case *InvalidUnmarshalError: + fmt.Printf("InvalidUnmarshalError: Type[%v]\n", e.Type) + default: + fmt.Println(err) + } + return + } + + fmt.Println("Name:", u.Name) +} + +// Unmarshal simulates an unmarshal call that always fails. +func Unmarshal(data []byte, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + + return &UnmarshalTypeError{"string", reflect.TypeOf(v)} +} diff --git a/_content/tour/grc/error-handling/example4.go b/_content/tour/grc/error-handling/example4.go new file mode 100644 index 00000000..40f8303e --- /dev/null +++ b/_content/tour/grc/error-handling/example4.go @@ -0,0 +1,93 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Package example4 provides code to show how to implement behavior as context. +package example4 + +import ( + "bufio" + "fmt" + "io" + "log" + "net" +) + +// client represents a single connection in the room. +type client struct { + name string + reader *bufio.Reader +} + +// TypeAsContext shows how to check multiple types of possible custom error +// types that can be returned from the net package. +func (c *client) TypeAsContext() { + for { + line, err := c.reader.ReadString('\n') + if err != nil { + switch e := err.(type) { + case *net.OpError: + if !e.Temporary() { + log.Println("Temporary: Client leaving chat") + return + } + + case *net.AddrError: + if !e.Temporary() { + log.Println("Temporary: Client leaving chat") + return + } + + case *net.DNSConfigError: + if !e.Temporary() { + log.Println("Temporary: Client leaving chat") + return + } + + default: + if err == io.EOF { + log.Println("EOF: Client leaving chat") + return + } + + log.Println("read-routine", err) + } + } + + fmt.Println(line) + } +} + +// temporary is declared to test for the existence of the method coming +// from the net package. +type temporary interface { + Temporary() bool +} + +// BehaviorAsContext shows how to check for the behavior of an interface +// that can be returned from the net package. +func (c *client) BehaviorAsContext() { + for { + line, err := c.reader.ReadString('\n') + if err != nil { + switch e := err.(type) { + case temporary: + if !e.Temporary() { + log.Println("Temporary: Client leaving chat") + return + } + + default: + if err == io.EOF { + log.Println("EOF: Client leaving chat") + return + } + + log.Println("read-routine", err) + } + } + + fmt.Println(line) + } +} diff --git a/_content/tour/grc/error-handling/example5.go b/_content/tour/grc/error-handling/example5.go new file mode 100644 index 00000000..de920b11 --- /dev/null +++ b/_content/tour/grc/error-handling/example5.go @@ -0,0 +1,47 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show see if the class can find the bug. +package main + +import ( + "fmt" + "log" +) + +// customError is just an empty struct. +type customError struct{} + +// Error implements the error interface. +func (c *customError) Error() string { + return "Find the bug." +} + +// fail returns nil values for both return types. +func fail() ([]byte, *customError) { + return nil, nil +} + +func main() { + var err error + if _, err = fail(); err != nil { + log.Fatal("Why did this fail?") + } + + log.Println("No Error") +} + +// ============================================================================= + +func reason() { + var err error + fmt.Printf("Type of value stored inside the interface: %T\n", err) + + if _, err = fail(); err != nil { + fmt.Printf("Type of value stored inside the interface: %T\n", err) + } + + log.Println("No Error") +} diff --git a/_content/tour/grc/error-handling/example6.go b/_content/tour/grc/error-handling/example6.go new file mode 100644 index 00000000..ee990d3d --- /dev/null +++ b/_content/tour/grc/error-handling/example6.go @@ -0,0 +1,77 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how wrapping errors work with the stdlib. +package main + +import ( + "errors" + "fmt" +) + +// AppError represents a custom error type. +type AppError struct { + State int +} + +// Error implements the error interface. +func (ae *AppError) Error() string { + return fmt.Sprintf("App Error, State: %d", ae.State) +} + +// IsAppError checks if an error of type AppError exists. +func IsAppError(err error) bool { + var ae *AppError + return errors.As(err, &ae) +} + +// GetAppError returns a copy of the AppError pointer. +func GetAppError(err error) *AppError { + var ae *AppError + if !errors.As(err, &ae) { + return nil + } + return ae +} + +func main() { + + // Make the function call and validate the error. + if err := firstCall(10); err != nil { + + // Check if the error is an AppError. + if IsAppError(err) { + ae := GetAppError(err) + fmt.Printf("Is AppError, State: %d\n", ae.State) + } + + fmt.Print("\n********************************\n\n") + + // Display the error using the implementation of + // the error interface. + fmt.Printf("%v\n", err) + } +} + +// firstCall makes a call to a second function and wraps any error. +func firstCall(i int) error { + if err := secondCall(i); err != nil { + return fmt.Errorf("firstCall->secondCall(%d) : %w", i, err) + } + return nil +} + +// secondCall makes a call to a third function and wraps any error. +func secondCall(i int) error { + if err := thirdCall(); err != nil { + return fmt.Errorf("secondCall->thirdCall() : %w", err) + } + return nil +} + +// thirdCall create an error value we will validate. +func thirdCall() error { + return &AppError{99} +} diff --git a/_content/tour/grc/error-handling/exercise1.go b/_content/tour/grc/error-handling/exercise1.go new file mode 100644 index 00000000..0fb21e45 --- /dev/null +++ b/_content/tour/grc/error-handling/exercise1.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create two error variables, one called ErrInvalidValue and the other +// called ErrAmountTooLarge. Provide the static message for each variable. +// Then write a function called checkAmount that accepts a float64 type value +// and returns an error value. Check the value for zero and if it is, return +// the ErrInvalidValue. Check the value for greater than $1,000 and if it is, +// return the ErrAmountTooLarge. Write a main function to call the checkAmount +// function and check the return error value. Display a proper message to the screen. +package main + +// Add imports. + +var ( +// Declare an error variable named ErrInvalidValue using the New +// function from the errors package. + +// Declare an error variable named ErrAmountTooLarge using the New +// function from the errors package. +) + +// Declare a function named checkAmount that accepts a value of +// type float64 and returns an error interface value. +func checkAmount( /* parameter */ ) /* return arg */ { + + // Is the parameter equal to zero. If so then return + // the error variable. + + // Is the parameter greater than 1000. If so then return + // the other error variable. + + // Return nil for the error value. +} + +func main() { + + // Call the checkAmount function and check the error. Then + // use a switch/case to compare the error with each variable. + // Add a default case. Return if there is an error. + + // Display everything is good. +} diff --git a/_content/tour/grc/error-handling/exercise2.go b/_content/tour/grc/error-handling/exercise2.go new file mode 100644 index 00000000..59310aa9 --- /dev/null +++ b/_content/tour/grc/error-handling/exercise2.go @@ -0,0 +1,53 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create a custom error type called appError that contains three fields, err error, +// message string and code int. Implement the error interface providing your own message +// using these three fields. Implement a second method named temporary that returns false +// when the value of the code field is 9. Write a function called checkFlag that accepts +// a bool value. If the value is false, return a pointer of your custom error type +// initialized as you like. If the value is true, return a default error. Write a main +// function to call the checkFlag function and check the error using the temporary +// interface. +package main + +// Add imports. + +// Declare a struct type named appError with three fields, err of type error, +// message of type string and code of type int. + +// Declare a method for the appError struct type that implements the +// error interface. + +// Declare a method for the appError struct type named Temporary that returns +// true when the value of the code field is not 9. + +// Declare the temporary interface type with a method named Temporary that +// takes no parameters and returns a bool. + +// Declare a function named checkFlag that accepts a boolean value and +// returns an error interface value. +func checkFlag( /* parameter */ ) /* return arg */ { + + // If the parameter is false return an appError. + + // Return a default error. +} + +func main() { + + // Call the checkFlag function to simulate an error of the + // concrete type. + + // Check the concrete type and handle appropriately. + switch e := err.(type) { + + // Apply the case for the existence of the Temporary behavior. + // Log the error and write a second message only if the + // error is not temporary. + + // Apply the default case and just log the error. + } +} diff --git a/_content/tour/grc/exporting.article b/_content/tour/grc/exporting.article new file mode 100644 index 00000000..2c2626a0 --- /dev/null +++ b/_content/tour/grc/exporting.article @@ -0,0 +1,145 @@ +Exporting +Exporting provides the ability to declare if an identifier is accessible to code outside of the package it’s declared in. + +* Exporting + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Exporting provides the ability to declare if an identifier is accessible to code +outside of the package it’s declared in. + +** Code Review + +- *Example* *1:* Declare and access exported identifiers +- *Example* *2:* Declare unexported identifiers and restrictions +- *Example* *3:* Access values of unexported identifiers +- *Example* *4:* Unexported struct type fields +- *Example* *5:* Unexported embedded types + +.play exporting/example1.go +.play exporting/example2.go +.play exporting/example3.go +.play exporting/example4.go +.play exporting/example5.go + +** Exporting Mechanics + +A package is the basic unit of compiled code in Go. It represents a physical compiled +unit of code, usually as a compiled library on the host operating system. Exporting +determines access to identifiers across package boundaries. + + package counters + + type AlertCounter int + +In this case, since a capital letter is being used to name the type AlterCounter, +the type is exported and can be referenced directly by code outside of the +counters package. + + package counters + + type alertCounter int + +Now that you changed the type’s name to start with a lowercase letter, the type is +unexported. This means only code inside the counters package can reference this +type directly. + + package counters + + type alertCounter int + + func New(value int) alertCounter { + return alertCounter(value) + } + +Even though the code above is legal syntax and will compile, there is no value in it. +Returning a value of an unexported type is confusing since the caller (who will +probably exist in a different package) can’t reference the type name directly. + + package main + + import ( + "fmt" + + "github.com/ardanlabs/.../exporting/example3/counters" + ) + + func main() { + counter := counters.New(10) + fmt.Printf("Counter: %d\n", counter) + } + +In this case, the main function in package main calls the counters.New function +successfully and the compiler can declare and construct a variable of the unexported +type. This doesn't mean you should do this nor does it mean your're getting any real +protections for this. This should be avoided, and if New will return a value, it +should be of an exported type. + + package users + + type User struct { + Name string + ID int + + password string + } + +When it comes to fields in a struct, the first letter declares if the field is +accessible to code outside of the package it’s declared in. In this case, Name +and ID are accessible, but password is not. It’s an idiom to separate exported +and unexported fields in this manner if this is reasonable or practical to do. +Normally all fields would be one or the other. + + package users + + type user struct { + Name string + ID int + } + + type Manager struct { + Title string + user + } + +In this scenario, even though the user type is unexported, it has two exported +fields. This means that when the user type is embedded in the exported Manager +type, the user fields promote and are accessible. It’s common to have types that +are unexported with exported fields because the reflection package can only operate +on exported fields. Marshallers won’t work otherwise. + +The example creates a bad situation where code outside of package users can construct +a Manager, but since the embedded type user is unexported, the fields for those type +can be initialized. This creates partial construction problems that will lead to bugs. +You need to be consistent with exporting and unexporting. + +** Notes + +- Code in go is compiled into packages and then linked together. +- Identifiers are exported (or remain unexported) based on letter-case. +- We import packages to access exported identifiers. +- Any package can use a value of an unexported type, but this is annoying to use. + +** Extra Reading + +[[https://www.ardanlabs.com/blog/2014/03/exportedunexported-identifiers-in-go.html][Exported/Unexported Identifiers In Go]] - William Kennedy + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +*Part* *A:* Create a package named toy with a single exported struct type named Toy. +Add the exported fields Name and Weight. Then add two unexported fields named onHand +and sold. Declare a factory function called New to create values of type toy and accept +parameters for the exported fields. Then declare methods that return and update values +for the unexported fields. + +*Part* *B:* Create a program that imports the toy package. Use the New function to +create a value of type toy. Then use the methods to set the counts and display the +field values of that toy value. + +.play exporting/exercise1.go +.play exporting/answer1.go diff --git a/_content/tour/grc/exporting/answer1.go b/_content/tour/grc/exporting/answer1.go new file mode 100644 index 00000000..59f33b35 --- /dev/null +++ b/_content/tour/grc/exporting/answer1.go @@ -0,0 +1,93 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create a package named toy with a single exported struct type named Toy. Add +// the exported fields Name and Weight. Then add two unexported fields named +// onHand and sold. Declare a factory function called New to create values of +// type toy and accept parameters for the exported fields. Then declare methods +// that return and update values for the unexported fields. +// +// Create a program that imports the toy package. Use the New function to create a +// value of type toy. Then use the methods to set the counts and display the +// field values of that toy value. +package main + +import ( + "fmt" + + "play.ground/toy" +) + +func main() { + + // Create a value of type toy. + t := toy.New("Bat", 28) + + // Update the counts. + t.UpdateOnHand(100) + t.UpdateSold(2) + + // Display each field separately. + fmt.Println("Name", t.Name) + fmt.Println("Weight", t.Weight) + fmt.Println("OnHand", t.OnHand()) + fmt.Println("Sold", t.Sold()) +} + +// ----------------------------------------------------------------------------- +-- toy/toy.go -- + +// Package toy contains support for managing toy inventory. +package toy + +// Toy represents a toy we sell. +type Toy struct { + Name string + Weight int + + onHand int + sold int +} + +// New creates values of type toy. +func New(name string, weight int) *Toy { + return &Toy{ + Name: name, + Weight: weight, + } +} + +// OnHand returns the current number of this +// toy on hand. +func (t *Toy) OnHand() int { + return t.onHand +} + +// UpdateOnHand updates the on hand count and +// returns the current value. +func (t *Toy) UpdateOnHand(count int) int { + t.onHand += count + return t.onHand +} + +// Sold returns the current number of this +// toy sold. +func (t *Toy) Sold() int { + return t.sold +} + +// UpdateSold updates the sold count and +// returns the current value. +func (t *Toy) UpdateSold(count int) int { + t.sold += count + return t.sold +} + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/exporting/example1.go b/_content/tour/grc/exporting/example1.go new file mode 100644 index 00000000..6326f8d1 --- /dev/null +++ b/_content/tour/grc/exporting/example1.go @@ -0,0 +1,40 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to access an exported identifier. +package main + +import ( + "fmt" + + "play.ground/counters" +) + +func main() { + + // Create a variable of the exported type and initialize the value to 10. + counter := counters.AlertCounter(10) + + fmt.Printf("Counter: %d\n", counter) +} + +// ----------------------------------------------------------------------------- +-- counters/counters.go -- + +// Package counters provides alert counter support. +package counters + +// AlertCounter is an exported named type that +// contains an integer counter for alerts. +type AlertCounter int + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 + +replace "play.ground/counters" => ./counters diff --git a/_content/tour/grc/exporting/example2.go b/_content/tour/grc/exporting/example2.go new file mode 100644 index 00000000..5854e07b --- /dev/null +++ b/_content/tour/grc/exporting/example2.go @@ -0,0 +1,40 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to access an exported identifier. +package main + +import ( + "fmt" + + "play.ground/counters" +) + +func main() { + + // Create a variable of the exported type and initialize the value to 10. + counter := counters.alertCounter(10) + + // ./example2.go:16: undefined: counters.alertCounter + + fmt.Printf("Counter: %d\n", counter) +} + +// ----------------------------------------------------------------------------- +-- counters/counters.go -- + +// Package counters provides alert counter support. +package counters + +// alertCounter is an unexported named type that +// contains an integer counter for alerts. +type alertCounter int + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/exporting/example3.go b/_content/tour/grc/exporting/example3.go new file mode 100644 index 00000000..89e87cb0 --- /dev/null +++ b/_content/tour/grc/exporting/example3.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the program can access a value +// of an unexported identifier from another package. +package main + +import ( + "fmt" + + "play.ground/counters" +) + +func main() { + + // Create a variable of the unexported type using the exported + // New function from the package counters. + counter := counters.New(10) + + fmt.Printf("Counter: %d\n", counter) +} + +// ----------------------------------------------------------------------------- +-- counters/counters.go -- + +// Package counters provides alert counter support. +package counters + +// alertCounter is an unexported named type that +// contains an integer counter for alerts. +type alertCounter int + +// New creates and returns values of the unexported type alertCounter. +func New(value int) alertCounter { + return alertCounter(value) +} + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/exporting/example4.go b/_content/tour/grc/exporting/example4.go new file mode 100644 index 00000000..dba47fd1 --- /dev/null +++ b/_content/tour/grc/exporting/example4.go @@ -0,0 +1,50 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how unexported fields from an exported struct +// type can't be accessed directly. +package main + +import ( + "fmt" + + "play.ground/users" +) + +func main() { + + // Create a value of type User from the users package. + u := users.User{ + Name: "Chole", + ID: 10, + + password: "xxxx", + } + + // ./example4.go:21: unknown field password in struct literal of type users.User + + fmt.Printf("User: %#v\n", u) +} + +// ----------------------------------------------------------------------------- +-- users/users.go -- + +// Package users provides support for user management. +package users + +// User represents information about a user. +type User struct { + Name string + ID int + + password string +} + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/exporting/example5.go b/_content/tour/grc/exporting/example5.go new file mode 100644 index 00000000..f64ce5bc --- /dev/null +++ b/_content/tour/grc/exporting/example5.go @@ -0,0 +1,54 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to create values from exported types with +// embedded unexported types. +package main + +import ( + "fmt" + + "play.ground/users" +) + +func main() { + + // Create a value of type Manager from the users package. + u := users.Manager{ + Title: "Dev Manager", + } + + // Set the exported fields from the unexported user inner type. + u.Name = "Chole" + u.ID = 10 + + fmt.Printf("User: %#v\n", u) +} + +// ----------------------------------------------------------------------------- +-- users/users.go -- + +// Package users provides support for user management. +package users + +// User represents information about a user. +type user struct { + Name string + ID int +} + +// Manager represents information about a manager. +type Manager struct { + Title string + + user +} + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/exporting/exercise1.go b/_content/tour/grc/exporting/exercise1.go new file mode 100644 index 00000000..e90a1db2 --- /dev/null +++ b/_content/tour/grc/exporting/exercise1.go @@ -0,0 +1,62 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create a package named toy with a single exported struct type named Toy. Add +// the exported fields Name and Weight. Then add two unexported fields named +// onHand and sold. Declare a factory function called New to create values of +// type toy and accept parameters for the exported fields. Then declare methods +// that return and update values for the unexported fields. +// +// Create a program that imports the toy package. Use the New function to create a +// value of type toy. Then use the methods to set the counts and display the +// field values of that toy value. +package main + +import ( + "play.ground/toy" +) + +func main() { + + // Use the New function from the toy package to create a value of + // type toy. + + // Use the methods from the toy value to set some initialize + // values. + + // Display each field separately from the toy value. +} + +// ----------------------------------------------------------------------------- +-- toy/toy.go -- + +// Package toy contains support for managing toy inventory. +package toy + +// Declare a struct type named Toy with four fields. Name string, +// Weight int, onHand int and sold int. + +// Declare a function named New that accepts values for the +// exported fields. Return a pointer of type Toy that is initialized +// with the parameters. + +// Declare a method named OnHand with a pointer receiver that +// returns the current on hand count. + +// Declare a method named UpdateOnHand with a pointer receiver that +// updates and returns the current on hand count. + +// Declare a method named Sold with a pointer receiver that +// returns the current sold count. + +// Declare a method named UpdateSold with a pointer receiver that +// updates and returns the current sold count. + +// ----------------------------------------------------------------------------- +-- go.mod -- + +module "play.ground" + +go 1.21.0 diff --git a/_content/tour/grc/functions.article b/_content/tour/grc/functions.article new file mode 100644 index 00000000..81f730ec --- /dev/null +++ b/_content/tour/grc/functions.article @@ -0,0 +1,184 @@ +Συναρτήσεις +Οι συναrτήσεις βρίσκονται στον πυρήνα της γλώσσας και παρέχουν ένα μηχανισμό ομαδοποίησης και οργάνωσης του κώδικα σε διαφορετικά μέρη λειτουργικότητας. + +* Συναρτήσεις + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Παρακολουθήστε το Video]] +- Εαν Χρειάζεστε Οικονομική Συνδρομή, Χρησιμοποιείστε το σχετικό [[https://www.ardanlabs.com/scholarship/][Έγγραφο Υποτροφίας]] + +Οι συναρτήσεις βρίσκονται στον πυρήνα της γλώσσας και παρέχουν ένα μηχανισμό ομαδοποίησης +και οργάνωσης του κώδικα σε διαφορετικά μέρη λειτουργικότητας. Μπορούν να +χρησιμοποιηθούν για να παρέχουν ένα API στα πακέτα που γράφει κανείς και είναι ένα θεμελιώδες συστατικό στην +ταυτόχρονη εκτέλεση κώδικα. + +Οι συναρτήσεις στην Go παίζουν ένα σημαντικό ρόλο στην δομή του κώδικα και στην προαγωγή ξεκάθαρου, +συντηρήσιμου και αποτελεσματικού λογισμικού. Κατανοώντας πως δηλώνονται, πως ορίζονται και +πως χρησιμοποιούνται οι συναρτήσεις είναι σημαντικό για την συγγραφή αποτελεσματικών προγραμμάτων στην Go. + +** Επισκόπηση Κώδικα + +- *Παράδειγμα* *1:* Επιστροφή Πολλαπλών Τιμών +- *Παράδειγμα* *2:* Κενό Αναγνωριστικό +- *Παράδειγμα* *3:* Δηλώσεις Συναρτήσεων Ξανά +- *Παράδειγμα* *4:* Ανώνυμες Συναρτήσεις/Εγκολπώσεις +- *Παράδειγμα* *5:* Ανάκτηση ελέγχου μετά από Πανικούς + +.play functions/example1.go +.play functions/example2.go +.play functions/example3.go +.play functions/example4.go +.play functions/example5.go + +Οι συναρτήσεις είναι ένα θεμελιώδες μέρος των προγραμμάτων και χρησιμοποιούνται για την συλλογή +και την οργάνωση κώδικα για καλύτερη διάρθρωση και επαναχρησιμοποίηση. Οι συναρτήσεις είναι κομμάτια κώδικα +που επιτελούν συγκεγκριμένο εργο ή ένα σύνολο συνδεόμενων εργασιών. Παρακάτω ακολουθεί μια επισκόπηση +των συναρτήσεων στην Go: + +** Δηλώσεις Συναρτήσεων + +Για να δηλωθεί μια συνάρτηση στην Go, πρέπει κανείς να χρησιμοποιήσει την λέξη-κλειδί `func` ακολουθούμενη από +το όνομα της συνάρτησης, ένα κατάλογο παραμέτρων σε παρένθεση και έναν προαιρετικό +τύπο επιστροφής. Ο γενικός συντακτικός κανόνας είναι: + + func όνομαΣυνάρτησης(παράμετρος1 τύπος, παράμετρος2 τύπος, ...) τύπος_επιστροφής { + // Σώμα Συνάρτησης + } + +For example: + + func add(x int, y int) int { + return x + y + } + +** Παράμετροι και Τιμές Επιστροφής + +Οι συναρτήσεις μπορούν να πάρουν καμία ή περισσότερες παραμέτρους, οι οποίες είναι οι τιμές που δίνονται στην +συνάρτηση όταν καλείται. Κάθε παράμετρος αποτελείται από ένα όνομα και ένα τύπο. Στο +παράδειγμα παραπάνω, η `add` παίρνει δύο ακέραιες παραμέτρους, την `x` και την `y`. + +Οι συναρτήσεις επιστρέφουν καμία ή περισσότερες τιμές (όταν δεν επιστρέφουν καμία τιμή είναι δυνατόν να παραλειφθεί +ο τύπος επιστροφής). Η δήλωση `return` χρησιμοποιείται για να προσδιορίσει την τιμή επιστροφής. +Στο παράδειγμα παραπάνω, η `add` επιστρέφει έναν ακέραιο. + +Η Go επιτρέπει στις συναρτήσεις να επιστρέφουν πολλαπές τιμές. Αυτό είναι χρήσιμο σε περιπτώσεις που θέλει κανείς +να επιστρέψει περισσότερα του ενός αποτελέσματα από μια συνάρτηση. Για παράδειγμα: + + func divide(x, y float64) (float64, error) { + if y == 0 { + return 0, errors.New("division by zero") + } + return x / y, nil + } + +Σε αυτό το παράδειγμα, η συνάρτηση `divide` επιστρέφει τόσο ένα αποτέλεσμα κινητής υποδιαστολής όσο και +ένα σφάλμα (αν συμβεί διαίρεση με το μηδέν). + +Η Go επιτρέπει τον ορισμό ονομάτων για τις τιμές επιστροφής στην υπογραφή μιας συνάρτησης. +Οι επώνυμες τιμές επιστροφής παίρνουν αρχική τιμή αυτόματα και μπορούν να χρησιμοποιηθούν σαν κανονικές +μεταβλητές μέσα στη συνάρτηση. Είναι ιδιαίτερα χρήσιμες όταν πρέπει να διαχειριστεί κανείς +περίπλοκες συναρτήσεις ή αν πρέπει να διαχειριστεί σφάλματα. Για παράδειγμα: + + func divide(x, y float64) (result float64, err error) { + if y == 0 { + err = errors.New("division by zero") + return + } + result = x / y + return + } + +** Κλήσεις Συναρτήσεων + +Προκειμένου να καλέσει κανείς μια συνάρτηση στην Go, αρκεί να χρησιμοποιήσει το όνομα της συνάρτησης +ακολουθούμενο από ένα κατάλογο ορισμάτων σε παρενθέσεις. Αν η συνάρτηση έχει πολλαπλές τιμές επιστροφής +κανείς μπορέι να τις αποθηκεύσει σε μεταβλητές. Για παράδειγμα: + + sum := add(3, 5) // Κλήση της συνάρτησης add και ανάθεση του αποτελέσματος στην sum + result, err := divide(8, 2) // Κλήση της συνάρτησης divide και αποθήκευση τόσο του result + // όσο και του σφάλματος + +** Συναρτήσεις με Μεταβλητό Πλήθος Παραμέτρων + +Η Go υποστηρίζει συναρτήσεις με μεταβλητό πλήθος παραμέτρων, κάτι που επιτρέπει την ανάθεση μεταβλητού αριθμού +ορισμάτων σε μια συνάρτηση. Προκειμένου να οριστεί μια παράμετρος μεταβλητού μήκους, κανείς μπορεί να χρησιμοποιήσει +τρεις τελείες (`...`) μετά τον τύπο της παραμέτρου. Για παράδειγμα: + + func calculateSum(nums ...int) int { + sum := 0 + for _, num := range nums { + sum += num + } + return sum + } + +Η παραπάνω συνάρτηση μπορεί να κληθεί με οποιονδήποτε αριθμό ακεραίων. + +** Ανώνυμες Συναρτήσεις + +Η Go υποστηρίζει ανώνυμες συναρτήσεις, γνωστές και ως εγκολπώσεις. Αυτές είναι συναρτήσεις +χωρίς όνομα, και μπορούν να αποδωθούν σε μεταβλητές και να χρησιμοποιηθούν ως ορίσματα σε +άλλες συναρτήσεις. Οι εγκολπώσεις συχνά χρησιμοποιούνται στην Go για τον ορισμό ένθετων +συναρτήσεων ή για προγραμματισμό ταυτόχρονης εκτέλεσης με την χρήση goroutines. + + add := func(x, y int) int { + return x + y + } + + result := add(3, 5) + +** Συναρτήσεις ως Τύποι + +Στην Go, οι συναρτήσεις μπορούν επίσης να χρησιμοποιηθούν και ως τύποι. Αυτό επιτρέπει τον +ορισμό συναρτήσεων που παίρνουν άλλες συναρτήσεις ως ορίσματα ή τις επιστρέφουν ως αποτελέσματα. +Πρόκειται για ένα πολύ ισχυρό χαρακτηριστικό που βοηθάει στον ορισμό συναρτήσεων υψηλότερης τάξης +και συναρτήσεις που καλούν άλλες συναρτήσεις. + + type MathFunc func(int, int) int + + func operate(x, y int, op MathFunc) int { + return op(x, y) + } + +Μπορεί κανείς να περάσει συναρτήσεις σαν ορίσματα στην `operate`. + +** Αναβολή και Πανικός + +Η Go παρέχει δύο ειδικές προεγκατεστημένες συναρτήσεις, την `defer` και την `panic` για την διαχείριση +εξαιρετικών καταστάσεων και την διευθέτηση πόρων. Η `defer` χρησιμοποιείται για την εκτέλεση μιας συνάρτησης +αμέσως πριν επιστρέψει εκείνη που την περιέχει, ενώ η `panic` χρησιμοποιείται για την +πρόκληση ενός σφάλματος σταδίου εκτέλεσης και για το ξετύλιγμα της στοίβας. + +** Σημειώσεις + +- Οι συναρτήσεις μπορούν να επιστρέψουν πολλαπλές τιμές και οι περισσότερες επιστρέφουν μια τιμή σφάλματος. +- Το σφάλμα θα πρέπει να ελέγχεται πάντα ως μέρος της λογικής του προγράμματος. +- Το κενό αναγνωριστικό μπορεί να χρησιμοποιηθεί για να αγνοηθούν τιμές. +- Συντακτικός κανόνας: func, λήπτης μεθόδου, αναγνωριστικό, [παράμετροι], [επιστροφές], περιοχή κώδικα. +- Παράμετροι μεταβλητού πλήθους, ορίσματα μεταβλητού πλήθους και ξετύλιγμα ενος δυναμικού πίνακα. +- Οι συναρτήσεις είναι ένας τύπος: μπορούν να χρησιμοποιηθούν ως παράμετροι, ορίσματα και επιστροφές. +- Η defer χρησιμοποιείται για τον προγραμματισμό της κλήσης μιας συνάρτησης σε μια άλλη συνάρτηση. +- Η panic προκαλεί ένα σφάλμα σταδίου εκτέλεσης και ξετυλίγει την στοίβα. +- Οι επώνυμες επιστροφές δεν είναι ευανάγνωστες και δεν συναντώνται σε κώδικα χαρακτηριστικό της Go. + +** Πρόσθετα Αναγνώσματα + +- [[https://golang.org/doc/effective_go.html#functions][Αποτελεσματική Go]] +- [[https://www.ardanlabs.com/blog/2013/10/functions-and-naked-returns-in-go.html][Συναρτήσεις και Γυμνές επιστροφές στην Go]] +- [[https://www.ardanlabs.com/blog/2013/06/understanding-defer-panic-and-recover.html][Κατανοώντας την defer, τους πανικούς και την recover]] + +* Ασκήσεις + +Χρησιμοποιήστε το παρόν πρότυπο ως σημείο αναφοράς προκειμένου να ολοκληρώσετε τις +ασκήσεις. Σας παρέχεται μια πιθανή λύση. + +** Άσκηση 1 + +*Μέρος* *Α:* Δηλώστε έναν τύπο struct που διατηρεί πληροφορίες για ένα χρήστη. Δηλώστε μια +συνάρτηση που δημιουργεί μια τιμή και επιστρέφει δείκτες διεύθυνσης αυτού του τύπου και μια τιμή +σφάλματος. Καλέστε αυτή την συνάρτηση από την main και παρουσιάστε την τιμή. + +*Μέρος* *Β:* Κάντε μια δεύτερη κλήση στην συνάρτηση σας όμως αυτή τη φορά αγνοείστε την τιμή +και απλά ελέγξτε την τιμή σφάλματος. + +.play functions/exercise1.go +.play functions/answer1.go diff --git a/_content/tour/grc/functions/answer1.go b/_content/tour/grc/functions/answer1.go new file mode 100644 index 00000000..ba524152 --- /dev/null +++ b/_content/tour/grc/functions/answer1.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a struct type to maintain information about a user. Declare a function +// that creates value of and returns pointers of this type and an error value. Call +// this function from main and display the value. +// +// Make a second call to your function but this time ignore the value and just test +// the error value. +package main + +import "fmt" + +// user represents a user in the system. +type user struct { + name string + email string +} + +// newUser creates and returns pointers of user type values. +func newUser() (*user, error) { + return &user{"Bill", "bill@ardanlabs.com"}, nil +} + +func main() { + + // Create a value of type user. + u, err := newUser() + if err != nil { + fmt.Println(err) + return + } + + // Display the value. + fmt.Println(*u) + + // Call the function and just check the error on the return. + _, err = newUser() + if err != nil { + fmt.Println(err) + return + } +} diff --git a/_content/tour/grc/functions/example1.go b/_content/tour/grc/functions/example1.go new file mode 100644 index 00000000..7c0a4dc2 --- /dev/null +++ b/_content/tour/grc/functions/example1.go @@ -0,0 +1,56 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how functions can return multiple values while using +// named and struct types. +package main + +import ( + "encoding/json" + "fmt" +) + +// user is a struct type that declares user information. +type user struct { + ID int + Name string +} + +func main() { + + // Retrieve the user profile. + u, err := retrieveUser("sally") + if err != nil { + fmt.Println(err) + return + } + + // Display the user profile. + fmt.Printf("%+v\n", *u) +} + +// retrieveUser retrieves the user document for the specified +// user and returns a pointer to a user type value. +func retrieveUser(name string) (*user, error) { + + // Make a call to get the user in a json response. + r, err := getUser(name) + if err != nil { + return nil, err + } + + // Unmarshal the json document into a value of + // the user struct type. + var u user + err = json.Unmarshal([]byte(r), &u) + return &u, err +} + +// GetUser simulates a web call that returns a json +// document for the specified user. +func getUser(name string) (string, error) { + response := `{"id":1432, "name":"sally"}` + return response, nil +} diff --git a/_content/tour/grc/functions/example2.go b/_content/tour/grc/functions/example2.go new file mode 100644 index 00000000..083003b3 --- /dev/null +++ b/_content/tour/grc/functions/example2.go @@ -0,0 +1,68 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how we can use the blank identifier to +// ignore return values. +package main + +import ( + "encoding/json" + "errors" + "fmt" +) + +// user is a struct type that declares user information. +type user struct { + ID int + Name string +} + +// updateStats provides update stats. +type updateStats struct { + Modified int + Duration float64 + Success bool + Message string +} + +func main() { + + // Declare and initialize a value of type user. + u := user{ + ID: 1432, + Name: "Betty", + } + + // Update the user Name. Don't care about the update stats. + if _, err := updateUser(&u); err != nil { + fmt.Println(err) + return + } + + // Display the update was Successful. + fmt.Println("Updated user record for ID", u.ID) +} + +// updateUser updates the specified user document. +func updateUser(u *user) (*updateStats, error) { + + // response simulates a JSON response. + response := `{"Modified":1, "Duration":0.005, "Success" : true, "Message": "updated"}` + + // Unmarshal the json document into a value of + // the userStats struct type. + var us updateStats + if err := json.Unmarshal([]byte(response), &us); err != nil { + return nil, err + } + + // Check the update status to verify the update + // was Successful. + if us.Success != true { + return nil, errors.New(us.Message) + } + + return &us, nil +} diff --git a/_content/tour/grc/functions/example3.go b/_content/tour/grc/functions/example3.go new file mode 100644 index 00000000..98f12840 --- /dev/null +++ b/_content/tour/grc/functions/example3.go @@ -0,0 +1,50 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// From Spec: +// a short variable declaration may redeclare variables provided they +// were originally declared earlier in the same block with the same +// type, and at least one of the non-blank variables is new. + +// Sample program to show some of the mechanics behind the +// short variable declaration operator redeclares. +package main + +import "fmt" + +// user is a struct type that declares user information. +type user struct { + id int + name string +} + +func main() { + + // Declare the error variable. + var err1 error + + // The short variable declaration operator will + // declare u and redeclare err1. + u, err1 := getUser() + if err1 != nil { + return + } + + fmt.Println(u) + + // The short variable declaration operator will + // redeclare u and declare err2. + u, err2 := getUser() + if err2 != nil { + return + } + + fmt.Println(u) +} + +// getUser returns a pointer of type user. +func getUser() (*user, error) { + return &user{1432, "Betty"}, nil +} diff --git a/_content/tour/grc/functions/example4.go b/_content/tour/grc/functions/example4.go new file mode 100644 index 00000000..6eca9b21 --- /dev/null +++ b/_content/tour/grc/functions/example4.go @@ -0,0 +1,42 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how anonymous functions and closures work. +package main + +import "fmt" + +func main() { + var n int + + // Declare an anonymous function and call it. + func() { + fmt.Println("Direct:", n) + }() + + // Declare an anonymous function and assign it to a variable. + f := func() { + fmt.Println("Variable:", n) + } + + // Call the anonymous function through the variable. + f() + + // Defer the call to the anonymous function till after main returns. + defer func() { + fmt.Println("Defer 1:", n) + }() + + // Set the value of n to 3 before the return. + n = 3 + + // Call the anonymous function through the variable. + f() + + // Defer the call to the anonymous function till after main returns. + defer func() { + fmt.Println("Defer 2:", n) + }() +} diff --git a/_content/tour/grc/functions/example5.go b/_content/tour/grc/functions/example5.go new file mode 100644 index 00000000..97b6f474 --- /dev/null +++ b/_content/tour/grc/functions/example5.go @@ -0,0 +1,67 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to recover from panics. +package main + +import ( + "fmt" + "runtime" +) + +func main() { + + // Call the testPanic function to run the test. + if err := testPanic(); err != nil { + fmt.Println("Error:", err) + } +} + +// testPanic simulates a function that encounters a panic to +// test our catchPanic function. +func testPanic() (err error) { + + // Schedule the catchPanic function to be called when + // the testPanic function returns. + defer catchPanic(&err) + + fmt.Println("Start Test") + + // Mimic a traditional error from a function. + err = mimicError("1") + + // Trying to dereference a nil pointer will cause the + // runtime to panic. + var p *int + *p = 10 + + fmt.Println("End Test") + return err +} + +// catchPanic catches panics and processes the error. +func catchPanic(err *error) { + + // Check if a panic occurred. + if r := recover(); r != nil { + fmt.Println("PANIC Deferred") + + // Capture the stack trace. + buf := make([]byte, 10000) + runtime.Stack(buf, false) + fmt.Println("Stack Trace:", string(buf)) + + // If the caller wants the error back provide it. + if err != nil { + *err = fmt.Errorf("%v", r) + } + } +} + +// mimicError is a function that simulates an error for +// testing the code. +func mimicError(key string) error { + return fmt.Errorf("Mimic Error : %s", key) +} diff --git a/_content/tour/grc/functions/exercise1.go b/_content/tour/grc/functions/exercise1.go new file mode 100644 index 00000000..4ce1b034 --- /dev/null +++ b/_content/tour/grc/functions/exercise1.go @@ -0,0 +1,33 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a struct type to maintain information about a user. Declare a function +// that creates value of and returns pointers of this type and an error value. Call +// this function from main and display the value. +// +// Make a second call to your function but this time ignore the value and just test +// the error value. +package main + +// Add imports. + +// Declare a type named user. + +// Declare a function that creates user type values and returns a pointer +// to that value and an error value of nil. +func funcName() /* (pointer return arg, error return arg) */ { + + // Create a value of type user and return the proper values. +} + +func main() { + + // Use the function to create a value of type user. Check + // the error being returned. + + // Display the value that the pointer points to. + + // Call the function again and just check the error. +} diff --git a/_content/tour/grc/generics-basics.article b/_content/tour/grc/generics-basics.article new file mode 100644 index 00000000..19181622 --- /dev/null +++ b/_content/tour/grc/generics-basics.article @@ -0,0 +1,114 @@ +Basics +Learn how to write a basic generic print function. + +* Generics - Basics + +Learn how to write a basic generic print function. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Concrete implementation of print +- *Example* *2*: Type Assertion implementation of print +- *Example* *3*: Reflection implementation of print +- *Example* *4*: Generic implementation of print + +.play generics/basics/example1.go +.play generics/basics/example2.go +.play generics/basics/example3.go +.play generics/basics/example4.go + +** Explained + +If you want to write a single print function that can output a slice of any given type +and not use reflection, you can use the new generics syntax. + + func print[T any](slice []T) { + fmt.Print("Generic: ") + + for _, v := range slice { + fmt.Print(v, " ") + } + + fmt.Print("\n") + } + +This is an implementation of a single print function that can output a slice of any +given type using the new generics syntax. What’s nice about this syntax is that the +code inside the function can use syntax and built-in functions that would work with +a concrete type. This is not the case when you use the empty interface to write generic +code. + +There needs to be a way to tell the compiler that you won’t be declaring type T explicitly, but it +has to be determined by the compiler at compile time. The new syntax uses square +brackets for this. The brackets define a list of generic type identifiers that represent +types specific to the function that need to be determined at compile time. It’s how you +tell the compiler that types with these names won’t be declared before the program +is compiled. These types need to be figured out at compile time. + +Note: You can have multiple type identifiers defined inside the brackets though the +current example is only using one. Ex. [T, S, R any] + +You can name these type identifiers anything you want to help with the readability of the +code. In this case, the code is using the capital letter T to describe that a slice of some type +T (to be determined at compile time) will be passed in. It's an idiom to use a single +capitalized letters when it comes to collections and it’s also a convention that goes +back to older programming languages like C++ and Java. + +There is the use of the word any inside the brackets as well. This represents a +constraint on what type T can be. The compiler requires that all generic types have +a well defined constraint. The any constraint is predeclared by the compiler and +states there are no constraints on what type T can be. + + numbers := []int{1, 2, 3} + print[int](numbers) + + strings := []string{"A", "B", "C"} + print[string](strings) + + floats := []float64{1.7, 2.2, 3.14} + print[float64](floats) + +This is how to make calls to the generic print function where the type information +for T is explicitly provided at the call site. The syntax emulates the idea that the +function declaration func name[T any](slice []T) defines two sets of parameters. +The first set is the type that maps to the corresponding type identifiers, and the +second is the data that maps to the corresponding input variables. + +Luckily, the compiler can infer the type and eliminate the need to explicitly pass in +the type information at the call site. + + numbers := []int{1, 2, 3} + print(numbers) + + strings := []string{"A", "B", "C"} + print(strings) + + floats := []float64{1.7, 2.2, 3.14} + print(floats) + +This code shows how you can call the generic print functions without the need to pass +the type information explicitly. At the function call site, the compiler is able to +identify the type to use for T and construct a concrete version of the function to +support slices of that type. The compiler has the ability to infer the type with the +information it has at the call site from the data being passed in. + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Implement a generic function named marshal that can accept any value and +marshal that value into JSON, returning the JSON and an error. Declare +a struct type named User with two fields, Name and Age. Then construct a +value of the User type and pass the value to the generic marshal function. + +.play generics/basics/exercise1.go +.play generics/basics/answer1.go diff --git a/_content/tour/grc/generics-behavior-constraints.article b/_content/tour/grc/generics-behavior-constraints.article new file mode 100644 index 00000000..e90152df --- /dev/null +++ b/_content/tour/grc/generics-behavior-constraints.article @@ -0,0 +1,107 @@ +Behavior As Constraint +Every generic type requires a constraint to be declared so the compiler knows what concrete type substitutions it can accept or reject at compile time. + +* Generics - Behavior As Constraint + +Every generic type requires a constraint to be declared so the compiler knows what +concrete type substitutions it can accept or reject at compile time. This is required +even if there is no real constraint on what the generic type can be, hence the +predeclared constraint identifier any. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Concrete stringify function +- *Example* *2*: Type assertion stringify function +- *Example* *3*: Interface stringify function +- *Example* *4*: Generic stringify function + +.play generics/behavior-constraints/example1.go +.play generics/behavior-constraints/example2.go +.play generics/behavior-constraints/example3.go +.play generics/behavior-constraints/example4.go + +** Explained + +Interesting enough, the concept of a constraint already exists in the language. + + type User struct { + name string + } + + func (u User) String() string { + return u.name + } + + type Stringer interface { + String() string + } + + func Concrete(u User) { + u.String() + } + + func Polymorphic(s Stringer) { + s.String() + } + +The code defines a concrete type named User and implements a method named +String that returns the user’s name. Then an interface type is declared named +Stringer, which declares one act of behavior String, which returns a string. Thanks +to the method declared for User, you can say that the concrete type User implements +the Stringer interface using value semantics. + +The Concrete function is just that, a function that accepts concrete data based on +what it is. The Polymorphic is just that as well, a function that accepts concrete data +based on what it can do. This is the primary difference between a concrete and +polymorphic function. One is limited to one type of data, the other isn’t. However, +there is a constraint on what concrete data can be passed into the polymorphic +function. + +The Stringer interface defines that constraint by declaring a method set of behavior +that concrete data must be able to exhibit. When applied as the input type, the +compiler can guarantee the behavioral constraint is met every time the function is +called. + +There are generic functions that will require the same type of behavioral constraint. + + func stringify[T fmt.Stringer](slice []T) []string { + ret := make([]string, 0, len(slice)) + + for _, value := range slice { + ret = append(ret, value.String()) + } + + return ret + } + +Here is the generic function stringify. It accepts a slice of some type T and returns a +slice of string values that contain a stringified version of each value from the input +collection. The key to making this function work is the method call to String against +each value of type T. + +The problem is that the compiler needs to know and verify that values of type T do +have a method named String. When the generic type T is declared, the fmt.Stringer +interface is provided as the constraint. The compiler now knows to check any type +substitution and data being passed into the function for this method set of behavior. + +This is excellent because the interface is being used again for the same purpose and +the language doesn’t need a new keyword. + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Implement a generic function named marshal that can marshal JSON but only +accepts values that implement the json.Marshaler interface. + +.play generics/behavior-constraints/exercise1.go +.play generics/behavior-constraints/answer1.go diff --git a/_content/tour/grc/generics-channels.article b/_content/tour/grc/generics-channels.article new file mode 100644 index 00000000..ca06ce54 --- /dev/null +++ b/_content/tour/grc/generics-channels.article @@ -0,0 +1,178 @@ +Channels +Explore how the Go team could add a package of concurrency patterns into the standard library thanks to generics. + +* Generics - Channels + +Explore how the Go team could add a package of concurrency patterns into the standard library thanks to generics. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Work Function +- *Example* *2*: Pooling + +.play generics/channels/example1.go +.play generics/channels/example2.go + +** Explained + +This would require declaring channels and functions using generic types. + + type workFn[Result any] func(context.Context) Result + +In this example, a type is declared that represents a function which accepts a context +and returns a value of generic type Result. This function declaration describes a +function that implements the concurrent work that will be performed and the result +of that work. + + func doWork[Result any](ctx context.Context, work workFn[Result]) chan Result { + ch := make(chan Result, 1) + + go func() { + ch <- work(ctx) + fmt.Println("doWork : work complete") + }() + + return ch + } + +Now write a function named doWork that executes the specified work function +concurrently and returns a channel so the caller can receive the result of the work +performed by the work function. A generic type named Result is declared to +represent the return type for the work function and the type for the channel. + +In the implementation of the doWork function, a buffered channel of one is +constructed of generic type Result. That’s the channel returned to the caller to +receive the result of the concurrent work. In the middle of the function, a goroutine +is constructed to execute the work function concurrently. Once the work function +returns, the return argument is sent back to the caller through the channel. + +To test the use of the doWork function, build a small program. + + func main() { + duration := 100 * time.Millisecond + + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + dwf := func(ctx context.Context) string { + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + return "work complete" + } + + result := doWork(ctx, dwf) + + select { + case v := <-result: + fmt.Println("main:", v) + case <-ctx.Done(): + fmt.Println("main: timeout") + } + } + +Output: + + doWork : work complete + main: work complete + +The program starts by declaring a context that will timeout in 100 milliseconds. +Then a work function is declared that waits for up to 200 milliseconds before +returning the string, "work complete". With the context and the work function in +place, a call to doWork is made and a channel of type string is returned and +assigned to the variable result. + +The compiler is able to determine the concrete type to use for the generic type +Result by inspecting the return type of the literal work function that is passed into +the doWork function. This is brilliant because it means you didn’t have to pass the type +in on the call to doWork. + +With the channel of type string assigned to the variable result, a select case is used +to wait for the result to be returned on time, or for the timeout to occur. The +doWork function can be used to perform this concurrent work for any concrete type +required. + +This same idea could be applied to a pool of goroutines that could execute work on +a generic input and return a generic result. + +type workFn[Input any, Result any] func(input Input) Result + +In this example, change the function type to accept a generic input and return a +generic result. + + func poolWork[Input any, Result any](size int, work workFn[Input, Result]) (chan Input, func()) { + var wg sync.WaitGroup + wg.Add(size) + + ch := make(chan Input) + + for i := 0; i < size; i++ { + go func() { + defer wg.Done() + for input := range ch { + result := work(input) + fmt.Println("pollWork :", result) + } + }() + } + + cancel := func() { + close(ch) + wg.Wait() + } + + return ch, cancel + } + +In the poolWork function, the same two generic types are declared to represent the +input and return type for the work function. A WaitGroup is constructed to manage +the lifecycle of the Goroutines in the pool. Then a channel is constructed of the +generic Input type. This channel is used by the Goroutines in the pool to receive the +input data for the work function. + +Then the pool of Goroutines are created with each Goroutine waiting in a receive +operation using a for-range loop against the channel. Finally, a cancel function is +constructed to allow the caller to shutdown the pool and wait for all the Goroutines +to signal they have terminated. + +To test the use of the poolWork function, built a second small program. + + func main() { + size := runtime.GOMAXPROCS(0) + + pwf := func(input int) string { + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + return fmt.Sprintf("%d : received", input) + } + + ch, cancel := poolWork(size, pwf) + defer cancel() + + for i := 0; i < 4; i++ { + ch <- i + } + } + +Output: + + pollWork : 3 : received + pollWork : 2 : received + pollWork : 1 : received + pollWork : 0 : received + +The size of the pool is calculated based on the number of Goroutines that can run in +parallel. Then a work function is constructed to sleep for a random amount of time +and then return a string that represents the input. + +With that in place, the poolWork function is executed and the channel and cancel +function returned. The cancel function is deferred and a loop is constructed to send +4 values into the pool. The output will be different each time you run the program +since this work is happening concurrently. + +These little examples provide some insight into how a concurrent package could be +implemented. diff --git a/_content/tour/grc/generics-hash-table.article b/_content/tour/grc/generics-hash-table.article new file mode 100644 index 00000000..97ab2e3d --- /dev/null +++ b/_content/tour/grc/generics-hash-table.article @@ -0,0 +1,349 @@ +Hash Tables +A hash table is a classic example of a container type that can take real advantage of generics. + +* Generics - Hash Tables + +A hash table is a classic example of a container type that can take real advantage of +generics. This implementation was coded by Matt Layher (@mdlayer) in a blog post +he wrote. It's a great example of what is possible with generics. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Hash Table + +.play generics/hash-table/example1.go + +** Explained + +This code is a bit more complex than what you have so far. It’s what you can +expect to see from real world implementations. Throughout this section, you will +see two views of the code. One before and after applying the new syntax for +generics. + + type hashFunc func(key K, buckets int) int + +This type declares a hash function signature that is used by the hash table to +calculate a bucket position for data storage and retrieval. The user must implement +and provide this function when constructing a hash table. The function accepts a +key and the number of buckets it can choose from. Since you want this system to be +generic in terms of the types used for the key and value, you declare a parameter +named key with a type of the single capital letter K. + +Next, you can apply the generics syntax to make K an actual generic type. + + type hashFunc[K comparable] func(key K, buckets int) int <-- CHANGED + +After the type name, add the square brackets with the generic type K and a +constraint of comparable. Since values of the key type need to be used in a +compare operation, Documenting this now makes sense, even if the +implementation of the hash function doesn’t require it. Consistency is everything in +terms of readability, comprehension, and maintainability over time. + +This type represents a key/value pair of data that will be stored by the hash table. + + type keyValuePair struct { + Key K + Value V + } + +The job of this type is to hold the actual data with the corresponding key. Next the +code declares a key field of type K, and a value field of type V. + +Now you can apply the generics syntax to make K and V an actual generic type. + + type keyValuePair[K comparable, V any] struct { <-- CHANGED + Key K + Value V + } + +After the type name, add the square brackets with the generic types K and V. In +this declaration, K represents the key as before and V represents a value, which can +be anything. + +This type represents a hash table that manages a hash function and a set of +buckets for key/value data storage. + + type Table struct { + hashFunc hashFunc + buckets int + data [][]keyValuePair + } + +The Table type has three fields, a hash function, the number of buckets, and the +data which is represented as a slice of a slice of key/value pairs. The outer slice +represents buckets and the inner slice represents key/value pairs that are stored +inside a bucket. + +Now apply the generics syntax to declare the key and value generic types and +apply them to the field declarations. + + type Table[K comparable, V any] struct { <-- CHANGED + hashFunc hashFunc[K] <-- CHANGED + buckets int + data [][]keyValuePair[K, V] <-- CHANGED + } + +After the type name, add the square brackets with the generic types K and V. The +hashFunc type declaration requires information about the concrete type to use for +the key. The keyValuePair type declaration requires information about the concrete +type for the key and value. + +This is a factory function that can construct a Table for use. + + func New( + buckets int, + hf hashFunc + ) *Table { + + return &Table{ + hashFunc: hf, + buckets: buckets, + data: make([][]keyValuePair, buckets), + } + } + +The factory function accepts the number of buckets to manage and a hash function +for selecting a bucket for data storage and lookup. When a Table value is +constructed, the number of buckets is used to construct the slice, setting the length +of the outer slice to the number of buckets that will be used. + +Now apply the generics syntax to declare the key and value generic types and +apply them to the types that need to be constructed. + + func New[K comparable, V any]( <-- CHANGED + buckets int, + hf hashFunc[K], <-- CHANGED + ) *Table[K, V] { <-- CHANGED + + return &Table[K, V]{ <-- CHANGED + hashFunc: hf, + buckets: buckets, + data: make([][]keyValuePair[K, V], buckets), <-- CHANGED + } + } + +After the type name, add the square brackets and the generic types K and V. Then +K is applied to the hf input parameter to complete the hashFunc type declaration. +The K and V types are applied to the Table type being constructed and returned. +Finally, the initialization of the data field requires K and V to be applied to the +construction syntax for the keyValuePair type. + +This is a method that can insert values into the hash table based on a specified key. + + type Table[K comparable, V any] struct { + hashFunc hashFunc[K] + buckets int + table [][]keyValuePair[K, V] + } + + func (t *Table) Insert(key K, value V) { + bucket := t.hashFunc(key, t.buckets) + for idx, kvp := range t.table[bucket] { + if key == kvp.Key { + t.table[bucket][idx].Value = value + return + } + } + + kvp := keyValuePair{ + Key: key, + Value: value, + } + t.table[bucket] = append(t.table[bucket], kvp) + } + +The Insert method is declared to accept a key and value of the same generic types +that are declared with the Table type. The first step of inserting is to identify the +bucket to use for storage. That is performed by calling the hash function with the +specified key. The hash function returns an integer value that represents the bucket +to use. + +Then the function checks to see if the specified key has already been used to store +a value in the selected bucket. This is performed by ranging over the existing set of +key/value pairs in the bucket. If the key already exists, the value for that key is +updated. If the key is not found, then a new key/value pair value is constructed, +initialized, and appended to the slice for the selected bucket. + +Now apply the generics syntax to declare the key and value generic types and +apply them to the types that need to be constructed. + + func (t *Table[K, V]) Insert(key K, value V) { <-- CHANGED + bucket := t.hashFunc(key, t.buckets) + for idx, kvp := range t.table[bucket] { + if key == kvp.Key { + t.table[bucket][idx].Value = value + return + } + } + + kvp := keyValuePair[K, V]{ <-- CHANGED + Key: key, + Value: value, + } + t.table[bucket] = append(t.table[bucket], kvp) + } + +After the type name in the receiver, add the square brackets and the generic +types K and V. The only other change is to apply K and V to the construction syntax +of the keyValuePair type. + +This is a method that can retrieve values from the hash table based on a specified +key. + + func (t *Table) Retrieve(key K) (V, bool) { + bucket := t.hashFunc(key, t.buckets) + for idx, kvp := range t.data[bucket] { + if key == kvp.Key { + return t.data[bucket][idx].Value, true + } + } + + var zero V + return zero, false + } + +The Retrieve method is declared to accept a key and return a copy of the value +stored for that key. The first step of retrieving is to identify the bucket that was +used for storage. That is performed by calling the hash function with the specified +key. The hash function returns an integer value that represents the bucket to look +at. + +Then the function iterates over the collection of key/value pairs stored inside the +bucket, looking for the specified key one by one. If the key is found, a copy of the +value is returned and true is provided to the caller. If the key is not found, zero +value is returned and false is provided to the caller. + +Now apply the generics syntax to declare the key and value generic types and +apply them to the types that need to be constructed. + + func (t *Table[K, V]) Get(key K) (V, bool) { <-- CHANGED + bucket := t.hashFunc(key, t.buckets) + for idx, kvp := range t.data[bucket] { + if key == kvp.Key { + return t.data[bucket][idx].Value, true + } + } + + var zero V + return zero, false + } + +After the type name in the receiver, add the square brackets and the generic +types K and V. No other code changes are required. + +This is a small program to test the hash table implementation. + + func main() { + const buckets = 8 + . . . + } + +Start with a constant that defines the number of buckets to use in the hash table. + + import ( + "hash/fnv" + ) + + func main() { + . . . + + hashFunc1 := func(key string, buckets int) int { + h := fnv.New32() + h.Write([]byte(key)) + return int(h.Sum32()) % buckets + } + + . . . + } + +Next, declare a hash function that declares a string for the key. The +implementation uses the fnv package from the standard library which implements +the FNV-1 and FNV-1a, non-cryptographic hash functions created by Glenn Fowler, +Landon Curt Noll, and Phong Vo. FNV stands for the Fowler-Noll-Vo hash function. + +The modulus operation with the buckets value forces the final value to fall within +the range for the number of buckets. + + import ( + "hash/fnv" + ) + + func main() { + . . . + + table1 := New[/*key*/ string, /*value*/ int](buckets, hashFunc1) + + . . . + } + +Next, construct a hash table, explicitly stating that the key will be of type string +and the value of type int. There is nothing in the input parameters that can help the +compiler infer this information. + +To show the nature of the hash table being generic, defined a second hash +function and table. + + import ( + "hash/fnv" + ) + + func main() { + . . . + + hashFunc2 := func(key int, buckets int) int { + return key % buckets + } + + table2 := New[/*key*/ int, /*value*/ string](buckets, hashFunc2) + + . . . + } + +This hash function declares an integer for the key and performs a simple modulus +operation with the bucket value against the key. Then a new table is constructed +where the key is specified to be an integer and the value a string. The reverse of +the first table. + + + import ( + "hash/fnv" + ) + + func main() { + . . . + + words := []string{"foo", "bar", "baz"} + for i, word := range words { + table1.Insert(word, i) + table2.Insert(i, word) + } + + for i, s := range append(words, "nope!") { + v1, ok1 := table1.Retrieve(s) + fmt.Printf("t1.Rtr(%v) = (%v, %v)\n", s, v1, ok1) + + v2, ok2 := table2.Retrieve(i) + fmt.Printf("t2.Rtr(%v) = (%v, %v)\n", i, v2, ok2) + } + } + +Output: + + t1.Rtr(foo) = (0, true) + t2.Rtr(0) = (foo, true) + t1.Rtr(bar) = (1, true) + t2.Rtr(1) = (bar, true) + t1.Rtr(baz) = (2, true) + t2.Rtr(2) = (baz, true) + t1.Rtr(nope!) = (0, false) + t2.Rtr(3) = (, false) + +Finally write some code to store and retrieve values from the two respective +tables. diff --git a/_content/tour/grc/generics-multi-type-params.article b/_content/tour/grc/generics-multi-type-params.article new file mode 100644 index 00000000..793aa712 --- /dev/null +++ b/_content/tour/grc/generics-multi-type-params.article @@ -0,0 +1,36 @@ +Multi-Type Parameters +You're not restricted to using just one generic type at a time. + +* Generics - Multi-Type Parameters + +You're not restricted to using just one generic type at a time. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Print function + +.play generics/multi-type-params/example1.go + +** Explained + + func Print[L any, V fmt.Stringer](labels []L, vals []V) { + for i, v := range vals { + fmt.Println(labels[i], v.String()) + } + } + +The Print function accepts a collection of some type L and a collection of some type +V. Type L can be anything, but type V is constrained to values that know how to +String. The collection of some type V is iterated over and printed with the +corresponding label from the collection of type L. + +The name of the generic type can be anything. The naming convention for generic +types is something that needs to be better defined for best practices. For now, try +to stick to single letter capital letters when it works for readability. diff --git a/_content/tour/grc/generics-slice-constraints.article b/_content/tour/grc/generics-slice-constraints.article new file mode 100644 index 00000000..c44eaa4e --- /dev/null +++ b/_content/tour/grc/generics-slice-constraints.article @@ -0,0 +1,126 @@ +Slice Constraints +There may be times where you need to constrain the generic type to be only a slice. + +* Generics - Slice Constraints + +There may be times where you need to constrain the generic type to be only a slice. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Operate function + +.play generics/slice-constraints/example1.go + +** Explained + +Here the user-defined Numbers type has an underlying type that is a slice of +integers. + + type Numbers []int + +The compiler allows you to convert variables based on a slice of integers +with variables of type Numbers. This is usually good and what you want. +Because of this functionality, you can write a generic function that can operate on a +slice respecting the underlying type. + + type operateFunc[T any] func(t T) T + + func operate[T any](slice []T, fn operateFunc[T]) []T { + ret := make([]T, len(slice)) + for i, v := range slice { + ret[i] = fn(v) + } + + return ret + } + +Here the operate function declares a generic type T that can be anything. The type +is used to declare a parameter named slice that accepts a slice of that same type T. +The function also accepts a generic function of the same type T and returns a slice +of T as well. + + type Numbers []int + + func Double(n Numbers) Numbers { + fn := func(n int) int { + return 2 * n + } + + numbers := operate(n, fn) + fmt.Printf("%T", numbers) + return numbers + } + +Output: + + []int + +The Double function accepts a value of type Numbers and passes that value to the +operate function. In this case, the compiler leverages the underlying type in for type +T and the Numbers value can be passed into the function. However, what is +returned is a slice of type int, as seen in the output. + +If you need to make sure that only a Numbers value can be passed in, and is returned +by the operate function, you can make the following changes. + + type Slice[T any] interface { + ~ []T + } + +This interface declares a constraint to restrict a generic type to an actual slice of +some type T. The use of the approximation element ~ restricts to all types whose +underlying type is T. With this interface, you can change the operate function. + + type operateFunc[T any] func(t T) T + type Slice[T any] interface { + ~ []T + } + + // func operate[T any](slice []T, fn operateFunc[T]) []T { + // ret := make([]T, len(slice)) + // for i, v := range slice { + // ret[i] = fn(v) + // } + // return ret + // } + + func operate[S Slice[T], T any](slice S, fn operateFunc[T]) S { + ret := make(S, len(slice)) + for i, v := range slice { + ret[i] = fn(v) + } + + return ret + } + +Now change the operate function to declare two generic types. Type S which represents +a slice value of some type T, and T which is a type that can be anything. The +function returns a value of type S. + + type Numbers []int + + func Double(n Numbers) Numbers { + fn := func(n int) int { + return 2 * n + } + + numbers := operate(n, fn) + fmt.Printf("%T", numbers) + + return numbers + } + +Output: + + main.Numbers + +This time when you pass the Numbers value into the operate function, the slice that is +returned is of type Numbers. The underlying type is ignored and the user-defined +type is respected. diff --git a/_content/tour/grc/generics-struct-types.article b/_content/tour/grc/generics-struct-types.article new file mode 100644 index 00000000..50fb141d --- /dev/null +++ b/_content/tour/grc/generics-struct-types.article @@ -0,0 +1,143 @@ +Struct Types +You can declare a generic type using a struct type. + +* Generics - Struct Types + +You can declare a generic type using a struct type. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Linked List + +.play generics/struct-types/example1.go + +** Explained + +What if you wanted to declare your own generic type using a struct type? + + type node[T any] struct { + Data T + next *node[T] + prev *node[T] + } + +This struct type is declared to represent a node for the linked list. Each node +contains an individual piece of data that is stored and managed by the list. The use +of square brackets declares that type T is a generic type to be determined at +compile time. The use of the constraint "any" describes there is no constraint on +what type T can become. + +With type T declared, the Data field can now be defined as a field of some type T to +be determined later. The next and prev fields need to point to a node of that same +type T. These are the pointers to the next and previous node in the linked list, +respectively. To make this connection, the fields are declared as pointers to a node +that is bound to type T through the use of the square brackets. + + type list[T any] struct { + first *node[T] + last *node[T] + } + +The second struct type is named list and represents a collection of nodes by +pointing to the first and last node in a list. These fields need to point to a node of +some type T, just like the next and prev fields from the node type. + +Once again, the identifier T is defined as a generic type (to be determined later) +that can be substituted for "any" concrete type. Then the first and last fields are +declared as pointers to a node of some type T using the square bracket syntax. + + func (l *list[T]) add(data T) *node[T] { + n := node[T]{ + Data: data, + prev: l.last, + } + + if l.first == nil { + l.first = &n + l.last = &n + return &n + } + + l.last.next = &n + l.last = &n + + return &n + } + +This is an implementation of a method named add for the list type. No formal generic type +list declaration is required (as with functions) since the method is bound to the list through +the receiver. The add method’s receiver is declared as a pointer to a list of some type T and +the return is declared as a pointer to a node of the same type T. + +The code after the construction of a node will always be the same, regardless of +what type of data is being stored in the list since that is just pointer manipulation. +It’s only the construction of a new node that is affected by the type of data that will +be managed. Thanks to generics, the construction of a node can be bound to type T +which gets substituted later at compile time. + +Without generics, this entire method would need to be duplicated since the +construction of a node would need to be hard coded to a known, declared type prior +to compilation. Since the amount of code (for the entire list implementation) that +needs to change for different data types is very small, being able to declare a node +and list to manage data of some type T reduces the cost of code duplication and +maintenance. + + type user struct { + name string + } + + func main() { + // Store values of type user into the list. + var lv list[user] + n1 := lv.add(user{"bill"}) + n2 := lv.add(user{"ale"}) + fmt.Println(n1.Data, n2.Data) + + // Store pointers of type user into the list. + var lp list[*user] + n3 := lp.add(&user{"bill"}) + n4 := lp.add(&user{"ale"}) + fmt.Println(n3.Data, n4.Data) + } + +Output: + + {bill} {ale} + &{bill} &{ale} + +Here is a small application. A type named user is declared and then a list is +constructed to its zero value state to manage values of type user. A second list is +then constructed to its zero value state and this list manages pointers to values of +type user. The only difference between these two lists is one manages values of +type user and the other pointers of type user. + +Since type user is explicitly specified during the construction of the list type, the add +method in turn accepts values of type user. Since a pointer of type user is explicitly +specified during the construction of the list type, the add method accepts pointers of +type user. + +You can see in the output of the program, the Data field for the nodes in the respective +lists match the data semantic used in the construction. + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Declare a generic type named stack that uses a struct with a single +field named data declared as a slice of some type T. Declare a method +named push that accepts a value of some type T and appends the value to +the slice. Declare a method named pop that returns the latest value of +some type T that was appended to the slice and an error. Then write a main +function that uses the methods. + +.play generics/struct-types/exercise1.go +.play generics/struct-types/answer1.go diff --git a/_content/tour/grc/generics-type-constraints.article b/_content/tour/grc/generics-type-constraints.article new file mode 100644 index 00000000..d8fa4ede --- /dev/null +++ b/_content/tour/grc/generics-type-constraints.article @@ -0,0 +1,123 @@ +Type As Constraint +This is a new concept in Go where a constraint can be based on a set of concrete types. + +* Generics - Type As Constraint + +This is a new concept in Go where a constraint can be based on a set of concrete types. +This only works for generics. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Type based constraint +- *Example* *2*: Predeclared type constraint "comparable" +- *Example* *3*: Mix type and behavior constraints + +.play generics/type-constraints/example1.go +.play generics/type-constraints/example2.go +.play generics/type-constraints/example3.go + +** Explained + +Generic functions create a new type of constraint that can’t be resolved by declaring +a method set of behavior. + + func Add[T ???](v1 T, v2 T) T { + return v1 + v2 + } + +Here is a generic function that wants to accept two values of some type T, add them +together, and then return the sum back to the caller. This is an interesting problem +because the compiler needs to constrain the call to the function for only values that +can be used in an add operation. Currently there is no mechanic for declaring this +kind of constraint. + +The decision was to continue to use the interface to declare the constraint and add +something new. + + type addOnly interface { + string | int | int8 | int16 | int32 | int64 | float64 + } + +You can declare an interface that defines a set of types that form the constraint. Then +apply this interface to the generic function. + + func Add[T addOnly](v1 T, v2 T) T { + return v1 + v2 + } + +Now the compiler can validate that the set of types is compliant with the operations +the function needs to perform against values of those types. When the interface is +using the built-in types, the interfaces are reusable across packages. When the list +of types represent user-defined types from the package, You must remember these +generic functions are bound to the packages types and nothing more. + +Interfaces declared with a set of types can’t be used in a traditional polymorphic +function. This wouldn’t make sense anyway, but it’s something that doesn’t feel like +Go in the sense that this change to the interface is not orthogonal. + +One idea is to have pre-declared identifiers for common operation constraints. + + func index[T comparable](list []T, find T) int { + for i, v := range list { + if v == find { + return i + } + } + + return -1 + } + +The comparable constraint is declared by the language and applies a constraint that +types must be capable of being used in a comparison statement. In this example, +both v and find are variables of type T and are being compared. There is an idea +that a package in the standard library could provide a common set of constraints as +well. + +There is no restriction on an interface being declared with both a set of types and a +method set of behavior. + + type matcher[T any] interface { + type person, food + match(v T) bool + } + + func match[T matcher[T]](list []T, find T) int { + for i, v := range list { + if v.match(find) { + return i + } + } + + return -1 + } + +A generic interface is declared where T is the type of value to be passed into a +method named match. The interface also constrains its use to only values of userdefined +type person and food. + +When you look at the match function, there isn’t an obvious need to restrict the +function to just the person and food types. If this is the case, the match function +should be a traditional polymorphic function, not a generic function. If there was a +good reason, a generic function can be used to apply this type of constraint. + +As a side note, not sure this functionality is necessary or makes sense. This is +something the community will need to figure out over time. + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Implement a generic function named copyfy that is constrained to only making +copies of slices of type string or int. + +.play generics/type-constraints/exercise1.go +.play generics/type-constraints/answer1.go diff --git a/_content/tour/grc/generics-underlying-types.article b/_content/tour/grc/generics-underlying-types.article new file mode 100644 index 00000000..2edf9005 --- /dev/null +++ b/_content/tour/grc/generics-underlying-types.article @@ -0,0 +1,162 @@ +Underlying Types +You can declare a generic type using an underlying type. + +* Generics - Underlying Types + +You can declare a generic type using an underlying type. + +** Video + +Watch the talk I gave about Generics which walks you through all the +examples in this section of the Tour. + +.html generics-video.html + +** Code Review + +- *Example* *1*: Concrete vector type +- *Example* *2*: Interface vector type +- *Example* *3*: Generic vector type + +.play generics/underlying-types/example1.go +.play generics/underlying-types/example2.go +.play generics/underlying-types/example3.go + +** Explained + +What if you wanted to declare my own generic type using an underlying type? + + type vector[T any] []T + + func (v vector[T]) last() (T, error) { + var zero T + + if len(v) == 0 { + return zero, errors.New("empty") + } + + return v[len(v)-1], nil + } + +This example shows a generic vector type that restricts the construction of a vector +to a single type of data. The use of square brackets declares that type T is a generic +type to be determined at compile time. The use of the constraint "any" describes +there is no constraint on what type T can become. + +The last method is declared with a value receiver of type vector[T] to represent a +value of type vector with an underlying slice of some type T. The method returns a +value of that same type T. + + func main() { + fmt.Print("vector[int] : ") + + vGenInt := vector[int]{10, -1} + + i, err := vGenInt.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + if i < 0 { + fmt.Print("negative integer: ") + } + + fmt.Printf("value: %d\n", i) + + // ------------------------------------------------------------------------- + + fmt.Print("vector[string] : ") + + vGenStr := vector[string]{"A", "B", string([]byte{0xff})} + + s, err := vGenStr.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + if !utf8.ValidString(s) { + fmt.Print("non-valid string: ") + } + + fmt.Printf("value: %q\n", s) + } + +Output: + + vector[int] : negative integer: value: -1 + vector[string] : non-valid string: value: "\xff" + +This is how to construct a value of type vector with an underlying type of int when I +will set values in the vector at construction. An important aspect of this code is the +construction calls. + + // Zero Value Construction + var vGenInt vector[int] + var vGenStr vector[string] + + // Non-Zero Value Construction + vGenInt := vector{10, -1} + vGenStr := vector{"A", "B", string([]byte{0xff})} + +When it comes to constructing these generic types to their zero value, it’s not +possible for the compiler to infer the type. However, in cases where there is +initialization during construction, the compiler can infer the type. + +There is an aspect of the spec that focuses on the construction of a generic type to +its zero value state. + + type vector[T any] []T + + func (v vector[T]) last() (T, error) { + var zero T + + if len(v) == 0 { + return zero, errors.New("empty") + } + + return v[len(v)-1], nil + } + +You need to focus on the method declaration for the last method and how the method +returns a value of the generic type T. On the first return is a situation where you need +to return the zero value for type T. The current implementation provides two solutions to write +this code. The first solution you see already. A variable named zero is constructed to +its zero value state of type T and then that variable is used for the return. + +The other option is to use the built-in function new and dereference the returned +pointer within the return statement. + + type vector[T any] []T + + func (v vector[T]) last() (T, error) { + if len(v) == 0 { + return *new(T), errors.New("empty") + } + + return v[len(v)-1], nil + } + +This version of the last method is using the built-in function new for zero value +construction and dereferencing of the returned pointer to satisfy return type T. + +Note: You might think why not use T{} to perform zero value construction? The +problem is this syntax does not work with all types, such as the scalar types (int, +string, bool). So it’s not an option. + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Declare a generic type named keymap that uses an underlying type of map +with a key of type string and a value of some type T. Declare a method +named set that accepts a key of type string and a value of type T. Declare +a method named get that accepts a key of type string and return a value of +type T and true or false if the key is found. Then write a main function +that uses the methods. + +.play generics/underlying-types/exercise1.go +.play generics/underlying-types/answer1.go diff --git a/_content/tour/grc/generics-video.html b/_content/tour/grc/generics-video.html new file mode 100644 index 00000000..e0b1ed9e --- /dev/null +++ b/_content/tour/grc/generics-video.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_content/tour/grc/generics/basics/answer1.go b/_content/tour/grc/generics/basics/answer1.go new file mode 100644 index 00000000..8c43a105 --- /dev/null +++ b/_content/tour/grc/generics/basics/answer1.go @@ -0,0 +1,48 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic function that can marshal JSON. +package main + +import ( + "encoding/json" + "fmt" +) + +// Implement the generic function named marshal that can accept any value +// of type T and marshal that value into JSON. +func marshal[T any](v T) ([]byte, error) { + data, err := json.Marshal(v) + if err != nil { + return nil, err + } + + return data, nil +} + +// Declare a struct type named User with two fields, Name and Age. +type User struct { + Name string + Age int +} + +func main() { + + // Construct a value of type User. + u := User{ + Name: "Bill", + Age: 10, + } + + // Call the generic marshal function. + data, err := marshal(u) + if err != nil { + fmt.Println(err) + return + } + + // Print the JSON produced by the marshal function. + fmt.Println(string(data)) +} diff --git a/_content/tour/grc/generics/basics/example1.go b/_content/tour/grc/generics/basics/example1.go new file mode 100644 index 00000000..f84b4d94 --- /dev/null +++ b/_content/tour/grc/generics/basics/example1.go @@ -0,0 +1,40 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how concrete implementations of print functions that can +// only work with slices of the specified type. +package main + +import ( + "fmt" +) + +func printNumbers(numbers []int) { + fmt.Print("Numbers: ") + + for _, num := range numbers { + fmt.Print(num, " ") + } + + fmt.Print("\n") +} + +func printStrings(strings []string) { + fmt.Print("Strings: ") + + for _, str := range strings { + fmt.Print(str, " ") + } + + fmt.Print("\n") +} + +func main() { + numbers := []int{1, 2, 3} + printNumbers(numbers) + + strings := []string{"A", "B", "C"} + printStrings(strings) +} diff --git a/_content/tour/grc/generics/basics/example2.go b/_content/tour/grc/generics/basics/example2.go new file mode 100644 index 00000000..94d405a8 --- /dev/null +++ b/_content/tour/grc/generics/basics/example2.go @@ -0,0 +1,39 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to write a function that provides an empty interface +// solution which uses type assertions for the different concrete slices to be +// supported. We've basically moved the functions from above into case statements. +package main + +import ( + "fmt" +) + +func printAssert(v interface{}) { + fmt.Print("Assert: ") + + switch list := v.(type) { + case []int: + for _, num := range list { + fmt.Print(num, " ") + } + + case []string: + for _, str := range list { + fmt.Print(str, " ") + } + } + + fmt.Print("\n") +} + +func main() { + numbers := []int{1, 2, 3} + printAssert(numbers) + + strings := []string{"A", "B", "C"} + printAssert(strings) +} diff --git a/_content/tour/grc/generics/basics/example3.go b/_content/tour/grc/generics/basics/example3.go new file mode 100644 index 00000000..9f4e4c0d --- /dev/null +++ b/_content/tour/grc/generics/basics/example3.go @@ -0,0 +1,38 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to write a function that provides a reflection +// solution which allows a slice of any type to be provided and printed. This +// is a generic function thanks to the reflect package. +package main + +import ( + "fmt" + "reflect" +) + +func printReflect(v interface{}) { + fmt.Print("Reflect: ") + + val := reflect.ValueOf(v) + if val.Kind() != reflect.Slice { + return + } + + for i := 0; i < val.Len(); i++ { + fmt.Print(val.Index(i).Interface(), " ") + } + + fmt.Print("\n") +} + +func main() { + numbers := []int{1, 2, 3} + printReflect(numbers) + print(numbers) + + strings := []string{"A", "B", "C"} + printReflect(strings) +} diff --git a/_content/tour/grc/generics/basics/example4.go b/_content/tour/grc/generics/basics/example4.go new file mode 100644 index 00000000..5cf24c65 --- /dev/null +++ b/_content/tour/grc/generics/basics/example4.go @@ -0,0 +1,41 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to write a function that provides a generics +// solution which allows a slice of any type T (to be determined later) to be +// passed and printed. +package main + +import ( + "fmt" +) + +// To avoid the ambiguity with array declarations, type parameters require a +// constraint to be applied. The `any` constraint states there is no constraint +// on what type T can become. The predeclared identifier `any` is an alias for +// `interface{}`. +// +// This code more closely resembles the concrete implementations that we started +// with and is easier to read than the reflect implementation. + +func print[T any](slice []T) { + fmt.Print("Generic: ") + + for _, v := range slice { + fmt.Print(v, " ") + } + + fmt.Print("\n") +} + +// ============================================================================= + +func main() { + numbers := []int{1, 2, 3} + print(numbers) + + strings := []string{"A", "B", "C"} + print(strings) +} diff --git a/_content/tour/grc/generics/basics/exercise1.go b/_content/tour/grc/generics/basics/exercise1.go new file mode 100644 index 00000000..b4de737d --- /dev/null +++ b/_content/tour/grc/generics/basics/exercise1.go @@ -0,0 +1,22 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic function that can marshal JSON. +package main + +// Add imports. + +// Implement the generic function named marshal that can accept any value +// of type T and marshal that value into JSON. + +// Declare a struct type named User with two fields, Name and Age. + +func main() { + // Construct a value of type User. + + // Call the generic marshal function. + + // Print the JSON produced by the marshal function. +} diff --git a/_content/tour/grc/generics/behavior-constraints/answer1.go b/_content/tour/grc/generics/behavior-constraints/answer1.go new file mode 100644 index 00000000..dcaed7bf --- /dev/null +++ b/_content/tour/grc/generics/behavior-constraints/answer1.go @@ -0,0 +1,60 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic function named marshal that can marshal JSON but only +// accepts values that implement the json.Marshaler interface. +package main + +import ( + "encoding/json" + "fmt" +) + +// Implement the generic function named marshal that can accept only values +// of type T that implement the json.Marshaler interface. +func marshal[T json.Marshaler](v T) ([]byte, error) { + data, err := json.Marshal(v) + if err != nil { + return nil, err + } + + return data, nil +} + +// ============================================================================= + +// Define a type names user with two fields, name and email. +type user struct { + name string + email string +} + +// Declare a method that implements the json.Marshaler interface. Have the +// method return a value of type user as JSON. +func (u user) MarshalJSON() ([]byte, error) { + v := fmt.Sprintf("{\"name\": %q, \"email\": %q}", u.name, u.email) + return []byte(v), nil +} + +// ============================================================================= + +func main() { + + // Construct a value of type user. + user := user{ + name: "Bill", + email: "bill@ardanlabs.com", + } + + // Call the generic marshal function. + s1, err := marshal(user) + if err != nil { + fmt.Println(err) + return + } + + // Display the returned JSON. + fmt.Println("user:", string(s1)) +} diff --git a/_content/tour/grc/generics/behavior-constraints/example1.go b/_content/tour/grc/generics/behavior-constraints/example1.go new file mode 100644 index 00000000..a7be4dc5 --- /dev/null +++ b/_content/tour/grc/generics/behavior-constraints/example1.go @@ -0,0 +1,75 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to implement a stringify function that is +// specific to each of the concrete types implemented above. In each case, +// the stringify function returns a slice of strings. These function use +// the String method against the individual user or customer value. +package main + +import ( + "fmt" +) + +func stringifyUsers(users []user) []string { + ret := make([]string, 0, len(users)) + for _, user := range users { + ret = append(ret, user.String()) + } + return ret +} + +func stringifyCustomers(customers []customer) []string { + ret := make([]string, 0, len(customers)) + for _, customer := range customers { + ret = append(ret, customer.String()) + } + return ret +} + +// Defining two types that implement the fmt.Stringer interface. Each +// implementation creates a stringified version of the concrete type. + +type user struct { + name string + email string +} + +func (u user) String() string { + return fmt.Sprintf("{type: \"user\", name: %q, email: %q}", u.name, u.email) +} + +type customer struct { + name string + email string +} + +func (u customer) String() string { + return fmt.Sprintf("{type: \"customer\", name: %q, email: %q}", u.name, u.email) +} + +// ============================================================================= + +func main() { + users := []user{ + {name: "Bill", email: "bill@ardanlabs.com"}, + {name: "Ale", email: "ale@whatever.com"}, + } + + s1 := stringifyUsers(users) + + fmt.Println("users:", s1) + + // ------------------------------------------------------------------------- + + customers := []customer{ + {name: "Google", email: "you@google.com"}, + {name: "MSFT", email: "you@msft.com"}, + } + + s2 := stringifyCustomers(customers) + + fmt.Println("customers:", s2) +} diff --git a/_content/tour/grc/generics/behavior-constraints/example2.go b/_content/tour/grc/generics/behavior-constraints/example2.go new file mode 100644 index 00000000..54f27e20 --- /dev/null +++ b/_content/tour/grc/generics/behavior-constraints/example2.go @@ -0,0 +1,79 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to implement an empty interface solution which +// uses type assertions for the different concrete slices to be supported. +// We've basically moved the functions from above into case statements. +// This function uses the String method against the value. +package main + +import ( + "fmt" +) + +func stringifyAssert(v interface{}) []string { + switch list := v.(type) { + case []user: + ret := make([]string, 0, len(list)) + for _, value := range list { + ret = append(ret, value.String()) + } + return ret + + case []customer: + ret := make([]string, 0, len(list)) + for _, value := range list { + ret = append(ret, value.String()) + } + return ret + } + + return nil +} + +// Defining two types that implement the fmt.Stringer interface. Each +// implementation creates a stringified version of the concrete type. + +type user struct { + name string + email string +} + +func (u user) String() string { + return fmt.Sprintf("{type: \"user\", name: %q, email: %q}", u.name, u.email) +} + +type customer struct { + name string + email string +} + +func (u customer) String() string { + return fmt.Sprintf("{type: \"customer\", name: %q, email: %q}", u.name, u.email) +} + +// ============================================================================= + +func main() { + users := []user{ + {name: "Bill", email: "bill@ardanlabs.com"}, + {name: "Ale", email: "ale@whatever.com"}, + } + + s1 := stringifyAssert(users) + + fmt.Println("users:", s1) + + // ------------------------------------------------------------------------- + + customers := []customer{ + {name: "Google", email: "you@google.com"}, + {name: "MSFT", email: "you@msft.com"}, + } + + s2 := stringifyAssert(customers) + + fmt.Println("customers:", s2) +} diff --git a/_content/tour/grc/generics/behavior-constraints/example3.go b/_content/tour/grc/generics/behavior-constraints/example3.go new file mode 100644 index 00000000..8c396d36 --- /dev/null +++ b/_content/tour/grc/generics/behavior-constraints/example3.go @@ -0,0 +1,81 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to implement a reflection solution which allows +// a slice of any type to be provided and stringified. This is a generic +// function thanks to the reflect package. Notice the call to the String +// method via reflection. +package main + +import ( + "fmt" + "reflect" +) + +func stringifyReflect(v interface{}) []string { + val := reflect.ValueOf(v) + if val.Kind() != reflect.Slice { + return nil + } + + ret := make([]string, 0, val.Len()) + + for i := 0; i < val.Len(); i++ { + m := val.Index(i).MethodByName("String") + if !m.IsValid() { + return nil + } + + data := m.Call(nil) + ret = append(ret, data[0].String()) + } + + return ret +} + +// Defining two types that implement the fmt.Stringer interface. Each +// implementation creates a stringified version of the concrete type. + +type user struct { + name string + email string +} + +func (u user) String() string { + return fmt.Sprintf("{type: \"user\", name: %q, email: %q}", u.name, u.email) +} + +type customer struct { + name string + email string +} + +func (u customer) String() string { + return fmt.Sprintf("{type: \"customer\", name: %q, email: %q}", u.name, u.email) +} + +// ============================================================================= + +func main() { + users := []user{ + {name: "Bill", email: "bill@ardanlabs.com"}, + {name: "Ale", email: "ale@whatever.com"}, + } + + s1 := stringifyReflect(users) + + fmt.Println("users:", s1) + + // ------------------------------------------------------------------------- + + customers := []customer{ + {name: "Google", email: "you@google.com"}, + {name: "MSFT", email: "you@msft.com"}, + } + + s2 := stringifyReflect(customers) + + fmt.Println("customers:", s2) +} diff --git a/_content/tour/grc/generics/behavior-constraints/example4.go b/_content/tour/grc/generics/behavior-constraints/example4.go new file mode 100644 index 00000000..ea9623bd --- /dev/null +++ b/_content/tour/grc/generics/behavior-constraints/example4.go @@ -0,0 +1,71 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to implement a generics solution which allows +// a slice of some type T (to be determined later) to be passed and stringified. +// This code more closely resembles the concrete implementations that we started +// with and is easier to read than the reflect implementation. However, an +// interface constraint of type fmt.Stringer is applied to allow the compiler +// to know the value of type T passed requires a String method. +package main + +import ( + "fmt" +) + +func stringify[T fmt.Stringer](slice []T) []string { + ret := make([]string, 0, len(slice)) + + for _, value := range slice { + ret = append(ret, value.String()) + } + + return ret +} + +// Defining two types that implement the fmt.Stringer interface. Each +// implementation creates a stringified version of the concrete type. + +type user struct { + name string + email string +} + +func (u user) String() string { + return fmt.Sprintf("{type: \"user\", name: %q, email: %q}", u.name, u.email) +} + +type customer struct { + name string + email string +} + +func (u customer) String() string { + return fmt.Sprintf("{type: \"customer\", name: %q, email: %q}", u.name, u.email) +} + +// ============================================================================= + +func main() { + users := []user{ + {name: "Bill", email: "bill@ardanlabs.com"}, + {name: "Ale", email: "ale@whatever.com"}, + } + + s1 := stringify(users) + + fmt.Println("users:", s1) + + // ------------------------------------------------------------------------- + + customers := []customer{ + {name: "Google", email: "you@google.com"}, + {name: "MSFT", email: "you@msft.com"}, + } + + s2 := stringify(customers) + + fmt.Println("customers:", s2) +} diff --git a/_content/tour/grc/generics/behavior-constraints/exercise1.go b/_content/tour/grc/generics/behavior-constraints/exercise1.go new file mode 100644 index 00000000..7bc34a40 --- /dev/null +++ b/_content/tour/grc/generics/behavior-constraints/exercise1.go @@ -0,0 +1,27 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic function named marshal that can marshal JSON but only +// accepts values that implement the json.Marshaler interface. +package main + +// Add imports. + +// Declare the generic function named marshal that can accept only values +// of type T that implement the json.Marshaler interface. + +// Define a type names user with two fields, name and email. + +// Implement a method that implements the json.Marshaler interface. Have the +// method return a value of type user as JSON. + +func main() { + + // Construct a value of type user. + + // Call the generic marshal function. + + // Display the returned JSON. +} diff --git a/_content/tour/grc/generics/channels/example1.go b/_content/tour/grc/generics/channels/example1.go new file mode 100644 index 00000000..e73f1cbb --- /dev/null +++ b/_content/tour/grc/generics/channels/example1.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to execute a work function in a goroutine and +// return a channel of type Result (to be determined later) back to the caller. +package main + +import ( + "context" + "fmt" + "math/rand" + "time" +) + +type doworkFn[Result any] func(context.Context) Result + +func doWork[Result any](ctx context.Context, work doworkFn[Result]) chan Result { + ch := make(chan Result, 1) + + go func() { + ch <- work(ctx) + fmt.Println("doWork : work complete") + }() + + return ch +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + dwf := func(ctx context.Context) string { + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + return "work complete" + } + + select { + case v := <-doWork(ctx, dwf): + fmt.Println("main:", v) + case <-ctx.Done(): + fmt.Println("main: timeout") + } +} diff --git a/_content/tour/grc/generics/channels/example2.go b/_content/tour/grc/generics/channels/example2.go new file mode 100644 index 00000000..be3ffe98 --- /dev/null +++ b/_content/tour/grc/generics/channels/example2.go @@ -0,0 +1,59 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to execute a work function via a pool of goroutines +// and return a channel of type Input (to be determined later) back to the caller. +// Once input is received by any given goroutine, the work function is executed +// and the Result value is displayed. +package main + +import ( + "fmt" + "math/rand" + "runtime" + "sync" + "time" +) + +type poolWorkFn[Input any, Result any] func(input Input) Result + +func poolWork[Input any, Result any](size int, work poolWorkFn[Input, Result]) (chan Input, func()) { + var wg sync.WaitGroup + wg.Add(size) + + ch := make(chan Input) + + for i := 0; i < size; i++ { + go func() { + defer wg.Done() + for input := range ch { + result := work(input) + fmt.Println("pollWork :", result) + } + }() + } + + cancel := func() { + close(ch) + wg.Wait() + } + + return ch, cancel +} + +func main() { + size := runtime.GOMAXPROCS(0) + pwf := func(input int) string { + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + return fmt.Sprintf("%d : received", input) + } + + ch, cancel := poolWork(size, pwf) + defer cancel() + + for i := 0; i < 5; i++ { + ch <- i + } +} diff --git a/_content/tour/grc/generics/hash-table/example1.go b/_content/tour/grc/generics/hash-table/example1.go new file mode 100644 index 00000000..a19ddccb --- /dev/null +++ b/_content/tour/grc/generics/hash-table/example1.go @@ -0,0 +1,100 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// This code is provided by Matt Layher (@mdlayher) +// https://mdlayher.com/blog/go-generics-draft-design-building-a-hashtable/#a-generic-hashtable + +// Sample program to show how to write a generic hash table. +package main + +import ( + "fmt" + "hash/fnv" +) + +// ============================================================================= + +type hashFunc[K comparable] func(key K, buckets int) int + +type keyValuePair[K comparable, V any] struct { + Key K + Value V +} + +type Table[K comparable, V any] struct { + hashFunc hashFunc[K] + buckets int + data [][]keyValuePair[K, V] +} + +func New[K comparable, V any](buckets int, hf hashFunc[K]) *Table[K, V] { + return &Table[K, V]{ + hashFunc: hf, + buckets: buckets, + data: make([][]keyValuePair[K, V], buckets), + } +} + +func (t *Table[K, V]) Insert(key K, value V) { + bucket := t.hashFunc(key, t.buckets) + + for idx, kvp := range t.data[bucket] { + if key == kvp.Key { + t.data[bucket][idx].Value = value + return + } + } + + kvp := keyValuePair[K, V]{ + Key: key, + Value: value, + } + t.data[bucket] = append(t.data[bucket], kvp) +} + +func (t *Table[K, V]) Retrieve(key K) (V, bool) { + bucket := t.hashFunc(key, t.buckets) + + for idx, kvp := range t.data[bucket] { + if key == kvp.Key { + return t.data[bucket][idx].Value, true + } + } + + var zero V + return zero, false +} + +// ============================================================================= + +func main() { + const buckets = 8 + + hashFunc1 := func(key string, buckets int) int { + h := fnv.New32() + h.Write([]byte(key)) + return int(h.Sum32()) % buckets + } + table1 := New[ /*key*/ string /*value*/, int](buckets, hashFunc1) + + hashFunc2 := func(key int, buckets int) int { + return key % buckets + } + table2 := New[ /*key*/ int /*value*/, string](buckets, hashFunc2) + + words := []string{"foo", "bar", "baz"} + for i, word := range words { + table1.Insert(word, i) + table2.Insert(i, word) + } + + for i, s := range append(words, "nope!") { + v1, ok1 := table1.Retrieve(s) + fmt.Printf("t1.Rtr(%v) = (%v, %v)\n", s, v1, ok1) + + v2, ok2 := table2.Retrieve(i) + fmt.Printf("t2.Rtr(%v) = (%v, %v)\n", i, v2, ok2) + } +} diff --git a/_content/tour/grc/generics/multi-type-params/example1.go b/_content/tour/grc/generics/multi-type-params/example1.go new file mode 100644 index 00000000..87ca2d31 --- /dev/null +++ b/_content/tour/grc/generics/multi-type-params/example1.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to write a generic functions tha take multiple +// generic parameters. +package main + +import ( + "fmt" +) + +// Print is a function that accepts a slice of some type L and a slice of some +// type V (to be determined later). Each value of the labels slice will be +// joined with the vals slice at the same index position. This code shows how +// the generics type list can contain more than just one generic type and have +// different constraints for each. + +func Print[L any, V fmt.Stringer](labels []L, vals []V) { + for i, v := range vals { + fmt.Println(labels[i], v.String()) + } +} + +// This code defines a concrete type named user that implements the fmt.Stringer +// interface. The String method just returns the name field from the user type. + +type user struct { + name string +} + +func (u user) String() string { + return u.name +} + +// ============================================================================= + +func main() { + labels := []int{1, 2, 3} + names := []user{{"bill"}, {"jill"}, {"joan"}} + + Print(labels, names) +} diff --git a/_content/tour/grc/generics/slice-constraints/example1.go b/_content/tour/grc/generics/slice-constraints/example1.go new file mode 100644 index 00000000..9cb0cead --- /dev/null +++ b/_content/tour/grc/generics/slice-constraints/example1.go @@ -0,0 +1,103 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to define slice based constraints. +package main + +import ( + "fmt" +) + +// operateFunc defines a function type that takes a value of some type T and +// returns a value of the same type T (to be determined later). +// +// Slice defines a constraint that the data is a slice of some type T (to be +// determined later). + +type operateFunc[T any] func(t T) T + +type Slice[T any] interface { + ~[]T +} + +// When it's important that the slice being passed in is exactly the same +// as the slice being returned, use a slice constraint. This ensures that the +// result slice S is the same as the incoming slice S. + +func operate[S Slice[T], T any](slice S, fn operateFunc[T]) S { + ret := make(S, len(slice)) + for i, v := range slice { + ret[i] = fn(v) + } + return ret +} + +// If you don't care about the constraint defined above, then operate2 provides +// a simpler form. Operate2 still works because you can assign a slice of some +// type T to the input and output arguments. However, the concrete types of the +// input and output arguments will be based on the underlying types. In this +// case not a slice of Numbers, but a slice of integers. This is not the case +// with operate function above. + +func operate2[T any](slice []T, fn operateFunc[T]) []T { + ret := make([]T, len(slice)) + for i, v := range slice { + ret[i] = fn(v) + } + return ret +} + +// I suspect most of the time operate2 is what you want: it's simpler, and more +// flexible: You can always assign a []int back to a Numbers variable and vice +// versa. But if you need to preserve that incoming type in the result for some +// reason, you will need to use operate. + +// ============================================================================= + +// This code defines a named type that is based on a slice of integers. An +// integer is the underlying type. +// +// Double is a function that takes a value of type Numbers, multiplies each +// value in the underlying integer slice and returns that new Numbers value. +// +// Line 73 is commented out because the compiler is smart enough to infer the +// types for S and T. The commented code shows the types being inferred. +// +// operate2 is not used in the example. + +type Numbers []int + +func DoubleUnderlying(n Numbers) Numbers { + fn := func(n int) int { + return 2 * n + } + + numbers := operate2(n, fn) + fmt.Printf("%T", numbers) + return numbers +} + +func DoubleUserDefined(n Numbers) Numbers { + fn := func(n int) int { + return 2 * n + } + + numbers := operate(n, fn) + fmt.Printf("%T", numbers) + return numbers +} + +// ============================================================================= + +func main() { + n := Numbers{10, 20, 30, 40, 50} + fmt.Println(n) + + n = DoubleUnderlying(n) + fmt.Println(n) + + n = DoubleUserDefined(n) + fmt.Println(n) +} diff --git a/_content/tour/grc/generics/struct-types/answer1.go b/_content/tour/grc/generics/struct-types/answer1.go new file mode 100644 index 00000000..7c0580cb --- /dev/null +++ b/_content/tour/grc/generics/struct-types/answer1.go @@ -0,0 +1,80 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic stack type. +package main + +import ( + "errors" + "fmt" +) + +// Declare a generic type named stack that uses a struct with a single +// field named data declared as a slice of some type T. +type stack[T any] struct { + data []T +} + +// Declare a method named push that accepts a value of some type T and appends +// the value to the slice. +func (s *stack[T]) push(v T) { + s.data = append(s.data, v) +} + +// Declare a method named pop that returns the latest value of some type T +// that was appended to the slice and an error. +func (s *stack[T]) pop() (T, error) { + var zero T + + if len(s.data) == 0 { + return zero, errors.New("stack is empty") + } + + v := s.data[len(s.data)-1] + + s.data = s.data[:len(s.data)-1] + + return v, nil +} + +// ============================================================================= + +func main() { + + // Constructs a value of type stack that stores integers. + var s stack[int] + + // Push the values of 10 and 20 to the stack. + s.push(10) + s.push(20) + + // Pop a value from the stack. + v, err := s.pop() + if err != nil { + fmt.Println(err) + return + } + + // Print the value that was popped. + fmt.Println(v) + + // Pop another value from the stack. + v, err = s.pop() + if err != nil { + fmt.Println(err) + return + } + + // Print the value that was popped. + fmt.Println(v) + + // Pop another value from the stack. This should + // return an error. + v, err = s.pop() + if err != nil { + fmt.Println(err) + return + } +} diff --git a/_content/tour/grc/generics/struct-types/example1.go b/_content/tour/grc/generics/struct-types/example1.go new file mode 100644 index 00000000..9862bc57 --- /dev/null +++ b/_content/tour/grc/generics/struct-types/example1.go @@ -0,0 +1,67 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare a generic type using a struct type. +package main + +import ( + "fmt" +) + +// This code defines two user defined types that implement a linked list. The +// node type contains data of some type T (to be determined later) and points +// to other nodes of the same type T. The list type contains pointers to the +// first and last nodes of some type T. The add method is declared with a +// pointer receiver based on a list of some type T and is implemented to add +// nodes to the list of the same type T. + +type node[T any] struct { + Data T + next *node[T] + prev *node[T] +} + +type list[T any] struct { + first *node[T] + last *node[T] +} + +func (l *list[T]) add(data T) *node[T] { + n := node[T]{ + Data: data, + prev: l.last, + } + if l.first == nil { + l.first = &n + l.last = &n + return &n + } + l.last.next = &n + l.last = &n + return &n +} + +// This user type represents the data to be stored into the linked list. + +type user struct { + name string +} + +// ============================================================================= + +func main() { + + // Store values of type user into the list. + var lv list[user] + n1 := lv.add(user{"bill"}) + n2 := lv.add(user{"ale"}) + fmt.Println(n1.Data, n2.Data) + + // Store pointers of type user into the list. + var lp list[*user] + n3 := lp.add(&user{"bill"}) + n4 := lp.add(&user{"ale"}) + fmt.Println(n3.Data, n4.Data) +} diff --git a/_content/tour/grc/generics/struct-types/exercise1.go b/_content/tour/grc/generics/struct-types/exercise1.go new file mode 100644 index 00000000..09b44e54 --- /dev/null +++ b/_content/tour/grc/generics/struct-types/exercise1.go @@ -0,0 +1,36 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic stack type. +package main + +// Declare a generic type named stack that uses a struct with a single +// field named data declared as a slice of some type T. + +// Declare a method named push that accepts a value of some type T and appends +// the value to the slice. + +// Declare a method named pop that returns the latest value of some type T +// that was appended to the slice and an error. + +// ============================================================================= + +func main() { + + // Constructs a value of type stack that stores integers. + + // Push the values of 10 and 20 to the stack. + + // Pop a value from the stack. + + // Print the value that was popped. + + // Pop another value from the stack. + + // Print the value that was popped. + + // Pop another value from the stack. This should + // return an error. +} diff --git a/_content/tour/grc/generics/type-constraints/answer1.go b/_content/tour/grc/generics/type-constraints/answer1.go new file mode 100644 index 00000000..3ca1e1e0 --- /dev/null +++ b/_content/tour/grc/generics/type-constraints/answer1.go @@ -0,0 +1,55 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic function named copyfy that is constrained to only +// making copies of slices of type string or int. +package main + +import ( + "fmt" +) + +// Declare an interface named copyer that creates a constraint on +// string and int. +type copyer interface { + string | int +} + +// Implement a generic function named copyfy that accepts a slice of some +// type T but constrained on the copyer interface. +func copyfy[T copyer](src []T) []T { + dest := make([]T, len(src)) + + copy(dest, src) + + return dest +} + +// ============================================================================= + +func main() { + + // Construct a slice of string with three values. + src1 := []string{"Bill", "Jill", "Joan"} + + // Call the copyfy function to make a copy of the slice. + dest1 := copyfy(src1) + + // Display the slice and the copy. + fmt.Println("src string :", src1) + fmt.Println("dest string:", dest1) + + // ------------------------------------------------------------------------- + + // Construct a slice of int with three values. + src2 := []int{10, 20, 30} + + // Call the copyfy function to make a copy of the slice. + dest2 := copyfy(src2) + + // Display the slice and the copy. + fmt.Println("src int :", src2) + fmt.Println("dest int:", dest2) +} diff --git a/_content/tour/grc/generics/type-constraints/example1.go b/_content/tour/grc/generics/type-constraints/example1.go new file mode 100644 index 00000000..13680138 --- /dev/null +++ b/_content/tour/grc/generics/type-constraints/example1.go @@ -0,0 +1,26 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare a constraint not based on behavior but +// based on the type of data that is acceptable. This type of constrain is +// important when functions (like Add) need to perform operations (like +) +// that are not supported by all types. +package main + +import "fmt" + +type addOnly interface { + string | int | int8 | int16 | int32 | int64 | float64 +} + +func Add[T addOnly](v1 T, v2 T) T { + return v1 + v2 +} + +func main() { + fmt.Println(Add(10, 20)) + fmt.Println(Add("A", "B")) + fmt.Println(Add(3.14159, 2.96)) +} diff --git a/_content/tour/grc/generics/type-constraints/example2.go b/_content/tour/grc/generics/type-constraints/example2.go new file mode 100644 index 00000000..b5396b70 --- /dev/null +++ b/_content/tour/grc/generics/type-constraints/example2.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use the predeclared type constraint +// "comparable". A type parameter with the comparable constraint accepts as +// a type argument any comparable type. It permits the use of == and != with +// values of that type parameter. +package main + +import "fmt" + +func index[T comparable](list []T, find T) int { + for i, v := range list { + if v == find { + return i + } + } + return -1 +} + +type person struct { + name string + email string +} + +func main() { + durations := []int{5000, 10, 40} + findDur := 10 + + i := index(durations, findDur) + fmt.Printf("Index: %d for %d\n", i, findDur) + + people := []person{ + {name: "bill", email: "bill@email.com"}, + {name: "jill", email: "jill@email.com"}, + {name: "tony", email: "tony@email.com"}, + } + findPerson := person{name: "tony", email: "tony@email.com"} + + i = index(people, findPerson) + fmt.Printf("Index: %d for %s\n", i, findPerson.name) +} diff --git a/_content/tour/grc/generics/type-constraints/example3.go b/_content/tour/grc/generics/type-constraints/example3.go new file mode 100644 index 00000000..93e5d36f --- /dev/null +++ b/_content/tour/grc/generics/type-constraints/example3.go @@ -0,0 +1,79 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to mix type and behavior constraints. +package main + +import "fmt" + +// Defining two concrete types that implement a match method. + +type person struct { + name string + email string +} + +func (p person) match(v person) bool { + return p.name == v.name +} + +type food struct { + name string + category string +} + +func (f food) match(v food) bool { + return f.name == v.name +} + +// The matcher interface defines two constraints. First, it constrains the data +// to what type is acceptable. Second, it constrains the behavior of the data. +// The match method requires that a value of type T (to be determined later) +// will be the input of the method. + +// Note: The type list inside the interface is not needed for match to work. +// I'm trying to show how the type list and behavior can be combined. + +type matcher[T any] interface { + person | food + match(v T) bool +} + +// The match function declares that the value of type T must implement the +// matcher interface and is used for the slice and value arguments to the +// function. + +func match[T matcher[T]](list []T, find T) int { + for i, v := range list { + if v.match(find) { + return i + } + } + return -1 +} + +// ============================================================================= + +func main() { + people := []person{ + {name: "bill", email: "bill@email.com"}, + {name: "jill", email: "jill@email.com"}, + {name: "tony", email: "tony@email.com"}, + } + findPerson := person{name: "tony"} + + i := match(people, findPerson) + fmt.Printf("Match: Idx: %d for %s\n", i, findPerson.name) + + foods := []food{ + {name: "apple", category: "fruit"}, + {name: "carrot", category: "veg"}, + {name: "chicken", category: "meat"}, + } + findFood := food{name: "apple"} + + i = match(foods, findFood) + fmt.Printf("Match: Idx: %d for %s\n", i, findFood.name) +} diff --git a/_content/tour/grc/generics/type-constraints/exercise1.go b/_content/tour/grc/generics/type-constraints/exercise1.go new file mode 100644 index 00000000..01dd1324 --- /dev/null +++ b/_content/tour/grc/generics/type-constraints/exercise1.go @@ -0,0 +1,29 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic function named copyfy that is constrained to only +// making copies of slices of type string or int. +package main + +// Declare an interface named copyer that creates a constraint on +// string and int. + +// Implement a generic function named copyfy that accepts a slice of some +// type T but constrained on the copyer interface. + +func main() { + + // Construct a slice of string with three values. + + // Call the copyfy function to make a copy of the slice. + + // Display the slice and the copy. + + // Construct a slice of int with three values. + + // Call the copyfy function to make a copy of the slice. + + // Display the slice and the copy. +} diff --git a/_content/tour/grc/generics/underlying-types/answer1.go b/_content/tour/grc/generics/underlying-types/answer1.go new file mode 100644 index 00000000..a871edfd --- /dev/null +++ b/_content/tour/grc/generics/underlying-types/answer1.go @@ -0,0 +1,68 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic map type. +package main + +import ( + "fmt" +) + +// Declare a generic type named keymap that uses an underlying type of map +// with a key of type string and a value of some type T. +type keymap[T any] map[string]T + +// Implement a method named set that accepts a key of type string and a value +// of type T. +func (km keymap[T]) set(k string, v T) { + km[k] = v +} + +// Implement a method named get that accepts a key of type string and return +// a value of type T and true or false if the key is found. +func (km keymap[T]) get(k string) (T, bool) { + var zero T + + v, found := km[k] + if !found { + return zero, false + } + + return v, true +} + +// ============================================================================= + +func main() { + + // Construct a value of type keymap that stores integers. + km := make(keymap[int]) + + // Add a value with key "jack" and a value of 10. + km.set("jack", 10) + + // Add a value with key "jill" and a value of 20. + km.set("jill", 20) + + // Get the value for "jack" and verify it's found. + jack, found := km.get("jack") + if !found { + fmt.Println("jack not found") + return + } + + // Print the value for the key "jack". + fmt.Println("jack", jack) + + // Get the value for "jill" and verify it's found. + jill, found := km.get("jill") + if !found { + fmt.Println("jill not found") + return + } + + // Print the value for the key "jill". + fmt.Println("jill", jill) +} diff --git a/_content/tour/grc/generics/underlying-types/example1.go b/_content/tour/grc/generics/underlying-types/example1.go new file mode 100644 index 00000000..b76cc503 --- /dev/null +++ b/_content/tour/grc/generics/underlying-types/example1.go @@ -0,0 +1,76 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare two user defined types based on an +// underlying concrete type. Each type implements a method named last that +// returns the value stored at the highest index position in the vector or an +// error when the vector is empty. +package main + +import ( + "errors" + "fmt" + "unicode/utf8" +) + +type vectorInt []int + +func (v vectorInt) last() (int, error) { + if len(v) == 0 { + return 0, errors.New("empty") + } + + return v[len(v)-1], nil +} + +// ============================================================================= + +type vectorString []string + +func (v vectorString) last() (string, error) { + if len(v) == 0 { + return "", errors.New("empty") + } + + return v[len(v)-1], nil +} + +// ============================================================================= + +func main() { + fmt.Print("vectorInt : ") + + vInt := vectorInt{10, -1} + + i, err := vInt.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + if i < 0 { + fmt.Print("negative integer: ") + } + + fmt.Printf("value: %d\n", i) + + // ------------------------------------------------------------------------- + + fmt.Print("vectorString : ") + + vStr := vectorString{"A", "B", string([]byte{0xff})} + + s, err := vStr.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + if !utf8.ValidString(s) { + fmt.Print("non-valid string: ") + } + + fmt.Printf("value: %q\n", s) +} diff --git a/_content/tour/grc/generics/underlying-types/example2.go b/_content/tour/grc/generics/underlying-types/example2.go new file mode 100644 index 00000000..57c026be --- /dev/null +++ b/_content/tour/grc/generics/underlying-types/example2.go @@ -0,0 +1,56 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare a type that is based on the empty +// interface so any value can be added into the vector. Since the last +// function is using the empty interface for the return, users will need to +// perform type assertions to get back to the concrete value being stored +// inside the interface. +package main + +import ( + "errors" + "fmt" + "unicode/utf8" +) + +type vectorInterface []interface{} + +func (v vectorInterface) last() (interface{}, error) { + if len(v) == 0 { + return nil, errors.New("empty") + } + + return v[len(v)-1], nil +} + +// ============================================================================= + +func main() { + fmt.Print("vectorInterface : ") + + vItf := vectorInterface{10, "A", 20, "B", 3.14} + + itf, err := vItf.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + switch v := itf.(type) { + case int: + if v < 0 { + fmt.Print("negative integer: ") + } + case string: + if !utf8.ValidString(v) { + fmt.Print("non-valid string: ") + } + default: + fmt.Printf("unknown type %T: ", v) + } + + fmt.Printf("value: %v\n", itf) +} diff --git a/_content/tour/grc/generics/underlying-types/example3.go b/_content/tour/grc/generics/underlying-types/example3.go new file mode 100644 index 00000000..b01260db --- /dev/null +++ b/_content/tour/grc/generics/underlying-types/example3.go @@ -0,0 +1,66 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare a generics version of the user defined +// type. It represents a slice of some type T (to be determined later). The +// last method is declared with a value receiver based on a vector of the +// same type T and returns a value of that same type T as well. +package main + +import ( + "errors" + "fmt" + "unicode/utf8" +) + +type vector[T any] []T + +func (v vector[T]) last() (T, error) { + var zero T + + if len(v) == 0 { + return zero, errors.New("empty") + } + + return v[len(v)-1], nil +} + +// ============================================================================= + +func main() { + fmt.Print("vector[int] : ") + + vGenInt := vector[int]{10, -1} + + i, err := vGenInt.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + if i < 0 { + fmt.Print("negative integer: ") + } + + fmt.Printf("value: %d\n", i) + + // ------------------------------------------------------------------------- + + fmt.Print("vector[string] : ") + + vGenStr := vector[string]{"A", "B", string([]byte{0xff})} + + s, err := vGenStr.last() + if err != nil { + fmt.Print("ERROR:", err) + return + } + + if !utf8.ValidString(s) { + fmt.Print("non-valid string: ") + } + + fmt.Printf("value: %q\n", s) +} diff --git a/_content/tour/grc/generics/underlying-types/exercise1.go b/_content/tour/grc/generics/underlying-types/exercise1.go new file mode 100644 index 00000000..211e173b --- /dev/null +++ b/_content/tour/grc/generics/underlying-types/exercise1.go @@ -0,0 +1,32 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Implement a generic map type. +package main + +// Declare a generic type named keymap that uses an underlying type of map +// with a key of type string and a value of some type T. + +// Implement a method named set that accepts a key of type string and a value +// of type T. + +// Implement a method named get that accepts a key of type string and return +// a value of type T and true or false if the key is found. + +func main() { + // Construct a value of type keymap that stores integers. + + // Add a value with key "jack" and a value of 10. + + // Add a value with key "jill" and a value of 20. + + // Get the value for "jack" and verify it's found. + + // Print the value for the key "jack". + + // Get the value for "jill" and verify it's found. + + // Print the value for the key "jill". +} diff --git a/_content/tour/grc/goroutines.article b/_content/tour/grc/goroutines.article new file mode 100644 index 00000000..b2e15404 --- /dev/null +++ b/_content/tour/grc/goroutines.article @@ -0,0 +1,419 @@ +Goroutines +Goroutines are functions that are created and scheduled to be run independently by the Go scheduler. + +* Goroutines + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Goroutines are functions that are created and scheduled to be run independently +by the Go scheduler. The Go scheduler is responsible for the management and +execution of goroutines. + +** Code Review + +- *Example* *1:* Goroutines and Concurrency +- *Example* *2:* Goroutine context switching +- *Example* *3:* Goroutines and Parallelism + +.play goroutines/example1.go +.play goroutines/example2.go +.play goroutines/example3.go + +** Scheduler Semantics + +When a Go program starts up, the Go runtime asks the machine (virtual or physical) +how many operating system threads can run in parallel. This is based on the number +of cores that are available to the program. For each thread that can be run in parallel, +the runtime creates an operating system thread (M) and attaches that to a data structure +that represents a logical processor (P) inside the program. This P and M represent the +compute power or execution context for running the Go program. + +Also, an initial Goroutine (G) is created to manage the execution of instructions +on a selected M/P. Just like an M manages the execution of instructions on the hardware, +a G manages the execution of instructions on the M. This creates a new layer of +abstraction above the operating system, but it moves execution control to the +application level. + +.image /tour/grc/static/img/gor1.png + +Since the Go scheduler sits on top of the operating system scheduler, it’s important +to have some semantic understanding of the operating system scheduler and the constraints +it applies to the Go scheduler and applications. + +The operating system scheduler has the job of creating the illusions that multiple +pieces of work are being executed at the same time. Even when this is physically +impossible. This requires some tradeoffs in the design of the scheduler. Before I +go any further, it’s important to define some words. + +*Work:* A set of instructions to be executed for a running application. This is +accomplished by threads and an application can have 1 to many threads. + +*Thread:* A path of execution that is scheduled and performed. Threads are responsible +for the execution of instructions on the hardware. + +*Thread* *States:* A thread can be in one of three states: Running, Runnable, or +Waiting. Running means the thread is executing its assigned instructions on the +hardware by having a G placed on the M. Runnable means the thread wants time on +the hardware to execute its assigned instructions and is sitting in a run queue. +Waiting means the thread is waiting for something before it can resume its work. +Waiting threads are not a concern of the scheduler. + +*Concurrency:* This means undefined out of order execution. In other words, given +a set of instructions that would be executed in the order provided, they are executed +in a different undefined order, but all executed. The key is, the result of executing +the full set of instructions in any undefined order produces the same result. You will +say work can be done concurrently when the order the work is executed in doesn’t matter, +as long as all the work is completed. + +*Parallelism:* This means doing a lot of things at once. For this to be an option, +you need the ability to physically execute two or more operating system threads at +the same time on the hardware. + +*CPU* *Bound* *Work:* This is work that does not cause the thread to naturally move +into a waiting state. Calculating fibonacci numbers would be considered CPU-Bound work. + +*I/O* *Bound* *Work:* This is work that does cause the thread to naturally move into +a waiting state. Fetching data from different URLs would be considered I/O-Bound work. + +*Synchronization:* When two or more Goroutines will need to access the same memory +location potentially at the same time, they need to be synchronized and take turns. +If this synchronization doesn’t take place, and at least one Goroutine is performing +a write, you can end up with a data race. Data races are a cause of data corruption +bugs that can be difficult to find. + +*Orchestration:* When two or more Goroutines need to signal each other, with or +without data, orchestration is the mechanic required. If orchestration does not +take place, guarantees about concurrent work being performed and completed will +be missed. This can cause all sorts of data corruption bugs. + +There are lots of little details related to the scheduling semantics, so to learn +more read the three posts in chapter 14 titled, Scheduling In Go. + +** Concurrency Basics + +Starting with a basic concurrency problem that requires orchestration. + + func init() { + runtime.GOMAXPROCS(1) + } + +The call to GOMAXPROCS is being used to run the Go program as a single threaded +Go program. This program will be single threaded and have a single P/M to execute +all Goroutines. The function is capitalized because it’ s also an environment variable. +Though this function call will overwrite the variable. + + g := runtime.GOMAXPROCS(0) + +This function is an important function when you set CPU quotas to a container +configuration. When passing 0, the number of threads the Go program will be using +is reported. You must make sure that number matches the number of operating system +threads you have available in my containerized environment. If the numbers are not +the same, the Go program won’t run as well as it otherwise could. You might want +to use the environment variable or this call to match things up. + + func main() { + var wg sync.WaitGroup + wg.Add(2) + + go func() { + lowercase() + wg.Done() + }() + + go func() { + uppercase() + wg.Done() + }() + + fmt.Println("Waiting To Finish") + wg.Wait() + + fmt.Println("\nTerminating Program") + } + +This program has to solve an orchestration problem. The main Goroutine can’t allow +the main function to return until there is a guarantee the two Goroutines being +created finish their work first. A WaitGroup is a perfect tool for orchestration +problems that don’t require data to be passed between Goroutines. The signaling +here is performed through an API that allows a Goroutine to wait for other Goroutines +to signal they’re done. + +In this code, a WaitGroup is constructed to its zero value state and then immediately +the Add method is called to set the WaitGroup to 2, which will match the number of +Goroutines to be created. When you know how many Goroutines upfront that will be +created, you should call Add once with that number. When you don’t know (like in +a streaming service) then calling Add(1) is acceptable. + +At the end of main is the call to Wait. Wait holds the main Goroutine from causing +the function to return. When the main function returns, the Go program is shut +down with extreme prejudice. This is why managing the orchestration with the proper +guarantees is important. The Wait call will block until the WaitGroup is set back +to 0. + +In the middle of the program, you have the creation of the two Goroutines. + + func main() { + . . . + + go func() { + lowercase() + wg.Done() + }() + + go func() { + uppercase() + wg.Done() + }() + + . . . + } + +Literal functions are declared and executed with the use of the keyword go. At +this point, you are telling the Go scheduler to execute these functions concurrently. +To execute them in an undefined order. Inside the implementation of each Goroutine +is the call to Done. That call is what decrements the WaitGroup by 1. Once both +calls to Done are made, the WaitGroup will change from 2 to 0, and then the main +Goroutine will be allowed to be unblocked from the call to Wait, terminating the +program. + + func main() { + var wg sync.WaitGroup + wg.Add(2) + + go func() { + lowercase() + wg.Done() + }() + + . . . + } + +An important part of this orchestration pattern is keeping the Add and Done calls +in the same line of sight. Try not to pass the WaitGroup as a function parameter +where the calls get lost. This will help to reduce bugs. + + Output: + + Start Goroutines + Waiting To Finish + A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z + Terminating Program + +When you build and run this program, you see how this program runs concurrently. +The second Goroutine created was scheduled first. It got to finish its work and +then the other Goroutine ran. Both ran to completion before the program terminated. +The next time you run this program, there is no guarantee you see the same output. +The only guarantee in this program is that the program won’t terminate until the +two Goroutines are done. + +Even if you run this program 100 times and see the same output, there is no guarantee +it will happen again. It may be highly probable, but not guaranteed. Especially not +guaranteed across different versions, operating systems and architectures. + + func main() { + . . . + + fmt.Println("Waiting To Finish") + // wg.Wait() <-- CHANGED + + fmt.Println("\nTerminating Program") + } + +If you comment the call to Wait what will happen when you run this program? Once +again, there is no guarantee at all anymore with what will happen, but there are +different possibilities. + +The program could behave as before since calls to Println are system calls that +do allow the scheduler to make a context switch. The program could execute just +one of the two Goroutines or possibly terminate immediately. + + func main() { + var wg sync.WaitGroup + wg.Add(2) + + go func() { + lowercase() + // wg.Done() <-- CHANGED + }() + + . . . + } + +What happens if you forget to call Done in one of the Goroutines? In this case, +the program would deadlock since the WaitGroup can’t get back down to 0. The Wait +call will block forever. + + Output: + + Start Goroutines + Waiting To Finish + A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z + fatal error: all goroutines are asleep - deadlock! + + goroutine 1 [semacquire]: + sync.runtime_Semacquire(0xc00001a0a8) + /usr/local/go/src/runtime/sema.go:56 +0x45 + sync.(*WaitGroup).Wait(0xc00001a0a0) + /usr/local/go/src/sync/waitgroup.go:130 +0x65 + main.main() + concurrency/goroutines/example1/example1.go:42 +0x145 + exit status 2 + +You can see how the Go Runtime identifies the program is deadlocked on line 42 where +the call to Wait is happening. You shouldn’t get too excited about deadlock detection +since every single Goroutine needs to be blocked with no way out. This shows why keeping +the Add and Done call together is so important. + + func main() { + var wg sync.WaitGroup + wg.Add(1) <-- CHANGED, Number Too Small + + go func() { + lowercase() + wg.Done() + }() + + go func() { + uppercase() + wg.Done() + }() + + . . . + } + +What happens if you don’t give the WaitGroup the correct number of Goroutines to +wait on? If the number is too large, you will have another deadlock. If the number +is too small, there are no guarantees that the work is done before the program moves +on. The output of the program is undefined. + +** Preemptive Scheduler + +Even though the scheduler runs within the scope of the application, it’s important +to see how the schedule is preemptive. This means you can’t predict when a context +switch will take place and this will change every time you run the program. + + func main() { + var wg sync.WaitGroup + wg.Add(2) + + go func() { + printHashes("A") + wg.Done() + }() + + go func() { + printHashes("B") + wg.Done() + }() + + fmt.Println("Waiting To Finish") + wg.Wait() + + fmt.Println("\nTerminating Program") + } + +Using the same orchestration pattern as before, this program has each Goroutine doing +a lot more work. Work that the scheduler won’t give a Goroutine enough time to finish +completely in one time slice. + + func printHashes(prefix string) { + for i := 1; i <= 50000; i++ { + num := strconv.Itoa(i) + sum := sha1.Sum([]byte(num)) + fmt.Printf("%s: %05d: %x\n", prefix, i, sum) + } + fmt.Println("Completed", prefix) + } + +This function is performing a lot of I/O bound work that has the potential of +being context switched. + + $ ./example2 | cut -c1 | grep '[AB]' | uniq + B + A + B + A + B + A + B + A + B + A 9 Context Switches + + $ ./example2 | cut -c1 | grep '[AB]' | uniq + B + A + B + A 3 Context Switches + +As you can see, every time you run the program, there are a different number of +context switches. This is a great thing because a scheduler shouldn’t be predictable. +Concurrency needs to remain undefined and you must remember that when you use concurrency +to solve my performance problems. + + func init() { + runtime.GOMAXPROCS(2) + } + +What happens if you go back to the original program but change GOMAXPROCS so the program runs as a two threaded Go program? + + Output: + + Start Goroutines + Waiting To Finish + A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N a b c d e f g h i j k l m n o O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z + Terminating Program + +What you see is that the concurrency of the program is now more fine grained. The output +to the letter is undefined and out of order. + +** Notes + +- Goroutines are functions that are scheduled to run independently. +- We must always maintain an account of running goroutines and shutdown cleanly. +- Concurrency is not parallelism. + +- Concurrency is about dealing with lots of things at once. +- Parallelism is about doing lots of things at once. + +"Parallelism is about physically doing two or more things at the same time. Concurrency is about undefined, out of order, execution." - William Kennedy + +"By default, goroutines shouldn't outlive the function they were created from. this forces you into a extremely good design posture." - Peter Bourgon + +** Design Guidelines + +- Learn about the [[https://github.com/ardanlabs/gotraining/blob/master/topics/go/#concurrent-software-design][design guidelines]] for concurrency. + +** Extra Reading + +- [[https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html][Scheduling In Go - Part I]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html][Scheduling In Go - Part II]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2015/02/scheduler-tracing-in-go.html][Scheduler Tracing In Go]] - William Kennedy +- [[https://blog.golang.org/advanced-go-concurrency-patterns][Advanced Go Concurrency Patterns]] - Sameer Ajmani +- [[https://blog.golang.org/context][Go Concurrency Patterns: Context]] - Sameer Ajmani +- [[https://blog.golang.org/concurrency-is-not-parallelism][Concurrency is not parallelism]] - Rob Pike +- [[https://talks.golang.org/2013/distsys.slide][Go, for Distributed Systems]] - Russ Cox +- [[https://docs.google.com/document/d/1At2Ls5_fhJQ59kDK2DFVhFu3g5mATSXqqV5QrxinasI/edit][Go 1.5 GOMAXPROCS Default]] +- [[https://www.ardanlabs.com/blog/2014/01/concurrency-goroutines-and-gomaxprocs.html][Concurrency, Goroutines and GOMAXPROCS]] - William Kennedy +- [[http://www.ece.ubc.ca/~sasha/papers/eurosys16-final29.pdf][The Linux Scheduler: a Decade of Wasted Cores]] +- [[https://news.ycombinator.com/item?id=12460807][Explanation of the Scheduler]] +- [[http://joeduffyblog.com/2016/11/30/15-years-of-concurrency/][15 Years of Concurrency]] - Joe Duffy +- [[https://www.quora.com/How-does-the-golang-scheduler-work/answer/Ian-Lance-Taylor][How does the golang scheduler work?]] - Ian Lance Taylor +- [[https://www.youtube.com/watch?v=YHRO5WQGh0k][The Scheduler Saga]] - Kavya Joshi + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +*Part* *A* Create a program that declares two anonymous functions. One that counts +down from 100 to 0 and one that counts up from 0 to 100. Display each number with +an unique identifier for each goroutine. Then create goroutines from these functions +and don't let main return until the goroutines complete. + +*Part* *B* Run the program in parallel. + +.play goroutines/exercise1.go +.play goroutines/answer1.go diff --git a/_content/tour/grc/goroutines/answer1.go b/_content/tour/grc/goroutines/answer1.go new file mode 100644 index 00000000..57789858 --- /dev/null +++ b/_content/tour/grc/goroutines/answer1.go @@ -0,0 +1,62 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create a program that declares two anonymous functions. One that counts down from +// 100 to 0 and one that counts up from 0 to 100. Display each number with an +// unique identifier for each goroutine. Then create goroutines from these functions +// and don't let main return until the goroutines complete. +// +// Run the program in parallel. +package main + +import ( + "fmt" + "runtime" + "sync" +) + +func init() { + + // Allocate one logical processor for the scheduler to use. + runtime.GOMAXPROCS(1) +} + +func main() { + + // Declare a wait group and set the count to two. + var wg sync.WaitGroup + wg.Add(2) + + fmt.Println("Start Goroutines") + + // Declare an anonymous function and create a goroutine. + go func() { + // Count down from 100 to 0. + for count := 100; count >= 0; count-- { + fmt.Printf("[A:%d]\n", count) + } + + // Tell main we are done. + wg.Done() + }() + + // Declare an anonymous function and create a goroutine. + go func() { + // Count up from 0 to 100. + for count := 0; count <= 100; count++ { + fmt.Printf("[B:%d]\n", count) + } + + // Tell main we are done. + wg.Done() + }() + + // Wait for the goroutines to finish. + fmt.Println("Waiting To Finish") + wg.Wait() + + // Display "Terminating Program". + fmt.Println("\nTerminating Program") +} diff --git a/_content/tour/grc/goroutines/example1.go b/_content/tour/grc/goroutines/example1.go new file mode 100644 index 00000000..db406e3f --- /dev/null +++ b/_content/tour/grc/goroutines/example1.go @@ -0,0 +1,69 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to create goroutines and +// how the scheduler behaves. +package main + +import ( + "fmt" + "runtime" + "sync" +) + +func init() { + + // Allocate one logical processor for the scheduler to use. + runtime.GOMAXPROCS(1) +} + +func main() { + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(2) + + fmt.Println("Start Goroutines") + + // Create a goroutine from the lowercase function. + go func() { + lowercase() + wg.Done() + }() + + // Create a goroutine from the uppercase function. + go func() { + uppercase() + wg.Done() + }() + + // Wait for the goroutines to finish. + fmt.Println("Waiting To Finish") + wg.Wait() + + fmt.Println("\nTerminating Program") +} + +// lowercase displays the set of lowercase letters three times. +func lowercase() { + + // Display the alphabet three times + for count := 0; count < 3; count++ { + for r := 'a'; r <= 'z'; r++ { + fmt.Printf("%c ", r) + } + } +} + +// uppercase displays the set of uppercase letters three times. +func uppercase() { + + // Display the alphabet three times + for count := 0; count < 3; count++ { + for r := 'A'; r <= 'Z'; r++ { + fmt.Printf("%c ", r) + } + } +} diff --git a/_content/tour/grc/goroutines/example2.go b/_content/tour/grc/goroutines/example2.go new file mode 100644 index 00000000..a1819c4c --- /dev/null +++ b/_content/tour/grc/goroutines/example2.go @@ -0,0 +1,72 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// $ ./example2 | cut -c1 | grep '[AB]' | uniq + +// Sample program to show how the goroutine scheduler +// will time slice goroutines on a single thread. +package main + +import ( + "crypto/sha1" + "fmt" + "runtime" + "strconv" + "sync" +) + +func init() { + + // Allocate one logical processor for the scheduler to use. + runtime.GOMAXPROCS(1) +} + +func main() { + + // wg is used to manage concurrency. + var wg sync.WaitGroup + wg.Add(2) + + fmt.Println("Create Goroutines") + + // Create the first goroutine and manage its lifecycle here. + go func() { + printHashes("A") + wg.Done() + }() + + // Create the second goroutine and manage its lifecycle here. + go func() { + printHashes("B") + wg.Done() + }() + + // Wait for the goroutines to finish. + fmt.Println("Waiting To Finish") + wg.Wait() + + fmt.Println("Terminating Program") +} + +// printHashes calculates the sha1 hash for a range of +// numbers and prints each in hex encoding. +func printHashes(prefix string) { + + // print each has from 1 to 10. Change this to 50000 and + // see how the scheduler behaves. + for i := 1; i <= 50000; i++ { + + // Convert i to a string. + num := strconv.Itoa(i) + + // Calculate hash for string num. + sum := sha1.Sum([]byte(num)) + + // Print prefix: 5-digit-number: hex encoded hash + fmt.Printf("%s: %05d: %x\n", prefix, i, sum) + } + + fmt.Println("Completed", prefix) +} diff --git a/_content/tour/grc/goroutines/example3.go b/_content/tour/grc/goroutines/example3.go new file mode 100644 index 00000000..52807782 --- /dev/null +++ b/_content/tour/grc/goroutines/example3.go @@ -0,0 +1,64 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to create goroutines and +// how the goroutine scheduler behaves with two contexts. +package main + +import ( + "fmt" + "runtime" + "sync" +) + +func init() { + + // Allocate two logical processors for the scheduler to use. + runtime.GOMAXPROCS(2) +} + +func main() { + + // wg is used to wait for the program to finish. + // Add a count of two, one for each goroutine. + var wg sync.WaitGroup + wg.Add(2) + + fmt.Println("Start Goroutines") + + // Declare an anonymous function and create a goroutine. + go func() { + + // Display the alphabet three times. + for count := 0; count < 3; count++ { + for r := 'a'; r <= 'z'; r++ { + fmt.Printf("%c ", r) + } + } + + // Tell main we are done. + wg.Done() + }() + + // Declare an anonymous function and create a goroutine. + go func() { + + // Display the alphabet three times. + for count := 0; count < 3; count++ { + for r := 'A'; r <= 'Z'; r++ { + fmt.Printf("%c ", r) + } + } + + // Tell main we are done. + wg.Done() + }() + + // Wait for the goroutines to finish. + fmt.Println("Waiting To Finish") + wg.Wait() + + fmt.Println("\nTerminating Program") +} diff --git a/_content/tour/grc/goroutines/exercise1.go b/_content/tour/grc/goroutines/exercise1.go new file mode 100644 index 00000000..3b914650 --- /dev/null +++ b/_content/tour/grc/goroutines/exercise1.go @@ -0,0 +1,46 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Create a program that declares two anonymous functions. One that counts down from +// 100 to 0 and one that counts up from 0 to 100. Display each number with an +// unique identifier for each goroutine. Then create goroutines from these functions +// and don't let main return until the goroutines complete. +// +// Run the program in parallel. +package main + +// Add imports. +import "runtime" + +func init() { + + // Allocate one logical processor for the scheduler to use. + runtime.GOMAXPROCS(1) +} + +func main() { + + // Declare a wait group and set the count to two. + + // Declare an anonymous function and create a goroutine. + { + // Declare a loop that counts down from 100 to 0 and + // display each value. + + // Tell main we are done. + } + + // Declare an anonymous function and create a goroutine. + { + // Declare a loop that counts up from 0 to 100 and + // display each value. + + // Tell main we are done. + } + + // Wait for the goroutines to finish. + + // Display "Terminating Program". +} diff --git a/_content/tour/grc/interfaces.article b/_content/tour/grc/interfaces.article new file mode 100644 index 00000000..2bb0ab2b --- /dev/null +++ b/_content/tour/grc/interfaces.article @@ -0,0 +1,406 @@ +Interfaces +Interfaces give programs structure and encourage design by composition. + +* Interfaces + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Interfaces give programs structure and encourage design by composition. They enable +and enforce clean divisions between components. The standardization of interfaces +can set clear and consistent expectations. Decoupling means reducing the dependencies +between components and the types they use. This leads to correctness, quality and +maintainability. + +** Code Review + +- *Example* *1:* Repetitive Code That Needs Polymorphism +- *Example* *2:* Polymorphism +- *Example* *3:* Method Sets +- *Example* *4:* Address Of Value +- *Example* *5:* Storage By Value +- *Example* *6:* Type Assertions +- *Example* *7:* Conditional Type Assertions +- *Example* *8:* The Empty Interface and Type Switches +- *Example* *9:* Storing Values + +.play interfaces/example1.go +.play interfaces/example2.go +.play interfaces/example3.go +.play interfaces/example4.go +.play interfaces/example5.go +.play interfaces/example6.go +.play interfaces/example7.go +.play interfaces/example8.go +.play interfaces/example9.go + +** Interface Semantics + +Interfaces allow you to group concrete data together by what the data can do. +It’s about focusing on what data can do and not what the data is. Interfaces +also help my code decouple itself from change by asking for concrete data based +on what it can do. It’s not limited to one type of data. + +You must do my best to understand what data changes are coming and use interfaces +to decouple my program from that change. Interfaces should describe behavior and +not state. They should be verbs and not nouns. + +Generalized interfaces that focus on behavior are best. Interfaces with more than +one method have more than one reason to change. Interfaces that are based on nouns, +tend to be less reusable, are more susceptible to change, and defeat the purpose +of the interface. Uncertainty about change is not a license to guess but a directive +to STOP and learn more. You must distinguish between code that defends against +fraud vs protects against accidents. + +Use an interface when: + +- Users of the API need to provide an implementation detail. +- API’s have multiple implementations they need to maintain internally. +- Parts of the API that can change have been identified and require decoupling. + +Don't use an interface: + +- For the sake of using an interface. +- To generalize an algorithm. +- When users can declare their own interfaces. +- If it's not clear how the interface makes the code better. + +** Interfaces Are Valueless + +The first important thing to understand is that an interface type declares a valueless type. + + type reader interface { + read(b []byte) (int, error) + } + +Type reader is not a struct type, but an interface type. Its declaration is not +based on state, but behavior. Interface types declare a method-set of behavior +that concrete data must exhibit in order to satisfy the interface. There is nothing +\concrete about interface types, therefore they are valueless. + + var r reader + +Because they are valueless, the construction of a variable (like r) is odd because +in our programming model, r does not exist, it’s valueless. There is nothing about +r itself that you can manipulate or transform. This is a critical concept to understand. +I’m never working with interface values, only concrete values. An interface has a +compiler representation (internal type), but from our programming model, interfaces +are valueless. + +** Implementing Interfaces + +Go is a language that is about convention over configuration. When it comes to a +concrete type implementing an interface, there is no exception. + + type reader interface { + read(b []byte) (int, error) + } + + type file struct { + name string + } + + func (file) read(b []byte) (int, error) { + s := "Going Go" + copy(b, s) + return len(s), nil + } + +The code declares a type named file and then declares a method named read. Because +of these two declarations, you can say the following: + +"The concrete type file now implements the reader interface using value semantics" + +Every word said is important. In Go, all you have to do is declare the full method-set +of behavior defined by an interface to implement that interface. In this case, that is +what I’ve done since the reader interface only declares a single act of behavior named read. + + type reader interface { + read(b []byte) (int, error) + } + + type pipe struct { + name string + } + + func (pipe) read(b []byte) (int, error) { + s := `{name: "Bill", title: "developer"}` + copy(b, s) + return len(s), nil + } + +This code declares a type named pipe and then declares a method name read. Because +of these two declarations, you can say the following: + +"The concrete type pipe now implements the reader interface using value semantics" + +Now you have two concrete types implementing the reader interface. Two concrete types +each with their unique implementation. One type is reading file systems and the other +networks. + +** Polymorphism + +Polymorphism means that a piece of code changes its behavior depending on the concrete +data it’s operating on. This was said by Tom Kurtz, who is the inventor of BASIC. This +is the definition we will use moving forward. + + // retrieve can read any device and process the data. + func retrieve(r reader) error { + data := make([]byte, 100) + + len, err := r.read(data) + if err != nil { + return err + } + + fmt.Println(string(data[:len])) + return nil + } + +Take a look at the type of data this function accepts. It wants a value of type reader. +That’s impossible since reader is an interface and interfaces are valueless types. +It can’t be asking for a reader value, they don’t exist. + +If the function is not asking for a reader value then what is the function asking +for? It is asking for the only thing it can ask for, concrete data. + +The function retrieve is a polymorphic function because it’s asking for concrete +data not based on what the data is (concrete type), but based on what the data can +do (interface type). + + f := file{"data.json"} + p := pipe{"cfg_service"} + + retrieve(f) + retrieve(p) + +You can construct two concrete values, one of type file and one of type pipe. Then +you can pass a copy of each value to the polymorphic function. This is because each +of these values implement the full method set of behavior defined by the reader interface. + +When the concrete file value is passed into retrieve, the value is stored inside a +two word internal type representing the interface value. + +.image /tour/grc/static/img/i1.png + +The second word of the interface value points to the value being stored. In this +case, it’s a copy of the file value since value semantics are at play. The first +word points to a special data structure that is called the iTable. + +The iTable serves 2 purposes: + +- It describes the type of value being stored. In my case, it’s a file value. +- It provides function pointers to the concrete implementations of the method set for the type of value being stored. + +When the read call is made against the interface value, an iTable lookup is performed +to find the concrete implementation of the read method associated with the type. +Then the method call is made against the value being stored in the second word. + +You can say retrieve is a polymorphic function because the concrete value pipe can be +passed into retrieve and now the call to read against the interface value changes +its behavior. This time that call to read is reading a network instead of reading +a file. + +** Method Set Rules + +Implementing an interface using pointer semantics applies some constraints on +interface compliance. + + type notifier interface { + notify() + } + + type user struct { + name string + email string + } + + func (u *user) notify() { + fmt.Printf("Sending User Email To %s<%s>\n", u.name, u.email) + } + + func sendNotification(n notifier) { + n.notify() + } + + func main() { + u := user{"Bill", "bill@email.com"} + sendNotification(u) + } + +The notifier interface is implemented by the user type using pointer semantics. +When value semantics are used to make the polymorphic call, the following compiler +message is produced. + + "cannot use u (type user) as type notifier in argument to sendNotification: + user does not implement notifier (notify method has pointer receiver)" + +This is because there is a special set of rules in the specification about method +sets. These rules define what methods are attached to values and pointers of a type. +They are in place to maintain the highest level of integrity in my program. + +These are the rules defined in the specification: + +- For any value of type T, only those methods implemented with a value receiver for that type belong to the method set of that value. +- For any address of type T, all methods implemented for that type belong to the method set of that value. + +In other words, when working with an address (pointer), all methods implemented are +attached and available to be called. When working with a value, only those methods +implemented with value receivers are attached and available to be called. + +In the previous lesson about methods, you were able to call a method against a +concrete piece of data regardless of the data semantics declared by the receiver. +This is because the compiler can adjust to make the call. In this case, a value is +being stored inside an interface and the methods must exist. No adjustments can +be made. + +The question now becomes: Why can't methods implemented with pointer receivers be +attached to values of type T? What is the integrity issue here? + +One reason is because you can’t guarantee that every value of type T is addressable. +If a value doesn’t have an address, it can’t be shared. + + type duration int + + func (d *duration) notify() { + fmt.Println("Sending Notification in", *d) + } + + func main() { + duration(42).notify() + } + +Compiler Error: + + cannot call pointer method on duration(42) + cannot take the address of duration(42) + +In this example, the value of 42 is a constant of kind int. Even though the value +is converted into a value of type duration, it’s not being stored inside a variable. +This means the value is never on the stack or heap. There isn’t an address. Constants +only live at compile time. + +The second reason is the bigger reason. The compiler is telling you that you are not +allowed to use value semantics if you have chosen to use pointer semantics. In other +words, you are being forced to share the value with the interface since it’s not +safe to make a copy of a value that a pointer points to. If you chose to implement +the method with pointer semantics, you are stating that a value of this type isn’t +safe to be copied. + + func main() { + u := user{"Bill", "bill@email.com"} + sendNotification(&u) + } + +To fix the compiler message, you must use pointer semantics on the call to the +polymorphic function and share u. The answer is not to change the method to use +value semantics. + +** Slice of Interface + +When you declare a slice of an interface type, I’m capable of grouping different +concrete values together based on what they can do. This is why Go doesn’t need +the concept of sub-typing. It’s not about a common DNA, it’s about a common behavior. + + type printer interface { + print() + } + + type canon struct { + name string + } + + func (c canon) print() { + fmt.Printf("Printer Name: %s\n", c.name) + } + + type epson struct { + name string + } + + func (e *epson) print() { + fmt.Printf("Printer Name: %s\n", e.name) + } + + func main() { + c := canon{"PIXMA TR4520"} + e := epson{"WorkForce Pro WF-3720"} + + printers := []printer{ + c, + &e, + } + c.name = "PROGRAF PRO-1000" + e.name = "Home XP-4100" + + for _, p := range printers { + p.print() + } + } + +Output: + + Printer Name: PIXMA TR4520 + Printer Name: Home XP-4100 + +The code shows how a slice of the interface type printer allows me to create a +collection of different concrete printer types. Iterating over the collection and +leveraging polymorphism since the call to p.print changes its behavior depending +on the concrete value the code is operating against. + +The example also shows how the choice of data semantics changes the behavior of the +program. When storing the data using value semantics, the change to the original +value is not seen. This is because a copy is stored inside the interface. When +pointer semantics are used, any changes to the original value are seen. + +** Notes + +- The method set for a value, only includes methods implemented with a value receiver. +- The method set for a pointer, includes methods implemented with both pointer and value receivers. +- Methods declared with a pointer receiver, only implement the interface with pointer values. +- Methods declared with a value receiver, implement the interface with both a value and pointer receiver. +- The rules of method sets apply to interface types. +- Interfaces are reference types, don't share with a pointer. +- This is how we create polymorphic behavior in go. + +** Quotes + +"Polymorphism means that you write a certain program and it behaves differently depending on the data that it operates on." - Tom Kurtz (inventor of BASIC) + +"The empty interface says nothing." - Rob Pike + +"Design is the art of arranging code to work today, and be changeable forever." - Sandi Metz + +"A proper abstraction decouples the code so that every change doesn’t echo throughout the entire code base." - Ronna Steinburg + +** Extra Reading + +- [[https://golang.org/doc/effective_go.html#interfaces][Interfaces]] +- [[https://blog.golang.org/laws-of-reflection][The Laws of Reflection]] - Rob Pike +- [[https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html][Methods, Interfaces and Embedded Types in Go]] - William Kennedy +- [[https://rakyll.org/interface-pollution/][Interface Pollution]] - JBD +- [[https://bravenewgeek.com/abstraction-considered-harmful/][Abstraction Considered Harmful]] - Tyler Treat +- [[https://www.ardanlabs.com/blog/2018/03/interface-values-are-valueless.html][Interface Values Are Valueless]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2017/07/interface-semantics.html][Interface Semantics]] - William Kennedy +- [[https://www.hyrumslaw.com/][Hyrum's Law]] - Hyrum +- [[https://www.youtube.com/watch?v=Pjz9WrXeOW0][Engineering Innovation - Why Constraints Are Critical]] - André Eriksson (MUST WATCH) + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +*Part* *A:* Declare an interface named speaker with a method named speak. Declare a +struct named english that represents a person who speaks english and declare a struct +named chinese for someone who speaks chinese. Implement the speaker interface for each +struct using a value receiver and these literal strings "Hello World" and "你好世界". +Declare a variable of type speaker and assign the address of a value of type english +and call the method. Do it again for a value of type chinese. + +*Part* *B:* Add a new function named sayHello that accepts a value of type speaker. +Implement that function to call the speak method on the interface value. Then create + new values of each type and use the function. + +.play interfaces/exercise1.go +.play interfaces/answer1.go diff --git a/_content/tour/grc/interfaces/answer1.go b/_content/tour/grc/interfaces/answer1.go new file mode 100644 index 00000000..cf50e102 --- /dev/null +++ b/_content/tour/grc/interfaces/answer1.go @@ -0,0 +1,80 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare an interface named speaker with a method named speak. Declare a struct +// named english that represents a person who speaks english and declare a struct named +// chinese for someone who speaks chinese. Implement the speaker interface for each +// struct using a value receiver and these literal strings "Hello World" and "你好世界". +// Declare a variable of type speaker and assign the address of a value of type english +// and call the method. Do it again for a value of type chinese. +// +// Add a new function named sayHello that accepts a value of type speaker. +// Implement that function to call the speak method on the interface value. Then create +// new values of each type and use the function. +package main + +import "fmt" + +// speaker implements the voice of anyone. +type speaker interface { + speak() +} + +// english represents an english speaking person. +type english struct{} + +// speak implements the speaker interface using a +// value receiver. +func (english) speak() { + fmt.Println("Hello World") +} + +// chinese represents a chinese speaking person. +type chinese struct{} + +// speak implements the speaker interface using a +// pointer receiver. +func (*chinese) speak() { + fmt.Println("你好世界") +} + +func main() { + + // Declare a variable of the interface speaker type + // set to its zero value. + var sp speaker + + // Declare a variable of type english. + var e english + + // Assign the english value to the speaker variable. + sp = e + + // Call the speak method against the speaker variable. + sp.speak() + + // Declare a variable of type chinese. + var c chinese + + // Assign the chinese pointer to the speaker variable. + sp = &c + + // Call the speak method against the speaker variable. + sp.speak() + + // Call the sayHello function with new values and pointers + // of english and chinese. + sayHello(english{}) + sayHello(&english{}) + sayHello(&chinese{}) + + // Why does this not work? + // sayHello(chinese{}) +} + +// sayHello abstracts speaking functionality. +func sayHello(sp speaker) { + sp.speak() +} diff --git a/_content/tour/grc/interfaces/example1.go b/_content/tour/grc/interfaces/example1.go new file mode 100644 index 00000000..fc11d047 --- /dev/null +++ b/_content/tour/grc/interfaces/example1.go @@ -0,0 +1,70 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program that could benefit from polymorphic behavior with interfaces. +package main + +import "fmt" + +// file defines a system file. +type file struct { + name string +} + +// read implements the reader interface for a file. +func (file) read(b []byte) (int, error) { + s := "Going Go Programming" + copy(b, s) + return len(s), nil +} + +// pipe defines a named pipe network connection. +type pipe struct { + name string +} + +// read implements the reader interface for a network connection. +func (pipe) read(b []byte) (int, error) { + s := `{name: "bill", title: "developer"}` + copy(b, s) + return len(s), nil +} + +func main() { + + // Create two values one of type file and one of type pipe. + f := file{"data.json"} + p := pipe{"cfg_service"} + + // Call each retrieve function for each concrete type. + retrieveFile(f) + retrievePipe(p) +} + +// retrieveFile can read from a file and process the data. +func retrieveFile(f file) error { + data := make([]byte, 100) + + len, err := f.read(data) + if err != nil { + return err + } + + fmt.Println(string(data[:len])) + return nil +} + +// retrievePipe can read from a pipe and process the data. +func retrievePipe(p pipe) error { + data := make([]byte, 100) + + len, err := p.read(data) + if err != nil { + return err + } + + fmt.Println(string(data[:len])) + return nil +} diff --git a/_content/tour/grc/interfaces/example2.go b/_content/tour/grc/interfaces/example2.go new file mode 100644 index 00000000..a9e10aa4 --- /dev/null +++ b/_content/tour/grc/interfaces/example2.go @@ -0,0 +1,62 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how polymorphic behavior with interfaces. +package main + +import "fmt" + +// reader is an interface that defines the act of reading data. +type reader interface { + read(b []byte) (int, error) +} + +// file defines a system file. +type file struct { + name string +} + +// read implements the reader interface for a file. +func (file) read(b []byte) (int, error) { + s := "Going Go Programming" + copy(b, s) + return len(s), nil +} + +// pipe defines a named pipe network connection. +type pipe struct { + name string +} + +// read implements the reader interface for a network connection. +func (pipe) read(b []byte) (int, error) { + s := `{name: "bill", title: "developer"}` + copy(b, s) + return len(s), nil +} + +func main() { + + // Create two values one of type file and one of type pipe. + f := file{"data.json"} + p := pipe{"cfg_service"} + + // Call the retrieve function for each concrete type. + retrieve(f) + retrieve(p) +} + +// retrieve can read any device and process the data. +func retrieve(r reader) error { + data := make([]byte, 100) + + len, err := r.read(data) + if err != nil { + return err + } + + fmt.Println(string(data[:len])) + return nil +} diff --git a/_content/tour/grc/interfaces/example3.go b/_content/tour/grc/interfaces/example3.go new file mode 100644 index 00000000..2ed6c756 --- /dev/null +++ b/_content/tour/grc/interfaces/example3.go @@ -0,0 +1,48 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to understand method sets. +package main + +import "fmt" + +// notifier is an interface that defines notification +// type behavior. +type notifier interface { + notify() +} + +// user defines a user in the program. +type user struct { + name string + email string +} + +// notify implements the notifier interface with a pointer receiver. +func (u *user) notify() { + fmt.Printf("Sending User Email To %s<%s>\n", + u.name, + u.email) +} + +func main() { + + // Create a value of type User and send a notification. + u := user{"Bill", "bill@email.com"} + + // Values of type user do not implement the interface because pointer + // receivers don't belong to the method set of a value. + + sendNotification(u) + + // ./example1.go:36: cannot use u (type user) as type notifier in argument to sendNotification: + // user does not implement notifier (notify method has pointer receiver) +} + +// sendNotification accepts values that implement the notifier +// interface and sends notifications. +func sendNotification(n notifier) { + n.notify() +} diff --git a/_content/tour/grc/interfaces/example4.go b/_content/tour/grc/interfaces/example4.go new file mode 100644 index 00000000..acb8e2f6 --- /dev/null +++ b/_content/tour/grc/interfaces/example4.go @@ -0,0 +1,24 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how you can't always get the address of a value. +package main + +import "fmt" + +// duration is a named type with a base type of int. +type duration int + +// notify implements the notifier interface. +func (d *duration) notify() { + fmt.Println("Sending Notification in", *d) +} + +func main() { + duration(42).notify() + + // ./example3.go:18: cannot call pointer method on duration(42) + // ./example3.go:18: cannot take the address of duration(42) +} diff --git a/_content/tour/grc/interfaces/example5.go b/_content/tour/grc/interfaces/example5.go new file mode 100644 index 00000000..c07a8546 --- /dev/null +++ b/_content/tour/grc/interfaces/example5.go @@ -0,0 +1,71 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the concrete value assigned to +// the interface is what is stored inside the interface. +package main + +import "fmt" + +// printer displays information. +type printer interface { + print() +} + +// cannon defines a cannon printer. +type cannon struct { + name string +} + +// print displays the printer's name. +func (c cannon) print() { + fmt.Printf("Printer Name: %s\n", c.name) +} + +// epson defines a epson printer. +type epson struct { + name string +} + +// print displays the printer's name. +func (e *epson) print() { + fmt.Printf("Printer Name: %s\n", e.name) +} + +func main() { + + // Create a cannon and epson printer. + c := cannon{"PIXMA TR4520"} + e := epson{"WorkForce Pro WF-3720"} + + // Add the printers to the collection using both + // value and pointer semantics. + printers := []printer{ + + // Store a copy of the cannon printer value. + c, + + // Store a copy of the epson printer value's address. + &e, + } + + // Change the name field for both printers. + c.name = "PROGRAF PRO-1000" + e.name = "Home XP-4100" + + // Iterate over the slice of printers and call + // print against the copied interface value. + for _, p := range printers { + p.print() + } + + // When we store a value, the interface value has its own + // copy of the value. Changes to the original value will + // not be seen. + + // When we store a pointer, the interface value has its own + // copy of the address. Changes to the original value will + // be seen. +} diff --git a/_content/tour/grc/interfaces/example6.go b/_content/tour/grc/interfaces/example6.go new file mode 100644 index 00000000..68825182 --- /dev/null +++ b/_content/tour/grc/interfaces/example6.go @@ -0,0 +1,71 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show the syntax of type assertions. +package main + +import ( + "fmt" + "log" +) + +// user defines a user in our application. +type user struct { + id int + name string +} + +// finder represents the ability to find users. +type finder interface { + find(id int) (*user, error) +} + +// userSVC is a service for dealing with users. +type userSVC struct { + host string +} + +// find implements the finder interface using pointer semantics. +func (*userSVC) find(id int) (*user, error) { + return &user{id: id, name: "Anna Walker"}, nil +} + +func main() { + svc := userSVC{ + host: "localhost:3434", + } + + if err := run(&svc); err != nil { + log.Fatal(err) + } +} + +// run performs the find operation against the concrete data that +// is passed into the call. +func run(f finder) error { + u, err := f.find(1234) + if err != nil { + return err + } + fmt.Printf("Found user %+v\n", u) + + // Ideally the finder abstraction would encompass all of + // the behavior you care about. But what if, for some reason, + // you really need to get to the concrete value stored inside + // the interface? + + // Can you access the "host" field from the concrete userSVC type pointer + // that is stored inside this interface variable? No, not directly. + // All you know is the data has a method named "find". + // ./example5.go:61:26: f.host undefined (type finder has no field or method host) + log.Println("queried", f.host) + + // You can use a type assertion to get a copy of the userSVC pointer + // that is stored inside the interface. + svc := f.(*userSVC) + log.Println("queried", svc.host) + + return nil +} diff --git a/_content/tour/grc/interfaces/example7.go b/_content/tour/grc/interfaces/example7.go new file mode 100644 index 00000000..aa81ab23 --- /dev/null +++ b/_content/tour/grc/interfaces/example7.go @@ -0,0 +1,66 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show type assertions using the comma-ok idiom. +package main + +import ( + "fmt" + "log" +) + +// user defines a user in our application. +type user struct { + id int + name string +} + +// finder represents the ability to find users. +type finder interface { + find(id int) (*user, error) +} + +// userSVC is a service for dealing with users. +type userSVC struct { + host string +} + +// find implements the finder interface using pointer semantics. +func (*userSVC) find(id int) (*user, error) { + return &user{id: id, name: "Anna Walker"}, nil +} + +// mockSVC defines a mock service we will access. +type mockSVC struct{} + +// find implements the finder interface using pointer semantics. +func (*mockSVC) find(id int) (*user, error) { + return &user{id: id, name: "Jacob Walker"}, nil +} + +func main() { + var svc mockSVC + + if err := run(&svc); err != nil { + log.Fatal(err) + } +} + +func run(f finder) error { + u, err := f.find(1234) + if err != nil { + return err + } + fmt.Printf("Found user %+v\n", u) + + // If the concrete type value stored inside the interface value is of the + // type *userSVC, then "ok" will be true and "svc" will be a copy of the + // pointer stored inside the interface. + if svc, ok := f.(*userSVC); ok { + log.Println("queried", svc.host) + } + + return nil +} diff --git a/_content/tour/grc/interfaces/example8.go b/_content/tour/grc/interfaces/example8.go new file mode 100644 index 00000000..d20edb8e --- /dev/null +++ b/_content/tour/grc/interfaces/example8.go @@ -0,0 +1,50 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show the syntax and mechanics of type +// switches and the empty interface. +package main + +import "fmt" + +func main() { + + // fmt.Println can be called with values of any type. + fmt.Println("Hello, world") + fmt.Println(12345) + fmt.Println(3.14159) + fmt.Println(true) + + // How can we do the same? + myPrintln("Hello, world") + myPrintln(12345) + myPrintln(3.14159) + myPrintln(true) + + // - An interface is satisfied by any piece of data when the data exhibits + // the full method set of behavior defined by the interface. + // - The empty interface defines no method set of behavior and therefore + // requires no method by the data being stored. + + // - The empty interface says nothing about the data stored inside + // the interface. + // - Checks would need to be performed at runtime to know anything about + // the data stored in the empty interface. + // - Decouple around well defined behavior and only use the empty + // interface as an exception when it is reasonable and practical to do so. +} + +func myPrintln(a interface{}) { + switch v := a.(type) { + case string: + fmt.Printf("Is string : type(%T) : value(%s)\n", v, v) + case int: + fmt.Printf("Is int : type(%T) : value(%d)\n", v, v) + case float64: + fmt.Printf("Is float64 : type(%T) : value(%f)\n", v, v) + default: + fmt.Printf("Is unknown : type(%T) : value(%v)\n", v, v) + } +} diff --git a/_content/tour/grc/interfaces/example9.go b/_content/tour/grc/interfaces/example9.go new file mode 100644 index 00000000..a1b836f1 --- /dev/null +++ b/_content/tour/grc/interfaces/example9.go @@ -0,0 +1,66 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program that explores how interface assignments work when +// values are stored inside the interface. +package main + +import ( + "fmt" + "unsafe" +) + +// notifier provides support for notifying events. +type notifier interface { + notify() +} + +// user represents a user in the system. +type user struct { + name string +} + +// notify implements the notifier interface. +func (u user) notify() { + fmt.Println("Alert", u.name) +} + +func inspect(n *notifier, u *user) { + word := uintptr(unsafe.Pointer(n)) + uintptr(unsafe.Sizeof(&u)) + value := (**user)(unsafe.Pointer(word)) + fmt.Printf("Addr User: %p Word Value: %p Ptr Value: %v\n", u, *value, **value) +} + +func main() { + + // Create a notifier interface and concrete type value. + var n1 notifier + u := user{"bill"} + + // Store a copy of the user value inside the notifier + // interface value. + n1 = u + + // We see the interface has its own copy. + // Addr User: 0x1040a120 Word Value: 0x10427f70 Ptr Value: {bill} + inspect(&n1, &u) + + // Make a copy of the interface value. + n2 := n1 + + // We see the interface is sharing the same value stored in + // the n1 interface value. + // Addr User: 0x1040a120 Word Value: 0x10427f70 Ptr Value: {bill} + inspect(&n2, &u) + + // Store a copy of the user address value inside the + // notifier interface value. + n1 = &u + + // We see the interface is sharing the u variables value + // directly. There is no copy. + // Addr User: 0x1040a120 Word Value: 0x1040a120 Ptr Value: {bill} + inspect(&n1, &u) +} diff --git a/_content/tour/grc/interfaces/exercise1.go b/_content/tour/grc/interfaces/exercise1.go new file mode 100644 index 00000000..76b1f2f1 --- /dev/null +++ b/_content/tour/grc/interfaces/exercise1.go @@ -0,0 +1,57 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare an interface named speaker with a method named speak. Declare a struct +// named english that represents a person who speaks english and declare a struct named +// chinese for someone who speaks chinese. Implement the speaker interface for each +// struct using a value receiver and these literal strings "Hello World" and "你好世界". +// Declare a variable of type speaker and assign the address of a value of type english +// and call the method. Do it again for a value of type chinese. +// +// Add a new function named sayHello that accepts a value of type speaker. +// Implement that function to call the speak method on the interface value. Then create +// new values of each type and use the function. +package main + +// Add imports. + +// Declare the speaker interface with a single method called speak. + +// Declare an empty struct type named english. + +// Declare a method named speak for the english type +// using a value receiver. "Hello World" + +// Declare an empty struct type named chinese. + +// Declare a method named speak for the chinese type +// using a pointer receiver. "你好世界" + +// sayHello accepts values of the speaker type. +func sayHello( /* Declare parameter */ ) { + + // Call the speak method from the speaker parameter. +} + +func main() { + + // Declare a variable of the interface speaker type + // set to its zero value. + + // Declare a variable of type english. + + // Assign the english value to the speaker variable. + + // Call the speak method against the speaker variable. + + // Declare a variable of type chinese. + + // Assign the chinese pointer to the speaker variable. + + // Call the speak method against the speaker variable. + + // Call the sayHello function with new values and pointers + // of english and chinese. +} diff --git a/_content/tour/grc/maps.article b/_content/tour/grc/maps.article new file mode 100644 index 00000000..3bca9c77 --- /dev/null +++ b/_content/tour/grc/maps.article @@ -0,0 +1,179 @@ +Maps +A map is a data structure that provides support for storing and accessing data based on a key. + +* Maps + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +A map is a data structure that provides support for storing and accessing data +based on a key. It uses a hash map and bucket system that maintains a contiguous +block of memory underneath. + +** Code Review + +- *Example* *1:* Declare, write, read, and delete +- *Example* *2:* Absent keys +- *Example* *3:* Map key restrictions +- *Example* *4:* Map literals and range +- *Example* *5:* Sorting maps by key +- *Example* *6:* Taking an element's address +- *Example* *7:* Maps are Reference Types + +.play maps/example1.go +.play maps/example2.go +.play maps/example3.go +.play maps/example4.go +.play maps/example5.go +.play maps/example6.go +.play maps/example7.go + +** Declaring And Constructing Maps + +Declaring and constructing a map can be done in several ways. + + type user struct { + name string + username string + } + + // Construct a map set to its zero value, + // that can store user values based on a key of type string. + // Trying to use this map will result in a runtime error (panic). + var users map[string]user + + // Construct a map initialized using make, + // that can store user values based on a key of type string. + users := make(map[string]user) + + // Construct a map initialized using empty literal construction, + // that can store user values based on a key of type string. + users := map[string]user{} + +A map set to its zero value is not usable and will result in your program panicking. +The use of the built-in function make and literal construction constructs a map +ready for use. + + func main() { + users := make(map[string]user) + + users["Roy"] = user{"Rob", "Roy"} + users["Ford"] = user{"Henry", "Ford"} + users["Mouse"] = user{"Mickey", "Mouse"} + users["Jackson"] = user{"Michael", "Jackson"} + + for key, value := range users { + fmt.Println(key, value) + } + } + +Output: + + Roy {Rob Roy} + Ford {Henry Ford} + Mouse {Mickey Mouse} + Jackson {Michael Jackson} + +If the built-in function make is used to construct a map, then the assignment operator +can be used to add and update values in the map. The order of how keys/values are +returned when ranging over a map is undefined by the spec and up to the compiler to +implement. + + func main() { + users := map[string]user{ + "Roy": {"Rob", "Roy"}, + "Ford": {"Henry", "Ford"}, + "Mouse": {"Mickey", "Mouse"}, + "Jackson": {"Michael", "Jackson"}, + } + + for key, value := range users { + fmt.Println(key, value) + } + } + +Output: + + Ford {Henry Ford} + Jackson {Michael Jackson} + Roy {Rob Roy} + Mouse {Mickey Mouse} + +In this case, the output was returned in a different order from how they are +listed in the construction. The current algorithm for 1.16 will return the results +in a random order once the number of values reaches a certain limit. Once again, +this is a compiler implementation that is allowed to change. You can’t depend on it. + +** Lookups and Deleting Map Keys + +Once data is stored inside of a map, to extract any data a key lookup is required. + + user1, exists1 := users["Bill"] + user2, exists2 := users["Ford"] + + fmt.Println("Bill:", exists1, user1) + fmt.Println("Ford:", exists2, user2) + +Output: + + Bill: false { } + Ford: true {Henry Ford} + +To perform a key lookup, square brackets are used with the map variable. Two +values can be returned from a map lookup, the value and a boolean that represents +if the value was found or not. If you don’t need to know this, you can leave the +"exists" variable out. + +When a key is not found in the map, the operation returns a value of the map type +set to its zero value state. You can see this with the "Bill" key lookup. Don’t use +zero value to determine if a key exists in the map or not since zero value may be +valid and what was actually stored for the key. + + delete(users, "Roy") + +There is a built-in function named delete that allows for the deletion of data +from the map based on a key. + +** Key Map Restrictions + +Not all types can be used as a key. + + type slice []user + Users := make(map[slice]user) + +Compiler Error: + + invalid map key type users + +A slice is a good example of a type that can’t be used as a key. Only values that +can be run through the hash function are eligible. A good way to recognize types +that can be a key is if the type can be used in a comparison operation. You can’t +compare two slice values. + +** Notes + +- Maps provide a way to store and retrieve key/value pairs. +- Reading an absent key returns the zero value for the map's value type. +- Iterating over a map is always random. +- The map key must be a value that is comparable. +- Elements in a map are not addressable. +- Maps are a reference type. + +** Links + +- [[https://blog.golang.org/go-maps-in-action][Go maps in action]] - Andrew Gerrand +- [[https://www.ardanlabs.com/blog/2013/12/macro-view-of-map-internals-in-go.html][Macro View of Map Internals In Go]] - William Kennedy +- [[https://www.youtube.com/watch?v=Tl7mi9QmLns][Inside the Map Implementation]] - Keith Randall +- [[https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics][How the Go runtime implements maps efficiently (without generics)]] - Dave Cheney + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Declare and make a map of integer values with a string as the key. Populate the +map with five values and iterate over the map to display the key/value pairs. + +.play maps/exercise1.go +.play maps/answer1.go diff --git a/_content/tour/grc/maps/answer1.go b/_content/tour/grc/maps/answer1.go new file mode 100644 index 00000000..c19cf806 --- /dev/null +++ b/_content/tour/grc/maps/answer1.go @@ -0,0 +1,28 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare and make a map of integer values with a string as the key. Populate the +// map with five values and iterate over the map to display the key/value pairs. +package main + +import "fmt" + +func main() { + + // Declare and make a map of integer type values. + departments := make(map[string]int) + + // Initialize some data into the map. + departments["IT"] = 20 + departments["Marketing"] = 15 + departments["Executives"] = 5 + departments["Sales"] = 50 + departments["Security"] = 8 + + // Display each key/value pair. + for key, value := range departments { + fmt.Printf("Dept: %s People: %d\n", key, value) + } +} diff --git a/_content/tour/grc/maps/example1.go b/_content/tour/grc/maps/example1.go new file mode 100644 index 00000000..7dff61e5 --- /dev/null +++ b/_content/tour/grc/maps/example1.go @@ -0,0 +1,51 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to initialize a map, write to +// it, then read and delete from it. +package main + +import "fmt" + +// user represents someone using the program. +type user struct { + name string + surname string +} + +func main() { + + // Declare and make a map that stores values + // of type user with a key of type string. + users := make(map[string]user) + + // Add key/value pairs to the map. + users["Roy"] = user{"Rob", "Roy"} + users["Ford"] = user{"Henry", "Ford"} + users["Mouse"] = user{"Mickey", "Mouse"} + users["Jackson"] = user{"Michael", "Jackson"} + + // Read the value at a specific key. + mouse := users["Mouse"] + + fmt.Printf("%+v\n", mouse) + + // Replace the value at the Mouse key. + users["Mouse"] = user{"Jerry", "Mouse"} + + // Read the Mouse key again. + fmt.Printf("%+v\n", users["Mouse"]) + + // Delete the value at a specific key. + delete(users, "Roy") + + // Check the length of the map. There are only 3 elements. + fmt.Println(len(users)) + + // It is safe to delete an absent key. + delete(users, "Roy") + + fmt.Println("Goodbye.") +} diff --git a/_content/tour/grc/maps/example2.go b/_content/tour/grc/maps/example2.go new file mode 100644 index 00000000..711c5775 --- /dev/null +++ b/_content/tour/grc/maps/example2.go @@ -0,0 +1,43 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how maps behave when you read an +// absent key. +package main + +import "fmt" + +func main() { + + // Create a map to track scores for players in a game. + scores := make(map[string]int) + + // Read the element at key "anna". It is absent so we get + // the zero-value for this map's value type. + score := scores["anna"] + + fmt.Println("Score:", score) + + // If we need to check for the presence of a key we use + // a 2 variable assignment. The 2nd variable is a bool. + score, ok := scores["anna"] + + fmt.Println("Score:", score, "Present:", ok) + + // We can leverage the zero-value behavior to write + // convenient code like this: + scores["anna"]++ + + // Without this behavior we would have to code in a + // defensive way like this: + if n, ok := scores["anna"]; ok { + scores["anna"] = n + 1 + } else { + scores["anna"] = 1 + } + + score, ok = scores["anna"] + fmt.Println("Score:", score, "Present:", ok) +} diff --git a/_content/tour/grc/maps/example3.go b/_content/tour/grc/maps/example3.go new file mode 100644 index 00000000..a1fb2fa6 --- /dev/null +++ b/_content/tour/grc/maps/example3.go @@ -0,0 +1,32 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how only types that can have +// equality defined on them can be a map key. +package main + +import "fmt" + +// user represents someone using the program. +type user struct { + name string + surname string +} + +// users defines a set of users. +type users []user + +func main() { + + // Declare and make a map that uses a slice as the key. + u := make(map[users]int) + + // ./example3.go:22: invalid map key type users + + // Iterate over the map. + for key, value := range u { + fmt.Println(key, value) + } +} diff --git a/_content/tour/grc/maps/example4.go b/_content/tour/grc/maps/example4.go new file mode 100644 index 00000000..f711c9bf --- /dev/null +++ b/_content/tour/grc/maps/example4.go @@ -0,0 +1,40 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare, initialize and iterate +// over a map. Shows how iterating over a map is random. +package main + +import "fmt" + +// user represents someone using the program. +type user struct { + name string + surname string +} + +func main() { + + // Declare and initialize the map with values. + users := map[string]user{ + "Roy": {"Rob", "Roy"}, + "Ford": {"Henry", "Ford"}, + "Mouse": {"Mickey", "Mouse"}, + "Jackson": {"Michael", "Jackson"}, + } + + // Iterate over the map printing each key and value. + for key, value := range users { + fmt.Println(key, value) + } + + fmt.Println() + + // Iterate over the map printing just the keys. + // Notice the results are different. + for key := range users { + fmt.Println(key) + } +} diff --git a/_content/tour/grc/maps/example5.go b/_content/tour/grc/maps/example5.go new file mode 100644 index 00000000..2f492ab5 --- /dev/null +++ b/_content/tour/grc/maps/example5.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to walk through a map by +// alphabetical key order. +package main + +import ( + "fmt" + "sort" +) + +// user represents someone using the program. +type user struct { + name string + surname string +} + +func main() { + + // Declare and initialize the map with values. + users := map[string]user{ + "Roy": {"Rob", "Roy"}, + "Ford": {"Henry", "Ford"}, + "Mouse": {"Mickey", "Mouse"}, + "Jackson": {"Michael", "Jackson"}, + } + + // Pull the keys from the map. + var keys []string + for key := range users { + keys = append(keys, key) + } + + // Sort the keys alphabetically. + sort.Strings(keys) + + // Walk through the keys and pull each value from the map. + for _, key := range keys { + fmt.Println(key, users[key]) + } +} diff --git a/_content/tour/grc/maps/example6.go b/_content/tour/grc/maps/example6.go new file mode 100644 index 00000000..bfffd74e --- /dev/null +++ b/_content/tour/grc/maps/example6.go @@ -0,0 +1,34 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show that you cannot take the address +// of an element in a map. +package main + +// player represents someone playing our game. +type player struct { + name string + score int +} + +func main() { + + // Declare a map with initial values using a map literal. + players := map[string]player{ + "anna": {"Anna", 42}, + "jacob": {"Jacob", 21}, + } + + // Trying to take the address of a map element fails. + anna := &players["anna"] + anna.score++ + + // ./example4.go:23:10: cannot take the address of players["anna"] + + // Instead take the element, modify it, and put it back. + player := players["anna"] + player.score++ + players["anna"] = player +} diff --git a/_content/tour/grc/maps/example7.go b/_content/tour/grc/maps/example7.go new file mode 100644 index 00000000..a91ac703 --- /dev/null +++ b/_content/tour/grc/maps/example7.go @@ -0,0 +1,30 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how maps are reference types. +package main + +import "fmt" + +func main() { + + // Initialize a map with values. + scores := map[string]int{ + "anna": 21, + "jacob": 12, + } + + // Pass the map to a function to perform some mutation. + double(scores, "anna") + + // See the change is visible in our map. + fmt.Println("Score:", scores["anna"]) +} + +// double finds the score for a specific player and +// multiplies it by 2. +func double(scores map[string]int, player string) { + scores[player] = scores[player] * 2 +} diff --git a/_content/tour/grc/maps/exercise1.go b/_content/tour/grc/maps/exercise1.go new file mode 100644 index 00000000..1b1062f4 --- /dev/null +++ b/_content/tour/grc/maps/exercise1.go @@ -0,0 +1,19 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare and make a map of integer values with a string as the key. Populate the +// map with five values and iterate over the map to display the key/value pairs. +package main + +// Add imports. + +func main() { + + // Declare and make a map of integer type values. + + // Initialize some data into the map. + + // Display each key/value pair. +} diff --git a/_content/tour/grc/methods.article b/_content/tour/grc/methods.article new file mode 100644 index 00000000..3247283b --- /dev/null +++ b/_content/tour/grc/methods.article @@ -0,0 +1,434 @@ +Methods +A function is called a method when that function has a receiver declared. + +* Methods + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +A function is called a method when that function has a receiver declared. The +receiver is the parameter that is declared between the keyword func and the +function name. + +** Code Review + +- *Example* *1:* Declare and receiver behavior +- *Example* *2:* Named typed methods +- *Example* *3:* Function/Method variables +- *Example* *4:* Function Types +- *Example* *5:* Value and Pointer semantics + +.play methods/example1.go +.play methods/example2.go +.play methods/example3.go +.play methods/example4.go +.play methods/example5.go + +** Method Declarations + +There are two types of receivers, value receivers for implementing value semantics +and pointer receivers for implementing pointer semantics. + + type user struct { + name string + email string + } + + func (u user) notify() { + fmt.Printf("Sending User Email To %s<%s>\n", u.name, u.email) + } + + func (u *user) changeEmail(email string) { + u.email = email + fmt.Printf("Changed User Email To %s\n", email) + } + +The notify function is implemented with a value receiver. This means the method +operates under value semantics and will operate on its own copy of the value used +to make the call. + +The changeEmail function is implemented with a pointer receiver. This means the +method operates under pointer semantics and will operate on shared access to the +value used to make the call. + +Outside of a few exceptions, a method set for a type should not contain a mix of +value and pointer receivers. Data semantic consistency is critically important +and this includes declaring methods. + +** Method Calls + +When making a method call, the compiler doesn’t care if the value used to make +the call matches the receiver’s data semantics exactly. The compiler just wants +a value or pointer of the same type. + + bill := user{"Bill", "bill@email.com"} + bill.notify() + bill.changeEmail("bill@hotmail.com") + +You can see that a value of type user is constructed and assigned to the bill +variable. In the case of the notify call, the bill variable matches the receiver +type which is using a value receiver. In the case of the changeEmail call, the +bill variable doesn’t match the receiver type which is using a pointer receiver. +However, the compiler accepts the method call and shares the bill variable with +the method. Go will adjust to make the call. + +This works the same when the variable used to make the call is a pointer variable. + + bill := &user{"Bill", "bill@email.com"} + bill.notify() + bill.changeEmail("bill@hotmail.com") + +In this case, the bill variable is a pointer variable to a value of type user. +Once again, Go adjusts to make the method call when calling the notify method. + +If Go didn’t adjust, then this is what you would have to do to make those same method calls. + + bill := user{"Bill", "bill@email.com"} + (&bill).changeEmail("bill@hotmail.com") + + bill := &user{"Bill", "bill@email.com"} + (*bill).notify() + +I’m glad you don’t have to do that to make method calls in Go. + +** Data Semantic Guideline For Internal Types + +As a guideline, if the data I’m working with is an internal type (slice, map, +channel, function, interface) then use value semantics to move the data around +the program. This includes declaring fields on a type. However, when I’m reading +and writing you need to remember I’m using pointer semantics. + + type IP []byte + type IPMask []byte + +These types are declared in the net package that is part of the standard library. +They are declared with an underlying type which is a slice of bytes. Because of +this, these types follow the guidelines for internal types. + + func (ip IP) Mask(mask IPMask) IP { + if len(mask) == IPv6len && len(ip) == IPv4len && allFF(mask[:12]) { + mask = mask[12:] + } + if len(mask) == IPv4len && len(ip) == IPv6len && + bytesEqual(ip[:12], v4InV6Prefix) { + ip = ip[12:] + } + n := len(ip) + if n != len(mask) { + return nil + } + out := make(IP, n) + for i := 0; i < n; i++ { + out[i] = ip[i] & mask[i] + } + return out + } + +With the Mask method, value semantics are in play for both the receiver, parameter, +and return argument. This method accepts its own copy of a Mask value, it mutates +that value and then it returns a copy of the mutation. This method is using value +semantic mutation. This is not an accident or random. + +A function can decide what data input and output it needs. What it can’t decide +is the data semantics for how the data flows in or out. The data drives that +decision and the function must comply. This is why Mask implements a value semantic +mutation api. It must respect how a slice is designed to be moved around the program. + + func ipEmptyString(ip IP) string { + if len(ip) == 0 { + return "" + } + return ip.String() + } + +The `ipEmptyString` function is also using value semantics for the input and output. +This function accepts its own copy of an IP value and returns a string value. No +use of pointer semantics because the data dictates the data semantics and not the +function. + +One exception to using value semantics is when you need to share a slice or map +with a function that performs unmarshaling or decoding. + +** Data Semantic Guideline For Struct Types + +As a guideline, if the data I’m working with is a struct type then you have to +think about what the data represents to make a decision. A good general rule is +to ask if the struct represents data or an API. If the struct represents data, use +value semantics. If the struct represents an API, use pointer semantics. + + type Time struct { + sec int64 + nsec int32 + loc *Location + } + +Here is the Time struct from the time package. If you consider Time to represents +data, value semantics should be used for this struct. + +When looking at an existing code base and you want to know what data semantic was +chosen, look for a factory function. The return type of a factory function should +dictate the data semantic chosen by the developer. + + func Now() Time { + sec, nsec := now() + return Time{sec + unixToInternal, nsec, Local} + } + +Now is the factory function for constructing Time values. Look at the return, +it’s using value semantics. This tells you that you should be using value semantics +for Time values which means every function gets its own copy of a Time value and +fields in a struct should be declared as values of type Time. + + func (t Time) Add(d Duration) Time { + t.sec += int64(d / 1e9) + nsec := int32(t.nsec) + int32(d%1e9) + if nsec >= 1e9 { + t.sec++ + nsec -= 1e9 + } else if nsec < 0 { + t.sec-- + nsec += 1e9 + } + t.nsec = nsec + return t + } + +Add is a method that needs to perform a mutation operation. If you look closely, +you will see the function is using value semantic mutation. The Add method gets its +own copy of the Time value used to make the call, it mutates its own copy, then it +returns a copy back to the caller. Once again, this is the safest way to perform a +mutation operation. + + func div(t Time, d Duration) (qmod2 int, r Duration) {} + +Here is another example where the div function accepts a value of type Time and +Duration (int64), then returns values of type int and Duration. Value semantics +for the Time type and for all the built-in types. Duration has an underlying type +of int64. + + func (t *Time) UnmarshalBinary(data []byte) error {} + func (t *Time) GobDecode(data []byte) error {} + func (t *Time) UnmarshalJSON(data []byte) error {} + func (t *Time) UnmarshalText(data []byte) error {} + +These four methods from the Time package seem to break the rules for data semantic +consistency. They are using pointer semantics, why? Because they are implementing +an interface where the method signature is locked in. Since the implementation +requires a mutation, pointer semantics are the only choice. + +Here is a guideline: If value semantics are at play, you can switch to pointer +semantics for some functions as long as you don’t let the data in the remaining +call chain switch back to value semantics. Once you switch to pointer semantics, +all future calls from that point need to stick to pointer semantics. You can never, +ever, never, go from pointer to value. It’s never safe to make a copy of a value +that a pointer points to. + + func Open(name string) (file *File, err error) { + return OpenFile(name, O_RDONLY, 0) + } + +The Open function from the os package shows that when using a value of type File, +pointer semantics are at play. File values need to be shared and should never be +copied. + + func (f *File) Chdir() error { + if f == nil { + return ErrInvalid + } + if e := syscall.Fchdir(f.fd); e != nil { + return &PathError{"chdir", f.name, e} + } + return nil + } + +The method Chdir is using a pointer receiver even though this method does not +mutate the File value. This is because File values need to be shared and can’t +be copied. + + func epipecheck(file *File, e error) { + if e == syscall.EPIPE { + if atomic.AddInt32(&file.nepipe, 1) >= 10 { + sigpipe() + } + } else { + atomic.StoreInt32(&file.nepipe, 0) + } + } + +The epipecheck function as well accepts File values using pointer semantics. + +** Methods Are Just Functions + +Methods are really just functions that provide syntactic sugar to provide the +ability for data to exhibit behavior. + + type data struct { + name string + age int + } + + func (d data) displayName() { + fmt.Println("My Name Is", d.name) + } + + func (d *data) setAge(age int) { + d.age = age + fmt.Println(d.name, "Is Age", d.age) + } + +A type and two methods are declared. The displayName method is using value +semantics and setAge is using pointer semantics. + +Note: Do not implement setters and getters in Go. These are not apis with purpose +and in these cases it’s better to make those fields exported. + + d := data{ + name: "Bill", + } + + d.displayName() + d.setAge(21) + +A value of type data is constructed and method calls are made. + + data.displayName(d) + (*data).setAge(&d, 21) + +Since methods are really just functions with syntactic sugar, the methods can be +executed like functions. You can see that the receiver is really a parameter, it’s +the first parameter. When you call a method, the compiler converts that to a +function call underneath. + +Note: Do not execute methods like this, but you may see this syntax in tooling messages. + +** Know The Behavior of the Code + +If you know the data semantics at play, then you know the behavior of the code. If +you know the behavior of the code, then you know the cost of the code. Once you +know the cost, I’m engineering. + +Given this type and method set. + + type data struct { + name string + age int + } + + func (d data) displayName() { + fmt.Println("My Name Is", d.name) + } + + func (d *data) setAge(age int) { + d.age = age + fmt.Println(d.name, "Is Age", d.age) + } + +You can write the following code. + + func main() { + d := data{ + name: "Bill", + } + + f1 := d.displayName + f1() + d.name = "Joan" + f1() + } + +Output: + + My Name Is Bill + My Name Is Bill + +You start with constructing a value of type Data assigning it to the variable d. +Then you take the method displayName, bound to d, and assign that to a variable +named f1. This is not a method call but an assignment which creates a level of +indirection. Functions are values in Go and belong to the set of internal types. + +After the assignment, you can call the method indirectly through the use of the +f1 variable. This displays the name Bill. Then you change the data so the name +is now Joan, and call the method once again through the f1 variable. You don’t +see the change. Bill is the output once again. So Why? + +.image /tour/grc/static/img/m1.png + +It has to do with the data semantics at play. The displayName method is using a +value receiver so value semantics are at play. + + func (d data) displayName() { + fmt.Println("My Name Is", d.name) + } + +This means that the f1 variable maintains and operates against its own copy of d. +So calling the method through the f1 variable, will always use the copy and that +copy is protected against change. This is what you want with value semantics. + +Now you will do the same thing but with the setAge method. + + func main() { + d := data{ + name: "Bill", + } + + f2 := d.setAge + f2(45) + d.name = "Sammy" + f2(45) + } + +Output: + + Bill Is Age 45 + Sammy Is Age 45 + +This time the setAge method is assigned to the variable f2. Once again, the +method is executed indirectly through the f2 variable passing 45 for Bill’s age. +Then Bill’s name is changed to Sammy and the f2 variable is used again to make the +call. This time you see the name has changed. + +.image /tour/grc/static/img/m2.png + +The setAge function is using a pointer receiver so setAge doesn’t operate on its +own copy of the d variable, but is operating directly on the d variable. Therefore, +f2 is operating on shared access and you see the change. + + func (d *data) setAge(age int) { + d.age = age + fmt.Println(d.name, "Is Age", d.age) + } + +Without knowing the data semantics at play, you won’t know the behavior of the code. +These data semantics are real and affect the behavior. + +** Notes + +- Methods are functions that declare a receiver variable. +- Receivers bind a method to a type and can use value or pointer semantics. +- Value semantics mean a copy of the value is passed across program boundaries. +- Pointer semantics mean a copy of the values address is passed across program boundaries. +- Stick to a single semantic for a given type and be consistent. + +** Quotes + +"Methods are valid when it is practical or reasonable for a piece of data to expose a capability." - William Kennedy + +** Extra Reading + +- [[https://golang.org/doc/effective_go.html#methods][Methods]] +- [[https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html][Methods, Interfaces and Embedded Types in Go]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2018/01/escape-analysis-flaws.html][Escape-Analysis Flaws]] - William Kennedy + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +Declare a struct that represents a baseball player. Include name, atBats and hits. +Declare a method that calculates a players batting average. The formula is Hits / AtBats. +Declare a slice of this type and initialize the slice with several players. Iterate +over the slice displaying the players name and batting average. + +.play methods/exercise1.go +.play methods/answer1.go diff --git a/_content/tour/grc/methods/answer1.go b/_content/tour/grc/methods/answer1.go new file mode 100644 index 00000000..cc40a645 --- /dev/null +++ b/_content/tour/grc/methods/answer1.go @@ -0,0 +1,48 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a struct that represents a baseball player. Include name, atBats and hits. +// Declare a method that calculates a players batting average. The formula is hits / atBats. +// Declare a slice of this type and initialize the slice with several players. Iterate over +// the slice displaying the players name and batting average. +package main + +import "fmt" + +// player represents a person in the game. +type player struct { + name string + atBats int + hits int +} + +// average calculates the batting average for a player. +func (p *player) average() float64 { + if p.atBats == 0 { + return 0.0 + } + + return float64(p.hits) / float64(p.atBats) +} + +func main() { + + // Create a few players. + ps := []player{ + {"bill", 10, 7}, + {"jim", 12, 6}, + {"ed", 6, 4}, + } + + // Display the batting average for each player. + for i := range ps { + fmt.Printf("%s: AVG[.%.f]\n", ps[i].name, ps[i].average()*1000) + } + + // Why did I not choose this form? + for _, p := range ps { + fmt.Printf("%s: AVG[.%.f]\n", p.name, p.average()*1000) + } +} diff --git a/_content/tour/grc/methods/example1.go b/_content/tour/grc/methods/example1.go new file mode 100644 index 00000000..0c285234 --- /dev/null +++ b/_content/tour/grc/methods/example1.go @@ -0,0 +1,64 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare methods and how the Go +// compiler supports them. +package main + +import ( + "fmt" +) + +// user defines a user in the program. +type user struct { + name string + email string +} + +// notify implements a method with a value receiver. +func (u user) notify() { + fmt.Printf("Sending User Email To %s<%s>\n", + u.name, + u.email) +} + +// changeEmail implements a method with a pointer receiver. +func (u *user) changeEmail(email string) { + u.email = email +} + +func main() { + + // Values of type user can be used to call methods + // declared with both value and pointer receivers. + bill := user{"Bill", "bill@email.com"} + bill.changeEmail("bill@hotmail.com") + bill.notify() + + // Pointers of type user can also be used to call methods + // declared with both value and pointer receiver. + joan := &user{"Joan", "joan@email.com"} + joan.changeEmail("joan@hotmail.com") + joan.notify() + + // Create a slice of user values with two users. + users := []user{ + {"ed", "ed@email.com"}, + {"erick", "erick@email.com"}, + } + + // Iterate over the slice of users switching + // semantics. Not Good! + for _, u := range users { + u.changeEmail("it@wontmatter.com") + } + + // Exception example: Using pointer semantics + // for a collection of strings. + keys := make([]string, 10) + for i := range keys { + keys[i] = func() string { return "key-gen" }() + } +} diff --git a/_content/tour/grc/methods/example2.go b/_content/tour/grc/methods/example2.go new file mode 100644 index 00000000..2b74cca3 --- /dev/null +++ b/_content/tour/grc/methods/example2.go @@ -0,0 +1,49 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare methods against +// a named type. +package main + +import "fmt" + +// duration is a named type that represents a duration +// of time in Nanosecond. +type duration int64 + +const ( + nanosecond duration = 1 + microsecond = 1000 * nanosecond + millisecond = 1000 * microsecond + second = 1000 * millisecond + minute = 60 * second + hour = 60 * minute +) + +// setHours sets the specified number of hours. +func (d *duration) setHours(h float64) { + *d = duration(h) * hour +} + +// hours returns the duration as a floating point number of hours. +func (d duration) hours() float64 { + hour := d / hour + nsec := d % hour + return float64(hour) + float64(nsec)*(1e-9/60/60) +} + +func main() { + + // Declare a variable of type duration set to + // its zero value. + var dur duration + + // Change the value of dur to equal + // five hours. + dur.setHours(5) + + // Display the new value of dur. + fmt.Println("Hours:", dur.hours()) +} diff --git a/_content/tour/grc/methods/example3.go b/_content/tour/grc/methods/example3.go new file mode 100644 index 00000000..f0eb89ba --- /dev/null +++ b/_content/tour/grc/methods/example3.go @@ -0,0 +1,82 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare function variables. +package main + +import "fmt" + +// data is a struct to bind methods to. +type data struct { + name string + age int +} + +// displayName provides a pretty print view of the name. +func (d data) displayName() { + fmt.Println("My Name Is", d.name) +} + +// setAge sets the age and displays the value. +func (d *data) setAge(age int) { + d.age = age + fmt.Println(d.name, "Is Age", d.age) +} + +func main() { + + // Declare a variable of type data. + d := data{ + name: "Bill", + } + + fmt.Println("Proper Calls to Methods:") + + // How we actually call methods in Go. + d.displayName() + d.setAge(45) + + fmt.Println("\nWhat the Compiler is Doing:") + + // This is what Go is doing underneath. + data.displayName(d) + (*data).setAge(&d, 45) + + // ========================================================================= + + fmt.Println("\nCall Value Receiver Methods with Variable:") + + // Declare a function variable for the method bound to the d variable. + // The function variable will get its own copy of d because the method + // is using a value receiver. + f1 := d.displayName + + // Call the method via the variable. + f1() + + // Change the value of d. + d.name = "Joan" + + // Call the method via the variable. We don't see the change. + f1() + + // ========================================================================= + + fmt.Println("\nCall Pointer Receiver Method with Variable:") + + // Declare a function variable for the method bound to the d variable. + // The function variable will get the address of d because the method + // is using a pointer receiver. + f2 := d.setAge + + // Call the method via the variable. + f2(45) + + // Change the value of d. + d.name = "Sammy" + + // Call the method via the variable. We see the change. + f2(45) +} diff --git a/_content/tour/grc/methods/example4.go b/_content/tour/grc/methods/example4.go new file mode 100644 index 00000000..d4d15309 --- /dev/null +++ b/_content/tour/grc/methods/example4.go @@ -0,0 +1,76 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare and use function types. +package main + +import "fmt" + +// event displays global events. +func event(message string) { + fmt.Println(message) +} + +// data is a struct to bind methods to. +type data struct { + name string + age int +} + +// event displays events for this data. +func (d *data) event(message string) { + fmt.Println(d.name, message) +} + +// ============================================================================= + +// fireEvent1 uses an anonymous function type. +func fireEvent1(f func(string)) { + f("anonymous") +} + +// handler represents a function for handling events. +type handler func(string) + +// fireEvent2 uses a function type. +func fireEvent2(h handler) { + h("handler") +} + +// ============================================================================= + +func main() { + + // Declare a variable of type data. + d := data{ + name: "Bill", + } + + // Use the fireEvent1 handler that accepts any + // function or method with the right signature. + fireEvent1(event) + fireEvent1(d.event) + + // Use the fireEvent2 handler that accepts any + // function or method of type `handler` or any + // literal function or method with the right signature. + fireEvent2(event) + fireEvent2(d.event) + + // Declare a variable of type handler for the + // global and method based event functions. + h1 := handler(event) + h2 := handler(d.event) + + // User the fireEvent2 handler that accepts + // values of type handler. + fireEvent2(h1) + fireEvent2(h2) + + // User the fireEvent1 handler that accepts + // any function or method with the right signature. + fireEvent1(h1) + fireEvent1(h2) +} diff --git a/_content/tour/grc/methods/example5.go b/_content/tour/grc/methods/example5.go new file mode 100644 index 00000000..fb0d8cf7 --- /dev/null +++ b/_content/tour/grc/methods/example5.go @@ -0,0 +1,142 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +import ( + "sync/atomic" + "syscall" +) + +// Sample code to show how it is important to use value or pointer semantics +// in a consistent way. Choose the semantic that is reasonable and practical +// for the given type and be consistent. One exception is an unmarshal +// operation since that always requires the address of a value. + +// ***************************************************************************** + +// These is a named type from the net package called IP and IPMask with a base +// type that is a slice of bytes. Since we use value semantics for reference +// types, the implementation is using value semantics for both. + +type IP []byte +type IPMask []byte + +// Mask is using a value receiver and returning a value of type IP. This +// method is using value semantics for type IP. + +func (ip IP) Mask(mask IPMask) IP { + if len(mask) == IPv6len && len(ip) == IPv4len && allFF(mask[:12]) { + mask = mask[12:] + } + if len(mask) == IPv4len && len(ip) == IPv6len && bytesEqual(ip[:12], v4InV6Prefix) { + ip = ip[12:] + } + n := len(ip) + if n != len(mask) { + return nil + } + out := make(IP, n) + for i := 0; i < n; i++ { + out[i] = ip[i] & mask[i] + } + return out +} + +// ipEmptyString accepts a value of type IP and returns a value of type string. +// The function is using value semantics for type IP. + +func ipEmptyString(ip IP) string { + if len(ip) == 0 { + return "" + } + return ip.String() +} + +// ***************************************************************************** + +// Should time use value or pointer semantics? If you need to modify a time +// value should you mutate the value or create a new one? + +type Time struct { + sec int64 + nsec int32 + loc *Location +} + +// Factory functions dictate the semantics that will be used. The Now function +// returns a value of type Time. This means we should be using value +// semantics and copy Time values. + +func Now() Time { + sec, nsec := now() + return Time{sec + unixToInternal, nsec, Local} +} + +// Add is using a value receiver and returning a value of type Time. This +// method is using value semantics for Time. + +func (t Time) Add(d Duration) Time { + t.sec += int64(d / 1e9) + nsec := int32(t.nsec) + int32(d%1e9) + if nsec >= 1e9 { + t.sec++ + nsec -= 1e9 + } else if nsec < 0 { + t.sec-- + nsec += 1e9 + } + t.nsec = nsec + return t +} + +// div accepts a value of type Time and returns values of built-in types. +// The function is using value semantics for type Time. + +func div(t Time, d Duration) (qmod2 int, r Duration) { + // Code here +} + +// The only use pointer semantics for the `Time` api are these +// unmarshal related functions. + +func (t *Time) UnmarshalBinary(data []byte) error { +func (t *Time) GobDecode(data []byte) error { +func (t *Time) UnmarshalJSON(data []byte) error { +func (t *Time) UnmarshalText(data []byte) error { + +// ***************************************************************************** + +// Factory functions dictate the semantics that will be used. The Open function +// returns a pointer of type File. This means we should be using pointer +// semantics and share File values. + +func Open(name string) (file *File, err error) { + return OpenFile(name, O_RDONLY, 0) +} + +// Chdir is using a pointer receiver. This method is using pointer +// semantics for File. + +func (f *File) Chdir() error { + if f == nil { + return ErrInvalid + } + if e := syscall.Fchdir(f.fd); e != nil { + return &PathError{"chdir", f.name, e} + } + return nil +} + +// epipecheck accepts a pointer of type File. +// The function is using pointer semantics for type File. + +func epipecheck(file *File, e error) { + if e == syscall.EPIPE { + if atomic.AddInt32(&file.nepipe, 1) >= 10 { + sigpipe() + } + } else { + atomic.StoreInt32(&file.nepipe, 0) + } +} \ No newline at end of file diff --git a/_content/tour/grc/methods/exercise1.go b/_content/tour/grc/methods/exercise1.go new file mode 100644 index 00000000..555d0ef1 --- /dev/null +++ b/_content/tour/grc/methods/exercise1.go @@ -0,0 +1,27 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a struct that represents a baseball player. Include name, atBats and hits. +// Declare a method that calculates a player's batting average. The formula is hits / atBats. +// Declare a slice of this type and initialize the slice with several players. Iterate over +// the slice displaying the players name and batting average. +package main + +// Add imports. + +// Declare a struct that represents a ball player. +// Include fields called name, atBats and hits. + +// Declare a method that calculates the batting average for a player. +func ( /* receiver */ ) average() /* return type */ { +} + +func main() { + + // Create a slice of players and populate each player + // with field values. + + // Display the batting average for each player in the slice. +} diff --git a/_content/tour/grc/pointers.article b/_content/tour/grc/pointers.article new file mode 100644 index 00000000..44110724 --- /dev/null +++ b/_content/tour/grc/pointers.article @@ -0,0 +1,341 @@ +Δείκτες Διεύθυνσης +Οι δείκτες διεύθυνσης είναι χρήσιμοι προκειμένου να μοιράζεται κανείς τιμές κατά μήκος των ορίων ενός προγράμματος. + +* Δείκτες Διεύθυνσης + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Παρακολουθήστε το Video]] +- Εαν Χρειάζεστε Οικονομική Συνδρομή, Χρησιμοποιείστε το σχετικό [[https://www.ardanlabs.com/scholarship/][Έγγραφο Υποτροφίας]] + +Οι δέικτες διεύθυνσης είναι χρήσιμοι προκειμένου να μοιράζεται κανείς τιμές κατά μήκος των ορίων ενός προγράμματος. Υπάρχουν +διάφορα είδη ορίων σε κάθε πρόγραμμα. Το πιο κοινό απ' όλα είναι αυτό μεταξύ κλήσεων συναρτήσεων. +Υπάρχει ακόμα ένα όριο μεταξύ διαφορετικών Goroutines για το οποίο υπάρχουν σημειώσεις παρακάτω. + +** Ανασκόπηση Κώδικα + +- *Παράδειγμα* *1:* Περνώντας Παραμέτρους ως Τιμές +- *Παράδειγμα* *2:* Κοινή Χρήση Δεδομένων I +- *Παράδειγμα* *3:* Κοινή Χρήση Δεδομένων II +- *Παράδειγμα* *4:* Ανάλυση Διαφυγής +- *Παράδειγμα* *5:* Μεγέθυνση Στοίβας + +.play pointers/example1.go +.play pointers/example2.go +.play pointers/example3.go +.play pointers/example4.go +.play pointers/example5.go + +Όταν ένα πρόγραμμα γραμμένο στην Go ξεκινά, το εκτελέσιμο περιβάλλον της Go δημιουργεί μια Goroutine. Οι Goroutines είναι +ελαφριά νήματα εκτέλεσης λειτουργιών επιπέδου εφαρμογής που φέρουν την ίδια σημασιολογία ως επί το πλείστον με την λειτουργία +των νημάτων εκτέλεσης λειτουργιών του συστήματος. Η δουλειά τους είναι ο έλεγχος της εκτέλεσης ενός διακριτού συνόλου +οδηγιών. Κάθε πρόγραμμα της Go έχει τουλάχιστον 1 Goroutine, η οποία αποκαλείται ως η κύρια Goroutine. + +Κάθε Goroutine αποκτά το δικό της τμήμα μνήμης, που ονομάζεται στοίβα. Κάθε στοίβα ξεκινάει +σαν μια δεύσμευση μνήμης 2048 byte (2k). Είναι πολύ μικρή αρχικά, αλλά οι στοίβες μπορούν να μεγαλώσουν με το πέρας του χρόνου. + +.image /tour/grc/static/img/p1.png + +Κάθε φορά που καλείται μια συνάρτηση, δεσμεύεται ένα τμήμα διαθέσιμου χώρου για την στοίβα, προκειμένου να επιτρέψει στην Goroutine +να εκτελέσει τις οδηγίες που σχετίζονται με αυτή την συνάρτηση. Κάθε ξεχωριστό τμήμα της μνήμης +αποκαλείται πλαίσιο μνήμης. + +Το μέγεθος του πλαισίου μνήμης για μια συνάρτηση υπολογίζεται στο στάδιο της μεταγλώττισης. Καμία τιμή +δεν μπορεί να σημιουργηθεί στην στοίβα αν ο μεταγλωτιστής δεν γνωρίζει το μέγεθος αυτής της τιμής κατά το +στάδιο της μεταγλώττισης. Αν ο μεταγλωττιστής δεν γνωρίζει το μέγεθος της τιμής στο στάδιο της μεταγλώττισης, η +τιμή πρέπει να δημιουργηθεί στον σωρό. + +Οι στοίβες εκκαθαρίζονται αυτόματα και η μηδενικές τιμές μεταβλητών συμβάλλουν στον ορισμό αρχικής τιμής της στοίβας. +Κάθε φορά που καλεί κανείς μια συνάρτηση και σχηματίζεται ένα πλαίσιο μνήμης, η μνήμη +για αυτό το πλαίσιο λαμβάνει αρχική τιμή, και με αυτόν τον τρόπο η στοίβα εκκαθαρίζεται αυτόματα. Όταν μια συνάρτηση +επιστρέφει, η μνήμη για το συγκεκριμένο πλαίσιο μνήμης αφήνεται στην ησυχία της, καθώς δεν είναι γνωστό αν η συγκεκριμένη μνήμη +θα χρειαστεί ξανά. Θα ήταν αναποτελεσματικό να λαμβάνει αρχική τιμή η μνήμη μετά την επιστροφή των συναρτήσεων. + +*Περνώντας* *Παραμέτρους* *ως* *Τιμές* + +Όλα τα δεδομένα μετακινούνται μέσα στο πρόγραμμα ως τιμές. Αυτό σημαίνει ότι καθώς τα δεδομένα διασχίζουν +τα όρια του προγράμματος, η κάθε συνάρτηση ή η κάθε Goroutine αποκτά το δικό της αντίγραφο των +δεδομένων. Υπάρχουν δύο είδη δεδομένων με τα οποία μπορεί κανείς να δουλέψει, η τιμή αυτή καθ' ευατή (int, +string, user) ή η διεύθυνση της τιμής στην μνήμη. Οι διευθύνσεις μνήμης είναι δεδομένα που χρειάζεται να αντιγραφούν +και να αποθηκευτούν διασχίζοντας τα όρια ενός προγράμματος. + +Ο ακόλουθος κώδικας προσπαθεί να εξηγήσει τα παραπάνω περισσότερο. + + func main() { + + // Δημιουργία μεταβλητής ακέραιου τύπου με τιμή 10. + count := 10 + + // Προκειμένου να πάρει κανείς την διεύθυνση μνήμης μιας τιμής, μπορεί να + // χρησιμοποιήσει τον τελεστή &. + println("count:\tValue Of[", count, "]\tAddr Of[", &count, "]") + + // Περνώντας ένα αντίγραφο της "τιμής της" count (αυτό που βρίσκεται μέσα στο κουτί) + // στην συνάρτηση increment1. + increment1(count) + + // Τυπώνοντας την "τιμή της" και την "διεύθυνση της" παραμέτρου count. + // Η τιμή της count δεν θα αλλάξει μετά την κλήση της συνάρτησης. + println("count:\tValue Of[", count, "]\tAddr Of[", &count, "]") + + // Περνώντας ένα αντίγραφο της "διεύθυνσης της" count (πού βρίσκεται το κουτί) + // στην συνάρτηση increment2. Αυτός ο τρόπος θεωρείται πάλι ότι περνάει + // την παράμετρο ως τιμή και όχι ως αναφορά καθώς οι διευθύνσεις είναι τιμές. + increment2(&count) + + // Τυπώνοντας την "τιμή της" και την "διεύθυνση της" παραμέτρου count. + // Η τιμή της count έχει αλλάξει μετά την κλήση της συνάρτησης. + println( + "count:\tValue Of[", + count, "]\tAddr Of[", &count, "]") + } + + // Η increment1 ορίζει την συνάρτηση να δέχεται το δικό της αντίγραφο μιας + // ακέραιας τιμής. + func increment1(inc int) { + + // Αυξάνοντας το τοπικό αντίγραφο της int τιμής του καλούντος. + inc++ + println("inc1:\tValue Of[", inc, "]\tAddr Of[", &inc, "]") + } + + // Η increment2 ορίζει την συνάρτηση να δέχεται το δικό της αντίγραφο μιας + // διεύθυνσης που δείχνει σε ακέραια τιμή. + // Οι μεταβλητές διεύθυνσης μνήμης είναι ρητοί τύποι και ορίζονται με την χρήση του τελεστή *. + func increment2(inc *int) { + + // Αυξάνοντας την int τιμή του καλούντος μέσω της διεύθυνσης μνήμης. + *inc++ + println( + "inc2:\tValue Of[", + inc, "]\tAddr Of[", &inc, + "]\tPoints To[", *inc, "]") + } + +Αποτέλεσμα: + + count: Value Of[ 10 ] Addr Of[ 0xc000050738 ] + inc1: Value Of[ 11 ] Addr Of[ 0xc000050730 ] + count: Value Of[ 10 ] Addr Of[ 0xc000050738 ] + inc2: Value Of[ 0xc000050738 ] Addr Of[ 0xc000050748 ] Points To[ 11 ] + count: Value Of[ 11 ] Addr Of[ 0xc000050738 ] + +** Σημειώσεις + +- Κανείς χρησιμοποιεί διευθύνσεις μνήμης προκειμένου να μπορεί να μοιράζεται τα δεδομένα. +- Οι τιμές στην Go πάντα διαθέτονται προκειμένου να περάσουν ως παράμετροι σε συναρτήσεις ως τιμές (pass by value). +- "Η τιμή της", τι βρίσκεται δηλαδή μέσα στο κουτί. "Η διεύθυνση της" ( & ), πού βρίσκεται δηλαδή το κουτί. +- Ο τελεστής ( * ) ορίζει μια μεταβλητή διεύθυνσης μνήμης και "Η τιμή την οποία η διεύθυνση μνήμης υποδεικνύει". + +** Ανάλυση Διαφυγής + +Ο αλγόριθμος που χρησιμοποιεί ο μεταγλωττιστής προκειμένου να διαπιστώσει αν μια τιμή πρέπει να δημιουργηθεί +στην στοίβα ή στον σωρό ονομάζεται "ανάλυση διαφυγής". Η ονομασία του αλγορίθμου το +παρουσιάζει σαν οι τιμές να δημιουργούνται πρώτα στην στοίβα και στην συνέχεια διαφεύγουν (ή μετακινούνται) +στον σωρό, όταν αυτό κριθεί αναγκαίο. ΔΕΝ είναι έτσι όμως. Η δημιουργία μιας τιμής συμβαίνει μόνο +μια φορά, και ο αλγόριθμος ανάλυσης διαφυγής αποφασίζει που θα συμβεί αυτό (στοίβα +ή σωρός). Μόνο η δημιουργια στον σωρό αποκαλείται εκχώρηση μνήμης στην Go. + +Η κατανόηση της ανάλυσης διαφυγής αφορά την κατανόηση της ιδιοκτησίας των τιμών. Η ιδέα είναι ότι, +όταν μια τιμή δημιουργείται εντός του πλαισίου αναφοράς μιας συνάρτησης, τότε η συνάρτηση αυτή +κατέχει την ιδιοκτησία της τιμής. Από εκεί κανείς χρειάζεται να ρωτήσει αν η τιμή πρέπει να +συνεχίσει να υπάρχει όταν η συνάρτηση, στη οποία ανήκει, επιστρέψει; Αν η απάντηση είναι όχι, τότε η τιμή μπορεί +να δημιουργηθεί στην στοίβα. Αν η απάντηση είναι ναι, τότε η τιμή πρέπει να δημηουργηθεί +στον σωρό. + +Σημείωση: Ο κανόνας της ιδιοκτησίας είναι ένας καλός βασικός κανόνας για να αναγνωρίζει κανείς εκείνο τον κώδικα +που προκαλεί εκχώρηση μνήμης. Ομως, κανείς πρεπει αν συνειδητοποιήσει ότι η ανάλυση διαφυγής έχει μειονεκτήματα τα οποία μπορούν +να οδηγήσουν σε μη προφανείς εκχώρησεις μνήμης. Επίσης, ο αλγόριθμος χρησιμοποιεί κάθε ευκαιρία προκειμένου +να χρησιμοποιήσει βελτιστοποιήσεις του μεταγλωττιστή για να κάνει οικονομία σε εκχώρησεις μνήμης. + + // user represents a user in the system. + type user struct { + name string + email string + } + + func stayOnStack() user { + u := user{ + name: "Bill", + email: "bill@email.com", + } + + return u + } + +Η συνάρτηση stayOnStack χρησιμοποιεί σημειολογία τιμών προκειμένου να επιστρέψει μια τιμή user στον +καλούντα. Με άλλα λόγια, ο καλών αποκτά ένα αντίγραφο της τιμής user που +δημιουργείται. + +Οταν η συνάρτηση stayOnStack καλείται και επιστρέφει, η τιμή user που δημιουργεί +δεν χρειάζεται πια να υπάρχει, καθώς ο καλών αποκτά το δικό του αντίγραφο. Επομένως, +η δημουργία της τιμής user εντός της stayOnStack μπορεί να συμβεί στην στοίβα. +Δεν πραγματοποιείται καμία εκχώρηση μνήμης. + + type user struct { + name string + email string + } + + func escapeToHeap() *user { + u := user{ + name: "Bill", + email: "bill@email.com", + } + + return &u + } + +Η συνάρτηση escapeToHeap χρησιμοποιεί σημειολογία δείκτη διευθύνσεων προκειμένου να επιστρέψει μια τιμή user πίσω +στον καλώντα. Με άλλα λόγια, ο καλώντας αποκτάει κοινή πρόσβαση (μια διεύθυνση) στην +τιμή user που δημιουργείται. + +Οταν η συνάρτηση escapeToHeap καλείται και επιστρέφει, η τιμή user που δημιουργεί +χρειάζεται να συνεχίζει να υπάρχει, καθώς ο καλώντας έχει κοινή πρόσβαση στην τιμή. +Επομένως, η δημιουργία της τιμής user μέσα στην escapeToHeap δεν μπορεί να συμβεί +στην στοίβα, πρέπει να συμβεί στον σωρό. Σε αυτή την περίπτωση πραγματοποιείται εκχώρηση μνήμης. + +Αναλογιζόμενος κανείς τι θα συνέβαινε αν η τιμή user του τελευταίου παραδείγματος δημιουργούνταν +στην στοίβα ενώ την ίδια στιγμή χρησιμοποιούνταν σημειολογία δείκτων διευθύνσεων για την επιστροφή, +προκύπτουν τα ακόλουθα. + +.image /tour/grc/static/img/p2.png + +Ο καλών θα έπαιρνε ένα αντίγραφο της διεύθυνσης στοίβας από το επόμενο πλαίσιο μνήμης και η ακεραιότητα +των δεδομένων θα χανόταν. Οταν ο έλεγχος επιστρέφει στην καλούσα συνάρτηση, η μνήμη στη +στοίβα όπου βρίσκεται η τιμή user καθίσταται ξανά διαθέσιμη για χρήση. Την στιγμή που η καλούσα συνάρτηση +πραγματοποιεί κλήση άλλης συνάρτησης, ένα νέο πλαίσιο μνήμης διανέμεται και η μνήμη ακυρώνεται, +καταστρέφοντας έτσι την κοινή τιμή. + +Γι' αυτό κανείς σκέφτεται την στοίβα ως κάτι που ανανεώνεται. Η ανάθεση της μηδενικής τιμής +βοηθάει κάθε πλαίσιο μνήμης της στοίβας, που χρειάζεται κανείς, να εκκαθαρίζεται, χωρίς την χρήση του συλλέκτη απορριμάτων. Η στοίβα +εκκαθαρίζει τον εαυτό της, καθώς ένα πλαίσιο μνήμης λαμβάνεται και παίρνει αρχική τιμή για την εκτέλεση κάθε +κλήσης συνάρτησης. Η στοίβα εκκαθαρίζεται κατά την διάρκεια κλήσεων συναρτήσεων και όχι όταν επιστρέφουν επειδή +ο μεταγλωτιστής δεν γνωρίζει αν η μνήμη στην στοίβα θα χρειαστεί ξανά. + +Η ανάλυση διαφυγής αποφασίζει αν μια τιμή δημιουργείται στην στοίβα (το default) ή αν +δημιουργείται στον σωρό (η διαφυγή). Με την συνάρτηση stayOnStack, μεταφέρεται έαν αντίγραφο της τιμής +στον καλώντα, επομένως είναι ασφαλές να παραμείνει η τιμή στην στοίβα. Με την συνάρτηση escapeToHeap, +ο καλών λαμβάνει ένα αντίγραφο της διεύθυνσης της τιμής (γίνεται δηλαδή διαμοιρασμός της +στοίβας) επομένως δεν είναι ασφαλές να παραμείνει η τιμή στην στοίβα. + +Υπάρχουν πολλές μικρές λεπτομέρειες που σχετίζονται με την ανάλυση διαφυγής, επομένως για να μάθει κανείς περισσότερα +μπορεί να διαβάσει την ανάρτηση στο κεφάλαιο 14 με τίτλο, Μηχανισμοί της Ανάλυσης Διαφυγής. + +Σημείωση: Από την έκδοση 1.17, η Go άλλαξε το ABI (application binary interface) προκειμένου να +υλοποιήσει ένα νέο τρόπο για να περνάει κανείς τιμές εισόδου και εξόδου κάνοντας χρήση καταχωρητών +αντί για την μνήμη στην στοίβα. Αυτό είναι ενεργοποιημένο για το Linux, το MacOS, και τα Windows για την +αρχιτεκτονική 64-bit x86. Αυτό σημαίνει ότι ορισμένες τιμές εισόδου μιας συνάρητηση δεν θα +αντιγράφονται στην στοίβα, αλλά ορισμένες μπορεί, κάτι που εξαρτάται από την πρακτική εφαρμογή της χρήσης των καταχωρητών. +Κάτι τέτοιο δεν αλλάζει τίποτα από την σημειολογία που περιγράφεται σε αυτό το κεφάλαιο. + +** Σημειώσεις + +- Όταν μια τιμή μπορεί να έχει αναφορά σε αυτή, αφού η συνάρτηση που δημιουργεί την τιμή, επιστρέφει. +- Όταν ο μεταγλωττιστής αποφασίζει ότι μια τιμή δεν χωράει στην στοίβα. +- Όταν ο μεταγλωττιστής δεν γνωρίζει το μέγεθος της τιμής κατά το στάδιο της μεταγλώττισης. +- Όταν μια τιμή είναι αποσυνδεδεμένη εξαιτίας της χρήσης συναρτήσεων ή τιμών διεπαφών. + +** Σημειολογία Συλλογής Απορριμάτων + +Όταν μια τιμή δημιουργείται στον σωρό, ο Συλλέκτης Απορριμάτων (ΣΑ) πρέπει να +αναλάβει δράση. Το πιο σημαντικό μέρος του ΣΑ είναι ο αλγόριθμος βηματοδότησης. Αυτός αποφασίζει +την συχνότητα/βηματισμό που χρειάζεται να λειτουργεί ο ΣΑ προκειμένου να διατηρεί τον μικρότερο δυνατό +σωρό, σε συνδυασμό με την καλύτερη δυνατή απόδοση της εφαρμογής. + +- [[https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html][Σημειολογία Συλλογής Απορριμάτων Ι]] - William Kennedy + +** Στοίβα αντί Σωρού + +"Η στοίβα προορίζεται για δεδομένα που χρειάζεται να υπάρχουν μόνο για την διάρκεια ζωής της συνάρτησης +που τα δημιουργεί, τα οποία ανακυκλώνονται χωρίς κόστος όταν η συνάρτηση επιστρέφει. Ο +σωρός προορίζεται για δεδομένα τα οποία χρειάζεται να συνεχίσουν να υπάρχουν και μετά την επιστροφή της συνάρτησης που τα δημιούργησε, +ενώ ανακυκλώνονται με συλλογή απορριμάτων, η οποία κάποιες φορές κοστίζει." - Ayan George + +** Μεγέθυνση Στοίβας + +Το μέγεθος κάθε πλαισίου μνήμης για κάθε συνάρτηση υπολογίζεται κατά το στάδιο της μεταγλώττισης. Αυτό σημαίνει ότι, +αν ο μεταγλωττιστής δεν γνωρίζει το μέγεθος της τιμής κατά το στάδιο της μεταγλώττισης, η τιμή πρέπει να +δημιουργηθεί στον σωρό. Ένα παράδειγμα αυτού είναι η χρήση της προεγκατεστημένης συνάρτησης make προκειμένου να +δημιουργήσει κανείς έναν δυναμικό πίνακα (slice) του οποίου το μέγεθος εξαρτάται από μια μεταβλητή. + + b := make([]byte, size) // Ο πίνακας (array) στον οποίο βασίζεται ο δυναμικός πίνακας + // δεσμεύει μνήμη στον σωρό. + +Η Go χρησιμοποιεί μια υλοποίηση συνεχόμενης στοίβας (contiguous stack implementation) προκειμένου να μπορεί να προσδιορίζει πως μεγεθύνονται και σμικρύνονται οι στοίβες. +Μια εναλλακτική που θα μπορούσε να είχε χρησιμοποιήσει η Go είναι μια υλοποίηση τμηματικής στοίβας (segmented stack implementation), που +χρησιμοποιούν κάποια λειτουργικά συστήματα. + +Κάθε κλήση συνάρτησης συνοδεύεται από ένα εισαγωγικό σημείωμα που διατυπώνει την εξής ερώτηση: "Υπάρχει αρκετός χώρος στην στοίβα +για αυτό το νέο πλαίσιο μνήμης;". Αν η απάντηση είναι ναι, τότε δεν υπάρχει κανένα πρόβλημα και το πλαίσιο δεσμεύται και +λαμβάνει αρχική τιμή. Διαφορετικά, πρέπει να κατασκευαστεί μια μεγαλύτερη στοίβα και η μνήμη στην +υπάρχουσα στοίβα πρέπει να αντιγραφεί στην καινούργια. Αυτό απαιτεί αλλαγές σε +δείκτες διευθύνσεων μνήμης που έχουν αναφορές στην μνήμη της στοίβας. Τα πλεονεκτήματα της συνεχόμενης μνήμης (contiguous memory) και +οι γραμμικές προσπελάσεις των σύγχρονων υλικών αποτελούν το αντάλλαγμα ή το κόστος της ανάγκης αντιγραφής. + +Εξαιτίας της χρήσης συνεχόμενων στοιβών, καμία Goroutine δεν είναι δυνατόν να διατηρεί δείκτη διευθύνσεων στην +στοίβα κάποιας άλλης Goroutine. Θα κόστιζε πολύ στο εκτελέσιμο περιβάλλον να παρακολουθεί +κάθε δείκτη διεύθυνσης, σε κάθε στοίβα και να αναπροσαρμόζει αυτούς τους δείκτες, προκειμένου να δείχνουν στις νέες τοποθεσίες. + +** Πρόσθετα Αναγνώσματα + +**Μηχανισμοί* *Δεικτών* *Διεύθυνσης* *Μνήμης* + +- [[https://golang.org/doc/effective_go.html#pointers_vs_values][Δείκτες Διεύθυνσης αντί Τιμών]] +- [[https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-stacks-and-pointers.html][Οι Μηχανισμοί της Γλώσσας Σχετικά με τις Στοίβες και τους Δείκτες Διευθύνσεων]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2014/12/using-pointers-in-go.html][Χρησιμοποιώντας Δείκτες Διευθύνσεων στην Go]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2013/07/understanding-pointers-and-memory.html][Κατανοώντας τους Δείκτες Διευθύνσεων και την Διαδικασία Καταμερισμού της Μνήμης]] - William Kennedy + +*Στοίβες* + +- [[https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub][Πρόταση Συνεχόμενης Στοίβας]] + +*Ανάλυση* *Διαφυγής* *και* *Inlining* + +- [[https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw][Αδυναμίες της Ανάλυσης Διαφυγής της Go]] +- [[https://github.com/golang/go/wiki/CompilerOptimizations][Βελτιστοποιήσεις Μεταγλωττιστή]] + +*Συλλογή* *Απορριμάτων* + +- [[http://gchandbook.org/][Το Εγχειρίδιο Συλλογής Απορριμάτων]] +- [[https://github.com/golang/proposal/blob/master/design/44167-gc-pacer-redesign.md][Ανασχεδιασμός Βηματοδότη ΣΑ - 2021]] - Michael Knyszek +- [[https://en.wikipedia.org/wiki/Tracing_garbage_collection][Ιχνογραφώντας την Συλλογή Απορριμάτων]] +- [[https://blog.golang.org/go15gc][Το Blog της Go - 1.5 ΣΑ]] +- [[https://www.youtube.com/watch?v=aiv1JOfMjm0&index=16&list=PL2ntRZ1ySWBf-_z-gHCOR2N156Nw930Hm][ΣΑ της Go: Λύνοντας το Πρόβλημα της Καθυστέρησης]] +- [[http://rubinius.com/2013/06/22/concurrent-garbage-collection][Παράλληλη Συλλογή Απορριμάτων]] +- [[https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit][Βηματισμός του Παράλληλου Συλλέκτη Απορριμάτων της Go 1.5]] +- [[https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md][Εξαλείφοντας την Επανασάρωση της Στοίβας]] +- [[https://groups.google.com/forum/m/#!topic/golang-nuts/KJiyv2mV2pU][Γιατί ο συλλέκτης απορριμάτων της golang δεν υλοποιεί Γενεαλογική και Συμπαγή ΣΑ?]] - Ian Lance Taylor +- [[https://blog.golang.org/ismmkeynote][Φτάνοντας στην Go: Το ταξίδι του Συλλέκτη Απορριμάτων της Go]] - Rick Hudson +- [[https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html][Συλλογή Απορριμάτων στην : Μέρος I - Σημειολογία]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2019/05/garbage-collection-in-go-part2-gctraces.html][Συλλογή Απορριμάτων στην : Μέρος II - Ιχνηλάτιση ΣΑ]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2019/07/garbage-collection-in-go-part3-gcpacing.html][Συλλογή Απορριμάτων στην : Μέρος III - Βηματισμός ΣΑ]] - William Kennedy +- [[https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap-26c2462549a2/][Το έρμα της μνήμης στην Go: Πως έμαθα να σταματήσω να ανησυχώ και να αγαπάω τον σωρό]] - Ross Engers + +*Βελτιστοποιήσεις* *Στατικής* *Μοναδικής* *Ανάθεσης* + +- [[https://www.youtube.com/watch?v=D2-gaMvWfQY][GopherCon 2015: Ben Johnson - Ανάλυση Στατικού Κώδικα Χρησιμοποιώντας SSA]] +- [[https://godoc.org/golang.org/x/tools/go/ssa][Το Πακέτο SSA]] +- [[https://www.youtube.com/watch?v=FnGCDLhaxKU][Κατανοώντας τις Βελτιστοποιήσεις του Μεταγλωττιστή]] + +* Ασκήσεις + +Χρησιμοποιήστε το παρόν πρότυπο ως σημείο αναφοράς προκειμένου να ολοκληρώσετε τις ασκήσεις. Σας παρέχεται μια πιθανή λύση. + +** Άσκηση 1 + +*Μέρος* *Α:* Δηλώστε και δώστε αρχική τιμή σε μεταβλητή ακέραιου τύπου με την τιμή 20. +Παρουσιάστε την "διεύθυνση της" και την "τιμή της" μεταβλητής. + +*Μέρος* *Β:* Δηλώστε και δώστε αρχική τιμή σε μεταβλητή δείκτη διευθύνσεων ακέραιου τύπου που δείχνει προς +την τελευταία μεταβλητή που μόλις δημιουργήσατε. Παρουσιάστε την "διεύθυνση της", την "τιμή της" και την +"τιμή στην οποία ο δείκτης διεύθυνσης δείχνει προς". + +** Άσκηση 2 + +Δηλώστε έναν τύπο struct και δημιουργείστε μια τιμή αυτού του τύπου. Δημιουργήστε μια συνάρτηση που +μπορεί να αλλάξει την τιμή κάποιου πεδίου σε μεταβλητές αυτού του τύπου struct. Παρουσιάστε την τιμή πριν +και μετά την κλήση της συνάρτησης σας. + +.play pointers/exercise1.go +.play pointers/answer1.go +.play pointers/exercise2.go +.play pointers/answer2.go diff --git a/_content/tour/grc/pointers/answer1.go b/_content/tour/grc/pointers/answer1.go new file mode 100644 index 00000000..b530aceb --- /dev/null +++ b/_content/tour/grc/pointers/answer1.go @@ -0,0 +1,31 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare and initialize a variable of type int with the value of 20. Display +// the _address of_ and _value of_ the variable. +// +// Declare and initialize a pointer variable of type int that points to the last +// variable you just created. Display the _address of_ , _value of_ and the +// _value that the pointer points to_. +package main + +import "fmt" + +func main() { + + // Declare an integer variable with the value of 20. + value := 20 + + // Display the address of and value of the variable. + fmt.Println("Address Of:", &value, "Value Of:", value) + + // Declare a pointer variable of type int. Assign the + // address of the integer variable above. + p := &value + + // Display the address of, value of and the value the pointer + // points to. + fmt.Println("Address Of:", &p, "Value Of:", p, "Points To:", *p) +} diff --git a/_content/tour/grc/pointers/answer2.go b/_content/tour/grc/pointers/answer2.go new file mode 100644 index 00000000..7f91c863 --- /dev/null +++ b/_content/tour/grc/pointers/answer2.go @@ -0,0 +1,46 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a struct type and create a value of this type. Declare a function +// that can change the value of some field in this struct type. Display the +// value before and after the call to your function. +package main + +import "fmt" + +// user represents a user in the system. +type user struct { + name string + email string + accessLevel int +} + +func main() { + + // Create a variable of type user and initialize each field. + bill := user{ + name: "Bill", + email: "bill@ardanlabs.com", + accessLevel: 1, + } + + // Display the value of the accessLevel field. + fmt.Println("access:", bill.accessLevel) + + // Share the bill variable with the accessLevel function + // along with a value to update the accessLevel field with. + accessLevel(&bill, 10) + + // Display the value of the accessLevel field again. + fmt.Println("access:", bill.accessLevel) +} + +// accessLevel changes the value of the users access level. +func accessLevel(u *user, accessLevel int) { + + // Set of value of the accessLevel field to the value + // that is passed in. + u.accessLevel = accessLevel +} diff --git a/_content/tour/grc/pointers/example1.go b/_content/tour/grc/pointers/example1.go new file mode 100644 index 00000000..0bd24478 --- /dev/null +++ b/_content/tour/grc/pointers/example1.go @@ -0,0 +1,32 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show the basic concept of pass by value. +package main + +func main() { + + // Declare variable of type int with a value of 10. + count := 10 + + // Display the "value of" and "address of" count. + println("count:\tValue Of[", count, "]\tAddr Of[", &count, "]") + + // Pass the "value of" the count. + increment(count) + + println("count:\tValue Of[", count, "]\tAddr Of[", &count, "]") +} + +// increment declares count as a pointer variable whose value is +// always an address and points to values of type int. +// +//go:noinline +func increment(inc int) { + + // Increment the "value of" inc. + inc++ + println("inc:\tValue Of[", inc, "]\tAddr Of[", &inc, "]") +} diff --git a/_content/tour/grc/pointers/example2.go b/_content/tour/grc/pointers/example2.go new file mode 100644 index 00000000..58e39068 --- /dev/null +++ b/_content/tour/grc/pointers/example2.go @@ -0,0 +1,34 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show the basic concept of using a pointer +// to share data. +package main + +func main() { + + // Declare variable of type int with a value of 10. + count := 10 + + // Display the "value of" and "address of" count. + println("count:\tValue Of[", count, "]\t\tAddr Of[", &count, "]") + + // Pass the "address of" count. + increment(&count) + + println("count:\tValue Of[", count, "]\t\tAddr Of[", &count, "]") +} + +// increment declares count as a pointer variable whose value is +// always an address and points to values of type int. +// +//go:noinline +func increment(inc *int) { + + // Increment the "value of" count that the "pointer points to". + *inc++ + + println("inc:\tValue Of[", inc, "]\tAddr Of[", &inc, "]\tValue Points To[", *inc, "]") +} diff --git a/_content/tour/grc/pointers/example3.go b/_content/tour/grc/pointers/example3.go new file mode 100644 index 00000000..ddd0de1a --- /dev/null +++ b/_content/tour/grc/pointers/example3.go @@ -0,0 +1,52 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show the basic concept of using a pointer +// to share data. +package main + +import "fmt" + +// user represents a user in the system. +type user struct { + name string + email string + logins int +} + +func main() { + + // Declare and initialize a variable named bill of type user. + bill := user{ + name: "Bill", + email: "bill@ardanlabs.com", + } + + //** We don't need to include all the fields when specifying field + // names with a struct literal. + + // Pass the "address of" the bill value. + display(&bill) + + // Pass the "address of" the logins field from within the bill value. + increment(&bill.logins) + + // Pass the "address of" the bill value. + display(&bill) +} + +// increment declares logins as a pointer variable whose value is +// always an address and points to values of type int. +func increment(logins *int) { + *logins++ + fmt.Printf("&logins[%p] logins[%p] *logins[%d]\n\n", &logins, logins, *logins) +} + +// display declares u as user pointer variable whose value is always an address +// and points to values of type user. +func display(u *user) { + fmt.Printf("%p\t%+v\n", u, *u) + fmt.Printf("Name: %q Email: %q Logins: %d\n\n", u.name, u.email, u.logins) +} diff --git a/_content/tour/grc/pointers/example4.go b/_content/tour/grc/pointers/example4.go new file mode 100644 index 00000000..6d766dda --- /dev/null +++ b/_content/tour/grc/pointers/example4.go @@ -0,0 +1,109 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to teach the mechanics of escape analysis. +package main + +// user represents a user in the system. +type user struct { + name string + email string +} + +// main is the entry point for the application. +func main() { + u1 := createUserV1() + u2 := createUserV2() + + println("u1", &u1, "u2", u2) +} + +// createUserV1 creates a user value and passed +// a copy back to the caller. +// +//go:noinline +func createUserV1() user { + u := user{ + name: "Bill", + email: "bill@ardanlabs.com", + } + + println("V1", &u) + + return u +} + +// createUserV2 creates a user value and shares +// the value with the caller. +// +//go:noinline +func createUserV2() *user { + u := user{ + name: "Bill", + email: "bill@ardanlabs.com", + } + + println("V2", &u) + + return &u +} + +/* +// See escape analysis and inlining decisions. + +$ go build -gcflags -m=2 +# github.com/ardanlabs/gotraining/topics/go/language/pointers/example4 +./example4.go:24:6: cannot inline createUserV1: marked go:noinline +./example4.go:38:6: cannot inline createUserV2: marked go:noinline +./example4.go:14:6: cannot inline main: function too complex: cost 132 exceeds budget 80 +./example4.go:39:2: u escapes to heap: +./example4.go:39:2: flow: ~r0 = &u: +./example4.go:39:2: from &u (address-of) at ./example4.go:46:9 +./example4.go:39:2: from return &u (return) at ./example4.go:46:2 +./example4.go:39:2: moved to heap: u + +// See the intermediate representation phase before +// generating the actual arch-specific assembly. + +$ go build -gcflags -S +CALL "".createUserV1(SB) + 0x0026 00038 MOVQ (SP), AX + 0x002a 00042 MOVQ 8(SP), CX + 0x002f 00047 MOVQ 16(SP), DX + 0x0034 00052 MOVQ 24(SP), BX + 0x0039 00057 MOVQ AX, "".u1+40(SP) + 0x003e 00062 MOVQ CX, "".u1+48(SP) + 0x0043 00067 MOVQ DX, "".u1+56(SP) + 0x0048 00072 MOVQ BX, "".u1+64(SP) + 0x004d 00077 PCDATA $1, + +// See bounds checking decisions. + +go build -gcflags="-d=ssa/check_bce/debug=1" + +// See the actual machine representation by using +// the disassembler. + +$ go tool objdump -s main.main example4 +TEXT main.main(SB) github.com/ardanlabs/gotraining/topics/go/language/pointers/example4/example4.go + example4.go:15 0x105e281 e8ba000000 CALL main.createUserV1(SB) + example4.go:15 0x105e286 488b0424 MOVQ 0(SP), AX + example4.go:15 0x105e28a 488b4c2408 MOVQ 0x8(SP), CX + example4.go:15 0x105e28f 488b542410 MOVQ 0x10(SP), DX + example4.go:15 0x105e294 488b5c2418 MOVQ 0x18(SP), BX + example4.go:15 0x105e299 4889442428 MOVQ AX, 0x28(SP) + example4.go:15 0x105e29e 48894c2430 MOVQ CX, 0x30(SP) + example4.go:15 0x105e2a3 4889542438 MOVQ DX, 0x38(SP) + example4.go:15 0x105e2a8 48895c2440 MOVQ BX, 0x40(SP) + +// See a list of the symbols in an artifact with +// annotations and size. + +$ go tool nm example4 + 105e340 T main.createUserV1 + 105e420 T main.createUserV2 + 105e260 T main.main + 10cb230 B os.executablePath +*/ diff --git a/_content/tour/grc/pointers/example5.go b/_content/tour/grc/pointers/example5.go new file mode 100644 index 00000000..c29ea4d8 --- /dev/null +++ b/_content/tour/grc/pointers/example5.go @@ -0,0 +1,32 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how stacks grow/change. +package main + +// Number of elements to grow each stack frame. +// Run with 1 and then with 1024 +const size = 1 + +// main is the entry point for the application. +func main() { + s := "HELLO" + stackCopy(&s, 0, [size]int{}) +} + +// stackCopy recursively runs increasing the size +// of the stack. +// +//go:noinline +func stackCopy(s *string, c int, a [size]int) { + println(c, s, *s) + + c++ + if c == 10 { + return + } + + stackCopy(s, c, a) +} diff --git a/_content/tour/grc/pointers/exercise1.go b/_content/tour/grc/pointers/exercise1.go new file mode 100644 index 00000000..f6ac4575 --- /dev/null +++ b/_content/tour/grc/pointers/exercise1.go @@ -0,0 +1,27 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare and initialize a variable of type int with the value of 20. Display +// the _address of_ and _value of_ the variable. +// +// Declare and initialize a pointer variable of type int that points to the last +// variable you just created. Display the _address of_ , _value of_ and the +// _value that the pointer points to_. +package main + +// Add imports. + +func main() { + + // Declare an integer variable with the value of 20. + + // Display the address of and value of the variable. + + // Declare a pointer variable of type int. Assign the + // address of the integer variable above. + + // Display the address of, value of and the value the pointer + // points to. +} diff --git a/_content/tour/grc/pointers/exercise2.go b/_content/tour/grc/pointers/exercise2.go new file mode 100644 index 00000000..9cf55dab --- /dev/null +++ b/_content/tour/grc/pointers/exercise2.go @@ -0,0 +1,31 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a struct type and create a value of this type. Declare a function +// that can change the value of some field in this struct type. Display the +// value before and after the call to your function. +package main + +// Add imports. + +// Declare a type named user. + +// Create a function that changes the value of one of the user fields. +func funcName( /* add pointer parameter, add value parameter */ ) { + + // Use the pointer to change the value that the + // pointer points to. +} + +func main() { + + // Create a variable of type user and initialize each field. + + // Display the value of the variable. + + // Share the variable with the function you declared above. + + // Display the value of the variable. +} diff --git a/_content/tour/grc/slices.article b/_content/tour/grc/slices.article new file mode 100644 index 00000000..c418f52b --- /dev/null +++ b/_content/tour/grc/slices.article @@ -0,0 +1,513 @@ +Slices +Slices are an incredibly important data structure in Go. They form the basis for how we manage and manipulate data in a flexible, performant, and dynamic way. It's very beneficial for all Go programmers to learn how slices work, and how to use them. + +* Slices + +- [[https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/][Watch The Video]] +- Need Financial Assistance, Use Our [[https://www.ardanlabs.com/scholarship/][Scholarship Form]] + +Slices are an incredibly important data structure in Go. They form the basis for +how we manage and manipulate data in a flexible, performant, and dynamic way. It's +very beneficial for all Go programmers to learn how slices work, and how to use them. + +.image /tour/grc/static/img/sl1.png + +** Code Review + +- *Example* *1:* Declare and Length +- *Example* *2:* Reference Types +- *Example* *3:* Appending slices +- *Example* *4:* Taking slices of slices +- *Example* *5:* Slices and References +- *Example* *6:* Strings and slices +- *Example* *7:* Variadic functions +- *Example* *8:* Range mechanics +- *Example* *9:* Efficient Traversals +- *Example* *10:* Three index slicing + +.play slices/example1.go +.play slices/example2.go +.play slices/example3.go +.play slices/example4.go +.play slices/example5.go +.play slices/example6.go +.play slices/example7.go +.play slices/example8.go +.play slices/example9.go +.play slices/example10.go + +** Slice Construction + +Constructing a slice can be done in several ways. + + // Slice of string set to its zero value state. + var slice []string + + // Slice of string set to its empty state. + slice := []string{} + + // Slice of string set with a length and capacity of 5. + slice := make([]string, 5) + + // Slice of string set with a length of 5 and capacity of 8. + slice := make([]string, 5, 8) + + // Slice of string set with values with a length and capacity of 5. + slice := []string{"A", "B", "C", "D", "E"} + +You can see the built-in function make allows me to pre-allocate both length and +capacity for the backing array. If the compiler knows the size at compile time, +the backing array could be constructed on the stack. + +** Slice Length vs Capacity + +The length of a slice represents the number of elements that can be read and +written to. The capacity represents the total number of elements that exist +in the backing array from that pointer position. + +Because of syntactic sugar, slices look and feel like an array. + + slice := make([]string, 5) + slice[0] = "Apple" + slice[1] = "Orange" + slice[2] = "Banana" + slice[3] = "Grape" + slice[4] = "Plum" + +You can tell the difference between slice and array construction since an array +has a known size at compile time and slices necessarily don’t. + +If you try to access an element beyond the slice’s length, you will get a runtime error. + + slice := make([]string, 5) + slice[5] = "Raspberry" + +Compiler Error: + + Error: panic: runtime error: index out of range slice[5] = "Runtime error" + +In this example, the length of the slice is 5 and I’m attempting to access the +6th element, which does not exist. + +** Data Semantic Guideline For Slices + +As a guideline, if the data I’m working with is a slice, then use value semantics +to move the data around my program. This includes declaring fields on a type. + + func Foo(data []byte) []byte + + type Foo struct { + X []int + Y []string + Z []bool + } + +This goes for all of Go’s internal data structures (slices, maps, channels, interfaces, and functions). + +One reason to switch to pointer semantics is if you need to share the slice for a +decoding or unmarshaling operation. Using pointers for these types of operations +are ok, but document this if it’s not obvious. + +** Contiguous Memory Layout + +The idea behind the slice is to have an array, which is the most efficient data +structure as it relates to the hardware. However, you still need the ability to be +dynamic and efficient with the amount of data you need at runtime and future growth. + + func main() { + slice := make([]string, 5, 8) + slice[0] = "Apple" + slice[1] = "Orange" + slice[2] = "Banana" + slice[3] = "Grape" + slice[4] = "Plum" + + inspectSlice(slice) + } + + func inspectSlice(slice []string) { + fmt.Printf("Length[%d] Capacity[%d]\n", len(slice), cap(slice)) + for i := range slice { + fmt.Printf("[%d] %p %s\n", i, &slice[i], slice[i]) + } + } + +Output: + + Length[5] Capacity[8] + [0] 0xc00007e000 Apple + [1] 0xc00007e010 Orange + [2] 0xc00007e020 Banana + [3] 0xc00007e030 Grape + [4] 0xc00007e040 Plum + +The inspectSlice function shows how a slice does have a contiguous backing array +with a predictable stride. It also shows how a slice has a length and capacity +which may be different. Notice how the print function only iterates over the +length of a slice. + +** Appending With Slices + +The language provides a built-in function called append to add values to an +existing slice. + + var data []string + + for record := 1; record <= 102400; record++ { + data = append(data, fmt.Sprintf("Rec: %d", record)) + } + +The append function works with a slice even when the slice is initialized to its +zero value state. The API design of append is what’s interesting because it uses +value semantic mutation. Append gets its own copy of a slice value, it mutates its +own copy, then it returns a copy back to the caller. + +Why is the API designed this way? This is because the idiom is to use value semantics +to move a slice value around a program. This must still be respected even with a +mutation operation. Plus, value semantic mutation is the safest way to perform mutation +since the mutation is being performed on the function’s own copy of the data in isolation. + +Append always maintains a contiguous block of memory for the slice’s backing array, +even after growth. This is important for the hardware. + +.image /tour/grc/static/img/sl2.png + +Every time the append function is called, the function checks if the length and +capacity of the slice is the same or not. If it’s the same, it means there is no +more room in the backing array for the new value. In this case, append creates a +new backing array (doubling or growing by 25%) and then copies the values from the +old array into the new one. Then the new value can be appended. + +.image /tour/grc/static/img/sl3.png + +If it’s not the same, it means that there is an extra element of capacity existing +for the append. An element is taken from capacity and added to the length of the +slice. This makes an append operation very efficient. + +When the backing array has 1024 elements of capacity or less, new backing arrays +are constructed by doubling the size of the existing array. Once the backing array +grows past 1024 elements, growth happens at 25%. + +*NOTE:* *How* *the* *append* *function* *grows* *the* *capacity* *of* *the* +*backing* *array* *has* *changed* *since* *Go* *1.18.* + +- [[https://tip.golang.org/doc/go1.18#runtime][https://tip.golang.org/doc/go1.18#runtime]] + +** Slicing Slices + +Slices provide the ability to avoid extra copies and heap allocations of the backing +array when needing to isolate certain elements of the backing array for different +operations. + +The slicing syntax represents the list notation [a:b) which means, include +elements from index a through b, but not including b. + + slice1 := []string{"A", "B", "C", "D", "E"} + slice2 := slice1[2:4] + +The variable slice2 is a new slice value that is now sharing the same backing +array that slice1 is using. However, slice2 only allows you to access the elements +at index 2 and 3 (C and D) of the original slice’s backing array. The length of +slice2 is 2 and not 5 like in slice1 and the capacity is 3 since there are now +3 elements from that pointer position. + +.image /tour/grc/static/img/sl4.png + +A better way to think about slicing is to focus on the length using this notation +[a:a+len] index a through a plus the length. This will reduce errors in +calculating new slices. + +Using this inspect function. + + func inspectSlice(slice []string) { + fmt.Printf("Length[%d] Capacity[%d]\n", len(slice), cap(slice)) + for i, s := range slice { + fmt.Printf("[%d] %p %s\n", + i, + &slice[i], + s) + } + } + +You can see this in action. + + slice1 := []string{"A", "B", "C", "D", "E"} + slice2 := slice1[2:4] + inspectSlice(slice1) + inspectSlice(slice2) + +Output: + + Length[5] Capacity[5] + [0] 0xc00007e000 A + [1] 0xc00007e010 B + [2] 0xc00007e020 C + [3] 0xc00007e030 D + [4] 0xc00007e040 E + Length[2] Capacity[3] + [0] 0xc00007e020 C <-- SAME AS INDEX 2 IN SLICE 1 + [1] 0xc00007e030 D <-- SAME AS INDEX 3 IN SLICE 1 + +Notice how the two different slices are sharing the same backing array. You can +see this by comparing addresses. + +The nice thing here is there are no allocations. The compiler knows the size of +the backing array for slice1 at compile time. Passing a copy of the slice value +down into the inspectSlice function keeps everything on the stack. + +** Mutations To The Backing Array + +When you use slice2 to change the value of the string at index 0, any slice value +that is sharing the same backing array (where the address for that index is part +of that slice’s length) will see the change. + + slice1 := []string{"A", "B", "C", "D", "E"} + slice2 := slice1[2:4] + slice2[0] = "CHANGED" + inspectSlice(slice1) + inspectSlice(slice2) + +Output: + + Length[5] Capacity[5] + [0] 0xc00007e000 A + [1] 0xc00007e010 B + [2] 0xc00007e020 CHANGED + [3] 0xc00007e030 D + [4] 0xc00007e040 E + Length[2] Capacity[3] + [0] 0xc00007e020 CHANGED + [1] 0xc00007e030 D + +You always have to be aware when you are modifying a value at an index position if +the backing array is being shared with another slice. + +.image /tour/grc/static/img/sl5.png + +What if you use the built-in function append instead? + + slice1 := []string{"A", "B", "C", "D", "E"} + slice2 := slice1[2:4] + slice2 = append(slice2, "CHANGED") + inspectSlice(slice1) + inspectSlice(slice2) + +Output: + + Length[5] Capacity[5] + [0] 0xc00007e000 A + [1] 0xc00007e010 B + [2] 0xc00007e020 C + [3] 0xc00007e030 D + [4] 0xc00007e040 CHANGED + Length[3] Capacity[3] + [0] 0xc00007e020 C + [1] 0xc00007e030 D + [2] 0xc00007e040 CHANGED + +The append function creates the same side effect, but it’s hidden. In this case, +bringing in more length from capacity for slice2 has caused the value at address +0xc00007e040 to be changed. Unfortunately, slice1 had this address already as +part of its length. + +.image /tour/grc/static/img/sl6.png + +One way to avert the side effect is to use a three index slice when constructing +slice2 so the length and capacity is the same at 2. + + slice1 := []string{"A", "B", "C", "D", "E"} + slice2 := slice1[2:4:4] + inspectSlice(slice1) + inspectSlice(slice2) + +Output: + + Length[5] Capacity[5] + [0] 0xc00007e000 A + [1] 0xc00007e010 B + [2] 0xc00007e020 C + [3] 0xc00007e030 D + [4] 0xc00007e040 E + Length[2] Capacity[2] + [0] 0xc00007e020 C + [1] 0xc00007e030 D + +The syntax for a three index slice is [a:b:c] when b and c should be the same +since [a-b] sets the length and [a-c] sets the capacity. Now the length and +capacity of slice2 is the same. + +Now you use the built-in function append again like before. + + slice1 := []string{"A", "B", "C", "D", "E"} + slice2 := slice1[2:4:4] + slice2 = append(slice2, "CHANGED") + inspectSlice(slice1) + inspectSlice(slice2) + +Output: + + Length[5] Capacity[5] + [0] 0xc00007e000 A + [1] 0xc00007e010 B + [2] 0xc00007e020 C + [3] 0xc00007e030 D + [4] 0xc00007e040 E + Length[3] Capacity[4] + [0] 0xc000016080 C + [1] 0xc000016090 D + [2] 0xc0000160a0 CHANGED + +Notice after the call to append, slice2 has a new backing array. + +.image /tour/grc/static/img/sl7.png + +This can be seen by comparing the addresses of each slice. In this case, the +mutation against slice2 didn’t cause a side effect against slice1. + +** Copying Slices Manually + +There is a built-in function named copy that will allow for the shallow copying +of slices. Since a string has a backing array of bytes that are immutable, it +can be used as a source but never a destination. + + slice1 := []string{"A", "B", "C", "D", "E"} + slice3 := make([]string, len(slice1)) + copy(slice3, slice1) + + inspectSlice(slice1) + inspectSlice(slice3) + +Output: + + Length[5] Capacity[5] + [0] 0xc00005c050 A + [1] 0xc00005c060 B + [2] 0xc00005c070 C + [3] 0xc00005c080 D + [4] 0xc00005c090 E + Length[5] Capacity[5] + [0] 0xc00005c0a0 A + [1] 0xc00005c0b0 B + [2] 0xc00005c0c0 C + [3] 0xc00005c0d0 D + [4] 0xc00005c0e0 E + +As long as the destination slice has the proper type and length, the built-in +function copy can perform a shallow copy. + +** Slices Use Pointer Semantic Mutation + +It’s important to remember that even though you use value semantics to move a slice +around the program, when reading and writing a slice, You are using pointer semantics. +Sharing individual elements of a slice with different parts of my program can +cause unwanted side effects. + + // Construct a slice of 1 user, set a pointer to that user, + // use the pointer to update likes. + + users := make([]user, 1) + ptrUsr0 := &users[0] + ptrUsr0.likes++ + + for i := range users { + fmt.Printf("User: %d Likes: %d\n", i, users[i].likes) + } + +Output: + + User: 0 Likes: 1 + +A slice is used to maintain a collection of users. Then a pointer is set to the +first user and used to update likes. The output shows that using the pointer is +working. + +.image /tour/grc/static/img/sl8.png + +Then a new user is appended to the collection and the pointer is used again to +add a like to the first user. + + // Append a new user to the collection. Use the pointer again + // to update likes. + + users = append(users, user{}) + ptrUsr0.likes++ + + for i := range users { + fmt.Printf("User: %d Likes: %d\n", i, users[i].likes) + } + +Output: + + User: 0 Likes: 1 + User: 1 Likes: 0 + +However, since the append function replaced the backing array with a new one, +the pointer is updating the old backing array and the likes are lost. The output +shows the likes for the first user did not increase. + +.image /tour/grc/static/img/sl9.png + +You have to be careful to know if a slice is going to be used in an append operation +during the course of a running program. How you share the slice needs to be +considered. Sharing individual indexes may not be the best idea. Sharing an +entire slice value may not work either when appending is in operation. Probably +using a slice as a field in a struct, and sharing the struct value is a better +way to go. + +** Linear Traversal Efficiency +The beauty of a slice is its ability to allow for performing linear traversals +that are mechanically sympathetic while sharing data using value semantics to +minimize heap allocations. + + x := []byte{0x0A, 0x15, 0x0e, 0x28, 0x05, 0x96, 0x0b, 0xd0, 0x0} + + a := x[0] + b := binary.LittleEndian.Uint16(x[1:3]) + c := binary.LittleEndian.Uint16(x[3:5]) + d := binary.LittleEndian.Uint32(x[5:9]) + + println(a, b, c, d) + +The code is performing a linear traversal by creating slice values that read +different sections of the byte array from beginning to end. + +.image /tour/grc/static/img/sl10.png + +All the data in this code stays on the stack. No extra copies of the data inside +the byte slice are copied. + +** Notes + +- Slices are like dynamic arrays with special and built-in functionality. +- There is a difference between a slices length and capacity and they each service a purpose. +- Slices allow for multiple "views" of the same underlying array. +- Slices can grow through the use of the built-in function append. + +** Extra Reading + +- [[https://blog.golang.org/go-slices-usage-and-internals][Go Slices: usage and internals]] - Andrew Gerrand +- [[https://blog.golang.org/strings][Strings, bytes, runes and characters in Go]] - Rob Pike +- [[https://blog.golang.org/slices][Arrays, slices (and strings): The mechanics of 'append']] - Rob Pike +- [[https://www.ardanlabs.com/blog/2013/08/understanding-slices-in-go-programming.html][Understanding Slices in Go Programming]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2013/08/collections-of-unknown-length-in-go.html][Collections Of Unknown Length in Go]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2013/09/iterating-over-slices-in-go.html][Iterating Over Slices In Go]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2013/09/slices-of-slices-of-slices-in-go.html][Slices of Slices of Slices in Go]] - William Kennedy +- [[https://www.ardanlabs.com/blog/2013/12/three-index-slices-in-go-12.html][Three-Index Slices in Go 1.2]] - William Kennedy +- [[https://github.com/golang/go/wiki/SliceTricks][SliceTricks]] +- [[https://go-review.googlesource.com/c/go/+/347917][runtime: Make slice growth formula a bit smoother]] - Go Team + +* Exercises + +Use the template as a starting point to complete the exercises. A possible solution is provided. + +** Exercise 1 + +*Part* *A:* Declare a nil slice of integers. Create a loop that appends 10 values +to the slice. Iterate over the slice and display each value. + +*Part* *B:* Declare a slice of five strings and initialize the slice with string +literal values. Display all the elements. Take a slice of index one and two and +display the index position and value of each element in the new slice. + +.play slices/exercise1.go +.play slices/answer1.go diff --git a/_content/tour/grc/slices/answer1.go b/_content/tour/grc/slices/answer1.go new file mode 100644 index 00000000..e8255b3d --- /dev/null +++ b/_content/tour/grc/slices/answer1.go @@ -0,0 +1,46 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a nil slice of integers. Create a loop that appends 10 values to the +// slice. Iterate over the slice and display each value. +// +// Declare a slice of five strings and initialize the slice with string literal +// values. Display all the elements. Take a slice of index one and two +// and display the index position and value of each element in the new slice. +package main + +import "fmt" + +func main() { + + // Declare a nil slice of integers. + var numbers []int + + // Append numbers to the slice. + for i := 0; i < 10; i++ { + numbers = append(numbers, i*10) + } + + // Display each value. + for _, number := range numbers { + fmt.Println(number) + } + + // Declare a slice of strings. + names := []string{"Bill", "Joan", "Jim", "Cathy", "Beth"} + + // Display each index position and name. + for i, name := range names { + fmt.Printf("Index: %d Name: %s\n", i, name) + } + + // Take a slice of index 1 and 2. + slice := names[1:3] + + // Display the value of the new slice. + for i, name := range slice { + fmt.Printf("Index: %d Name: %s\n", i, name) + } +} diff --git a/_content/tour/grc/slices/example1.go b/_content/tour/grc/slices/example1.go new file mode 100644 index 00000000..36f418d4 --- /dev/null +++ b/_content/tour/grc/slices/example1.go @@ -0,0 +1,28 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the capacity of the slice +// is not available for use. +package main + +import "fmt" + +func main() { + + // Create a slice with a length of 5 elements. + fruits := make([]string, 5) + fruits[0] = "Apple" + fruits[1] = "Orange" + fruits[2] = "Banana" + fruits[3] = "Grape" + fruits[4] = "Plum" + + // You can't access an index of a slice beyond its length. + fruits[5] = "Runtime error" + + // Error: panic: runtime error: index out of range + + fmt.Println(fruits) +} diff --git a/_content/tour/grc/slices/example10.go b/_content/tour/grc/slices/example10.go new file mode 100644 index 00000000..f131cedf --- /dev/null +++ b/_content/tour/grc/slices/example10.go @@ -0,0 +1,40 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to use a third index slice. +package main + +import "fmt" + +func main() { + + // Create a slice of strings with different types of fruit. + slice := []string{"Apple", "Orange", "Banana", "Grape", "Plum"} + inspectSlice(slice) + + // Take a slice of slice. We want just index 2 + takeOne := slice[2:3] + inspectSlice(takeOne) + + // Take a slice of just index 2 with a length and capacity of 1 + takeOneCapOne := slice[2:3:3] // Use the third index position to + inspectSlice(takeOneCapOne) // set the capacity to 1. + + // Append a new element which will create a new + // underlying array to increase capacity. + takeOneCapOne = append(takeOneCapOne, "Kiwi") + inspectSlice(takeOneCapOne) +} + +// inspectSlice exposes the slice header for review. +func inspectSlice(slice []string) { + fmt.Printf("Length[%d] Capacity[%d]\n", len(slice), cap(slice)) + for i, s := range slice { + fmt.Printf("[%d] %p %s\n", + i, + &slice[i], + s) + } +} diff --git a/_content/tour/grc/slices/example2.go b/_content/tour/grc/slices/example2.go new file mode 100644 index 00000000..6b9d5a9d --- /dev/null +++ b/_content/tour/grc/slices/example2.go @@ -0,0 +1,34 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show the components of a slice. It has a +// length, capacity and the underlying array. +package main + +import "fmt" + +func main() { + + // Create a slice with a length of 5 elements and a capacity of 8. + fruits := make([]string, 5, 8) + fruits[0] = "Apple" + fruits[1] = "Orange" + fruits[2] = "Banana" + fruits[3] = "Grape" + fruits[4] = "Plum" + + inspectSlice(fruits) +} + +// inspectSlice exposes the slice header for review. +func inspectSlice(slice []string) { + fmt.Printf("Length[%d] Capacity[%d]\n", len(slice), cap(slice)) + for i, s := range slice { + fmt.Printf("[%d] %p %s\n", + i, + &slice[i], + s) + } +} diff --git a/_content/tour/grc/slices/example3.go b/_content/tour/grc/slices/example3.go new file mode 100644 index 00000000..d63f69b0 --- /dev/null +++ b/_content/tour/grc/slices/example3.go @@ -0,0 +1,44 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to grow a slice using the built-in function append +// and how append grows the capacity of the underlying array. +package main + +import "fmt" + +func main() { + + // Declare a nil slice of strings. + var data []string + + // Capture the capacity of the slice. + lastCap := cap(data) + + // Append ~100k strings to the slice. + for record := 1; record <= 1e5; record++ { + + // Use the built-in function append to add to the slice. + value := fmt.Sprintf("Rec: %d", record) + data = append(data, value) + + // When the capacity of the slice changes, display the changes. + if lastCap != cap(data) { + + // Calculate the percent of change. + capChg := float64(cap(data)-lastCap) / float64(lastCap) * 100 + + // Save the new values for capacity. + lastCap = cap(data) + + // Display the results. + fmt.Printf("Addr[%p]\tIndex[%d]\t\tCap[%d - %2.f%%]\n", + &data[0], + record, + cap(data), + capChg) + } + } +} diff --git a/_content/tour/grc/slices/example4.go b/_content/tour/grc/slices/example4.go new file mode 100644 index 00000000..96ade7e0 --- /dev/null +++ b/_content/tour/grc/slices/example4.go @@ -0,0 +1,56 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to takes slices of slices to create different +// views of and make changes to the underlying array. +package main + +import "fmt" + +func main() { + + // Create a slice with a length of 5 elements and a capacity of 8. + slice1 := make([]string, 5, 8) + slice1[0] = "Apple" + slice1[1] = "Orange" + slice1[2] = "Banana" + slice1[3] = "Grape" + slice1[4] = "Plum" + + inspectSlice(slice1) + + // Take a slice of slice1. We want just indexes 2 and 3. + // Parameters are [starting_index : (starting_index + length)] + slice2 := slice1[2:4] + inspectSlice(slice2) + + fmt.Println("*************************") + + // Change the value of the index 0 of slice2. + slice2[0] = "CHANGED" + + // Display the change across all existing slices. + inspectSlice(slice1) + inspectSlice(slice2) + + fmt.Println("*************************") + + // Make a new slice big enough to hold elements of slice 1 and copy the + // values over using the builtin copy function. + slice3 := make([]string, len(slice1)) + copy(slice3, slice1) + inspectSlice(slice3) +} + +// inspectSlice exposes the slice header for review. +func inspectSlice(slice []string) { + fmt.Printf("Length[%d] Capacity[%d]\n", len(slice), cap(slice)) + for i, s := range slice { + fmt.Printf("[%d] %p %s\n", + i, + &slice[i], + s) + } +} diff --git a/_content/tour/grc/slices/example5.go b/_content/tour/grc/slices/example5.go new file mode 100644 index 00000000..cfe55cf6 --- /dev/null +++ b/_content/tour/grc/slices/example5.go @@ -0,0 +1,45 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how one needs to be careful when appending +// to a slice when you have a reference to an element. +package main + +import "fmt" + +type user struct { + likes int +} + +func main() { + + // Declare a slice of 3 users. + users := make([]user, 3) + + // Share the user at index 1. + shareUser := &users[1] + + // Add a like for the user that was shared. + shareUser.likes++ + + // Display the number of likes for all users. + for i := range users { + fmt.Printf("User: %d Likes: %d\n", i, users[i].likes) + } + + // Add a new user. + users = append(users, user{}) + + // Add another like for the user that was shared. + shareUser.likes++ + + // Display the number of likes for all users. + fmt.Println("*************************") + for i := range users { + fmt.Printf("User: %d Likes: %d\n", i, users[i].likes) + } + + // Notice the last like has not been recorded. +} diff --git a/_content/tour/grc/slices/example6.go b/_content/tour/grc/slices/example6.go new file mode 100644 index 00000000..bc8723d5 --- /dev/null +++ b/_content/tour/grc/slices/example6.go @@ -0,0 +1,61 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +/* + https://blog.golang.org/strings + + Go source code is always UTF-8. + A string holds arbitrary bytes. + A string literal, absent byte-level escapes, always holds valid UTF-8 sequences. + Those sequences represent Unicode code points, called runes. + No guarantee is made in Go that characters in strings are normalized. + + ---------------------------------------------------------------------------- + + Multiple runes can represent different characters: + + The lower case grave-accented letter à is a character, and it's also a code + point (U+00E0), but it has other representations. + + We can use the "combining" grave accent code point, U+0300, and attach it to + the lower case letter a, U+0061, to create the same character à. + + In general, a character may be represented by a number of different sequences + of code points (runes), and therefore different sequences of UTF-8 bytes. +*/ + +// Sample program to show how strings have a UTF-8 encoded byte array. +package main + +import ( + "fmt" + "unicode/utf8" +) + +func main() { + + // Declare a string with both chinese and english characters. + s := "世界 means world" + + // UTFMax is 4 -- up to 4 bytes per encoded rune. + var buf [utf8.UTFMax]byte + + // Iterate over the string. + for i, r := range s { + + // Capture the number of bytes for this rune. + rl := utf8.RuneLen(r) + + // Calculate the slice offset for the bytes associated + // with this rune. + si := i + rl + + // Copy of rune from the string to our buffer. + copy(buf[:], s[i:si]) + + // Display the details. + fmt.Printf("%2d: %q; codepoint: %#6x; encoded bytes: %#v\n", i, r, r, buf[:rl]) + } +} diff --git a/_content/tour/grc/slices/example7.go b/_content/tour/grc/slices/example7.go new file mode 100644 index 00000000..c28040f5 --- /dev/null +++ b/_content/tour/grc/slices/example7.go @@ -0,0 +1,61 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how to declare and use variadic functions. +package main + +import "fmt" + +// user is a struct type that declares user information. +type user struct { + id int + name string +} + +func main() { + + // Declare and initialize a value of type user. + u1 := user{ + id: 1432, + name: "Betty", + } + + // Declare and initialize a value of type user. + u2 := user{ + id: 4367, + name: "Janet", + } + + // Display both user values. + display(u1, u2) + + // Create a slice of user values. + u3 := []user{ + {24, "Bill"}, + {32, "Joan"}, + } + + // Display all the user values from the slice. + display(u3...) + + change(u3...) + fmt.Println("**************************") + for _, u := range u3 { + fmt.Printf("%+v\n", u) + } +} + +// display can accept and display multiple values of user types. +func display(users ...user) { + fmt.Println("**************************") + for _, u := range users { + fmt.Printf("%+v\n", u) + } +} + +// change shows how the backing array is shared. +func change(users ...user) { + users[1] = user{99, "Same Backing Array"} +} diff --git a/_content/tour/grc/slices/example8.go b/_content/tour/grc/slices/example8.go new file mode 100644 index 00000000..af4361da --- /dev/null +++ b/_content/tour/grc/slices/example8.go @@ -0,0 +1,26 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how the for range has both value and pointer semantics. +package main + +import "fmt" + +func main() { + + // Using the value semantic form of the for range. + friends := []string{"Annie", "Betty", "Charley", "Doug", "Edward"} + for _, v := range friends { + friends = friends[:2] + fmt.Printf("v[%s]\n", v) + } + + // Using the pointer semantic form of the for range. + friends = []string{"Annie", "Betty", "Charley", "Doug", "Edward"} + for i := range friends { + friends = friends[:2] + fmt.Printf("v[%s]\n", friends[i]) + } +} diff --git a/_content/tour/grc/slices/example9.go b/_content/tour/grc/slices/example9.go new file mode 100644 index 00000000..97e0fce8 --- /dev/null +++ b/_content/tour/grc/slices/example9.go @@ -0,0 +1,32 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Sample program to show how slices allow for efficient linear traversals. +package main + +import ( + "encoding/binary" + "fmt" +) + +func main() { + + // Given a stream of bytes to be processed. + x := []byte{0x0A, 0x15, 0x0e, 0x28, 0x05, 0x96, 0x0b, 0xd0, 0x0} + + // Perform a linear traversal across the bytes, never making + // copies of the actual data but still passing those bytes + // to the binary function for processing. + + a := x[0] + b := binary.LittleEndian.Uint16(x[1:3]) + c := binary.LittleEndian.Uint16(x[3:5]) + d := binary.LittleEndian.Uint32(x[5:9]) + + // The result is zero allocation data access that is mechanically + // sympathetic with the hardware. + + fmt.Println(a, b, c, d) +} diff --git a/_content/tour/grc/slices/exercise1.go b/_content/tour/grc/slices/exercise1.go new file mode 100644 index 00000000..1b92a4a4 --- /dev/null +++ b/_content/tour/grc/slices/exercise1.go @@ -0,0 +1,31 @@ +//go:build OMIT + +// All material is licensed under the Apache License Version 2.0, January 2004 +// http://www.apache.org/licenses/LICENSE-2.0 + +// Declare a nil slice of integers. Create a loop that appends 10 values to the +// slice. Iterate over the slice and display each value. +// +// Declare a slice of five strings and initialize the slice with string literal +// values. Display all the elements. Take a slice of index one and two +// and display the index position and value of each element in the new slice. +package main + +// Add imports. + +func main() { + + // Declare a nil slice of integers. + + // Append numbers to the slice. + + // Display each value in the slice. + + // Declare a slice of strings and populate the slice with names. + + // Display each index position and slice value. + + // Take a slice of index 1 and 2 of the slice of strings. + + // Display each index position and slice values for the new slice. +} diff --git a/_content/tour/grc/static/css/app.css b/_content/tour/grc/static/css/app.css new file mode 100644 index 00000000..bf823ed6 --- /dev/null +++ b/_content/tour/grc/static/css/app.css @@ -0,0 +1,899 @@ +/* Generic elements */ +html, body { + margin: 0; + padding: 0; + font-size: 16px; + height: 100%; + font-family: sans-serif; + line-height: 24px; + word-wrap: break-word; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + /* Prevent font scaling in landscape */ + -webkit-text-size-adjust: none; + -webkit-font-smoothing: antialiased; +} +[data-theme='dark'] body { + background-color: #2B2D2F; +} +* { + outline: none; +} +#explorer::-webkit-scrollbar { + display: none; +} +#explorer { + background: #fafafa; + height: 32px; + overflow-x: auto; + padding: 5px 15px 5px 30px; + white-space: nowrap; +} +.space-relative { + margin-top:10px; +} +a { + color: #e3430e; + text-decoration: none; +} +[data-theme='dark'] a { + color: #53B4DB; +} +[data-theme='dark'] a:hover { + text-decoration: underline; +} +[data-theme='dark'] p { + color: #F0F1F2; +} +.ardan-logo { + position: fixed; + top: 7px; + width: 4.75rem; +} +a.logo, .toc a { + color: inherit; +} + +a.logo { + color: inherit; + margin-left: 95px; + position: relative; + top: 4px; +} + +h1, h2, h3, h4 { + color: #333; + line-height: 32px; + margin: 0; +} +[data-theme='dark'] h1, [data-theme='dark'] h2, [data-theme='dark'] h3, [data-theme='dark'] h4 { + color: #F0F1F2; +} +pre, code { + border-radius: 4px; + color: #333; + background-color: #fafafa; +} +[data-theme='dark'] pre, [data-theme='dark'] code, [data-theme='dark'] .info { + background-color: #2B2D2F !important; + color: #BABABA; +} +pre { + padding: 10px 15px; + overflow-x: auto; +} +code { + padding: 2px; +} +.left { + display: block; + float: left; + margin-right: 10px; +} +.right { + display: flex; + float: right; + margin-left: 10px; +} +.right * { + vertical-align: middle; +} +@media (max-width: 600px) and (orientation: portrait) { + .right { + display: none; + } +} +.bar { + display: block; + overflow: hidden; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.wrapper { + position: fixed; + overflow: auto; + top: 48px; + bottom: 0; + left: 0; + right: 0; +} +[data-theme='dark'] .wrapper { + background: #171717; +} +.container { + max-width: 800px; + width: 90%; + margin: 0 auto 36px auto; + padding: 16px 5%; + background: #ffffff; +} +[data-theme='dark'] .container { + background: #171717; +} +.container a { + color: #e3430e; +} +[data-theme='dark'] .container a { + color: #53B4DB; +} +[data-theme='dark'] .container p { + color: #F0F1F2; +} +.relative-content { + display: block; + position: relative; + height: 100%; +} + +.relative-content.space-relative { + height:calc(100% - 10px) +} +.highlight { + background: #b5533b !important; + color: yellow !important; +} +.hidden { + display: none !important; +} +p { + margin: 16px 0; +} + +li { + margin: 8px 0; +} +[data-theme='dark'] li { + color: #F0F1F2; +} +ul { + list-style: circle; + margin: 0; + padding-left: 32px; +} + +h2 { + margin-top:1.5em; +} + +h2:first-of-type { + margin-top:0px; +} + +/* Navigation bars */ +.top-bar { + position: fixed; + left: 0; + right: 0; + top: 0; + z-index: 1000; + font-size: 1.4em; + padding: 8px 24px; + text-align: center; + color: #fff; + background: #e3430e; + height: 2rem; +} +.top-bar a { + vertical-align: middle; + font-weight: bold; +} +.nav { + float: right; + padding: 2px; + height: 25px; + width: 25px; + margin-left: 10px; + cursor: pointer; + fill: #fff; +} +/* Module list */ +.page-header { + font-size: 1.2em; + line-height: 32px; + margin: 32px 0; +} +@media (max-width: 515px) { + .page-header { + font-size: 0.75em; + } +} +.module { + margin: 32px 0; +} +.module-title { + font-size: 1.3em; + font-weight: bold; + color: #333; + margin: 0; +} +.lesson { + background: #f1f1f1; + padding: 8px 16px; + margin: 16px 0; + position: relative; +} +[data-theme='dark'] .lesson { + background: #202224; +} +.lesson-title { + display: inline-block; + font-size: 1.2em; + font-weight: bold; + margin: 16px 0 0 0; + padding-right: 48px; +} +/* Lesson viewer */ +.slide-content { + padding: 40px 16px; + background: #fff; +} +@media (max-width: 600px) { + .slide-content { + margin-top: 40px; + } +} +[data-theme='dark'] .slide-content { + background: #202224; +} +.module-bar { + font-size: 1.5em; + padding: 8px 0; + text-align: center; + line-height: 24px; + font-size: 24px; +} +[data-theme='dark'] .module-bar { + background: #202224; + color: #F0F1F2; +} +.module-bar a { + color: #e3430e; + position: relative; + font-weight: bold; + margin: 5px; +} +[data-theme='dark'] .module-bar a { + color: #53B4DB; +} +.menu-button { + display: inline-block; + text-decoration: none; + cursor: pointer; + font-size: 0.9em; + border-radius: 2px; + background-color: #f1f1f1; + border: 1px solid rgba(0, 0, 0, 0.1); + margin: 2px; + height: 24px; + padding: 1px 8px; + line-height: 24px; + color: #444; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.menu-button:hover:not(.active) { + border: 1px solid #C6C6C6; + background-color: #fafafa; +} +[data-theme='dark'] .menu-button:hover { + background-color: #D9D9D9; + color: #53B4DB; + text-decoration: none; +} +[data-theme='dark'] .menu-button:active { + background-color: #202224; +} +.menu-button.active { + background: #fff; +} +[data-theme='dark'] .menu-button { + background: #3D3D3E; + color: #BABABA; +} +.menu-button[imports-checkbox]:after { + content: ' off'; +} +.menu-button[imports-checkbox].active:after { + content: ' on'; +} +.menu-button[syntax-checkbox]:after { + content: ' off'; +} +.menu-button[syntax-checkbox].active:after { + content: ' on'; +} +#file-menu .menu-button { + float: right; +} +[data-theme='dark'] #file-menu .menu-button { + border: 1px solid #202224; + color: #53B4DB; +} +a#run, a#kill { + background-color: #e3430e; + color: #fff; + width: 40px; + text-align: center; +} +[data-theme='dark'] a#run, [data-theme='dark'] a#kill { + background-color: #007F9F; +} +[data-theme='dark'] a#run:hover, [data-theme='dark'] a#kill:hover { + background: #1A5D8359; + border: 1px solid #53B4DB; + color: #53B4DB; +} +[data-theme='dark'] a#run:active, [data-theme='dark'] a#kill:active { + background: #1A5D8326; + border: 1px solid #007F9F; + color: #007F9F; +} +#run:hover:not(:active), #kill:hover:not(:active) { + background-color: #fff; + color: #e3430e; +} +.output:not(.active) { + display: none; +} +.output > pre { + font-family: 'Inconsolata', monospace; + background: #fafafa; + margin: 0; +} +.output .system { + color: #888; +} +[data-theme='dark'] .output .system { + color: #F0F1F2; +} +.output .stderr { + color: #D00A0A; +} +.output-menu .menu-button { + float: left; +} +.output-menu, #file-menu { + background: #fafafa; +} +[data-theme='dark'] #explorer { + background: #171717; +} +#explorer .imports-checkbox { + float: right; +} +#explorer .menu-button.active { + cursor: default; +} +#explorer .syntax-checkbox { + float: right; +} +/* CodeMirror */ +#file-editor { + background: #FFFFD8; + overflow: auto; +} +#file-editor > textarea { + display: none; +} +#file-editor .CodeMirror { + height: 100%; + background: #FFFFD8; +} +#file-editor .CodeMirror-lines, #file-editor .CodeMirror-gutters { + background: #FFFFD8; + font-family: 'Inconsolata', monospace; + line-height: 1.2em; +} +[data-theme='dark'] #file-editor, [data-theme='dark'] #file-editor .CodeMirror, [data-theme='dark'] #file-editor .CodeMirror-lines, [data-theme='dark'] #file-editor .CodeMirror-gutters { + background: #2B2D2F; + color: #BABABA; +} +.CodeMirror-code > .line-error { + background: #FF8080; +} +.CodeMirror-code > .line-error .CodeMirror-linenumber { + color: #FF5555; + font-weight: bolder; +} +#file-editor .CodeMirror-gutters { + width: 36px; +} +[data-theme='dark'] .cm-s-default .cm-keyword { + color: #B984FF; +} +[data-theme='dark'] .cm-s-default .cm-number { + color: #63EC8C; +} +[data-theme='dark'] .cm-s-default .cm-string { + color: #F585C0; +} +.slide-content img { + width:100%; +} +@media (min-width: 601px) { + #editor-container { + position: fixed; + top: 48px; + left: 0px; + right: 0px; + bottom: 0px; + overflow: hidden; + background: #fff; + } + #left-side { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 50%; + overflow: hidden; + } + .output { + background-image: url(/tour/static/img/gopher.png); + background-repeat: no-repeat; + background-position: bottom; + background-color: #fff; + } + [data-theme='dark'] .output { + background-color: #202224; + } + div[vertical-slide] { + position: absolute; + top: 0px; + bottom: 0px; + width: 5px; + background: #f1f1f1; + left: 50%; + right: 50%; + z-index: 100; + cursor: move; + } + [data-theme='dark'] div[vertical-slide] { + background: #171717; + } + #right-side { + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 50%; + background: #fff; + } + [data-theme='dark'] #right-side { + background: #202224; + } + .slide-content { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 30px; + overflow: auto; + } + .module-bar { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 4px 0; + margin: 0; + } + #top-part { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 33%; + background: #f1f1f1; + } + #file-editor { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + } + div[horizontal-slide] { + position: absolute; + left: 0; + right: 0; + bottom: 33%; + height: 5px; + background: #f1f1f1; + z-index: 100; + cursor: move; + } + [data-theme='dark'] div[horizontal-slide] { + background: #171717; + } + #bottom-part { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 67%; + min-height: 100px; + z-index: 50; + } + #explorer { + position: absolute; + top: 0; + left: 0; + right: 0; + } + #explorer + div { + top: 32px; + } + #file-menu { + position: absolute; + top: 0; + right: 0; + left: 0; + background: #fafafa; + padding: 5px 15px; + } + [data-theme='dark'] #file-menu { + background: #171717; + } + .output { + background: #fafafa; + position: absolute; + top: 42px; + bottom: 0; + left: 0; + right: 0; + margin: 0; + padding: 0; + overflow: auto; + } +} +@media (max-width: 600px) { + #top-part { + border: 1px solid #ccc; + } + #left-side { + background: #f1f1f1; + } + #right-side { + padding-top: 48px; + } + #file-menu { + height: 32px; + } + .output { + background: white; + max-height: 300px; + overflow: auto; + } + #editor-container { + padding-bottom: 40px; + } + .module-bar { + position: fixed; + background: #f1f1f1; + left: 0; + right: 0; + bottom: 0; + z-index: 10; + height: 42px; + padding: 0; + overflow: hidden; + text-align: center; + } + .module-bar * { + display: flex; + width: 32%; + font-size: 29px; + padding: 8px 0; + margin: 0 auto; + } + .module-bar a { + position: relative; + top: -7px; + } + div[horizontal-slide], div[vertical-slide] { + display: none; + } +} +/* Table of contents */ +.toc { + display: none; + position: fixed; + z-index: 200; + font-size: 1.3em; + top: 48px; + bottom: 0; + right: 0; + width: 500px; + background: #f1f1f1; + color: black; + overflow-y: auto; + padding: 0; + margin: 0; + border-left: 4px solid #f1f1f1; + border-bottom: 4px solid #f1f1f1; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +[data-theme='dark'] .toc { + background: #171717; + border-left: 4px solid #171717; + border-bottom: 4px solid #171717; +} +.click-catcher, .click-catcher-search { + position: fixed; + z-index: -100; + top: 0; + bottom: 0; + left: 0; + right: 10px; /* avoid covering the TOC scroller */ + background: rgba(0, 0, 0, 0); +} +.toc * { + margin: 0; + padding: 0; + font-size: 0.95em; + display: block; +} +.toc span, .toc a { + padding: 4px; +} +.toc-module { + color: #e3430e; + background: #f1f1f1; +} +[data-theme='dark'] .toc-module { + color: #53B4DB; + background: #171717; +} +[data-theme='dark'] .toc-module li { + color: #F0F1F2; + background: #2B2D2F; +} +.toc-lesson { + background: #fafafa; + color: #333; + margin: 1px 0; + cursor: pointer; +} +.toc-page { + background: #fff; + color: #333; + padding-left: 4px; + display: list-item; +} +.toc-lesson.active .toc-page { + display: list-item; +} +.toc-page.active { + color: #e3430e; + font-weight: bold; +} +@media (max-width: 600px) { + .toc { + position: absolute; + left: 0; + right: 0; + bottom: 0; + width: 100%; + border: none; + } + .toc ul { + width: 100%; + } + .click-catcher, .click-catcher-search { + display: none; + } +} +.header-toggleTheme [data-value] { + display: none; +} + +[data-theme='auto'] .header-toggleTheme [data-value='auto'] { + display: block; +} + +[data-theme='dark'] .header-toggleTheme [data-value='dark'] { + display: block; +} + +[data-theme='light'] .header-toggleTheme [data-value='light'] { + display: block; +} + +.header-toggleTheme { + background-color: transparent; + border: none; + cursor: pointer; + font-size: 16px; +} + +.go-Icon { + height: 1.125em; + vertical-align: text-bottom; + width: auto; +} +.go-Icon--inverted { + filter: brightness(0) saturate(100%) invert(100%) sepia(97%) saturate(13%) + hue-rotate(245deg) brightness(103%) contrast(107%); +} +#reading-progress { + border-radius: 0; + position: fixed; + width: 100%; + left: 0; + z-index: 100; + top: 48px; + height: 6px; +} + +::-webkit-progress-bar { +background-color: transparent; +} +::-webkit-progress-value { +background-color: #B22C00; +} + +/* Table of search contents */ +.search { + display: none; + position: fixed; + z-index: 200; + font-size: 1.3em; + top: 48px; + bottom: 0; + right: 0; + width: 500px; + background: #f1f1f1; + color: black; + overflow-y: auto; + padding: 0; + margin: 0; + border-left: 4px solid #f1f1f1; + border-bottom: 4px solid #f1f1f1; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.search * { + margin: 0; + padding: 0; + font-size: 0.95em; + display: block; +} +[data-theme='dark'] .search { + background: #171717; + border-left: 4px solid #171717; + border-bottom: 4px solid #171717; +} +.search span, .search a { + padding: 4px; +} +.search { + color: #e3430e; + background: #f1f1f1; +} +.search-lesson { + background: #fafafa; + color: #333; + margin: 1px 0; + cursor: pointer; +} +.search-page { + background: #fff; + color: #333; + padding-left: 4px; + display: list-item; +} +[data-theme='dark'] .search { + color: #53B4DB; + background: #171717; +} +[data-theme='dark'] .search li { + color: #F0F1F2; + background: #2B2D2F; +} +.search-lesson.active .search-page { + display: list-item; +} +.search-page.active { + color: #e3430e; + font-weight: bold; +} +@media (max-width: 600px) { + .search { + position: absolute; + left: 0; + right: 0; + bottom: 0; + width: 100%; + border: none; + } + .search ul { + width: 100%; + } + .click-catcher, .click-catcher-search { + display: none; + } +} + +/* Style the form to be 100% width based on the parent div */ +.search-form { + width: 100%; + box-sizing: border-box; + padding-right: 3px; +} + +.search-input-container { + display: flex; + align-items: center; + margin-top: 5px; + margin-bottom: 5px; +} + +.search-input { + width: 100%; + padding: 8px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 4px; + flex: 1; + box-sizing: border-box; +} + +/* Language switcher */ +.language-switcher { + display: inline-block; + margin: 0 10px; +} + +.language-switcher select { + padding: 5px 10px; + border: none; + border-radius: 5px; + background: #f0f0f0; + color: #333; + font-size: 16px; + outline: none; + cursor: pointer; + transition: background 0.3s; +} + +.language-switcher select:hover { + background: #e0e0e0; +} + +.language-switcher select:focus { + background: #d0d0d0; +} + +.language-switcher select:active { + background: #c0c0c0; +} \ No newline at end of file diff --git a/_content/tour/grc/static/img/a1.png b/_content/tour/grc/static/img/a1.png new file mode 100644 index 00000000..a14c593f Binary files /dev/null and b/_content/tour/grc/static/img/a1.png differ diff --git a/_content/tour/grc/static/img/a2.png b/_content/tour/grc/static/img/a2.png new file mode 100644 index 00000000..009c998f Binary files /dev/null and b/_content/tour/grc/static/img/a2.png differ diff --git a/_content/tour/grc/static/img/a3.png b/_content/tour/grc/static/img/a3.png new file mode 100644 index 00000000..a999625c Binary files /dev/null and b/_content/tour/grc/static/img/a3.png differ diff --git a/_content/tour/grc/static/img/a4.png b/_content/tour/grc/static/img/a4.png new file mode 100644 index 00000000..181beaa7 Binary files /dev/null and b/_content/tour/grc/static/img/a4.png differ diff --git a/_content/tour/grc/static/img/algos-circle.png b/_content/tour/grc/static/img/algos-circle.png new file mode 100644 index 00000000..7e9cd5be Binary files /dev/null and b/_content/tour/grc/static/img/algos-circle.png differ diff --git a/_content/tour/grc/static/img/ardan-labs-go-tour-banner.png b/_content/tour/grc/static/img/ardan-labs-go-tour-banner.png new file mode 100644 index 00000000..d1b006aa Binary files /dev/null and b/_content/tour/grc/static/img/ardan-labs-go-tour-banner.png differ diff --git a/_content/tour/grc/static/img/brightness_2_gm_grey_24dp.svg b/_content/tour/grc/static/img/brightness_2_gm_grey_24dp.svg new file mode 100644 index 00000000..76e555b0 --- /dev/null +++ b/_content/tour/grc/static/img/brightness_2_gm_grey_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_content/tour/grc/static/img/brightness_6_gm_grey_24dp.svg b/_content/tour/grc/static/img/brightness_6_gm_grey_24dp.svg new file mode 100644 index 00000000..fbde09ff --- /dev/null +++ b/_content/tour/grc/static/img/brightness_6_gm_grey_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_content/tour/grc/static/img/buffered.png b/_content/tour/grc/static/img/buffered.png new file mode 100644 index 00000000..7146e405 Binary files /dev/null and b/_content/tour/grc/static/img/buffered.png differ diff --git a/_content/tour/grc/static/img/cache_latencies_graph.png b/_content/tour/grc/static/img/cache_latencies_graph.png new file mode 100644 index 00000000..5a138021 Binary files /dev/null and b/_content/tour/grc/static/img/cache_latencies_graph.png differ diff --git a/_content/tour/grc/static/img/comp1.png b/_content/tour/grc/static/img/comp1.png new file mode 100644 index 00000000..c59b4c73 Binary files /dev/null and b/_content/tour/grc/static/img/comp1.png differ diff --git a/_content/tour/grc/static/img/comp2.png b/_content/tour/grc/static/img/comp2.png new file mode 100644 index 00000000..40e2a787 Binary files /dev/null and b/_content/tour/grc/static/img/comp2.png differ diff --git a/_content/tour/grc/static/img/comp3.png b/_content/tour/grc/static/img/comp3.png new file mode 100644 index 00000000..9d4c881f Binary files /dev/null and b/_content/tour/grc/static/img/comp3.png differ diff --git a/_content/tour/grc/static/img/context_figure1.png b/_content/tour/grc/static/img/context_figure1.png new file mode 100644 index 00000000..f1ab8b68 Binary files /dev/null and b/_content/tour/grc/static/img/context_figure1.png differ diff --git a/_content/tour/grc/static/img/data_race.png b/_content/tour/grc/static/img/data_race.png new file mode 100644 index 00000000..4d8d3153 Binary files /dev/null and b/_content/tour/grc/static/img/data_race.png differ diff --git a/_content/tour/grc/static/img/f1.png b/_content/tour/grc/static/img/f1.png new file mode 100644 index 00000000..72630b48 Binary files /dev/null and b/_content/tour/grc/static/img/f1.png differ diff --git a/_content/tour/grc/static/img/f2.png b/_content/tour/grc/static/img/f2.png new file mode 100644 index 00000000..ab21e7dc Binary files /dev/null and b/_content/tour/grc/static/img/f2.png differ diff --git a/_content/tour/grc/static/img/f3.png b/_content/tour/grc/static/img/f3.png new file mode 100644 index 00000000..997a7c82 Binary files /dev/null and b/_content/tour/grc/static/img/f3.png differ diff --git a/_content/tour/grc/static/img/favicon.ico b/_content/tour/grc/static/img/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/_content/tour/grc/static/img/figure1.png b/_content/tour/grc/static/img/figure1.png new file mode 100644 index 00000000..dc8fba1b Binary files /dev/null and b/_content/tour/grc/static/img/figure1.png differ diff --git a/_content/tour/grc/static/img/figure1_data_race.png b/_content/tour/grc/static/img/figure1_data_race.png new file mode 100644 index 00000000..01600a1e Binary files /dev/null and b/_content/tour/grc/static/img/figure1_data_race.png differ diff --git a/_content/tour/grc/static/img/go-tour-banner.jpg b/_content/tour/grc/static/img/go-tour-banner.jpg new file mode 100644 index 00000000..d7a21131 Binary files /dev/null and b/_content/tour/grc/static/img/go-tour-banner.jpg differ diff --git a/_content/tour/grc/static/img/gopher.png b/_content/tour/grc/static/img/gopher.png new file mode 100644 index 00000000..e69de29b diff --git a/_content/tour/grc/static/img/gor1.png b/_content/tour/grc/static/img/gor1.png new file mode 100644 index 00000000..53eb9e2c Binary files /dev/null and b/_content/tour/grc/static/img/gor1.png differ diff --git a/_content/tour/grc/static/img/guarantee_of_delivery.png b/_content/tour/grc/static/img/guarantee_of_delivery.png new file mode 100644 index 00000000..7f345dec Binary files /dev/null and b/_content/tour/grc/static/img/guarantee_of_delivery.png differ diff --git a/_content/tour/grc/static/img/i1.png b/_content/tour/grc/static/img/i1.png new file mode 100644 index 00000000..ed1f1ce1 Binary files /dev/null and b/_content/tour/grc/static/img/i1.png differ diff --git a/_content/tour/grc/static/img/light_mode_gm_grey_24dp.svg b/_content/tour/grc/static/img/light_mode_gm_grey_24dp.svg new file mode 100644 index 00000000..e5cd8da2 --- /dev/null +++ b/_content/tour/grc/static/img/light_mode_gm_grey_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_content/tour/grc/static/img/m1.png b/_content/tour/grc/static/img/m1.png new file mode 100644 index 00000000..fbee2e89 Binary files /dev/null and b/_content/tour/grc/static/img/m1.png differ diff --git a/_content/tour/grc/static/img/m2.png b/_content/tour/grc/static/img/m2.png new file mode 100644 index 00000000..1ef082f7 Binary files /dev/null and b/_content/tour/grc/static/img/m2.png differ diff --git a/_content/tour/grc/static/img/p1.png b/_content/tour/grc/static/img/p1.png new file mode 100644 index 00000000..0a150d71 Binary files /dev/null and b/_content/tour/grc/static/img/p1.png differ diff --git a/_content/tour/grc/static/img/p2.png b/_content/tour/grc/static/img/p2.png new file mode 100644 index 00000000..eb244d69 Binary files /dev/null and b/_content/tour/grc/static/img/p2.png differ diff --git a/_content/tour/grc/static/img/pro1.png b/_content/tour/grc/static/img/pro1.png new file mode 100644 index 00000000..6ecd30d8 Binary files /dev/null and b/_content/tour/grc/static/img/pro1.png differ diff --git a/_content/tour/grc/static/img/pro2.png b/_content/tour/grc/static/img/pro2.png new file mode 100644 index 00000000..418e60b7 Binary files /dev/null and b/_content/tour/grc/static/img/pro2.png differ diff --git a/_content/tour/grc/static/img/pro3.png b/_content/tour/grc/static/img/pro3.png new file mode 100644 index 00000000..bdf3eb54 Binary files /dev/null and b/_content/tour/grc/static/img/pro3.png differ diff --git a/_content/tour/grc/static/img/pro4.png b/_content/tour/grc/static/img/pro4.png new file mode 100644 index 00000000..303fa20c Binary files /dev/null and b/_content/tour/grc/static/img/pro4.png differ diff --git a/_content/tour/grc/static/img/pro5.png b/_content/tour/grc/static/img/pro5.png new file mode 100644 index 00000000..b1cc4430 Binary files /dev/null and b/_content/tour/grc/static/img/pro5.png differ diff --git a/_content/tour/grc/static/img/pro6.png b/_content/tour/grc/static/img/pro6.png new file mode 100644 index 00000000..e3085f70 Binary files /dev/null and b/_content/tour/grc/static/img/pro6.png differ diff --git a/_content/tour/grc/static/img/pro7.png b/_content/tour/grc/static/img/pro7.png new file mode 100644 index 00000000..14e6e456 Binary files /dev/null and b/_content/tour/grc/static/img/pro7.png differ diff --git a/_content/tour/grc/static/img/scheduler.png b/_content/tour/grc/static/img/scheduler.png new file mode 100644 index 00000000..e971153a Binary files /dev/null and b/_content/tour/grc/static/img/scheduler.png differ diff --git a/_content/tour/grc/static/img/signaling_with_data.png b/_content/tour/grc/static/img/signaling_with_data.png new file mode 100644 index 00000000..21c72d28 Binary files /dev/null and b/_content/tour/grc/static/img/signaling_with_data.png differ diff --git a/_content/tour/grc/static/img/signaling_without_data.png b/_content/tour/grc/static/img/signaling_without_data.png new file mode 100644 index 00000000..11db7b6e Binary files /dev/null and b/_content/tour/grc/static/img/signaling_without_data.png differ diff --git a/_content/tour/grc/static/img/sl1.png b/_content/tour/grc/static/img/sl1.png new file mode 100644 index 00000000..2e89e0dd Binary files /dev/null and b/_content/tour/grc/static/img/sl1.png differ diff --git a/_content/tour/grc/static/img/sl10.png b/_content/tour/grc/static/img/sl10.png new file mode 100644 index 00000000..175e36ff Binary files /dev/null and b/_content/tour/grc/static/img/sl10.png differ diff --git a/_content/tour/grc/static/img/sl2.png b/_content/tour/grc/static/img/sl2.png new file mode 100644 index 00000000..e4c9789f Binary files /dev/null and b/_content/tour/grc/static/img/sl2.png differ diff --git a/_content/tour/grc/static/img/sl3.png b/_content/tour/grc/static/img/sl3.png new file mode 100644 index 00000000..95a204f4 Binary files /dev/null and b/_content/tour/grc/static/img/sl3.png differ diff --git a/_content/tour/grc/static/img/sl4.png b/_content/tour/grc/static/img/sl4.png new file mode 100644 index 00000000..f773a789 Binary files /dev/null and b/_content/tour/grc/static/img/sl4.png differ diff --git a/_content/tour/grc/static/img/sl5.png b/_content/tour/grc/static/img/sl5.png new file mode 100644 index 00000000..180136c4 Binary files /dev/null and b/_content/tour/grc/static/img/sl5.png differ diff --git a/_content/tour/grc/static/img/sl6.png b/_content/tour/grc/static/img/sl6.png new file mode 100644 index 00000000..f2f5f99f Binary files /dev/null and b/_content/tour/grc/static/img/sl6.png differ diff --git a/_content/tour/grc/static/img/sl7.png b/_content/tour/grc/static/img/sl7.png new file mode 100644 index 00000000..13c13b14 Binary files /dev/null and b/_content/tour/grc/static/img/sl7.png differ diff --git a/_content/tour/grc/static/img/sl8.png b/_content/tour/grc/static/img/sl8.png new file mode 100644 index 00000000..91b5078b Binary files /dev/null and b/_content/tour/grc/static/img/sl8.png differ diff --git a/_content/tour/grc/static/img/sl9.png b/_content/tour/grc/static/img/sl9.png new file mode 100644 index 00000000..6571f629 Binary files /dev/null and b/_content/tour/grc/static/img/sl9.png differ diff --git a/_content/tour/grc/static/img/state.png b/_content/tour/grc/static/img/state.png new file mode 100644 index 00000000..d521010d Binary files /dev/null and b/_content/tour/grc/static/img/state.png differ diff --git a/_content/tour/grc/static/img/testing_coverage.png b/_content/tour/grc/static/img/testing_coverage.png new file mode 100644 index 00000000..b537e03b Binary files /dev/null and b/_content/tour/grc/static/img/testing_coverage.png differ diff --git a/_content/tour/grc/static/img/tree.png b/_content/tour/grc/static/img/tree.png new file mode 100644 index 00000000..e69de29b diff --git a/_content/tour/grc/static/img/unbuffered.png b/_content/tour/grc/static/img/unbuffered.png new file mode 100644 index 00000000..0590251a Binary files /dev/null and b/_content/tour/grc/static/img/unbuffered.png differ diff --git a/_content/tour/grc/static/js/app.js b/_content/tour/grc/static/js/app.js new file mode 100644 index 00000000..e581341e --- /dev/null +++ b/_content/tour/grc/static/js/app.js @@ -0,0 +1,67 @@ +/* Copyright 2012 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +'use strict'; + +angular.module('tour', ['ui', 'tour.services', 'tour.controllers', 'tour.directives', 'tour.values', 'ng']). + +config(['$routeProvider', '$locationProvider', + function($routeProvider, $locationProvider) { + $routeProvider. + when('/tour/', { + redirectTo: '/tour/grc/list' + }). + when('/tour/grc/', { + redirectTo: '/tour/grc/list' + }). + when('/tour/grc/list', { + templateUrl: '/tour/grc/static/partials/list.html', + }). + when('/tour/grc/:lessonId/:pageNumber', { + templateUrl: '/tour/grc/static/partials/editor.html', + controller: 'EditorCtrl' + }). + when('/tour/grc/:lessonId', { + redirectTo: '/tour/grc/:lessonId/1' + }). + otherwise({ + redirectTo: '/tour/grc/list' + }); + + $locationProvider.html5Mode(true).hashPrefix('!'); + } +]). + +// handle mapping from old paths (#42) to the new organization. +run(function($rootScope, $location, mapping) { + $rootScope.$on( "$locationChangeStart", function(event, next) { + var url = document.createElement('a'); + url.href = next; + if (url.pathname != '/tour/' || url.hash == '') { + return; + } + if (url.pathname != '/tour/grc/' || url.hash == '') { + return; + } + $location.hash(''); + var m = mapping[url.hash]; + if (m === undefined) { + console.log('unknown url, redirecting home'); + $location.path('/tour/grc/list'); + return; + } + + $location.path('/tour/grc' + m); + }); +}); + +window.scrollTo = (id) => { + + const element = document.getElementById(id); + + if(!element) + return + + element.scrollIntoView(); +} \ No newline at end of file diff --git a/_content/tour/grc/static/js/controllers.js b/_content/tour/grc/static/js/controllers.js new file mode 100644 index 00000000..8c18a698 --- /dev/null +++ b/_content/tour/grc/static/js/controllers.js @@ -0,0 +1,143 @@ +/* Copyright 2012 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +'use strict'; + +/* Controllers */ + + +angular.module('tour.controllers', []). + +// Navigation controller +controller('EditorCtrl', ['$scope', '$routeParams', '$location', 'toc', 'i18n', 'run', 'fmt', 'editor', 'analytics', 'storage', + function($scope, $routeParams, $location, toc, i18n, run, fmt, editor, analytics, storage) { + var lessons = []; + toc.lessons.then(function(v) { + lessons = v; + $scope.gotoPage($scope.curPage); + + // Store changes on the current file to local storage. + $scope.$watch(function() { + var f = file(); + return f && f.Content; + }, function(val) { + if (val) storage.set(file().Hash, val); + }); + }); + + $scope.toc = toc; + $scope.lessonId = $routeParams.lessonId; + $scope.curPage = parseInt($routeParams.pageNumber); + $scope.curFile = 0; + $scope.job = null; + + $scope.nextPageClick = function(event) { + event.preventDefault(); + $scope.nextPage(); + }; + $scope.prevPageClick = function(event) { + event.preventDefault(); + $scope.prevPage(); + }; + $scope.nextPage = function() { + $scope.gotoPage($scope.curPage + 1); + }; + $scope.prevPage = function() { + $scope.gotoPage($scope.curPage - 1); + }; + $scope.gotoPage = function(page) { + $scope.kill(); + var l = $routeParams.lessonId; + if (page >= 1 && page <= lessons[$scope.lessonId].Pages.length) { + $scope.curPage = page; + } else { + l = (page < 1) ? toc.prevLesson(l) : toc.nextLesson(l); + if (l === '') { // If there's no previous or next + $location.path('/tour/grc/list'); + return; + } + page = (page < 1) ? lessons[l].Pages.length : 1; + } + $location.path('/tour/grc/' + l + '/' + page); + $scope.openFile($scope.curFile); + analytics.trackView(); + }; + $scope.openFile = function(file) { + $scope.curFile = file; + editor.paint(); + }; + + function log(mode, text) { + $('.output.active').html('
' + text + '
'); + } + + function clearOutput() { + $('.output.active').html(''); + } + + function file() { + return lessons[$scope.lessonId].Pages[$scope.curPage - 1].Files[$scope.curFile]; + } + + $scope.run = function() { + log('info', i18n.l('waiting')); + var f = file(); + $scope.job = run(f.Content, $('.output.active > pre')[0], { + path: f.Name + }, function() { + $scope.job = null; + $scope.$apply(); + }); + }; + + $scope.kill = function() { + if ($scope.job !== null) $scope.job.Kill(); + }; + + $scope.format = function() { + log('info', i18n.l('waiting')); + fmt(file().Content, editor.imports).then( + function(data) { + if (data.data.Error !== '') { + log('stderr', data.data.Error); + return; + } + clearOutput(); + file().Content = data.data.Body; + }, + function(error) { + log('stderr', error); + }); + }; + + $scope.reset = function() { + file().Content = file().OrigContent; + }; + } +]) + +.controller('SearchController', ['$scope', '$http', function($scope, $http) { + $scope.searchTerm = ''; + $scope.searchResults = []; + + $scope.performSearch = function() { + if ($scope.searchTerm && $scope.searchTerm.length >= 3) { + $http.get('/tour/grc/bleve/', { params: { search: $scope.searchTerm } }) + .success(function(response) { + for (const key in response) { + response[key].Key = key + } + + // Update searchResults with the data from the backend. + $scope.searchResults = response; + }) + .error(function(error) { + console.error('Error during search request:', error); + }); + } else { + // Clear search results if search term is empty. + $scope.searchResults = []; + } + }; +}]); diff --git a/_content/tour/grc/static/js/directives.js b/_content/tour/grc/static/js/directives.js new file mode 100644 index 00000000..74bcb338 --- /dev/null +++ b/_content/tour/grc/static/js/directives.js @@ -0,0 +1,312 @@ +/* Copyright 2012 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +'use strict'; + +/* Directives */ +angular.module('tour.directives', []). + +directive('progressbar', function() { + return function(scope, el, attrs) { + const progress = document.getElementById("reading-progress"); + + let elm = el[0] + + // Get the percentage scrolled of an element. It returns zero if its not in view. + function getScrollProgress(el) { + let coords = el.getBoundingClientRect(); + let height = el.scrollHeight - el.clientHeight; + + let progressPercentage = 0; + if ( el.scrollTop > 4) { + progressPercentage = (Math.abs(el.scrollTop) / height) * 100; + } + return progressPercentage; + } + function showReadingProgress() { + progress.setAttribute("value", getScrollProgress(elm)); + } + + //scroll event listener + elm.onscroll = showReadingProgress; + }; +}). + +directive('settitle', function() { + return function(scope, elm, attrs) { + const section = scope.$eval(attrs.settitle) + const str = scope.lessonId + + if(!str) + return $("title").html(`Ultimate Go : ${section}`) + + const grp = str.charAt(0).toUpperCase() + str.slice(1); + const title = section ? `${grp} - ${section}` : grp + + $("title").html(`Ultimate Go : ${title}`) + }; +}). +// onpageup executes the given expression when Page Up is released. +directive('onpageup', function() { + return function(scope, elm, attrs) { + elm.attr('tabindex', 0); + elm.keyup(function(evt) { + var key = evt.which || evt.keyCode; + if (key == 33 && !evt.ctrlKey) { + scope.$apply(attrs.onpageup); + evt.preventDefault(); + } + }); + }; +}). + +// onpagedown executes the given expression when Page Down is released. +directive('onpagedown', function() { + return function(scope, elm, attrs) { + elm.attr('tabindex', 0); + elm.keyup(function(evt) { + var key = evt.which || evt.keyCode; + if (key == 34 && !evt.ctrlKey) { + scope.$apply(attrs.onpagedown); + evt.preventDefault(); + } + }); + }; +}). + +// autofocus sets the focus on the given element when the condition is true. +directive('autofocus', function() { + return function(scope, elm, attrs) { + elm.attr('tabindex', 0); + scope.$watch(function() { + return scope.$eval(attrs.autofocus); + }, function(val) { + if (val === true) $(elm).focus(); + }); + }; +}). + +// imports-checkbox activates and deactivates +directive('importsCheckbox', ['editor', + function(editor) { + return function(scope, elm) { + elm.click(function() { + editor.toggleImports(); + scope.$digest(); + }); + scope.editor = editor; + }; + } +]). + +// syntax-checkbox activates and deactivates +directive('syntaxCheckbox', ['editor', + function(editor) { + return function(scope, elm) { + elm.click(function() { + editor.toggleSyntax(); + scope.$digest(); + }); + scope.editor = editor; + }; + } +]). + +// verticalSlide creates a sliding separator between the left and right elements. +// e.g.: +// +//
+// +directive('verticalSlide', ['editor', + function(editor) { + return function(scope, elm, attrs) { + var moveTo = function(x) { + if (x < 0) { + x = 0; + } + if (x > $(window).width()) { + x = $(window).width(); + } + elm.css('left', x); + $(attrs.left).width(x); + $(attrs.right).offset({ + left: x + }); + editor.x = x; + }; + + elm.draggable({ + axis: 'x', + drag: function(event) { + moveTo(event.clientX); + return true; + }, + containment: 'parent', + }); + + if (editor.x !== undefined) { + moveTo(editor.x); + } + }; + } +]). + +// horizontalSlide creates a sliding separator between the top and bottom elements. +// +//
+//
Some content
+directive('horizontalSlide', ['editor', + function(editor) { + return function(scope, elm, attrs) { + var moveTo = function(y) { + var top = $(attrs.top).offset().top; + if (y < top) { + y = top; + } + elm.css('top', y - top); + $(attrs.top).height(y - top); + $(attrs.bottom).offset({ + top: y, + height: 0 + }); + editor.y = y; + }; + elm.draggable({ + axis: 'y', + drag: function(event) { + moveTo(event.clientY); + return true; + }, + containment: 'parent', + }); + + if (editor.y !== undefined) { + moveTo(editor.y); + } + }; + } +]). + +directive('searchButton', ['i18n', function(i18n) { + const speed = 250; + return { + restrict: 'A', + templateUrl: '/tour/grc/static/partials/search-button.html', + link: function(scope, elm, attrs) { + scope.searchMessage = i18n.l('search'); + elm.on('click', function() { + const search = $(attrs.searchButton); + + search.toggle('slide', { + direction: 'right' + }, speed); + + // if fullscreen hide the rest of the content when showing the atoc. + const fullScreen = search.width() === $(window).width(); + if (fullScreen) $('#editor-container')[visible ? 'show' : 'hide'](); + }); + } + }; +}]). + +// side bar with dynamic search result content. +directive('searchContents', function() { + const speed = 250; + return { + restrict: 'A', + templateUrl: '/tour/grc/static/partials/search.html', + link: function(scope, elm) { + scope.hideSearch = function() { + $('.search').toggle('slide', { + direction: 'right' + }, speed); + + $('#editor-container').show(); + }; + } + }; +}). + +directive('tableOfContentsButton', ['i18n', function(i18n) { + var speed = 250; + return { + restrict: 'A', + templateUrl: '/tour/grc/static/partials/toc-button.html', + link: function(scope, elm, attrs) { + scope.tocMessage = i18n.l('toc'); + elm.on('click', function() { + var toc = $(attrs.tableOfContentsButton); + // hide all non active lessons before displaying the toc. + var visible = toc.css('display') != 'none'; + if (!visible) { + toc.find('.toc-lesson:not(.active) .toc-page').hide(); + toc.find('.toc-lesson.active .toc-page').show(); + } + toc.toggle('slide', { + direction: 'right' + }, speed); + + // if fullscreen hide the rest of the content when showing the atoc. + var fullScreen = toc.width() == $(window).width(); + if (fullScreen) $('#editor-container')[visible ? 'show' : 'hide'](); + }); + } + }; +}]). + +// side bar with dynamic table of contents +directive('tableOfContents', ['$routeParams', 'toc', + function($routeParams, toc) { + var speed = 250; + return { + restrict: 'A', + templateUrl: '/tour/grc/static/partials/toc.html', + link: function(scope, elm) { + scope.toc = toc; + scope.params = $routeParams; + + scope.toggleLesson = function(id) { + var l = $('#toc-l-' + id + ' .toc-page'); + l[l.css('display') == 'none' ? 'slideDown' : 'slideUp'](); + }; + + scope.$watch(function() { + return scope.params.lessonId + scope.params.lessonId; + }, function() { + $('.toc-lesson:not(#toc-l-' + scope.params.lessonId + ') .toc-page').slideUp(speed); + }); + + scope.hideTOC = function() { + $('.toc').toggle('slide', { + direction: 'right' + }, speed); + $('#editor-container').show(); + }; + } + }; + } +]). + +directive('feedbackButton', ['i18n', function(i18n) { + return { + restrict: 'A', + templateUrl: '/tour/grc/static/partials/feedback-button.html', + link: function(scope, elm, attrs) { + scope.feedbackMessage = i18n.l('submit-feedback'); + + elm.on('click', function() { + var context = window.location.pathname === '/tour/grc/list' + ? '/tour/grc/list' + : '/tour/grc/' + scope.params.lessonId + '/' + scope.params.pageNumber; + context = window.location.protocol + '//' + window.location.host + context; + var title = i18n.l('issue-title'); + var body = i18n.l('context') + ': '+ context + '\n\n'+ i18n.l('issue-message'); + var url = 'https://' + i18n.l('github-repo') + '/issues/new' + + '?title=' + encodeURIComponent(title) + + '&body=' + encodeURIComponent(body); + window.open(url); + }); + } + }; +}]); diff --git a/_content/tour/grc/static/js/page.js b/_content/tour/grc/static/js/page.js new file mode 100644 index 00000000..82de622a --- /dev/null +++ b/_content/tour/grc/static/js/page.js @@ -0,0 +1,122 @@ +window.transport = "{{.Transport}}()"; // TODO: Code inside double quotes was originally 'naked', giving out warnings; please, *verify* that the addition of the double quotes is valid. +window.socketAddr = "{{.SocketAddr}}"; + +// highlight applies a highlight effect to a specific element determined by +// the selector parameter. +function highlight(selector) { + var speed = 50; + var obj = $(selector).stop(true, true) + for (var i = 0; i < 5; i++) { + obj.addClass("highlight", speed) + obj.delay(speed) + obj.removeClass("highlight", speed) + } +} + +// highlightAndClick highlights an element (as per the highlight function) +// and then simulates a click on the element after a delay. +function highlightAndClick(selector) { + highlight(selector); + setTimeout(function() { + $(selector)[0].click() + }, 750); +} + +// click simulates a click event on the element determined by the selector +// parameter. +function click(selector) { + $(selector)[0].click(); +} + +// setThemeAttribute sets the theme attribute on the document element and +// updates the theme preference cookie. +// Theme to set ('auto', 'dark', or 'light'). +function setThemeAttribute(theme) { + document.documentElement.setAttribute('data-theme', theme); + let domain = ''; + if (location.hostname === 'tour.ardanlabs.com') { + // Include subdomains to apply the setting to pkg.go.dev. + domain = 'domain=.ardanlabs.com;'; + } + + document.cookie = `prefers-color-scheme=${theme};${domain}path=/;max-age=31536000;`; +} + +// setInitialTheme sets the initial theme based on the OS preference when +// the page is loaded. +// If the OS preference cannot be determined, defaults to 'auto'. +function setInitialTheme() { + let initialTheme = 'auto'; + + if (window.matchMedia) { + const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches; + const prefersLightScheme = window.matchMedia("(prefers-color-scheme: light)").matches; + + initialTheme = prefersDarkScheme ? 'dark' : prefersLightScheme ? 'light' : 'auto'; + } + + setThemeAttribute(initialTheme); +} + +// Set the initial theme based on OS preference when the page loads. +setInitialTheme(); + +// toggleTheme toggles the theme attribute between +// 'auto', 'dark', and 'light' based on the current setting. +function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme'); + let nextTheme = currentTheme === 'dark' ? 'light' : currentTheme === 'light' ? 'auto' : 'dark'; + + setThemeAttribute(nextTheme); +} + +// setThemeButtons adds click event listeners to all elements with the class +// 'js-toggleTheme' to toggle the theme when clicked. +function setThemeButtons() { + for (const el of document.querySelectorAll('.js-toggleTheme')) { + el.addEventListener('click', () => { + toggleTheme(); + }); + } +} + +setThemeButtons(); + +// setLanguageSelectorChange initializes the language selector change event. +// It updates the URL and cookie based on the selected language. +function setLanguageSelectorChange() { + const languageSelector = document.getElementById('languageSelector'); + languageSelector.addEventListener('change', (event) => { + const currentUrl = window.location.pathname; + const newLanguage = event.target.value; + const newURL = replaceLanguageInUrl(currentUrl, newLanguage); + + window.location.href = newURL; + document.cookie = `language-preference=${newLanguage};path=/;max-age=31536000;`; + }); +} + +// setLanguageOptionBasedOnUrl sets the selected option of the language selector +// based on the language segment in the current URL path. +function setLanguageOptionBasedOnUrl() { + const languageSelector = document.getElementById('languageSelector'); + const currentUrl = window.location.pathname; + + for (let option of languageSelector.options) { + if (currentUrl.includes(option.value)) { + option.selected = true; + break; + } + } +} + +// replaceLanguageInUrl takes a URL and a new language as arguments, +// and returns a new URL with the language segment replaced by the new language. +function replaceLanguageInUrl(url, newLanguage) { + return url.replace(/(\/tour\/)(eng|rus|grc)(\/)/, `$1${newLanguage}$3`); +} + +window.onload = function() { + setLanguageOptionBasedOnUrl(); + setLanguageSelectorChange(); +}; \ No newline at end of file diff --git a/_content/tour/grc/static/js/services.js b/_content/tour/grc/static/js/services.js new file mode 100644 index 00000000..6615955a --- /dev/null +++ b/_content/tour/grc/static/js/services.js @@ -0,0 +1,242 @@ +/* Copyright 2012 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +'use strict'; + +/* Services */ + +angular.module('tour.services', []). + +// Google Analytics +factory('analytics', ['$window', + function(win) { + var track = win.trackPageview || (function() {}); + return { + trackView: track + }; + } +]). + +// Internationalization +factory('i18n', ['translation', + function(translation) { + return { + l: function(key) { + if (translation[key]) return translation[key]; + return '(no translation for ' + key + ')'; + } + }; + } +]). + +// Running code +factory('run', ['$window', 'editor', + function(win, editor) { + var writeInterceptor = function(writer, done) { + return function(write) { + if (write.Kind == 'stderr') { + var lines = write.Body.split('\n'); + for (var i in lines) { + var match = lines[i].match(/.*\.go:([0-9]+): ([^\n]*)/); + if (match !== null) { + editor.highlight(match[1], match[2]); + } + } + } + writer(write); + if (write.Kind == 'end') done(); + }; + }; + return function(code, output, options, done) { + // We want to build tour snippets in module mode, so append + // a default go.mod file when it is not already included in + // the txtar archive. + // + // The exercises use github.com/ardanlabs/gotour/external/tour/grc/{pic,reader,tree,wc} + // packages, so include the github.com/ardanlabs/gotour/external/toureng module in the + // build list. + const hasGoMod = code.indexOf('\n-- go.mod --\n') !== -1 || code.startsWith('-- go.mod --\n'); + if (!hasGoMod) { + code += '\n' + + '-- go.mod --\n' + + 'module example\n\n' + + 'go 1.21.0\n' + + '-- go.sum --\n'; + } + + // PlaygroundOutput is defined in playground.js which is prepended + // to the generated script.js in gotour/tour.go. + // The next line removes the jshint warning. + // global PlaygroundOutput + return win.transport.Run(code, writeInterceptor(PlaygroundOutput(output), done), options); + }; + } +]). + +// Formatting code +factory('fmt', ['$http', + function($http) { + return function(body, imports) { + var params = $.param({ + 'body': body, + 'imports': imports, + }); + var headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + }; + return $http.post('/_/fmt', params, { + headers: headers + }); + }; + } +]). + +// Local storage, persistent to page refreshing. +factory('storage', ['$window', + function(win) { + try { + // This will raise an exception if cookies are disabled. + win.localStorage = win.localStorage; + return { + get: function(key) { + return win.localStorage.getItem(key); + }, + set: function(key, val) { + win.localStorage.setItem(key, val); + } + }; + } catch (e) { + return { + get: function() { + return null; + }, + set: function() {} + }; + } + } +]). + +// Editor context service, kept through the whole app. +factory('editor', ['$window', 'storage', + function(win, storage) { + var ctx = { + imports: storage.get('imports') === 'true', + toggleImports: function() { + ctx.imports = !ctx.imports; + storage.set('imports', ctx.imports); + }, + syntax: storage.get('syntax') === 'true', + toggleSyntax: function() { + ctx.syntax = !ctx.syntax; + storage.set('syntax', ctx.syntax); + ctx.paint(); + }, + paint: function() { + var mode = ctx.syntax && 'text/x-go' || 'text/x-go-comment'; + // Wait for codemirror to start. + var set = function() { + if ($('.CodeMirror').length > 0) { + var cm = $('.CodeMirror')[0].CodeMirror; + if (cm.getOption('mode') == mode) { + cm.refresh(); + return; + } + cm.setOption('mode', mode); + } + win.setTimeout(set, 10); + }; + set(); + }, + highlight: function(line, message) { + $('.CodeMirror-code > div:nth-child(' + line + ')') + .addClass('line-error').attr('title', message); + }, + onChange: function() { + $('.line-error').removeClass('line-error').attr('title', null); + } + }; + // Set in the window so the onChange function in the codemirror config + // can call it. + win.codeChanged = ctx.onChange; + return ctx; + } +]). + +// Table of contents management and navigation +factory('toc', ['$http', '$q', '$log', 'tableOfContents', 'storage', + function($http, $q, $log, tableOfContents, storage) { + var modules = tableOfContents; + + var lessons = {}; + + var prevLesson = function(id) { + var mod = lessons[id].module; + var idx = mod.lessons.indexOf(id); + if (idx < 0) return ''; + if (idx > 0) return mod.lessons[idx - 1]; + + idx = modules.indexOf(mod); + if (idx <= 0) return ''; + mod = modules[idx - 1]; + return mod.lessons[mod.lessons.length - 1]; + }; + + var nextLesson = function(id) { + var mod = lessons[id].module; + var idx = mod.lessons.indexOf(id); + if (idx < 0) return ''; + if (idx + 1 < mod.lessons.length) return mod.lessons[idx + 1]; + + idx = modules.indexOf(mod); + if (idx < 0 || modules.length <= idx + 1) return ''; + mod = modules[idx + 1]; + return mod.lessons[0]; + }; + + $http.get('/tour/grc/lesson/').then( + function(data) { + lessons = data.data; + for (var m = 0; m < modules.length; m++) { + var module = modules[m]; + module.lesson = {}; + for (var l = 0; l < modules[m].lessons.length; l++) { + var lessonName = module.lessons[l]; + var lesson = lessons[lessonName]; + lesson.module = module; + module.lesson[lessonName] = lesson; + + // replace file contents with locally stored copies. + for (var p = 0; p < lesson.Pages.length; p++) { + var page = lesson.Pages[p]; + for (var f = 0; f < page.Files.length; f++) { + page.Files[f].OrigContent = page.Files[f].Content; + var val = storage.get(page.Files[f].Hash); + if (val !== null) { + page.Files[f].Content = val; + } + } + } + } + } + moduleQ.resolve(modules); + lessonQ.resolve(lessons); + }, + function(error) { + $log.error('error loading lessons : ', error); + moduleQ.reject(error); + lessonQ.reject(error); + } + ); + + var moduleQ = $q.defer(); + var lessonQ = $q.defer(); + + return { + modules: moduleQ.promise, + lessons: lessonQ.promise, + prevLesson: prevLesson, + nextLesson: nextLesson + }; + } +]); \ No newline at end of file diff --git a/_content/tour/grc/static/js/values.js b/_content/tour/grc/static/js/values.js new file mode 100644 index 00000000..35bd7bb5 --- /dev/null +++ b/_content/tour/grc/static/js/values.js @@ -0,0 +1,164 @@ +/* Copyright 2012 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +'use strict'; + +angular.module('tour.values', []). + +// List of modules with description and lessons in it. +value('tableOfContents', [{ + 'id': 'welcome', + 'title': '', + 'description': ` +

Αυτό είναι υλικό για κάθε developer ενδιάμεσου επιπέδου με κάποια εμπειρία σε άλλες γλώσσες προγραμματισμού που θέλει να μάθει την Go. Πιστεύουμε ότι αυτό το υλικό είναι τέλειο για οποιονδήποτε θέλει να αποκτήσει ένα προβάδισμα στην κατανόηση της Go ή που θέλει μια βαθύτερη κατανόηση της γλώσσας και των εσωτερικών της μηχανισμών.

+

Την πρώτη φορά που θα τρέξετε ένα παράδειγμα μπορεί να πάρει κάποιο χρόνο για να ολοκληρωθεί. Αυτό συμβαίνει γιατί η υπηρεσία της επισκόπησης ενδέχεται να μην λειτουργεί ήδη. Παρακαλούμε, δώστε της λίγο χρόνο προκειμένου να ολοκληρωθεί.

+ `, + 'lessons': [ + 'welcome', + ] +}, { + 'id': 'language-mechanics', + 'title': 'Μηχανισμοί της Γλώσσας', + 'description': ` +

Αυτό το υλικό καλύπτει ολόκληρο το συντακτικό της γλώσσας, τα ιδιώματα, την υλοποίηση και τις προδιαγραφές της γλώσσας. Όταν ολοκληρώσετε το υλικό θα κατανοείτε τους μηχανισμούς της γλώσσας και τις μηχανικές συμπάθειες που έχει η γλώσσα τόσο για το υλικό όσο και για το λειτουργικό σύστημα.

+ `, + 'lessons': [ + 'variables', + 'struct-types', + 'pointers', + 'constants', + 'functions', + 'arrays', + 'slices', + 'maps', + 'methods', + 'interfaces', + 'embedding', + 'exporting', + ] +}, { + 'id': 'composition-interfaces', + 'title': 'Σύνθεση και Διεπαφές', + 'description': ` +

Αυτό το υλικό καλύπτει τα πρακτικά πράγματα που χρειάζεται να ξέρετε σχετικά με την σύνθεση και τις διεπαφές.

+ `, + 'lessons': [ + 'composition-grouping', + 'composition-assertions', + 'composition-pollution', + 'composition-mocking', + 'composition-decoupling', + 'error-handling', + ] +},{ + 'id': 'concurrency', + 'title': 'Ταυτόχρονη Εκτέλεση', + 'description': ` +

Αυτό το υλικό καλύπτει όλες τις πτυχές της γλώσσας που αφορούν την ταυτόχρονη εκτέλεση. Όταν ολοκληρώσετε αυτό το υλικό θα κατανοείτε τους μηχανισμούς της παράλληλης εκτέλεσης της γλώσσας και τις μηχανικές συμπάθειες που έχει η γλώσσα τόσο για το υλικό όσο και για το λειτουργικό σύστημα στα ζητήματα που αφορούν την ταυτόχρονη εκτέλεση.

+ `, + 'lessons': [ + 'goroutines', + 'data_race', + 'context', + 'channels', + ] +},{ + 'id': 'generics', + 'title': 'Generics', + 'description': ` +

Αυτό το υλικό καλύπτει όλες της πτυχές της γλώσσας που σχετίζονται με τα generics. Τα generics αφορούν την δυνατότητα συγγραφής πραγματικών πολύμορφων συναρτήσεων με την υποστήριξη παραμέτρων τύπων.

+ `, + 'lessons': [ + 'generics-basics', + 'generics-underlying-types', + 'generics-struct-types', + 'generics-behavior-constraints', + 'generics-type-constraints', + 'generics-multi-type-params', + 'generics-slice-constraints', + 'generics-channels', + 'generics-hash-table', + ] +},{ + 'id': 'algorithms', + 'title': 'Αλγόριθμοι', + 'description': ` +

Αυτό το υλικό παρέχει κώδικα της Go που υλοποιεί ένα σύνολο κοινών και διασκεδαστικών αλγορίθμων.

+ `, + 'lessons': [ + 'algorithms-bits-seven', + 'algorithms-strings', + 'algorithms-numbers', + 'algorithms-slices', + 'algorithms-sorting', + 'algorithms-data', + 'algorithms-searches', + 'algorithms-fun', + ] +}]). +// translation +value('translation', { + 'off': 'off', + 'on': 'on', + 'syntax': 'Υπογράμμιση Σύνταξης', + 'lineno': 'Αριθμοί Γραμμής', + 'reset': 'Επαναφορά Slide', + 'format': 'Μορφοποίηση Πηγαίου Κώδικα', + 'kill': 'Διακοπή Προγράμματος', + 'run': 'Εκτέλεση', + 'compile': 'Μεταγλώττιση και Εκτέλεση', + 'more': 'Επιλογές', + 'toc': 'Πίνακας Περιεχομένων', + 'prev': 'Προηγούμενο', + 'next': 'Επόμενο', + 'waiting': 'Αναμένοντας τον απομακρυσμένο server...', + 'errcomm': 'Σφάλμα επικοινωνίας με τον απομακρυσμένο server.', + 'submit-feedback': 'Υποβολή Σχολίων σχετικά με την σελίδα', + 'search': 'Αναζήτηση για περιεχόμενο', + + // GitHub issue template: update repo and messaging when translating. + 'github-repo': 'github.com/ardanlabs/gotour', + 'issue-title': 'επισκόπηση: [ΑΝΤΙΚΑΤΑΣΤΕIΣΤΕ ΜΕ ΣΥΝΤΟΜΗ ΠΕΡΙΓΡΑΦΗ]', + 'issue-message': 'Αλλάξτε τον παραπάνω τίτλο για να περιγράψετε το πρόβλημα που αντιμετωπίζετε και προσθέστε το σχόλιο σας εδώ, μαζί με τον όποιο κώδικα αν αυτό είναι αναγκαίο', + 'context': 'Πλαίσιο', +}). + +// Config for codemirror plugin +value('ui.config', { + codemirror: { + mode: 'text/x-go', + matchBrackets: true, + lineNumbers: true, + autofocus: true, + indentWithTabs: true, + indentUnit: 4, + tabSize: 4, + lineWrapping: true, + extraKeys: { + 'Shift-Enter': function() { + $('#run').click(); + }, + 'Ctrl-Enter': function() { + $('#format').click(); + }, + 'PageDown': function() { + return false; + }, + 'PageUp': function() { + return false; + }, + }, + // TODO: is there a better way to do this? + // AngularJS values can't depend on factories. + onChange: function() { + if (window.codeChanged !== null) window.codeChanged(); + } + } +}). + +// mapping from the old paths (#42) to the new organization. +// The values have been generated with the map.sh script in the tools directory. +value('mapping', { + '#1': '/variables/1', // Variables +}); diff --git a/_content/tour/grc/static/lib/angular-ui.min.js b/_content/tour/grc/static/lib/angular-ui.min.js new file mode 100644 index 00000000..6b031abf --- /dev/null +++ b/_content/tour/grc/static/lib/angular-ui.min.js @@ -0,0 +1,7 @@ +/** + * AngularUI - The companion suite for AngularJS + * @version v0.4.0 - 2013-02-15 + * @link http://angular-ui.github.com + * @license MIT License, http://www.opensource.org/licenses/MIT + */ +angular.module("ui.config",[]).value("ui.config",{}),angular.module("ui.filters",["ui.config"]),angular.module("ui.directives",["ui.config"]),angular.module("ui",["ui.filters","ui.directives","ui.config"]),angular.module("ui.directives").directive("uiAnimate",["ui.config","$timeout",function(e,t){var n={};return angular.isString(e.animate)?n["class"]=e.animate:e.animate&&(n=e.animate),{restrict:"A",link:function(e,r,i){var s={};i.uiAnimate&&(s=e.$eval(i.uiAnimate),angular.isString(s)&&(s={"class":s})),s=angular.extend({"class":"ui-animate"},n,s),r.addClass(s["class"]),t(function(){r.removeClass(s["class"])},20,!1)}}}]),angular.module("ui.directives").directive("uiCalendar",["ui.config","$parse",function(e,t){return e.uiCalendar=e.uiCalendar||{},{require:"ngModel",restrict:"A",link:function(t,n,r,i){function a(){t.calendar=n.html("");var i=t.calendar.fullCalendar("getView");i&&(i=i.name);var o,u={defaultView:i,eventSources:s};r.uiCalendar?o=t.$eval(r.uiCalendar):o={},angular.extend(u,e.uiCalendar,o),t.calendar.fullCalendar(u)}var s=t.$eval(r.ngModel),o=0,u=function(){var e=t.$eval(r.equalsTracker);return o=0,angular.forEach(s,function(e,t){angular.isArray(e)&&(o+=e.length)}),angular.isNumber(e)?o+s.length+e:o+s.length};a(),t.$watch(u,function(e,t){a()})}}}]),angular.module("ui.directives").directive("uiCodemirror",["ui.config","$timeout",function(e,t){"use strict";var n=["cursorActivity","viewportChange","gutterClick","focus","blur","scroll","update"];return{restrict:"A",require:"ngModel",link:function(r,i,s,o){var u,a,f,l,c;if(i[0].type!=="textarea")throw new Error("uiCodemirror3 can only be applied to a textarea element");u=e.codemirror||{},a=angular.extend({},u,r.$eval(s.uiCodemirror)),f=function(e){return function(t,n){var i=t.getValue();i!==o.$viewValue&&(o.$setViewValue(i),r.$apply()),typeof e=="function"&&e(t,n)}},l=function(){c=CodeMirror.fromTextArea(i[0],a),c.on("change",f(a.onChange));for(var e=0,u=n.length,l;e0),r.toggleClass(o.neg,n<0),r.toggleClass(o.zero,n===0),e===""?r.text(""):r.text(t(n,o.symbol)),!0},s.$render=function(){a=s.$viewValue,r.val(a),u(a)}}}}]),angular.module("ui.directives").directive("uiDate",["ui.config",function(e){"use strict";var t;return t={},angular.isObject(e.date)&&angular.extend(t,e.date),{require:"?ngModel",link:function(t,n,r,i){var s=function(){return angular.extend({},e.date,t.$eval(r.uiDate))},o=function(){var e=s();if(i){var r=function(){t.$apply(function(){var e=n.datepicker("getDate");n.datepicker("setDate",n.val()),i.$setViewValue(e),n.blur()})};if(e.onSelect){var o=e.onSelect;e.onSelect=function(e,n){r(),t.$apply(function(){o(e,n)})}}else e.onSelect=r;n.bind("change",r),i.$render=function(){var e=i.$viewValue;if(angular.isDefined(e)&&e!==null&&!angular.isDate(e))throw new Error("ng-Model value must be a Date object - currently it is a "+typeof e+" - use ui-date-format to convert it from a string");n.datepicker("setDate",e)}}n.datepicker("destroy"),n.datepicker(e),i&&i.$render()};t.$watch(s,o,!0)}}}]).directive("uiDateFormat",["ui.config",function(e){var t={require:"ngModel",link:function(t,n,r,i){var s=r.uiDateFormat||e.dateFormat;s?(i.$formatters.push(function(e){if(angular.isString(e))return $.datepicker.parseDate(s,e)}),i.$parsers.push(function(e){if(e)return $.datepicker.formatDate(s,e)})):(i.$formatters.push(function(e){if(angular.isString(e))return new Date(e)}),i.$parsers.push(function(e){if(e)return e.toISOString()}))}};return t}]),angular.module("ui.directives").directive("uiEvent",["$parse",function(e){return function(t,n,r){var i=t.$eval(r.uiEvent);angular.forEach(i,function(r,i){var s=e(r);n.bind(i,function(e){var n=Array.prototype.slice.call(arguments);n=n.splice(1),t.$apply(function(){s(t,{$event:e,$params:n})})})})}}]),angular.module("ui.directives").directive("uiIf",[function(){return{transclude:"element",priority:1e3,terminal:!0,restrict:"A",compile:function(e,t,n){return function(e,t,r){var i,s;e.$watch(r.uiIf,function(r){i&&(i.remove(),i=undefined),s&&(s.$destroy(),s=undefined),r&&(s=e.$new(),n(s,function(e){i=e,t.after(e)}))})}}}}]),angular.module("ui.directives").directive("uiJq",["ui.config","$timeout",function(t,n){return{restrict:"A",compile:function(r,i){if(!angular.isFunction(r[i.uiJq]))throw new Error('ui-jq: The "'+i.uiJq+'" function does not exist');var s=t.jq&&t.jq[i.uiJq];return function(t,r,i){function u(){n(function(){r[i.uiJq].apply(r,o)},0,!1)}var o=[];i.uiOptions?(o=t.$eval("["+i.uiOptions+"]"),angular.isObject(s)&&angular.isObject(o[0])&&(o[0]=angular.extend({},s,o[0]))):s&&(o=[s]),i.ngModel&&r.is("select,input,textarea")&&r.on("change",function(){r.trigger("input")}),i.uiRefresh&&t.$watch(i.uiRefresh,function(e){u()}),u()}}}}]),angular.module("ui.directives").factory("keypressHelper",["$parse",function(t){var n={8:"backspace",9:"tab",13:"enter",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"delete"},r=function(e){return e.charAt(0).toUpperCase()+e.slice(1)};return function(e,i,s,o){var u,a=[];u=i.$eval(o["ui"+r(e)]),angular.forEach(u,function(e,n){var r,i;i=t(e),angular.forEach(n.split(" "),function(e){r={expression:i,keys:{}},angular.forEach(e.split("-"),function(e){r.keys[e]=!0}),a.push(r)})}),s.bind(e,function(t){var r=t.metaKey||t.altKey,s=t.ctrlKey,o=t.shiftKey,u=t.keyCode;e==="keypress"&&!o&&u>=97&&u<=122&&(u-=32),angular.forEach(a,function(e){var u=e.keys[n[t.keyCode]]||e.keys[t.keyCode.toString()]||!1,a=e.keys.alt||!1,f=e.keys.ctrl||!1,l=e.keys.shift||!1;u&&a==r&&f==s&&l==o&&i.$apply(function(){e.expression(i,{$event:t})})})})}}]),angular.module("ui.directives").directive("uiKeydown",["keypressHelper",function(e){return{link:function(t,n,r){e("keydown",t,n,r)}}}]),angular.module("ui.directives").directive("uiKeypress",["keypressHelper",function(e){return{link:function(t,n,r){e("keypress",t,n,r)}}}]),angular.module("ui.directives").directive("uiKeyup",["keypressHelper",function(e){return{link:function(t,n,r){e("keyup",t,n,r)}}}]),function(){function t(e,t,n,r){angular.forEach(t.split(" "),function(t){var i={type:"map-"+t};google.maps.event.addListener(n,t,function(t){r.triggerHandler(angular.extend({},i,t)),e.$$phase||e.$apply()})})}function n(n,r){e.directive(n,[function(){return{restrict:"A",link:function(e,i,s){e.$watch(s[n],function(n){t(e,r,n,i)})}}}])}var e=angular.module("ui.directives");e.directive("uiMap",["ui.config","$parse",function(e,n){var r="bounds_changed center_changed click dblclick drag dragend dragstart heading_changed idle maptypeid_changed mousemove mouseout mouseover projection_changed resize rightclick tilesloaded tilt_changed zoom_changed",i=e.map||{};return{restrict:"A",link:function(e,s,o){var u=angular.extend({},i,e.$eval(o.uiOptions)),a=new google.maps.Map(s[0],u),f=n(o.uiMap);f.assign(e,a),t(e,r,a,s)}}}]),e.directive("uiMapInfoWindow",["ui.config","$parse","$compile",function(e,n,r){var i="closeclick content_change domready position_changed zindex_changed",s=e.mapInfoWindow||{};return{link:function(e,o,u){var a=angular.extend({},s,e.$eval(u.uiOptions));a.content=o[0];var f=n(u.uiMapInfoWindow),l=f(e);l||(l=new google.maps.InfoWindow(a),f.assign(e,l)),t(e,i,l,o),o.replaceWith("
");var c=l.open;l.open=function(n,i,s,u,a,f){r(o.contents())(e),c.call(l,n,i,s,u,a,f)}}}}]),n("uiMapMarker","animation_changed click clickable_changed cursor_changed dblclick drag dragend draggable_changed dragstart flat_changed icon_changed mousedown mouseout mouseover mouseup position_changed rightclick shadow_changed shape_changed title_changed visible_changed zindex_changed"),n("uiMapPolyline","click dblclick mousedown mousemove mouseout mouseover mouseup rightclick"),n("uiMapPolygon","click dblclick mousedown mousemove mouseout mouseover mouseup rightclick"),n("uiMapRectangle","bounds_changed click dblclick mousedown mousemove mouseout mouseover mouseup rightclick"),n("uiMapCircle","center_changed click dblclick mousedown mousemove mouseout mouseover mouseup radius_changed rightclick"),n("uiMapGroundOverlay","click dblclick")}(),angular.module("ui.directives").directive("uiMask",[function(){return{require:"ngModel",link:function(e,t,n,r){r.$render=function(){var i=r.$viewValue||"";t.val(i),t.mask(e.$eval(n.uiMask))},r.$parsers.push(function(e){var n=t.isMaskValid()||angular.isUndefined(t.isMaskValid())&&t.val().length>0;return r.$setValidity("mask",n),n?e:undefined}),t.bind("keyup",function(){e.$apply(function(){r.$setViewValue(t.mask())})})}}}]),angular.module("ui.directives").directive("uiReset",["ui.config",function(e){var t=null;return e.reset!==undefined&&(t=e.reset),{require:"ngModel",link:function(e,n,r,i){var s;s=angular.element(''),n.wrap('').after(s),s.bind("click",function(n){n.preventDefault(),e.$apply(function(){r.uiReset?i.$setViewValue(e.$eval(r.uiReset)):i.$setViewValue(t),i.$render()})})}}}]),angular.module("ui.directives").directive("uiRoute",["$location","$parse",function(e,t){return{restrict:"AC",compile:function(n,r){var i;if(r.uiRoute)i="uiRoute";else if(r.ngHref)i="ngHref";else{if(!r.href)throw new Error("uiRoute missing a route or href property on "+n[0]);i="href"}return function(n,r,s){function a(t){(hash=t.indexOf("#"))>-1&&(t=t.substr(hash+1)),u=function(){o(n,e.path().indexOf(t)>-1)},u()}function f(t){(hash=t.indexOf("#"))>-1&&(t=t.substr(hash+1)),u=function(){var i=new RegExp("^"+t+"$",["i"]);o(n,i.test(e.path()))},u()}var o=t(s.ngModel||s.routeModel||"$uiRoute").assign,u=angular.noop;switch(i){case"uiRoute":s.uiRoute?f(s.uiRoute):s.$observe("uiRoute",f);break;case"ngHref":s.ngHref?a(s.ngHref):s.$observe("ngHref",a);break;case"href":a(s.href)}n.$on("$routeChangeSuccess",function(){u()})}}}}]),angular.module("ui.directives").directive("uiScrollfix",["$window",function(e){"use strict";return{link:function(t,n,r){var i=n.offset().top;r.uiScrollfix?r.uiScrollfix.charAt(0)==="-"?r.uiScrollfix=i-r.uiScrollfix.substr(1):r.uiScrollfix.charAt(0)==="+"&&(r.uiScrollfix=i+parseFloat(r.uiScrollfix.substr(1))):r.uiScrollfix=i,angular.element(e).on("scroll.ui-scrollfix",function(){var t;if(angular.isDefined(e.pageYOffset))t=e.pageYOffset;else{var i=document.compatMode&&document.compatMode!=="BackCompat"?document.documentElement:document.body;t=i.scrollTop}!n.hasClass("ui-scrollfix")&&t>r.uiScrollfix?n.addClass("ui-scrollfix"):n.hasClass("ui-scrollfix")&&t'+t+""):e.replace(new RegExp(t,"gi"),'$&')):e}}),angular.module("ui.filters").filter("inflector",function(){function e(e){return e.replace(/^([a-z])|\s+([a-z])/g,function(e){return e.toUpperCase()})}function t(e,t){return e.replace(/[A-Z]/g,function(e){return t+e})}var n={humanize:function(n){return e(t(n," ").split("_").join(" "))},underscore:function(e){return e.substr(0,1).toLowerCase()+t(e.substr(1),"_").toLowerCase().split(" ").join("_")},variable:function(t){return t=t.substr(0,1).toLowerCase()+e(t.split("_").join(" ")).substr(1).split(" ").join(""),t}};return function(e,t,r){return t!==!1&&angular.isString(e)?(t=t||"humanize",n[t](e)):e}}),angular.module("ui.filters").filter("unique",function(){return function(e,t){if(t===!1)return e;if((t||angular.isUndefined(t))&&angular.isArray(e)){var n={},r=[],i=function(e){return angular.isObject(e)&&angular.isString(t)?e[t]:e};angular.forEach(e,function(e){var t,n=!1;for(var s=0;s=0&&b.splice(c,1);return a}function V(b,a){if(pa(b)|| +b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(E(b))for(var c=a.length=0;c2?ha.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=q:pa(a)?c="$WINDOW":a&&Y===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b,ic,a?" ":null)}function pb(b){return B(b)?JSON.parse(b):b}function Va(b){b&&b.length!== +0?(b=A(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function qa(b){b=u(b).clone();try{b.html("")}catch(a){}var c=u("
").append(b).html();try{return b[0].nodeType===3?A(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+A(b)})}catch(d){return A(c)}}function Wa(b){var a={},c,d;n((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=y(c[1])?decodeURIComponent(c[1]):!0)});return a}function qb(b){var a=[];n(b,function(b, +d){a.push(Xa(d,!0)+(b===!0?"":"="+Xa(b,!0)))});return a.length?a.join("&"):""}function Ya(b){return Xa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Xa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,h=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;n(h,function(a){h[a]=!0;c(Y.getElementById(a)); +a=a.replace(":","\\:");b.querySelectorAll&&(n(b.querySelectorAll("."+a),c),n(b.querySelectorAll("."+a+"\\:"),c),n(b.querySelectorAll("["+a+"]"),c))});n(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):n(a.attributes,function(b){if(!e&&h[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function rb(b,a){var c=function(){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=sb(a);c.invoke(["$rootScope","$rootElement", +"$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(N&&!d.test(N.name))return c();N.name=N.name.replace(d,"");Za.resumeBootstrap=function(b){n(b,function(b){a.push(b)});c()}}function $a(b,a){a=a||"_";return b.replace(kc,function(b,d){return(d?a:"")+b.toLowerCase()})}function ab(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&E(b)&&(b=b[b.length-1]);ab(H(b),a,"not a function, got "+ +(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return k}}if(!e)throw Error("No module: "+d);var b=[],c=[],i=a("$injector","invoke"),k={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"), +factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:i,run:function(a){c.push(a);return this}};g&&i(g);return k})}})}function tb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(nc,"Moz$1")}function bb(b,a){function c(){var e;for(var b=[this],c=a,h,f,j, +i,k,m;b.length;){h=b.shift();f=0;for(j=h.length;f 
"+b;a.removeChild(a.firstChild); +cb(this,a.childNodes);this.remove()}else cb(this,b)}function db(b){return b.cloneNode(!0)}function sa(b){ub(b);for(var a=0,b=b.childNodes||[];a-1}function xb(b,a){a&&n(a.split(" "),function(a){b.className=O((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+O(a)+" "," "))})}function yb(b,a){a&&n(a.split(" "),function(a){if(!Da(b,a))b.className=O(b.className+ +" "+O(a))})}function cb(b,a){if(a)for(var a=!a.nodeName&&y(a.length)&&!pa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(j.cookie!==$){$=j.cookie;d=$.split("; ");r={};for(f=0;f0&&(r[unescape(e.substring(0,i))]=unescape(e.substring(i+1)))}return r}};f.defer=function(a,b){var c;o++;c=m(function(){delete t[c];e(a)},b||0);t[c]=!0;return c}; +f.defer.cancel=function(a){return t[a]?(delete t[a],l(a),e(w),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b,d){function e(a){if(a!=m){if(l){if(l==a)l=a.n}else l=a;g(a.n,a.p);g(a,m);m=a;m.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var h=0,f=v({},d,{id:b}),j={},i=d&&d.capacity||Number.MAX_VALUE,k={},m=null,l=null;return a[b]= +{put:function(a,b){var c=k[a]||(k[a]={key:a});e(c);x(b)||(a in j||h++,j[a]=b,h>i&&this.remove(l.key))},get:function(a){var b=k[a];if(b)return e(b),j[a]},remove:function(a){var b=k[a];if(b){if(b==m)m=b.p;if(b==l)l=b.n;g(b.n,b.p);delete k[a];delete j[a];h--}},removeAll:function(){j={};h=0;k={};m=l=null},destroy:function(){k=f=j=null;delete a[b]},info:function(){return v({},f,{size:h})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}} +function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Db(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",h=/^\s*(https?|ftp|mailto|file):/;this.directive=function j(d,e){B(d)?(ab(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:I(g)}; +else if(!g.compile&&g.link)g.compile=I(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,ob(j));return this};this.urlSanitizationWhitelist=function(a){return y(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,i,k,m,l,t,o,p,s){function P(a,b,c){a instanceof u||(a=u(a)); +n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=u(b).wrap("").parent()[0])});var d=z(a,b,a,c);return function(b,c){ab(b,"scope");for(var e=c?va.clone.call(a):a,g=0,i=e.length;gr.priority)break;if(X=r.scope)ua("isolated scope",J,r,D),L(X)&&(C(D,"ng-isolate-scope"),J=r),C(D,"ng-scope"),p=p||r;F=r.name;if(X=r.controller)y=y||{},ua("'"+F+"' controller",y[F],r,D),y[F]=r;if(X=r.transclude)ua("transclusion",ka,r,D),ka=r,l=r.priority,X=="element"?(T=u(b),D=c.$$element=u(Y.createComment(" "+F+": "+c[F]+" ")),b=D[0],w(e,u(T[0]),b),S=P(T, +d,l)):(T=u(db(b)).contents(),D.html(""),S=P(T,d));if(X=r.template)if(ua("template",z,r,D),z=r,X=Fb(X),r.replace){T=u("
"+O(X)+"
").contents();b=T[0];if(T.length!=1||b.nodeType!==1)throw Error(g+X);w(e,D,b);F={$attr:{}};a=a.concat(W(b,a.splice(v+1,a.length-(v+1)),F));$(c,F);A=a.length}else D.html(X);if(r.templateUrl)ua("template",z,r,D),z=r,j=Q(a.splice(v,a.length-v),j,D,c,e,r.replace,S),A=a.length;else if(r.compile)try{x=r.compile(D,c,S),H(x)?i(null,x):x&&i(x.pre,x.post)}catch(G){k(G,qa(D))}if(r.terminal)j.terminal= +!0,l=Math.max(l,r.priority)}j.scope=p&&p.scope;j.transclude=ka&&S;return j}function r(d,e,i,g){var h=!1;if(a.hasOwnProperty(e))for(var l,e=b.get(e+c),o=0,m=e.length;ol.priority)&&l.restrict.indexOf(i)!=-1)d.push(l),h=!0}catch(t){k(t)}return h}function $(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,i){i=="class"?(C(e,b),a["class"]=(a["class"]?a["class"]+" ": +"")+b):i=="style"?e.attr("style",e.attr("style")+";"+b):i.charAt(0)!="$"&&!a.hasOwnProperty(i)&&(a[i]=b,d[i]=c[i])})}function Q(a,b,c,d,e,i,h){var j=[],k,o,t=c[0],s=a.shift(),p=v({},s,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");m.get(s.templateUrl,{cache:l}).success(function(l){var m,s,l=Fb(l);if(i){s=u("
"+O(l)+"
").contents();m=s[0];if(s.length!=1||m.nodeType!==1)throw Error(g+l);l={$attr:{}};w(e,c,m);W(m,a,l);$(d,l)}else m=t,c.html(l);a.unshift(p);k=J(a,m, +d,h);for(o=z(c[0].childNodes,h);j.length;){var ia=j.pop(),l=j.pop();s=j.pop();var r=j.pop(),D=m;s!==t&&(D=db(m),w(l,u(s),D));k(function(){b(o,r,D,e,ia)},r,D,e,ia)}j=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,i){j?(j.push(c),j.push(d),j.push(e),j.push(i)):k(function(){b(o,c,d,e,i)},c,d,e,i)}}function F(a,b){return b.priority-a.priority}function ua(a,b,c,d){if(b)throw Error("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+ +qa(d));}function y(a,b){var c=i(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);C(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function S(a,b,c,d){var e=i(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=i(c[d],!0));c[d]=q;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function w(a,b, +c){var d=b[0],e=d.parentNode,i,g;if(a){i=0;for(g=a.length;i0){var e=Q[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b, +c,d,f){return(b=h(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),Q.shift(),b):!1}function j(a){f(a)||e("is unexpected, expecting ["+a+"]",h())}function i(a,b){return function(c,d){return a(c,d,b)}}function k(a,b,c){return function(d,e){return b(d,e,a,c)}}function m(){for(var a=[];;)if(Q.length>0&&!h("}",")",";","]")&&a.push(x()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,s());return a}function n(){for(var a=C(),b;b=f("*","/","%");)a=k(a, +b.fn,C());return a}function C(){var a;return f("+")?z():(a=f("-"))?k(r,a.fn,C()):(a=f("!"))?i(a.fn,C()):z()}function z(){var a;if(f("("))a=x(),j(")");else if(f("["))a=W();else if(f("{"))a=J();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=S(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");return a}function W(){var a=[];if(g().text!="]"){do a.push(F());while(f(","))}j("]");return function(b,c){for(var d=[],e=0;e< +a.length;e++)d.push(a[e](b,c));return d}}function J(){var a=[];if(g().text!="}"){do{var b=f(),b=b.string||b.text;j(":");var c=F();a.push({key:b,value:c})}while(f(","))}j("}");return function(b,c){for(var d={},e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function hb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,h=0;h7),hasEvent:function(c){if(c=="input"&&Z==9)return!1;if(x(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=I(N)}function Ob(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b){e=b.indexOf(":");c=A(O(b.substr(0,e)));d=O(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Pb(b){var a= +L(b)?b:q;return function(c){a||(a=Ob(b));return c?a[A(c)]||null:a}}function Qb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){B(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=pb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&xa.apply(a)!=="[object File]"?da(a):a}],headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"}, +post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,j,i,k){function m(a){function c(a){var b=v({},a,{data:Qb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:i.reject(b)}a.method=ma(a.method);var e=a.transformRequest||d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=v({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]}, +g.common,g[A(a.method)],a.headers),e=Qb(a.data,Pb(g),e),j;x(a.data)&&delete g["Content-Type"];j=l(a,e,g);j=j.then(c,c);n(p,function(a){j=a(j)});j.success=function(b){j.then(function(c){b(c.data,c.status,c.headers,a)});return j};j.error=function(b){j.then(null,function(c){b(c.data,c.status,c.headers,a)});return j};return j}function l(b,c,d){function e(a,b,c){n&&(200<=a&&a<300?n.put(q,[a,b,Ob(c)]):n.remove(q));f(b,a,c);j.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a, +status:c,headers:Pb(d),config:b})}function h(){var a=Aa(m.pendingRequests,b);a!==-1&&m.pendingRequests.splice(a,1)}var k=i.defer(),l=k.promise,n,p,q=t(b.url,b.params);m.pendingRequests.push(b);l.then(h,h);b.cache&&b.method=="GET"&&(n=L(b.cache)?b.cache:o);if(n)if(p=n.get(q))if(p.then)return p.then(h,h),p;else E(p)?f(p[1],p[0],V(p[2])):f(p,200,{});else n.put(q,l);p||a(b.method,q,c,e,d,b.timeout,b.withCredentials);return l}function t(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==q||(L(a)&& +(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var o=c("$http"),p=[];n(e,function(a){p.push(B(a)?k.get(a):k.invoke(a))});m.pendingRequests=[];(function(a){n(arguments,function(a){m[a]=function(b,c){return m(v(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){n(arguments,function(a){m[a]=function(b,c,d){return m(v(d||{},{method:a,url:b,data:c}))}})})("post","put");m.defaults=d;return m}]}function Wc(){this.$get= +["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c)}return function(e,j,i,k,m,l,t){function o(a,c,d,e){c=(j.match(Hb)||["",g])[1]=="file"?d?200:404: +c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(w)}b.$$incOutstandingRequestCount();j=j||b.url();if(A(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};h(j.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?o(k,200,d[p].data):o(k,-2);delete d[p]})}else{var s=new a;s.open(e,j,!0);n(m,function(a,b){a&&s.setRequestHeader(b,a)});var q;s.onreadystatechange=function(){if(s.readyState==4){var a=s.getAllResponseHeaders(),b=["Cache-Control","Content-Language", +"Content-Type","Expires","Last-Modified","Pragma"];a||(a="",n(b,function(b){var c=s.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));o(k,q||s.status,s.responseText,a)}};if(t)s.withCredentials=!0;s.send(i||"");l>0&&c(function(){q=-1;s.abort()},l)}}}function Zc(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4", +negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y", +shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,j){var i=c.defer(),k=i.promise,m=y(j)&&!j,f=a.defer(function(){try{i.resolve(e())}catch(a){i.reject(a),d(a)}m||b.$apply()},f),j=function(){delete g[k.$$timeoutId]};k.$$timeoutId=f;g[f]=i;k.then(j,j);return k}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"), +a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Rb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Sb);a("date",Tb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number",Ub);a("orderBy",Vb);a("uppercase",ed)}function ad(){return function(b,a){if(!E(b))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;ce+1?h="0":(f=h,i=!0)}if(!i){h=(h.split(Xb)[1]||"").length;x(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));var h=Math.pow(10,e),b=Math.round(b*h)/h,b=(""+b).split(Xb),h=b[0],b=b[1]||"",i=0,k=a.lgSize,m=a.gSize;if(h.length>=k+m)for(var i=h.length-k,l=0;l0||e>-c)e+=c;e===0&&c==-12&&(e=12);return kb(e,a,d)}}function Ka(b,a){return function(c,d){var e=c["get"+b](),g=ma(a?"SHORT"+b:b);return d[g][e]}} +function Tb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,h=0;b[9]&&(g=G(b[9]+b[10]),h=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-h,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",h=[],f,j,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;B(c)&&(c=fd.test(c)?G(c):a(c));Ra(c)&&(c=new Date(c));if(!oa(c))return c;for(;e;)(j= +gd.exec(e))?(h=h.concat(ha.call(j,1)),e=h.pop()):(h.push(e),e=null);n(h,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return da(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dm?(d.$setValidity("maxlength",!1),q):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function lb(b,a){b="ngClass"+b;return R(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)j&&b!==j&&h(j),f(b);j=b}function h(a){L(a)&&!E(a)&&(a=Sa(a,function(a,b){if(a)return b}));d.removeClass(E(a)?a.join(" "):a)}function f(a){L(a)&&!E(a)&&(a=Sa(a,function(a,b){if(a)return b}));a&&d.addClass(E(a)?a.join(" "):a)}var j=q;c.$watch(e[b],g,!0);e.$observe("class",function(){var a= +c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var j=d%2;j!==g%2&&(j==a?f(c.$eval(e[b])):h(c.$eval(e[b])))})})}var A=function(b){return B(b)?b.toLowerCase():b},ma=function(b){return B(b)?b.toUpperCase():b},Z=G((/msie (\d+)/.exec(A(navigator.userAgent))||[])[1]),u,ca,ha=[].slice,Qa=[].push,xa=Object.prototype.toString,Za=N.angular||(N.angular={}),ta,gb,aa=["0","0","0"];w.$inject=[];na.$inject=[];gb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?ma(b.scopeName+ +":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,id={full:"1.0.6",major:1,minor:0,dot:6,codeName:"universal-irreversibility"},Ca=K.cache={},Ba=K.expando="ng-"+(new Date).getTime(),oc=1,$b=N.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},eb=N.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/, +va=K.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);K(N).bind("load",a)},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+b])},length:0,push:Qa,sort:[].sort,splice:[].splice},Fa={};n("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Fa[A(b)]=b});var Bb={};n("input,select,option,textarea,button,form".split(","),function(b){Bb[ma(b)]= +!0});n({data:wb,inheritedData:Ea,scope:function(b){return Ea(b,"$scope")},controller:zb,injector:function(b){return Ea(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Da,css:function(b,a,c){a=tb(a);if(y(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?q:d);return d}},attr:function(b,a,c){var d=A(a);if(Fa[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)|| +w).specified?d:q;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?q:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:v(Z<9?function(b,a){if(b.nodeType==1){if(x(a))return b.innerText;b.innerText=a}else{if(x(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(x(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(x(a))return b.value;b.value=a},html:function(b,a){if(x(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},jb={},Yc=N.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest."); +};Rb.$inject=["$provide"];Sb.$inject=["$locale"];Ub.$inject=["$locale"];var Xb=".",hd={yyyy:M("FullYear",4),yy:M("FullYear",2,0,!0),y:M("FullYear",1),MMMM:Ka("Month"),MMM:Ka("Month",!0),MM:M("Month",2,1),M:M("Month",1,1),dd:M("Date",2),d:M("Date",1),HH:M("Hours",2),H:M("Hours",1),hh:M("Hours",2,-12),h:M("Hours",1,-12),mm:M("Minutes",2),m:M("Minutes",1),ss:M("Seconds",2),s:M("Seconds",1),EEEE:Ka("Day"),EEE:Ka("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a= +-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=kb(Math[a>0?"floor":"ceil"](a/60),2)+kb(Math.abs(a%60),2);return c}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,fd=/^\d+$/;Tb.$inject=["$locale"];var dd=I(A),ed=I(ma);Vb.$inject=["$parse"];var jd=I({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(Y.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),mb={};n(Fa,function(a, +c){var d=ea("ng-"+c);mb[d]=function(){return{priority:100,compile:function(){return function(a,g,h){a.$watch(h[d],function(a){h.$set(c,!!a)})}}}}});n(["src","href"],function(a){var c=ea("ng-"+a);mb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Na={$addControl:w,$removeControl:w,$setValidity:w,$setDirty:w};Yb.$inject=["$element","$attrs","$scope"];var Qa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E", +controller:Yb,compile:function(){return{pre:function(a,d,h,f){if(!h.action){var j=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",j);d.bind("$destroy",function(){c(function(){eb(d[0],"submit",j)},0,!1)})}var i=d.parent().controller("form"),k=h.name||h.ngForm;k&&(a[k]=f);i&&d.bind("$destroy",function(){i.$removeControl(f);k&&(a[k]=q);v(f,Na)})}}}};return a?v(V(d),{restrict:"EAC"}):d}]},kd=Qa(),ld=Qa(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, +nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Pa,number:function(a,c,d,e,g,h){Pa(a,c,d,e,g,h);e.$parsers.push(function(a){var c=U(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),q)});e.$formatters.push(function(a){return U(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!U(a)&&aj?(e.$setValidity("max",!1),q):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return U(a)||Ra(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),q)})},url:function(a,c,d,e,g,h){Pa(a,c,d,e,g,h);a=function(a){return U(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a, +c,d,e,g,h){Pa(a,c,d,e,g,h);a=function(a){return U(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){x(d.name)&&c.attr("name",ya());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;B(g)||(g=!0);B(h)||(h=!1);c.bind("click", +function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:h})},hidden:w,button:w,submit:w,reset:w},cc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(bc[A(g.type)]||bc.text)(d,e,g,h,c,a)}}}],Ma="ng-valid",La="ng-invalid",Oa="ng-pristine",Zb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse", +function(a,c,d,e,g){function h(a,c){c=c?"-"+$a(c,"-"):"";e.removeClass((a?La:Ma)+c).addClass((a?Ma:La)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),j=f.assign;if(!j)throw Error(Eb+d.ngModel+" ("+qa(e)+")");this.$render=w;var i=e.inheritedData("$formController")||Na,k=0,m=this.$error={};e.addClass(Oa);h(!0);this.$setValidity=function(a, +c){if(m[a]!==!c){if(c){if(m[a]&&k--,!k)h(!0),this.$valid=!0,this.$invalid=!1}else h(!1),this.$invalid=!0,this.$valid=!1,k++;m[a]=!c;h(c,a);i.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(Oa).addClass(Zb),i.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,j(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(function(){var c= +f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],qd=function(){return{require:["ngModel","^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],h=e[1]||Na;h.$addControl(g);c.bind("$destroy",function(){h.$removeControl(g)})}}},rd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required= +!0;var g=function(a){if(d.required&&(U(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(O(a))});return c});e.$formatters.push(function(a){return E(a)?a.join(", "): +q})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=R(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==q?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate", +function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],yd=lb("",!0),zd=lb("Odd",0),Ad=lb("Even",1),Bd=R({compile:function(a,c){c.$set("ngCloak",q);a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "), +function(a){var c=ea("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.bind(A(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Ed=R(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,h){var f=h.ngInclude||h.src,j=h.onload||"",i=h.autoscroll;return function(g,h){var l=0,n,o=function(){n&&(n.$destroy(),n=null);h.html("")}; +g.$watch(f,function(f){var s=++l;f?a.get(f,{cache:c}).success(function(a){s===l&&(n&&n.$destroy(),n=g.$new(),h.html(a),e(h.contents())(n),y(i)&&(!i||g.$eval(i))&&d(),n.$emit("$includeContentLoaded"),g.$eval(j))}).error(function(){s===l&&o()}):o()})}}}}],Gd=R({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=R({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,j=g.attr(h.$attr.when),i=h.offset|| +0,k=e.$eval(j),m={},l=c.startSymbol(),t=c.endSymbol();n(k,function(a,e){m[e]=c(a.replace(d,l+f+"-"+i+t))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(k[c]||(c=a.pluralCat(c-i)),m[c](e,g,!0))},function(a){g.text(a)})}}}],Jd=R({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,h){var f=h.ngRepeat,h=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),j,i,k;if(!h)throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=h[1]; +j=h[2];h=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!h)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");i=h[3]||h[1];k=h[2];var m=new fb;a.$watch(function(a){var e,f,h=a.$eval(j),n=c,q=new fb,y,z,u,x,r,v;if(E(h))r=h||[];else{r=[];for(u in h)h.hasOwnProperty(u)&&u.charAt(0)!="$"&&r.push(u);r.sort()}y=r.length-1;e=0;for(f=r.length;eA;)u.pop().element.remove()}for(;r.length> +w;)r.pop()[0].element.remove()}var i;if(!(i=p.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+p+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),t=c(i[7]),r=[[{element:f,label:""}]];s&&(a(s)(e),s.removeClass("ng-scope"),s.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=t(e)||[],d={},h,i,j,m,p,s;if(o){i=[];m=0;for(s=r.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/_content/tour/grc/static/lib/codemirror/AUTHORS b/_content/tour/grc/static/lib/codemirror/AUTHORS new file mode 100644 index 00000000..2ac995f9 --- /dev/null +++ b/_content/tour/grc/static/lib/codemirror/AUTHORS @@ -0,0 +1,647 @@ +List of CodeMirror contributors. Updated before every release. + +4r2r +Aaron Brooks +Abdelouahab +Abe Fettig +Adam Ahmed +Adam King +Adam Particka +adanlobato +Adán Lobato +Adrian Aichner +Adrian Heine +Adrien Bertrand +aeroson +Ahmad Amireh +Ahmad M. Zawawi +ahoward +Akeksandr Motsjonov +Alasdair Smith +Alberto González Palomo +Alberto Pose +Albert Xing +Alexander Pavlov +Alexander Schepanovski +Alexander Shvets +Alexander Solovyov +Alexandre Bique +alexey-k +Alex Piggott +Aliaksei Chapyzhenka +Allen Sarkisyan +Ami Fischman +Amin Shali +Amin Ullah Khan +amshali@google.com +Amsul +amuntean +Amy +Ananya Sen +anaran +AndersMad +Anders Nawroth +Anderson Mesquita +Anders Wåglund +Andrea G +Andreas Reischuck +Andres Taylor +Andre von Houck +Andrew Cheng +Andrey Fedorov +Andrey Klyuchnikov +Andrey Lushnikov +Andrey Shchekin +Andy Joslin +Andy Kimball +Andy Li +Angelo +angelozerr +angelo.zerr@gmail.com +Ankit +Ankit Ahuja +Ansel Santosa +Anthony Dugois +anthonygego +Anthony Gégo +Anthony Grimes +Anton Kovalyov +Apollo Zhu +AQNOUCH Mohammed +areos +Arnab Bose +Arthur Müller +Arun Narasani +as3boyan +atelierbram +AtomicPages LLC +Atul Bhouraskar +Aurelian Oancea +Axel Lewenhaupt +Barret Rennie +Basarat Ali Syed +Bastian Müller +belhaj +Bem Jones-Bey +benbro +Beni Cherniavsky-Paskin +Benjamin DeCoste +Ben Keen +Ben Miller +Ben Mosher +Bernhard Sirlinger +Bert Chang +Bharad +BigBlueHat +Billy Moon +binny +B Krishna Chaitanya +Blaine G +blukat29 +boomyjee +borawjm +Brad Metcalf +Brandon Frohs +Brandon Wamboldt +Brett Zamir +Brian Grinstead +Brian Sletten +brrd +Bruce Mitchener +Bryan Massoth +Caitlin Potter +Calin Barbat +callodacity +Camilo Roca +Chad Jolly +Chandra Sekhar Pydi +Charles Skelton +Cheah Chu Yeow +Chris Coyier +Chris Ford +Chris Granger +Chris Houseknecht +Chris Lohfink +Chris Morgan +Chris Smith +Christian Oyarzun +Christian Petrov +Christopher Brown +Christopher Kramer +Christopher Mitchell +Christopher Pfohl +Chunliang Lyu +ciaranj +CodeAnimal +coderaiser +Cole R Lawrence +ComFreek +Curtis Gagliardi +dagsta +daines +Dale Jung +Dan Bentley +Dan Heberden +Daniel, Dao Quang Minh +Daniele Di Sarli +Daniel Faust +Daniel Huigens +Daniel Kesler +Daniel KJ +Daniel Neel +Daniel Parnell +Danny Yoo +darealshinji +Darius Roberts +Dave Brondsema +Dave Myers +David Barnett +David H. Bronke +David Mignot +David Pathakjee +David Vázquez +David Whittington +deebugger +Deep Thought +Devin Abbott +Devon Carew +Dick Choi +dignifiedquire +Dimage Sapelkin +Dmitry Kiselyov +domagoj412 +Dominator008 +Domizio Demichelis +Doug Wikle +Drew Bratcher +Drew Hintz +Drew Khoury +Drini Cami +Dror BG +duralog +eborden +edsharp +ekhaled +Elisée +Emmanuel Schanzer +Enam Mijbah Noor +Eric Allam +Erik Welander +eustas +Fabien Dubosson +Fabien O'Carroll +Fabio Zendhi Nagao +Faiza Alsaied +Fauntleroy +fbuchinger +feizhang365 +Felipe Lalanne +Felix Raab +ficristo +Filip Noetzel +Filip Stollár +flack +Florian Felten +ForbesLindesay +Forbes Lindesay +Ford_Lawnmower +Forrest Oliphant +Frank Wiegand +Gabriel Gheorghian +Gabriel Horner +Gabriel Nahmias +galambalazs +Gary Sheng +Gautam Mehta +Gavin Douglas +gekkoe +Geordie Hall +geowarin +Gerard Braad +Gergely Hegykozi +Giovanni Calò +Glebov Boris +Glenn Jorde +Glenn Ruehle +Golevka +Google Inc. +Gordon Smith +Grant Skinner +greengiant +Gregory Koberger +Grzegorz Mazur +Guillaume Massé +Guillaume Massé +guraga +Gustavo Rodrigues +Hakan Tunc +Hans Engel +Hardest +Harshvardhan Gupta +Hasan Karahan +Hector Oswaldo Caballero +Hendrik Wallbaum +Herculano Campos +Hiroyuki Makino +hitsthings +Hocdoc +Hugues Malphettes +Ian Beck +Ian Dickinson +Ian Wehrman +Ian Wetherbee +Ice White +ICHIKAWA, Yuji +idleberg +ilvalle +Ingo Richter +Irakli Gozalishvili +Ivan Kurnosov +Ivoah +Jacob Lee +Jake Peyser +Jakob Miland +Jakub Vrana +Jakub Vrána +James Campos +James Howard +James Thorne +Jamie Hill +Jamie Morris +Jan Jongboom +jankeromnes +Jan Keromnes +Jan Odvarko +Jan Schär +Jan T. Sott +Jared Dean +Jared Forsyth +Jared Jacobs +Jason +Jason Barnabe +Jason Grout +Jason Johnston +Jason San Jose +Jason Siefken +Jaydeep Solanki +Jean Boussier +Jeff Blaisdell +Jeff Jenkins +jeffkenton +Jeff Pickhardt +jem (graphite) +Jeremy Parmenter +Jim +Jim Avery +JobJob +jochenberger +Jochen Berger +Joel Einbinder +joelpinheiro +Johan Ask +John Connor +John-David Dalton +John Engler +John Lees-Miller +John Snelson +John Van Der Loo +Jon Ander Peñalba +Jonas Döbertin +Jonathan Malmaud +Jon Gacnik +jongalloway +Jon Malmaud +Jon Sangster +Joost-Wim Boekesteijn +Joseph Pecoraro +Josh Barnes +Josh Cohen +Josh Soref +Joshua Newman +Josh Watzman +jots +jsoojeon +ju1ius +Juan Benavides Romero +Jucovschi Constantin +Juho Vuori +Julien Rebetez +Justin Andresen +Justin Hileman +jwallers@gmail.com +kaniga +karevn +Kayur Patel +Kazuhito Hokamura +Ken Newman +ken restivo +Ken Rockot +Kevin Earls +Kevin Muret +Kevin Sawicki +Kevin Ushey +Klaus Silveira +Koh Zi Han, Cliff +komakino +Konstantin Lopuhin +koops +Kris Ciccarello +ks-ifware +kubelsmieci +KwanEsq +Kyle Kelley +Lanfei +Lanny +Laszlo Vidacs +leaf corcoran +Leonid Khachaturov +Leon Sorokin +Leonya Khachaturov +Liam Newman +Libo Cannici +LloydMilligan +LM +lochel +Lorenzo Stoakes +Luca Fabbri +Luciano Longo +Lu Fangjian +Luke Browning +Luke Granger-Brown +Luke Stagner +lynschinzer +M1cha +Madhura Jayaratne +Maksim Lin +Maksym Taran +Malay Majithia +Manideep +Manuel Rego Casasnovas +Marat Dreizin +Marcel Gerber +Marcelo Camargo +Marco Aurélio +Marco Munizaga +Marcus Bointon +Marek Rudnicki +Marijn Haverbeke +Mário Gonçalves +Mario Pietsch +Mark Anderson +Mark Lentczner +Marko Bonaci +Mark Peace +Markus Bordihn +Martin Balek +Martín Gaitán +Martin Hasoň +Martin Hunt +Martin Laine +Martin Zagora +Mason Malone +Mateusz Paprocki +Mathias Bynens +mats cronqvist +Matt Gaide +Matthew Bauer +Matthew Beale +matthewhayes +Matthew Rathbone +Matthias Bussonnier +Matthias BUSSONNIER +Matt McDonald +Matt Pass +Matt Sacks +mauricio +Maximilian Hils +Maxim Kraev +Max Kirsch +Max Schaefer +Max Xiantu +mbarkhau +McBrainy +mce2 +melpon +Metatheos +Micah Dubinko +Michael +Michael Goderbauer +Michael Grey +Michael Kaminsky +Michael Lehenbauer +Michael Zhou +Michal Dorner +Mighty Guava +Miguel Castillo +mihailik +Mike +Mike Brevoort +Mike Diaz +Mike Ivanov +Mike Kadin +Mike Kobit +MinRK +Miraculix87 +misfo +mkaminsky11 +mloginov +Moritz Schwörer +mps +ms +mtaran-google +Mu-An Chiou +Narciso Jaramillo +Nathan Williams +ndr +nerbert +nextrevision +ngn +nguillaumin +Ng Zhi An +Nicholas Bollweg +Nicholas Bollweg (Nick) +Nick Kreeger +Nick Small +Nicolò Ribaudo +Niels van Groningen +nightwing +Nikita Beloglazov +Nikita Vasilyev +Nikolay Kostov +nilp0inter +Nisarg Jhaveri +nlwillia +noragrossman +Norman Rzepka +Oreoluwa Onatemowo +Oskar Segersvärd +pablo +pabloferz +Pablo Zubieta +Page +Panupong Pasupat +paris +Paris +Paris Kasidiaris +Patil Arpith +Patrick Stoica +Patrick Strawderman +Paul Garvin +Paul Ivanov +Paul Masson +Pavel +Pavel Feldman +Pavel Petržela +Pavel Strashkin +Paweł Bartkiewicz +peteguhl +peter +Peter Flynn +peterkroon +Peter Kroon +Philipp A +Philip Stadermann +Pierre Gerold +Piët Delport +Pontus Melke +prasanthj +Prasanth J +Prayag Verma +Radek Piórkowski +Rahul +Rahul Anand +ramwin1 +Randall Mason +Randy Burden +Randy Edmunds +Rasmus Erik Voel Jensen +ray ratchup +Ray Ratchup +Remi Nyborg +Renaud Durlin +Richard Denton +Richard van der Meer +Richard Z.H. Wang +Rishi Goomar +Robert Crossfield +Roberto Abdelkader Martínez Pérez +robertop23 +Robert Plummer +Rrandom +Rrrandom +Ruslan Osmanov +Ryan Petrello +Ryan Prior +sabaca +Sam Lee +Samuel Ainsworth +Sam Wilson +sandeepshetty +Sander AKA Redsandro +Sander Verweij +santec +Sascha Peilicke +satamas +satchmorun +sathyamoorthi +Saul Costa +S. Chris Colbert +SCLINIC\jdecker +Scott Aikin +Scott Goodhew +Sebastian Zaha +Sergey Goder +Sergey Tselovalnikov +Se-Won Kim +shaund +shaun gilchrist +Shawn A +Shea Bunge +sheopory +Shiv Deepak +Shmuel Englard +Shubham Jain +Siamak Mokhtari +silverwind +sinkuu +snasa +soliton4 +sonson +spastorelli +srajanpaliwal +Stanislav Oaserele +Stas Kobzar +Stefan Borsje +Steffen Beyer +Steffen Bruchmann +Stephen Lavelle +Steve Champagne +Steve Hoover +Steve O'Hara +stoskov +Stu Kennedy +Sungho Kim +sverweij +Taha Jahangir +takamori +Tako Schotanus +Takuji Shimokawa +Tarmil +TDaglis +tel +tfjgeorge +Thaddee Tyl +thanasis +TheHowl +themrmax +think +Thomas Dvornik +Thomas Kluyver +Thomas Schmid +Tim Alby +Tim Baumann +Timothy Farrell +Timothy Gu +Timothy Hatcher +TobiasBg +Todd Berman +Tomas-A +Tomas Varaneckas +Tom Erik Støwer +Tom Klancer +Tom MacWright +Tony Jian +Travis Heppe +Triangle717 +Tristan Tarrant +TSUYUSATO Kitsune +twifkak +VapidWorx +Vestimir Markov +vf +Victor Bocharsky +Vincent Woo +Volker Mische +Weiyan Shao +wenli +Wes Cossick +Wesley Wiser +Will Binns-Smith +Will Dean +William Jamieson +William Stein +Willy +Wojtek Ptak +Wu Cheng-Han +Xavier Mendez +Yassin N. Hassan +YNH Webdev +Yunchi Luo +Yuvi Panda +Zac Anger +Zachary Dremann +Zeno Rocha +Zhang Hao +Ziv +zziuni +魏鹏刚 diff --git a/_content/tour/grc/static/lib/codemirror/LICENSE b/_content/tour/grc/static/lib/codemirror/LICENSE new file mode 100644 index 00000000..ff7db4b9 --- /dev/null +++ b/_content/tour/grc/static/lib/codemirror/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2017 by Marijn Haverbeke and others + +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. diff --git a/_content/tour/grc/static/lib/codemirror/README b/_content/tour/grc/static/lib/codemirror/README new file mode 100644 index 00000000..3328e3bd --- /dev/null +++ b/_content/tour/grc/static/lib/codemirror/README @@ -0,0 +1,34 @@ +# CodeMirror +[![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror) +[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror) +[![Join the chat at https://gitter.im/codemirror/CodeMirror](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/codemirror/CodeMirror) +[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?again)](https://marijnhaverbeke.nl/fund/) + +CodeMirror is a versatile text editor implemented in JavaScript for +the browser. It is specialized for editing code, and comes with over +100 language modes and various addons that implement more advanced +editing functionality. + +A rich programming API and a CSS theming system are available for +customizing CodeMirror to fit your application, and extending it with +new functionality. + +You can find more information (and the +[manual](http://codemirror.net/doc/manual.html)) on the [project +page](http://codemirror.net). For questions and discussion, use the +[discussion forum](https://discuss.codemirror.net/). + +See +[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md) +for contributing guidelines. + +The CodeMirror community aims to be welcoming to everybody. We use the +[Contributor Covenant +(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of +conduct. + +### Quickstart + +To build the project, make sure you have Node.js installed (at least version 6) +and then `npm install`. To run, just open `index.html` in your +browser (you don't need to run a webserver). Run the tests with `npm test`. diff --git a/_content/tour/grc/static/lib/codemirror/lib/codemirror.css b/_content/tour/grc/static/lib/codemirror/lib/codemirror.css new file mode 100644 index 00000000..72fefcc0 --- /dev/null +++ b/_content/tour/grc/static/lib/codemirror/lib/codemirror.css @@ -0,0 +1,348 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 0px 0 2px; + min-width: 30px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: -20px; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper ::selection { background-color: transparent } +.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-rtl pre { direction: rtl; } + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } + +/* Dark Mode Styles */ + +[data-theme='dark'] .cm-s-default .cm-atom {color: #292;} + +[data-theme='dark'] .CodeMirror-cursor { + border-left: 1px solid #ffffff; +} \ No newline at end of file diff --git a/_content/tour/grc/static/lib/codemirror/lib/codemirror.js b/_content/tour/grc/static/lib/codemirror/lib/codemirror.js new file mode 100644 index 00000000..8f2e33d5 --- /dev/null +++ b/_content/tour/grc/static/lib/codemirror/lib/codemirror.js @@ -0,0 +1,9331 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.CodeMirror = factory()); +}(this, (function () { 'use strict'; + +// Kludges for bugs and behavior differences that can't be feature +// detected are enabled based on userAgent etc sniffing. +var userAgent = navigator.userAgent +var platform = navigator.platform + +var gecko = /gecko\/\d/i.test(userAgent) +var ie_upto10 = /MSIE \d/.test(userAgent) +var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) +var edge = /Edge\/(\d+)/.exec(userAgent) +var ie = ie_upto10 || ie_11up || edge +var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]) +var webkit = !edge && /WebKit\//.test(userAgent) +var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) +var chrome = !edge && /Chrome\//.test(userAgent) +var presto = /Opera\//.test(userAgent) +var safari = /Apple Computer/.test(navigator.vendor) +var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) +var phantom = /PhantomJS/.test(userAgent) + +var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent) +var android = /Android/.test(userAgent) +// This is woefully incomplete. Suggestions for alternative methods welcome. +var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) +var mac = ios || /Mac/.test(platform) +var chromeOS = /\bCrOS\b/.test(userAgent) +var windows = /win/i.test(platform) + +var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) +if (presto_version) { presto_version = Number(presto_version[1]) } +if (presto_version && presto_version >= 15) { presto = false; webkit = true } +// Some browsers use the wrong event properties to signal cmd/ctrl on OS X +var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) +var captureRightClick = gecko || (ie && ie_version >= 9) + +function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + +var rmClass = function(node, cls) { + var current = node.className + var match = classTest(cls).exec(current) + if (match) { + var after = current.slice(match.index + match[0].length) + node.className = current.slice(0, match.index) + (after ? match[1] + after : "") + } +} + +function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + { e.removeChild(e.firstChild) } + return e +} + +function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e) +} + +function elt(tag, content, className, style) { + var e = document.createElement(tag) + if (className) { e.className = className } + if (style) { e.style.cssText = style } + if (typeof content == "string") { e.appendChild(document.createTextNode(content)) } + else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]) } } + return e +} +// wrapper for elt, which removes the elt from the accessibility tree +function eltP(tag, content, className, style) { + var e = elt(tag, content, className, style) + e.setAttribute("role", "presentation") + return e +} + +var range +if (document.createRange) { range = function(node, start, end, endNode) { + var r = document.createRange() + r.setEnd(endNode || node, end) + r.setStart(node, start) + return r +} } +else { range = function(node, start, end) { + var r = document.body.createTextRange() + try { r.moveToElementText(node.parentNode) } + catch(e) { return r } + r.collapse(true) + r.moveEnd("character", end) + r.moveStart("character", start) + return r +} } + +function contains(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + { child = child.parentNode } + if (parent.contains) + { return parent.contains(child) } + do { + if (child.nodeType == 11) { child = child.host } + if (child == parent) { return true } + } while (child = child.parentNode) +} + +function activeElt() { + // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. + // IE < 10 will throw when accessed while the page is loading or in an iframe. + // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. + var activeElement + try { + activeElement = document.activeElement + } catch(e) { + activeElement = document.body || null + } + while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) + { activeElement = activeElement.shadowRoot.activeElement } + return activeElement +} + +function addClass(node, cls) { + var current = node.className + if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls } +} +function joinClasses(a, b) { + var as = a.split(" ") + for (var i = 0; i < as.length; i++) + { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i] } } + return b +} + +var selectInput = function(node) { node.select() } +if (ios) // Mobile Safari apparently has a bug where select() is broken. + { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } } +else if (ie) // Suppress mysterious IE10 errors + { selectInput = function(node) { try { node.select() } catch(_e) {} } } + +function bind(f) { + var args = Array.prototype.slice.call(arguments, 1) + return function(){return f.apply(null, args)} +} + +function copyObj(obj, target, overwrite) { + if (!target) { target = {} } + for (var prop in obj) + { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + { target[prop] = obj[prop] } } + return target +} + +// Counts the column offset in a string, taking tabs into account. +// Used mostly to find indentation. +function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/) + if (end == -1) { end = string.length } + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i) + if (nextTab < 0 || nextTab >= end) + { return n + (end - i) } + n += nextTab - i + n += tabSize - (n % tabSize) + i = nextTab + 1 + } +} + +var Delayed = function() {this.id = null}; +Delayed.prototype.set = function (ms, f) { + clearTimeout(this.id) + this.id = setTimeout(f, ms) +}; + +function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + { if (array[i] == elt) { return i } } + return -1 +} + +// Number of pixels added to scroller and sizer to hide scrollbar +var scrollerGap = 30 + +// Returned or thrown by various protocols to signal 'I'm not +// handling this'. +var Pass = {toString: function(){return "CodeMirror.Pass"}} + +// Reused option objects for setSelection & friends +var sel_dontScroll = {scroll: false}; +var sel_mouse = {origin: "*mouse"}; +var sel_move = {origin: "+move"}; +// The inverse of countColumn -- find the offset that corresponds to +// a particular column. +function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos) + if (nextTab == -1) { nextTab = string.length } + var skipped = nextTab - pos + if (nextTab == string.length || col + skipped >= goal) + { return pos + Math.min(skipped, goal - col) } + col += nextTab - pos + col += tabSize - (col % tabSize) + pos = nextTab + 1 + if (col >= goal) { return pos } + } +} + +var spaceStrs = [""] +function spaceStr(n) { + while (spaceStrs.length <= n) + { spaceStrs.push(lst(spaceStrs) + " ") } + return spaceStrs[n] +} + +function lst(arr) { return arr[arr.length-1] } + +function map(array, f) { + var out = [] + for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i) } + return out +} + +function insertSorted(array, value, score) { + var pos = 0, priority = score(value) + while (pos < array.length && score(array[pos]) <= priority) { pos++ } + array.splice(pos, 0, value) +} + +function nothing() {} + +function createObj(base, props) { + var inst + if (Object.create) { + inst = Object.create(base) + } else { + nothing.prototype = base + inst = new nothing() + } + if (props) { copyObj(props, inst) } + return inst +} + +var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ +function isWordCharBasic(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) +} +function isWordChar(ch, helper) { + if (!helper) { return isWordCharBasic(ch) } + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } + return helper.test(ch) +} + +function isEmpty(obj) { + for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } + return true +} + +// Extending unicode characters. A series of a non-extending char + +// any number of extending chars is treated as a single unit as far +// as editing and measuring is concerned. This is not fully correct, +// since some scripts/fonts/browsers also treat other configurations +// of code points as a group. +var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/ +function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } + +// Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. +function skipExtendingChars(str, pos, dir) { + while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir } + return pos +} + +// Returns the value from the range [`from`; `to`] that satisfies +// `pred` and is closest to `from`. Assumes that at least `to` satisfies `pred`. +function findFirst(pred, from, to) { + for (;;) { + if (Math.abs(from - to) <= 1) { return pred(from) ? from : to } + var mid = Math.floor((from + to) / 2) + if (pred(mid)) { to = mid } + else { from = mid } + } +} + +// The display handles the DOM integration, both for input reading +// and content drawing. It holds references to DOM nodes and +// display-related state. + +function Display(place, doc, input) { + var d = this + this.input = input + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") + d.scrollbarFiller.setAttribute("cm-not-content", "true") + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") + d.gutterFiller.setAttribute("cm-not-content", "true") + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = eltP("div", null, "CodeMirror-code") + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") + d.cursorDiv = elt("div", null, "CodeMirror-cursors") + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure") + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure") + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none") + var lines = eltP("div", [d.lineSpace], "CodeMirror-lines") + // Moved around its parent to cover visible view. + d.mover = elt("div", [lines], null, "position: relative") + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer") + d.sizerWidth = null + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;") + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters") + d.lineGutter = null + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll") + d.scroller.setAttribute("tabIndex", "-1") + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror") + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 } + if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true } + + if (place) { + if (place.appendChild) { place.appendChild(d.wrapper) } + else { place(d.wrapper) } + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first + d.reportedViewFrom = d.reportedViewTo = doc.first + // Information about the rendered lines. + d.view = [] + d.renderedView = null + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null + // Empty space (in pixels) above the view + d.viewOffset = 0 + d.lastWrapHeight = d.lastWrapWidth = 0 + d.updateLineNumbers = null + + d.nativeBarWidth = d.barHeight = d.barWidth = 0 + d.scrollbarsClipped = false + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null + d.maxLineLength = 0 + d.maxLineChanged = false + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null + + // True when shift is held down. + d.shift = false + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null + + d.activeTouch = null + + input.init(d) +} + +// Find the line object corresponding to the given line number. +function getLine(doc, n) { + n -= doc.first + if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } + var chunk = doc + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize() + if (n < sz) { chunk = child; break } + n -= sz + } + } + return chunk.lines[n] +} + +// Get the part of a document between two positions, as an array of +// strings. +function getBetween(doc, start, end) { + var out = [], n = start.line + doc.iter(start.line, end.line + 1, function (line) { + var text = line.text + if (n == end.line) { text = text.slice(0, end.ch) } + if (n == start.line) { text = text.slice(start.ch) } + out.push(text) + ++n + }) + return out +} +// Get the lines between from and to, as array of strings. +function getLines(doc, from, to) { + var out = [] + doc.iter(from, to, function (line) { out.push(line.text) }) // iter aborts when callback returns truthy value + return out +} + +// Update the height of a line, propagating the height change +// upwards to parent nodes. +function updateLineHeight(line, height) { + var diff = height - line.height + if (diff) { for (var n = line; n; n = n.parent) { n.height += diff } } +} + +// Given a line object, find its line number by walking up through +// its parent links. +function lineNo(line) { + if (line.parent == null) { return null } + var cur = line.parent, no = indexOf(cur.lines, line) + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) { break } + no += chunk.children[i].chunkSize() + } + } + return no + cur.first +} + +// Find the line at the given vertical position, using the height +// information in the document tree. +function lineAtHeight(chunk, h) { + var n = chunk.first + outer: do { + for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { + var child = chunk.children[i$1], ch = child.height + if (h < ch) { chunk = child; continue outer } + h -= ch + n += child.chunkSize() + } + return n + } while (!chunk.lines) + var i = 0 + for (; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height + if (h < lh) { break } + h -= lh + } + return n + i +} + +function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} + +function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)) +} + +// A Pos instance represents a position within the text. +function Pos(line, ch, sticky) { + if ( sticky === void 0 ) sticky = null; + + if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } + this.line = line + this.ch = ch + this.sticky = sticky +} + +// Compare two positions, return 0 if they are the same, a negative +// number when a is less, and a positive number otherwise. +function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + +function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + +function copyPos(x) {return Pos(x.line, x.ch)} +function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } +function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + +// Most of the external API clips given positions to make sure they +// actually exist within the document. +function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} +function clipPos(doc, pos) { + if (pos.line < doc.first) { return Pos(doc.first, 0) } + var last = doc.first + doc.size - 1 + if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } + return clipToLen(pos, getLine(doc, pos.line).text.length) +} +function clipToLen(pos, linelen) { + var ch = pos.ch + if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } + else if (ch < 0) { return Pos(pos.line, 0) } + else { return pos } +} +function clipPosArray(doc, array) { + var out = [] + for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]) } + return out +} + +// Optimize some code when these features are not used. +var sawReadOnlySpans = false; +var sawCollapsedSpans = false; +function seeReadOnlySpans() { + sawReadOnlySpans = true +} + +function seeCollapsedSpans() { + sawCollapsedSpans = true +} + +// TEXTMARKER SPANS + +function MarkedSpan(marker, from, to) { + this.marker = marker + this.from = from; this.to = to +} + +// Search an array of spans for a span matching the given marker. +function getMarkedSpanFor(spans, marker) { + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i] + if (span.marker == marker) { return span } + } } +} +// Remove a span from an array, returning undefined if no spans are +// left (we don't store arrays for lines without spans). +function removeMarkedSpan(spans, span) { + var r + for (var i = 0; i < spans.length; ++i) + { if (spans[i] != span) { (r || (r = [])).push(spans[i]) } } + return r +} +// Add a span to a line. +function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] + span.marker.attachLine(line) +} + +// Used for the algorithm that adjusts markers for a change in the +// document. These functions cut an array of spans at a given +// character position, returning an array of remaining chunks (or +// undefined if nothing remains). +function markedSpansBefore(old, startCh, isInsert) { + var nw + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) + } + } } + return nw +} +function markedSpansAfter(old, endCh, isInsert) { + var nw + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)) + } + } } + return nw +} + +// Given a change object, compute the new set of marker spans that +// cover the line in which the change took place. Removes spans +// entirely within the change, reconnects spans belonging to the +// same marker that appear on both sides of the change, and cuts off +// spans partially within the change. Returns an array of span +// arrays with one element for each line in (after) the change. +function stretchSpansOverChange(doc, change) { + if (change.full) { return null } + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans + if (!oldFirst && !oldLast) { return null } + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert) + var last = markedSpansAfter(oldLast, endCh, isInsert) + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i] + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker) + if (!found) { span.to = startCh } + else if (sameLine) { span.to = found.to == null ? null : found.to + offset } + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i$1 = 0; i$1 < last.length; ++i$1) { + var span$1 = last[i$1] + if (span$1.to != null) { span$1.to += offset } + if (span$1.from == null) { + var found$1 = getMarkedSpanFor(first, span$1.marker) + if (!found$1) { + span$1.from = offset + if (sameLine) { (first || (first = [])).push(span$1) } + } + } else { + span$1.from += offset + if (sameLine) { (first || (first = [])).push(span$1) } + } + } + } + // Make sure we didn't create any zero-length spans + if (first) { first = clearEmptySpans(first) } + if (last && last != first) { last = clearEmptySpans(last) } + + var newMarkers = [first] + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers + if (gap > 0 && first) + { for (var i$2 = 0; i$2 < first.length; ++i$2) + { if (first[i$2].to == null) + { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)) } } } + for (var i$3 = 0; i$3 < gap; ++i$3) + { newMarkers.push(gapMarkers) } + newMarkers.push(last) + } + return newMarkers +} + +// Remove spans that are empty and don't have a clearWhenEmpty +// option of false. +function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i] + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + { spans.splice(i--, 1) } + } + if (!spans.length) { return null } + return spans +} + +// Used to 'clip' out readOnly ranges when making a change. +function removeReadOnlyRanges(doc, from, to) { + var markers = null + doc.iter(from.line, to.line + 1, function (line) { + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + { (markers || (markers = [])).push(mark) } + } } + }) + if (!markers) { return null } + var parts = [{from: from, to: to}] + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0) + for (var j = 0; j < parts.length; ++j) { + var p = parts[j] + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + { newParts.push({from: p.from, to: m.from}) } + if (dto > 0 || !mk.inclusiveRight && !dto) + { newParts.push({from: m.to, to: p.to}) } + parts.splice.apply(parts, newParts) + j += newParts.length - 3 + } + } + return parts +} + +// Connect or disconnect spans from a line. +function detachMarkedSpans(line) { + var spans = line.markedSpans + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.detachLine(line) } + line.markedSpans = null +} +function attachMarkedSpans(line, spans) { + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.attachLine(line) } + line.markedSpans = spans +} + +// Helpers used when computing which overlapping collapsed span +// counts as the larger one. +function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } +function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + +// Returns a number indicating which of two overlapping collapsed +// spans is larger (and thus includes the other). Falls back to +// comparing ids when the spans cover exactly the same range. +function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length + if (lenDiff != 0) { return lenDiff } + var aPos = a.find(), bPos = b.find() + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) + if (fromCmp) { return -fromCmp } + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) + if (toCmp) { return toCmp } + return b.id - a.id +} + +// Find out whether a line ends or starts in a collapsed span. If +// so, return the marker for that span. +function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i] + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + { found = sp.marker } + } } + return found +} +function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } +function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + +// Test whether there exists a collapsed span that partially +// overlaps (covers the start or end, but not both) of a new span. +// Such overlap is not allowed. +function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo) + var sps = sawCollapsedSpans && line.markedSpans + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i] + if (!sp.marker.collapsed) { continue } + var found = sp.marker.find(0) + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + { return true } + } } +} + +// A visual line is a line as drawn on the screen. Folding, for +// example, can cause multiple logical lines to appear on the same +// visual line. This finds the start of the visual line that the +// given line is part of (usually that is the line itself). +function visualLine(line) { + var merged + while (merged = collapsedSpanAtStart(line)) + { line = merged.find(-1, true).line } + return line +} + +function visualLineEnd(line) { + var merged + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line } + return line +} + +// Returns an array of logical lines that continue the visual line +// started by the argument, or undefined if there are no such lines. +function visualLineContinued(line) { + var merged, lines + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line) + } + return lines +} + +// Get the line number of the start of the visual line that the +// given line number is part of. +function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line) + if (line == vis) { return lineN } + return lineNo(vis) +} + +// Get the line number of the start of the next visual line after +// the given line. +function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) { return lineN } + var line = getLine(doc, lineN), merged + if (!lineIsHidden(doc, line)) { return lineN } + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line } + return lineNo(line) + 1 +} + +// Compute whether a line is hidden. Lines count as hidden when they +// are part of a visual line that starts with another line, or when +// they are entirely covered by collapsed, non-widget span. +function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i] + if (!sp.marker.collapsed) { continue } + if (sp.from == null) { return true } + if (sp.marker.widgetNode) { continue } + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + { return true } + } } +} +function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true) + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + { return true } + for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i] + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) { return true } + } +} + +// Find the height above the given line. +function heightAtLine(lineObj) { + lineObj = visualLine(lineObj) + + var h = 0, chunk = lineObj.parent + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i] + if (line == lineObj) { break } + else { h += line.height } + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i$1 = 0; i$1 < p.children.length; ++i$1) { + var cur = p.children[i$1] + if (cur == chunk) { break } + else { h += cur.height } + } + } + return h +} + +// Compute the character length of a line, taking into account +// collapsed ranges (see markText) that might hide parts, and join +// other lines onto it. +function lineLength(line) { + if (line.height == 0) { return 0 } + var len = line.text.length, merged, cur = line + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true) + cur = found.from.line + len += found.from.ch - found.to.ch + } + cur = line + while (merged = collapsedSpanAtEnd(cur)) { + var found$1 = merged.find(0, true) + len -= cur.text.length - found$1.from.ch + cur = found$1.to.line + len += cur.text.length - found$1.to.ch + } + return len +} + +// Find the longest line in the document. +function findMaxLine(cm) { + var d = cm.display, doc = cm.doc + d.maxLine = getLine(doc, doc.first) + d.maxLineLength = lineLength(d.maxLine) + d.maxLineChanged = true + doc.iter(function (line) { + var len = lineLength(line) + if (len > d.maxLineLength) { + d.maxLineLength = len + d.maxLine = line + } + }) +} + +// BIDI HELPERS + +function iterateBidiSections(order, from, to, f) { + if (!order) { return f(from, to, "ltr") } + var found = false + for (var i = 0; i < order.length; ++i) { + var part = order[i] + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr") + found = true + } + } + if (!found) { f(from, to, "ltr") } +} + +var bidiOther = null +function getBidiPartAt(order, ch, sticky) { + var found + bidiOther = null + for (var i = 0; i < order.length; ++i) { + var cur = order[i] + if (cur.from < ch && cur.to > ch) { return i } + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") { found = i } + else { bidiOther = i } + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") { found = i } + else { bidiOther = i } + } + } + return found != null ? found : bidiOther +} + +// Bidirectional ordering algorithm +// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm +// that this (partially) implements. + +// One-char codes used for character types: +// L (L): Left-to-Right +// R (R): Right-to-Left +// r (AL): Right-to-Left Arabic +// 1 (EN): European Number +// + (ES): European Number Separator +// % (ET): European Number Terminator +// n (AN): Arabic Number +// , (CS): Common Number Separator +// m (NSM): Non-Spacing Mark +// b (BN): Boundary Neutral +// s (B): Paragraph Separator +// t (S): Segment Separator +// w (WS): Whitespace +// N (ON): Other Neutrals + +// Returns null if characters are ordered as they appear +// (left-to-right), or an array of sections ({from, to, level} +// objects) in the order in which they occur visually. +var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" + // Character types for codepoints 0x600 to 0x6f9 + var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" + function charType(code) { + if (code <= 0xf7) { return lowTypes.charAt(code) } + else if (0x590 <= code && code <= 0x5f4) { return "R" } + else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } + else if (0x6ee <= code && code <= 0x8ac) { return "r" } + else if (0x2000 <= code && code <= 0x200b) { return "w" } + else if (code == 0x200c) { return "b" } + else { return "L" } + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ + + function BidiSpan(level, from, to) { + this.level = level + this.from = from; this.to = to + } + + return function(str, direction) { + var outerType = direction == "ltr" ? "L" : "R" + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } + var len = str.length, types = [] + for (var i = 0; i < len; ++i) + { types.push(charType(str.charCodeAt(i))) } + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { + var type = types[i$1] + if (type == "m") { types[i$1] = prev } + else { prev = type } + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { + var type$1 = types[i$2] + if (type$1 == "1" && cur == "r") { types[i$2] = "n" } + else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R" } } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { + var type$2 = types[i$3] + if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1" } + else if (type$2 == "," && prev$1 == types[i$3+1] && + (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 } + prev$1 = type$2 + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i$4 = 0; i$4 < len; ++i$4) { + var type$3 = types[i$4] + if (type$3 == ",") { types[i$4] = "N" } + else if (type$3 == "%") { + var end = (void 0) + for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" + for (var j = i$4; j < end; ++j) { types[j] = replace } + i$4 = end - 1 + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { + var type$4 = types[i$5] + if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" } + else if (isStrong.test(type$4)) { cur$1 = type$4 } + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i$6 = 0; i$6 < len; ++i$6) { + if (isNeutral.test(types[i$6])) { + var end$1 = (void 0) + for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} + var before = (i$6 ? types[i$6-1] : outerType) == "L" + var after = (end$1 < len ? types[end$1] : outerType) == "L" + var replace$1 = before == after ? (before ? "L" : "R") : outerType + for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 } + i$6 = end$1 - 1 + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m + for (var i$7 = 0; i$7 < len;) { + if (countsAsLeft.test(types[i$7])) { + var start = i$7 + for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} + order.push(new BidiSpan(0, start, i$7)) + } else { + var pos = i$7, at = order.length + for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} + for (var j$2 = pos; j$2 < i$7;) { + if (countsAsNum.test(types[j$2])) { + if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) } + var nstart = j$2 + for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} + order.splice(at, 0, new BidiSpan(2, nstart, j$2)) + pos = j$2 + } else { ++j$2 } + } + if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length + order.unshift(new BidiSpan(0, 0, m[0].length)) + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length + order.push(new BidiSpan(0, len - m[0].length, len)) + } + + return direction == "rtl" ? order.reverse() : order + } +})() + +// Get the bidi ordering for the given line (and cache it). Returns +// false for lines that are fully left-to-right, and an array of +// BidiSpan objects otherwise. +function getOrder(line, direction) { + var order = line.order + if (order == null) { order = line.order = bidiOrdering(line.text, direction) } + return order +} + +function moveCharLogically(line, ch, dir) { + var target = skipExtendingChars(line.text, ch + dir, dir) + return target < 0 || target > line.text.length ? null : target +} + +function moveLogically(line, start, dir) { + var ch = moveCharLogically(line, start.ch, dir) + return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") +} + +function endOfLine(visually, cm, lineObj, lineNo, dir) { + if (visually) { + var order = getOrder(lineObj, cm.doc.direction) + if (order) { + var part = dir < 0 ? lst(order) : order[0] + var moveInStorageOrder = (dir < 0) == (part.level == 1) + var sticky = moveInStorageOrder ? "after" : "before" + var ch + // With a wrapped rtl chunk (possibly spanning multiple bidi parts), + // it could be that the last bidi part is not on the last visual line, + // since visual lines contain content order-consecutive chunks. + // Thus, in rtl, we are looking for the first (content-order) character + // in the rtl chunk that is on the last line (that is, the same line + // as the last (content-order) character). + if (part.level > 0) { + var prep = prepareMeasureForLine(cm, lineObj) + ch = dir < 0 ? lineObj.text.length - 1 : 0 + var targetTop = measureCharPrepared(cm, prep, ch).top + ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) + if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1, true) } + } else { ch = dir < 0 ? part.to : part.from } + return new Pos(lineNo, ch, sticky) + } + } + return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") +} + +function moveVisually(cm, line, start, dir) { + var bidi = getOrder(line, cm.doc.direction) + if (!bidi) { return moveLogically(line, start, dir) } + if (start.ch >= line.text.length) { + start.ch = line.text.length + start.sticky = "before" + } else if (start.ch <= 0) { + start.ch = 0 + start.sticky = "after" + } + var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] + if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { + // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, + // nothing interesting happens. + return moveLogically(line, start, dir) + } + + var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); } + var prep + var getWrappedLineExtent = function (ch) { + if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } + prep = prep || prepareMeasureForLine(cm, line) + return wrappedLineExtentChar(cm, line, prep, ch) + } + var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) + + if (cm.doc.direction == "rtl" || part.level == 1) { + var moveInStorageOrder = (part.level == 1) == (dir < 0) + var ch = mv(start, moveInStorageOrder ? 1 : -1) + if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { + // Case 2: We move within an rtl part or in an rtl editor on the same visual line + var sticky = moveInStorageOrder ? "before" : "after" + return new Pos(start.line, ch, sticky) + } + } + + // Case 3: Could not move within this bidi part in this visual line, so leave + // the current bidi part + + var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { + var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder + ? new Pos(start.line, mv(ch, 1), "before") + : new Pos(start.line, ch, "after"); } + + for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { + var part = bidi[partPos] + var moveInStorageOrder = (dir > 0) == (part.level != 1) + var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) + if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } + ch = moveInStorageOrder ? part.from : mv(part.to, -1) + if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } + } + } + + // Case 3a: Look for other bidi parts on the same visual line + var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) + if (res) { return res } + + // Case 3b: Look for other bidi parts on the next visual line + var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) + if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { + res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) + if (res) { return res } + } + + // Case 4: Nowhere to move + return null +} + +// EVENT HANDLING + +// Lightweight event framework. on/off also work on DOM nodes, +// registering native DOM handlers. + +var noHandlers = [] + +var on = function(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false) + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, f) + } else { + var map = emitter._handlers || (emitter._handlers = {}) + map[type] = (map[type] || noHandlers).concat(f) + } +} + +function getHandlers(emitter, type) { + return emitter._handlers && emitter._handlers[type] || noHandlers +} + +function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false) + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, f) + } else { + var map = emitter._handlers, arr = map && map[type] + if (arr) { + var index = indexOf(arr, f) + if (index > -1) + { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } + } + } +} + +function signal(emitter, type /*, values...*/) { + var handlers = getHandlers(emitter, type) + if (!handlers.length) { return } + var args = Array.prototype.slice.call(arguments, 2) + for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } +} + +// The DOM events that CodeMirror handles can be overridden by +// registering a (non-DOM) handler on the editor for the event name, +// and preventDefault-ing the event in that handler. +function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} } + signal(cm, override || e.type, cm, e) + return e_defaultPrevented(e) || e.codemirrorIgnore +} + +function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity + if (!arr) { return } + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) + for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) + { set.push(arr[i]) } } +} + +function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 +} + +// Add on and off methods to a constructor's prototype, to make +// registering events on such objects more convenient. +function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f)} + ctor.prototype.off = function(type, f) {off(this, type, f)} +} + +// Due to the fact that we still support jurassic IE versions, some +// compatibility wrappers are needed. + +function e_preventDefault(e) { + if (e.preventDefault) { e.preventDefault() } + else { e.returnValue = false } +} +function e_stopPropagation(e) { + if (e.stopPropagation) { e.stopPropagation() } + else { e.cancelBubble = true } +} +function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false +} +function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} + +function e_target(e) {return e.target || e.srcElement} +function e_button(e) { + var b = e.which + if (b == null) { + if (e.button & 1) { b = 1 } + else if (e.button & 2) { b = 3 } + else if (e.button & 4) { b = 2 } + } + if (mac && e.ctrlKey && b == 1) { b = 3 } + return b +} + +// Detect drag-and-drop +var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) { return false } + var div = elt('div') + return "draggable" in div || "dragDrop" in div +}() + +var zwspSupported +function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b") + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) + if (measure.firstChild.offsetHeight != 0) + { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) } + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") + node.setAttribute("cm-text", "") + return node +} + +// Feature-detect IE's crummy client rect reporting for bidi text +var badBidiRects +function hasBadBidiRects(measure) { + if (badBidiRects != null) { return badBidiRects } + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) + var r0 = range(txt, 0, 1).getBoundingClientRect() + var r1 = range(txt, 1, 2).getBoundingClientRect() + removeChildren(measure) + if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) +} + +// See if "".split is the broken IE version, if so, provide an +// alternative way to split lines. +var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { + var pos = 0, result = [], l = string.length + while (pos <= l) { + var nl = string.indexOf("\n", pos) + if (nl == -1) { nl = string.length } + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) + var rt = line.indexOf("\r") + if (rt != -1) { + result.push(line.slice(0, rt)) + pos += rt + 1 + } else { + result.push(line) + pos = nl + 1 + } + } + return result +} : function (string) { return string.split(/\r\n?|\n/); } + +var hasSelection = window.getSelection ? function (te) { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } +} : function (te) { + var range + try {range = te.ownerDocument.selection.createRange()} + catch(e) {} + if (!range || range.parentElement() != te) { return false } + return range.compareEndPoints("StartToEnd", range) != 0 +} + +var hasCopyEvent = (function () { + var e = elt("div") + if ("oncopy" in e) { return true } + e.setAttribute("oncopy", "return;") + return typeof e.oncopy == "function" +})() + +var badZoomedRects = null +function hasBadZoomedRects(measure) { + if (badZoomedRects != null) { return badZoomedRects } + var node = removeChildrenAndAdd(measure, elt("span", "x")) + var normal = node.getBoundingClientRect() + var fromRange = range(node, 0, 1).getBoundingClientRect() + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 +} + +var modes = {}; +var mimeModes = {}; +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +function defineMode(name, mode) { + if (arguments.length > 2) + { mode.dependencies = Array.prototype.slice.call(arguments, 2) } + modes[name] = mode +} + +function defineMIME(mime, spec) { + mimeModes[mime] = spec +} + +// Given a MIME type, a {name, ...options} config object, or a name +// string, return a mode config object. +function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec] + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name] + if (typeof found == "string") { found = {name: found} } + spec = createObj(found, spec) + spec.name = found.name + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") { return {name: spec} } + else { return spec || {name: "null"} } +} + +// Given a mode spec (anything that resolveMode accepts), find and +// initialize an actual mode object. +function getMode(options, spec) { + spec = resolveMode(spec) + var mfactory = modes[spec.name] + if (!mfactory) { return getMode(options, "text/plain") } + var modeObj = mfactory(options, spec) + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name] + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) { continue } + if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] } + modeObj[prop] = exts[prop] + } + } + modeObj.name = spec.name + if (spec.helperType) { modeObj.helperType = spec.helperType } + if (spec.modeProps) { for (var prop$1 in spec.modeProps) + { modeObj[prop$1] = spec.modeProps[prop$1] } } + + return modeObj +} + +// This can be used to attach properties to mode objects from +// outside the actual mode definition. +var modeExtensions = {} +function extendMode(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) + copyObj(properties, exts) +} + +function copyState(mode, state) { + if (state === true) { return state } + if (mode.copyState) { return mode.copyState(state) } + var nstate = {} + for (var n in state) { + var val = state[n] + if (val instanceof Array) { val = val.concat([]) } + nstate[n] = val + } + return nstate +} + +// Given a mode and a state (for that mode), find the inner mode and +// state at the position that the state refers to. +function innerMode(mode, state) { + var info + while (mode.innerMode) { + info = mode.innerMode(state) + if (!info || info.mode == mode) { break } + state = info.state + mode = info.mode + } + return info || {mode: mode, state: state} +} + +function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true +} + +// STRING STREAM + +// Fed to the mode parsers, provides helper functions to make +// parsers more succinct. + +var StringStream = function(string, tabSize) { + this.pos = this.start = 0 + this.string = string + this.tabSize = tabSize || 8 + this.lastColumnPos = this.lastColumnValue = 0 + this.lineStart = 0 +}; + +StringStream.prototype.eol = function () {return this.pos >= this.string.length}; +StringStream.prototype.sol = function () {return this.pos == this.lineStart}; +StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; +StringStream.prototype.next = function () { + if (this.pos < this.string.length) + { return this.string.charAt(this.pos++) } +}; +StringStream.prototype.eat = function (match) { + var ch = this.string.charAt(this.pos) + var ok + if (typeof match == "string") { ok = ch == match } + else { ok = ch && (match.test ? match.test(ch) : match(ch)) } + if (ok) {++this.pos; return ch} +}; +StringStream.prototype.eatWhile = function (match) { + var start = this.pos + while (this.eat(match)){} + return this.pos > start +}; +StringStream.prototype.eatSpace = function () { + var this$1 = this; + + var start = this.pos + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos } + return this.pos > start +}; +StringStream.prototype.skipToEnd = function () {this.pos = this.string.length}; +StringStream.prototype.skipTo = function (ch) { + var found = this.string.indexOf(ch, this.pos) + if (found > -1) {this.pos = found; return true} +}; +StringStream.prototype.backUp = function (n) {this.pos -= n}; +StringStream.prototype.column = function () { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) + this.lastColumnPos = this.start + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) +}; +StringStream.prototype.indentation = function () { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) +}; +StringStream.prototype.match = function (pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; } + var substr = this.string.substr(this.pos, pattern.length) + if (cased(substr) == cased(pattern)) { + if (consume !== false) { this.pos += pattern.length } + return true + } + } else { + var match = this.string.slice(this.pos).match(pattern) + if (match && match.index > 0) { return null } + if (match && consume !== false) { this.pos += match[0].length } + return match + } +}; +StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; +StringStream.prototype.hideFirstChars = function (n, inner) { + this.lineStart += n + try { return inner() } + finally { this.lineStart -= n } +}; + +// Compute a style array (an array starting with a mode generation +// -- for invalidation -- followed by pairs of end positions and +// style strings), which is used to highlight the tokens on the +// line. +function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {} + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function (end, style) { return st.push(end, style); }, + lineClasses, forceToEnd) + + // Run overlays, adjust style array. + var loop = function ( o ) { + var overlay = cm.state.overlays[o], i = 1, at = 0 + runMode(cm, line.text, overlay.mode, true, function (end, style) { + var start = i + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i] + if (i_end > end) + { st.splice(i, 1, end, st[i+1], i_end) } + i += 2 + at = Math.min(end, i_end) + } + if (!style) { return } + if (overlay.opaque) { + st.splice(start, i - start, end, "overlay " + style) + i = start + 2 + } else { + for (; start < i; start += 2) { + var cur = st[start+1] + st[start+1] = (cur ? cur + " " : "") + "overlay " + style + } + } + }, lineClasses) + }; + + for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} +} + +function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var state = getStateBefore(cm, lineNo(line)) + var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state) + line.stateAfter = state + line.styles = result.styles + if (result.classes) { line.styleClasses = result.classes } + else if (line.styleClasses) { line.styleClasses = null } + if (updateFrontier === cm.doc.frontier) { cm.doc.frontier++ } + } + return line.styles +} + +function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display + if (!doc.mode.startState) { return true } + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter + if (!state) { state = startState(doc.mode) } + else { state = copyState(doc.mode, state) } + doc.iter(pos, n, function (line) { + processLine(cm, line.text, state) + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo + line.stateAfter = save ? copyState(doc.mode, state) : null + ++pos + }) + if (precise) { doc.frontier = pos } + return state +} + +// Lightweight form of highlight -- proceed over this line and +// update state, but don't save a style array. Used for lines that +// aren't currently visible. +function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode + var stream = new StringStream(text, cm.options.tabSize) + stream.start = stream.pos = startAt || 0 + if (text == "") { callBlankLine(mode, state) } + while (!stream.eol()) { + readToken(mode, stream, state) + stream.start = stream.pos + } +} + +function callBlankLine(mode, state) { + if (mode.blankLine) { return mode.blankLine(state) } + if (!mode.innerMode) { return } + var inner = innerMode(mode, state) + if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } +} + +function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) { inner[0] = innerMode(mode, state).mode } + var style = mode.token(stream, state) + if (stream.pos > stream.start) { return style } + } + throw new Error("Mode " + mode.name + " failed to advance stream.") +} + +// Utility for getTokenAt and getLineTokens +function takeToken(cm, pos, precise, asArray) { + var getObj = function (copy) { return ({ + start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state + }); } + + var doc = cm.doc, mode = doc.mode, style + pos = clipPos(doc, pos) + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise) + var stream = new StringStream(line.text, cm.options.tabSize), tokens + if (asArray) { tokens = [] } + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos + style = readToken(mode, stream, state) + if (asArray) { tokens.push(getObj(true)) } + } + return asArray ? tokens : getObj() +} + +function extractLineClasses(type, output) { + if (type) { for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) + if (!lineClass) { break } + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) + var prop = lineClass[1] ? "bgClass" : "textClass" + if (output[prop] == null) + { output[prop] = lineClass[2] } + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + { output[prop] += " " + lineClass[2] } + } } + return type +} + +// Run the given mode's parser over a line, calling f for each token. +function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans + if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } + var curStart = 0, curStyle = null + var stream = new StringStream(text, cm.options.tabSize), style + var inner = cm.options.addModeClass && [null] + if (text == "") { extractLineClasses(callBlankLine(mode, state), lineClasses) } + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false + if (forceToEnd) { processLine(cm, text, state, stream.pos) } + stream.pos = text.length + style = null + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses) + } + if (inner) { + var mName = inner[0].name + if (mName) { style = "m-" + (style ? mName + " " + style : mName) } + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000) + f(curStart, curStyle) + } + curStyle = style + } + stream.start = stream.pos + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + var pos = Math.min(stream.pos, curStart + 5000) + f(pos, curStyle) + curStart = pos + } +} + +// Finds the line to start with when starting a parse. Tries to +// find a line with a stateAfter, so that it can start with a +// valid state. If that fails, it returns the line with the +// smallest indentation, which tends to need the least context to +// parse correctly. +function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) + for (var search = n; search > lim; --search) { + if (search <= doc.first) { return doc.first } + var line = getLine(doc, search - 1) + if (line.stateAfter && (!precise || search <= doc.frontier)) { return search } + var indented = countColumn(line.text, null, cm.options.tabSize) + if (minline == null || minindent > indented) { + minline = search - 1 + minindent = indented + } + } + return minline +} + +// LINE DATA STRUCTURE + +// Line objects. These hold state related to a line, including +// highlighting info (the styles array). +var Line = function(text, markedSpans, estimateHeight) { + this.text = text + attachMarkedSpans(this, markedSpans) + this.height = estimateHeight ? estimateHeight(this) : 1 +}; + +Line.prototype.lineNo = function () { return lineNo(this) }; +eventMixin(Line) + +// Change the content (text, markers) of a line. Automatically +// invalidates cached information and tries to re-estimate the +// line's height. +function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text + if (line.stateAfter) { line.stateAfter = null } + if (line.styles) { line.styles = null } + if (line.order != null) { line.order = null } + detachMarkedSpans(line) + attachMarkedSpans(line, markedSpans) + var estHeight = estimateHeight ? estimateHeight(line) : 1 + if (estHeight != line.height) { updateLineHeight(line, estHeight) } +} + +// Detach a line from the document tree and its markers. +function cleanUpLine(line) { + line.parent = null + detachMarkedSpans(line) +} + +// Convert a style as returned by a mode (either null, or a string +// containing one or more styles) to a CSS style. This is cached, +// and also looks for line-wide styles. +var styleToClassCache = {}; +var styleToClassCacheWithMode = {}; +function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) { return null } + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) +} + +// Render the DOM representation of the text of a line. Also builds +// up a 'line map', which points at the DOM nodes that represent +// specific stretches of text, and is used by the measuring code. +// The returned object contains the DOM node, this map, and +// information about line-wide styles that were set by the mode. +function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) + var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} + lineView.measure = {} + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0) + builder.pos = 0 + builder.addToken = buildToken + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) + { builder.addToken = buildTokenBadBidi(builder.addToken, order) } + builder.map = [] + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) + if (line.styleClasses) { + if (line.styleClasses.bgClass) + { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") } + if (line.styleClasses.textClass) + { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") } + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) } + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map + lineView.measure.cache = {} + } else { + ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) + } + } + + // See issue #2901 + if (webkit) { + var last = builder.content.lastChild + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + { builder.content.className = "cm-tab-wrap-hack" } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre) + if (builder.pre.className) + { builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") } + + return builder +} + +function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar") + token.title = "\\u" + ch.charCodeAt(0).toString(16) + token.setAttribute("aria-label", token.title) + return token +} + +// Build up the DOM representation for a single token, and add it to +// the line map. Takes care to render special characters separately. +function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) { return } + var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text + var special = builder.cm.state.specialChars, mustWrap = false + var content + if (!special.test(text)) { + builder.col += text.length + content = document.createTextNode(displayText) + builder.map.push(builder.pos, builder.pos + text.length, content) + if (ie && ie_version < 9) { mustWrap = true } + builder.pos += text.length + } else { + content = document.createDocumentFragment() + var pos = 0 + while (true) { + special.lastIndex = pos + var m = special.exec(text) + var skipped = m ? m.index - pos : text.length - pos + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)) + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) } + else { content.appendChild(txt) } + builder.map.push(builder.pos, builder.pos + skipped, txt) + builder.col += skipped + builder.pos += skipped + } + if (!m) { break } + pos += skipped + 1 + var txt$1 = (void 0) + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize + txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) + txt$1.setAttribute("role", "presentation") + txt$1.setAttribute("cm-text", "\t") + builder.col += tabWidth + } else if (m[0] == "\r" || m[0] == "\n") { + txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) + txt$1.setAttribute("cm-text", m[0]) + builder.col += 1 + } else { + txt$1 = builder.cm.options.specialCharPlaceholder(m[0]) + txt$1.setAttribute("cm-text", m[0]) + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) } + else { content.appendChild(txt$1) } + builder.col += 1 + } + builder.map.push(builder.pos, builder.pos + 1, txt$1) + builder.pos++ + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || "" + if (startStyle) { fullStyle += startStyle } + if (endStyle) { fullStyle += endStyle } + var token = elt("span", [content], fullStyle, css) + if (title) { token.title = title } + return builder.content.appendChild(token) + } + builder.content.appendChild(content) +} + +function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) { return text } + var spaceBefore = trailingBefore, result = "" + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i) + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + { ch = "\u00a0" } + result += ch + spaceBefore = ch == " " + } + return result +} + +// Work around nonsense dimensions being reported for stretches of +// right-to-left text. +function buildTokenBadBidi(inner, order) { + return function (builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border" + var start = builder.pos, end = start + text.length + for (;;) { + // Find the part that overlaps with the start of this text + var part = (void 0) + for (var i = 0; i < order.length; i++) { + part = order[i] + if (part.to > start && part.from <= start) { break } + } + if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) } + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css) + startStyle = null + text = text.slice(part.to - start) + start = part.to + } + } +} + +function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode + if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) } + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + { widget = builder.content.appendChild(document.createElement("span")) } + widget.setAttribute("cm-marker", marker.id) + } + if (widget) { + builder.cm.display.input.setUneditable(widget) + builder.content.appendChild(widget) + } + builder.pos += size + builder.trailingSpace = false +} + +// Outputs a number of spans to make up a line, taking highlighting +// and marked text into account. +function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0 + if (!spans) { + for (var i$1 = 1; i$1 < styles.length; i$1+=2) + { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)) } + return + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = "" + collapsed = null; nextChange = Infinity + var foundBookmarks = [], endStyles = (void 0) + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m) + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to + spanEndStyle = "" + } + if (m.className) { spanStyle += " " + m.className } + if (m.css) { css = (css ? css + ";" : "") + m.css } + if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle } + if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to) } + if (m.title && !title) { title = m.title } + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + { collapsed = sp } + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from + } + } + if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) + { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1] } } } + + if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) + { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null) + if (collapsed.to == null) { return } + if (collapsed.to == pos) { collapsed = false } + } + } + if (pos >= len) { break } + + var upto = Math.min(len, nextChange) + while (true) { + if (text) { + var end = pos + text.length + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css) + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end + spanStartStyle = "" + } + text = allText.slice(at, at = styles[i++]) + style = interpretTokenStyle(styles[i++], builder.cm.options) + } + } +} + + +// These objects are used to represent the visible (currently drawn) +// part of the document. A LineView may correspond to multiple +// logical lines, if those are connected by collapsed ranges. +function LineView(doc, line, lineN) { + // The starting line + this.line = line + // Continuing lines, if any + this.rest = visualLineContinued(line) + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 + this.node = this.text = null + this.hidden = lineIsHidden(doc, line) +} + +// Create a range of LineView objects for the given lines. +function buildViewArray(cm, from, to) { + var array = [], nextPos + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos) + nextPos = pos + view.size + array.push(view) + } + return array +} + +var operationGroup = null + +function pushOperation(op) { + if (operationGroup) { + operationGroup.ops.push(op) + } else { + op.ownsGroup = operationGroup = { + ops: [op], + delayedCallbacks: [] + } + } +} + +function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0 + do { + for (; i < callbacks.length; i++) + { callbacks[i].call(null) } + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j] + if (op.cursorActivityHandlers) + { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } } + } + } while (i < callbacks.length) +} + +function finishOperation(op, endCb) { + var group = op.ownsGroup + if (!group) { return } + + try { fireCallbacksForOps(group) } + finally { + operationGroup = null + endCb(group) + } +} + +var orphanDelayedCallbacks = null + +// Often, we want to signal events at a point where we are in the +// middle of some work, but don't want the handler to start calling +// other methods on the editor, which might be in an inconsistent +// state or simply not expect any other events to happen. +// signalLater looks whether there are any handlers, and schedules +// them to be executed when the last operation ends, or, if no +// operation is active, when a timeout fires. +function signalLater(emitter, type /*, values...*/) { + var arr = getHandlers(emitter, type) + if (!arr.length) { return } + var args = Array.prototype.slice.call(arguments, 2), list + if (operationGroup) { + list = operationGroup.delayedCallbacks + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks + } else { + list = orphanDelayedCallbacks = [] + setTimeout(fireOrphanDelayed, 0) + } + var loop = function ( i ) { + list.push(function () { return arr[i].apply(null, args); }) + }; + + for (var i = 0; i < arr.length; ++i) + loop( i ); +} + +function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks + orphanDelayedCallbacks = null + for (var i = 0; i < delayed.length; ++i) { delayed[i]() } +} + +// When an aspect of a line changes, a string is added to +// lineView.changes. This updates the relevant part of the line's +// DOM structure. +function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j] + if (type == "text") { updateLineText(cm, lineView) } + else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) } + else if (type == "class") { updateLineClasses(cm, lineView) } + else if (type == "widget") { updateLineWidgets(cm, lineView, dims) } + } + lineView.changes = null +} + +// Lines with gutter elements, widgets or a background class need to +// be wrapped, and have the extra elements added to the wrapper div +function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative") + if (lineView.text.parentNode) + { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) } + lineView.node.appendChild(lineView.text) + if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 } + } + return lineView.node +} + +function updateLineBackground(cm, lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass + if (cls) { cls += " CodeMirror-linebackground" } + if (lineView.background) { + if (cls) { lineView.background.className = cls } + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } + } else if (cls) { + var wrap = ensureLineWrapped(lineView) + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) + cm.display.input.setUneditable(lineView.background) + } +} + +// Wrapper around buildLineContent which will reuse the structure +// in display.externalMeasured when possible. +function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null + lineView.measure = ext.measure + return ext.built + } + return buildLineContent(cm, lineView) +} + +// Redraw the line's text. Interacts with the background and text +// classes because the mode may output tokens that influence these +// classes. +function updateLineText(cm, lineView) { + var cls = lineView.text.className + var built = getLineContent(cm, lineView) + if (lineView.text == lineView.node) { lineView.node = built.pre } + lineView.text.parentNode.replaceChild(built.pre, lineView.text) + lineView.text = built.pre + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass + lineView.textClass = built.textClass + updateLineClasses(cm, lineView) + } else if (cls) { + lineView.text.className = cls + } +} + +function updateLineClasses(cm, lineView) { + updateLineBackground(cm, lineView) + if (lineView.line.wrapClass) + { ensureLineWrapped(lineView).className = lineView.line.wrapClass } + else if (lineView.node != lineView.text) + { lineView.node.className = "" } + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass + lineView.text.className = textClass || "" +} + +function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter) + lineView.gutter = null + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground) + lineView.gutterBackground = null + } + if (lineView.line.gutterClass) { + var wrap = ensureLineWrapped(lineView) + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")) + cm.display.input.setUneditable(lineView.gutterBackground) + wrap.insertBefore(lineView.gutterBackground, lineView.text) + } + var markers = lineView.line.gutterMarkers + if (cm.options.lineNumbers || markers) { + var wrap$1 = ensureLineWrapped(lineView) + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")) + cm.display.input.setUneditable(gutterWrap) + wrap$1.insertBefore(gutterWrap, lineView.text) + if (lineView.line.gutterClass) + { gutterWrap.className += " " + lineView.line.gutterClass } + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + { lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))) } + if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id] + if (found) + { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))) } + } } + } +} + +function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) { lineView.alignable = null } + for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { + next = node.nextSibling + if (node.className == "CodeMirror-linewidget") + { lineView.node.removeChild(node) } + } + insertLineWidgets(cm, lineView, dims) +} + +// Build a line's DOM representation from scratch +function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView) + lineView.text = lineView.node = built.pre + if (built.bgClass) { lineView.bgClass = built.bgClass } + if (built.textClass) { lineView.textClass = built.textClass } + + updateLineClasses(cm, lineView) + updateLineGutter(cm, lineView, lineN, dims) + insertLineWidgets(cm, lineView, dims) + return lineView.node +} + +// A lineView may contain multiple logical lines (when merged by +// collapsed spans). The widgets for all of them need to be drawn. +function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } } +} + +function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) { return } + var wrap = ensureLineWrapped(lineView) + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget") + if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true") } + positionLineWidget(widget, node, lineView, dims) + cm.display.input.setUneditable(node) + if (allowAbove && widget.above) + { wrap.insertBefore(node, lineView.gutter || lineView.text) } + else + { wrap.appendChild(node) } + signalLater(widget, "redraw") + } +} + +function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + ;(lineView.alignable || (lineView.alignable = [])).push(node) + var width = dims.wrapperWidth + node.style.left = dims.fixedPos + "px" + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth + node.style.paddingLeft = dims.gutterTotalWidth + "px" + } + node.style.width = width + "px" + } + if (widget.coverGutter) { + node.style.zIndex = 5 + node.style.position = "relative" + if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px" } + } +} + +function widgetHeight(widget) { + if (widget.height != null) { return widget.height } + var cm = widget.doc.cm + if (!cm) { return 0 } + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;" + if (widget.coverGutter) + { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" } + if (widget.noHScroll) + { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" } + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) + } + return widget.height = widget.node.parentNode.offsetHeight +} + +// Return true when the given mouse event happened in a widget +function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + { return true } + } +} + +// POSITION MEASUREMENT + +function paddingTop(display) {return display.lineSpace.offsetTop} +function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} +function paddingH(display) { + if (display.cachedPaddingH) { return display.cachedPaddingH } + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")) + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} + if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data } + return data +} + +function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } +function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth +} +function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight +} + +// Ensure the lineView.wrapping.heights array is populated. This is +// an array of bottom offsets for the lines that make up a drawn +// line. When lineWrapping is on, there might be more than one +// height. +function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping + var curWidth = wrapping && displayWidth(cm) + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = [] + if (wrapping) { + lineView.measure.width = curWidth + var rects = lineView.text.firstChild.getClientRects() + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1] + if (Math.abs(cur.bottom - next.bottom) > 2) + { heights.push((cur.bottom + next.top) / 2 - rect.top) } + } + } + heights.push(rect.bottom - rect.top) + } +} + +// Find a line map (mapping character offsets to text nodes) and a +// measurement cache for the given line number. (A line view might +// contain multiple lines when collapsed ranges are present.) +function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + { return {map: lineView.measure.map, cache: lineView.measure.cache} } + for (var i = 0; i < lineView.rest.length; i++) + { if (lineView.rest[i] == line) + { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } + for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) + { if (lineNo(lineView.rest[i$1]) > lineN) + { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } +} + +// Render a line into the hidden node display.externalMeasured. Used +// when measurement is needed for a line that's not in the viewport. +function updateExternalMeasurement(cm, line) { + line = visualLine(line) + var lineN = lineNo(line) + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) + view.lineN = lineN + var built = view.built = buildLineContent(cm, view) + view.text = built.pre + removeChildrenAndAdd(cm.display.lineMeasure, built.pre) + return view +} + +// Get a {top, bottom, left, right} box (in line-local coordinates) +// for a given character. +function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) +} + +// Find a line view that corresponds to the given line number. +function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + { return cm.display.view[findViewIndex(cm, lineN)] } + var ext = cm.display.externalMeasured + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + { return ext } +} + +// Measurement can be split in two steps, the set-up work that +// applies to the whole line, and the measurement of the actual +// character. Functions like coordsChar, that need to do a lot of +// measurements in a row, can thus ensure that the set-up work is +// only done once. +function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line) + var view = findViewForLine(cm, lineN) + if (view && !view.text) { + view = null + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)) + cm.curOp.forceUpdate = true + } + if (!view) + { view = updateExternalMeasurement(cm, line) } + + var info = mapFromLineView(view, line, lineN) + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + } +} + +// Given a prepared measurement object, measures the position of an +// actual character (or fetches it from the cache). +function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) { ch = -1 } + var key = ch + (bias || ""), found + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key] + } else { + if (!prepared.rect) + { prepared.rect = prepared.view.text.getBoundingClientRect() } + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect) + prepared.hasHeights = true + } + found = measureCharInner(cm, prepared, ch, bias) + if (!found.bogus) { prepared.cache[key] = found } + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom} +} + +var nullRect = {left: 0, right: 0, top: 0, bottom: 0} + +function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse, mStart, mEnd + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + mStart = map[i] + mEnd = map[i + 1] + if (ch < mStart) { + start = 0; end = 1 + collapse = "left" + } else if (ch < mEnd) { + start = ch - mStart + end = start + 1 + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart + start = end - 1 + if (ch >= mEnd) { collapse = "right" } + } + if (start != null) { + node = map[i + 2] + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + { collapse = bias } + if (bias == "left" && start == 0) + { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2] + collapse = "left" + } } + if (bias == "right" && start == mEnd - mStart) + { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2] + collapse = "right" + } } + break + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} +} + +function getUsefulRect(rects, bias) { + var rect = nullRect + if (bias == "left") { for (var i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) { break } + } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { + if ((rect = rects[i$1]).left != rect.right) { break } + } } + return rect +} + +function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias) + var node = place.node, start = place.start, end = place.end, collapse = place.collapse + + var rect + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start } + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end } + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + { rect = node.parentNode.getBoundingClientRect() } + else + { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) } + if (rect.left || rect.right || start == 0) { break } + end = start + start = start - 1 + collapse = "right" + } + if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect) } + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) { collapse = bias = "right" } + var rects + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + { rect = rects[bias == "right" ? rects.length - 1 : 0] } + else + { rect = node.getBoundingClientRect() } + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0] + if (rSpan) + { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} } + else + { rect = nullRect } + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top + var mid = (rtop + rbot) / 2 + var heights = prepared.view.measure.heights + var i = 0 + for (; i < heights.length - 1; i++) + { if (mid < heights[i]) { break } } + var top = i ? heights[i - 1] : 0, bot = heights[i] + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot} + if (!rect.left && !rect.right) { result.bogus = true } + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } + + return result +} + +// Work around problem with bounding client rects on ranges being +// returned incorrectly when zoomed on IE10 and below. +function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + { return rect } + var scaleX = screen.logicalXDPI / screen.deviceXDPI + var scaleY = screen.logicalYDPI / screen.deviceYDPI + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY} +} + +function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {} + lineView.measure.heights = null + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { lineView.measure.caches[i] = {} } } + } +} + +function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null + removeChildren(cm.display.lineMeasure) + for (var i = 0; i < cm.display.view.length; i++) + { clearLineMeasurementCacheFor(cm.display.view[i]) } +} + +function clearCaches(cm) { + clearLineMeasurementCache(cm) + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null + if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true } + cm.display.lineNumChars = null +} + +function pageScrollX() { + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 + // which causes page_Offset and bounding client rects to use + // different reference viewports and invalidate our calculations. + if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } + return window.pageXOffset || (document.documentElement || document.body).scrollLeft +} +function pageScrollY() { + if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } + return window.pageYOffset || (document.documentElement || document.body).scrollTop +} + +// Converts a {top, bottom, left, right} box from line-local +// coordinates into another coordinate system. Context may be one of +// "line", "div" (display.lineDiv), "local"./null (editor), "window", +// or "page". +function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { + if (!includeWidgets && lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]) + rect.top += size; rect.bottom += size + } } } + if (context == "line") { return rect } + if (!context) { context = "local" } + var yOff = heightAtLine(lineObj) + if (context == "local") { yOff += paddingTop(cm.display) } + else { yOff -= cm.display.viewOffset } + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect() + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) + rect.left += xOff; rect.right += xOff + } + rect.top += yOff; rect.bottom += yOff + return rect +} + +// Coverts a box from "div" coords to another coordinate system. +// Context may be "window", "page", "div", or "local"./null. +function fromCoordSystem(cm, coords, context) { + if (context == "div") { return coords } + var left = coords.left, top = coords.top + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX() + top -= pageScrollY() + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect() + left += localBox.left + top += localBox.top + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} +} + +function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) { lineObj = getLine(cm.doc, pos.line) } + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) +} + +// Returns a box for a given cursor position, which may have an +// 'other' property containing the position of the secondary cursor +// on a bidi boundary. +// A cursor Pos(line, char, "before") is on the same visual line as `char - 1` +// and after `char - 1` in writing order of `char - 1` +// A cursor Pos(line, char, "after") is on the same visual line as `char` +// and before `char` in writing order of `char` +// Examples (upper-case letters are RTL, lower-case are LTR): +// Pos(0, 1, ...) +// before after +// ab a|b a|b +// aB a|B aB| +// Ab |Ab A|b +// AB B|A B|A +// Every position after the last character on a line is considered to stick +// to the last character on the line. +function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line) + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) + if (right) { m.left = m.right; } else { m.right = m.left } + return intoCoordSystem(cm, lineObj, m, context) + } + var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky + if (ch >= lineObj.text.length) { + ch = lineObj.text.length + sticky = "before" + } else if (ch <= 0) { + ch = 0 + sticky = "after" + } + if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } + + function getBidi(ch, partPos, invert) { + var part = order[partPos], right = (part.level % 2) != 0 + return get(invert ? ch - 1 : ch, right != invert) + } + var partPos = getBidiPartAt(order, ch, sticky) + var other = bidiOther + var val = getBidi(ch, partPos, sticky == "before") + if (other != null) { val.other = getBidi(ch, other, sticky != "before") } + return val +} + +// Used to cheaply estimate the coordinates for a position. Used for +// intermediate scroll updates. +function estimateCoords(cm, pos) { + var left = 0 + pos = clipPos(cm.doc, pos) + if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch } + var lineObj = getLine(cm.doc, pos.line) + var top = heightAtLine(lineObj) + paddingTop(cm.display) + return {left: left, right: left, top: top, bottom: top + lineObj.height} +} + +// Positions returned by coordsChar contain some extra information. +// xRel is the relative x position of the input coordinates compared +// to the found position (so xRel > 0 means the coordinates are to +// the right of the character position, for example). When outside +// is true, that means the coordinates lie outside the line's +// vertical range. +function PosWithInfo(line, ch, sticky, outside, xRel) { + var pos = Pos(line, ch, sticky) + pos.xRel = xRel + if (outside) { pos.outside = true } + return pos +} + +// Compute the character position closest to the given coordinates. +// Input must be lineSpace-local ("div" coordinate system). +function coordsChar(cm, x, y) { + var doc = cm.doc + y += cm.display.viewOffset + if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 + if (lineN > last) + { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } + if (x < 0) { x = 0 } + + var lineObj = getLine(doc, lineN) + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y) + var merged = collapsedSpanAtEnd(lineObj) + var mergedPos = merged && merged.find(0, true) + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + { lineN = lineNo(lineObj = mergedPos.to.line) } + else + { return found } + } +} + +function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { + var measure = function (ch) { return intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line"); } + var end = lineObj.text.length + var begin = findFirst(function (ch) { return measure(ch - 1).bottom <= y; }, end, 0) + end = findFirst(function (ch) { return measure(ch).top > y; }, begin, end) + return {begin: begin, end: end} +} + +function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { + var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top + return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) +} + +function coordsCharInner(cm, lineObj, lineNo, x, y) { + y -= heightAtLine(lineObj) + var begin = 0, end = lineObj.text.length + var preparedMeasure = prepareMeasureForLine(cm, lineObj) + var pos + var order = getOrder(lineObj, cm.doc.direction) + if (order) { + if (cm.options.lineWrapping) { + ;var assign; + ((assign = wrappedLineExtent(cm, lineObj, preparedMeasure, y), begin = assign.begin, end = assign.end, assign)) + } + pos = new Pos(lineNo, begin) + var beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left + var dir = beginLeft < x ? 1 : -1 + var prevDiff, diff = beginLeft - x, prevPos + do { + prevDiff = diff + prevPos = pos + pos = moveVisually(cm, lineObj, pos, dir) + if (pos == null || pos.ch < begin || end <= (pos.sticky == "before" ? pos.ch - 1 : pos.ch)) { + pos = prevPos + break + } + diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x + } while ((dir < 0) != (diff < 0) && (Math.abs(diff) <= Math.abs(prevDiff))) + if (Math.abs(diff) > Math.abs(prevDiff)) { + if ((diff < 0) == (prevDiff < 0)) { throw new Error("Broke out of infinite loop in coordsCharInner") } + pos = prevPos + } + } else { + var ch = findFirst(function (ch) { + var box = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line") + if (box.top > y) { + // For the cursor stickiness + end = Math.min(ch, end) + return true + } + else if (box.bottom <= y) { return false } + else if (box.left > x) { return true } + else if (box.right < x) { return false } + else { return (x - box.left < box.right - x) } + }, begin, end) + ch = skipExtendingChars(lineObj.text, ch, 1) + pos = new Pos(lineNo, ch, ch == end ? "before" : "after") + } + var coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure) + if (y < coords.top || coords.bottom < y) { pos.outside = true } + pos.xRel = x < coords.left ? -1 : (x > coords.right ? 1 : 0) + return pos +} + +var measureText +// Compute the default text height. +function textHeight(display) { + if (display.cachedTextHeight != null) { return display.cachedTextHeight } + if (measureText == null) { + measureText = elt("pre") + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")) + measureText.appendChild(elt("br")) + } + measureText.appendChild(document.createTextNode("x")) + } + removeChildrenAndAdd(display.measure, measureText) + var height = measureText.offsetHeight / 50 + if (height > 3) { display.cachedTextHeight = height } + removeChildren(display.measure) + return height || 1 +} + +// Compute the default character width. +function charWidth(display) { + if (display.cachedCharWidth != null) { return display.cachedCharWidth } + var anchor = elt("span", "xxxxxxxxxx") + var pre = elt("pre", [anchor]) + removeChildrenAndAdd(display.measure, pre) + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 + if (width > 2) { display.cachedCharWidth = width } + return width || 10 +} + +// Do a bulk-read of the DOM positions and sizes needed to draw the +// view, so that we don't interleave reading and writing to the DOM. +function getDimensions(cm) { + var d = cm.display, left = {}, width = {} + var gutterLeft = d.gutters.clientLeft + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft + width[cm.options.gutters[i]] = n.clientWidth + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth} +} + +// Computes display.scroller.scrollLeft + display.gutters.offsetWidth, +// but using getBoundingClientRect to get a sub-pixel-accurate +// result. +function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left +} + +// Returns a function that estimates the height of a line, to use as +// first approximation until the line becomes visible (and is thus +// properly measurable). +function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) + return function (line) { + if (lineIsHidden(cm.doc, line)) { return 0 } + + var widgetsHeight = 0 + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height } + } } + + if (wrapping) + { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } + else + { return widgetsHeight + th } + } +} + +function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm) + doc.iter(function (line) { + var estHeight = est(line) + if (estHeight != line.height) { updateLineHeight(line, estHeight) } + }) +} + +// Given a mouse event, find the corresponding position. If liberal +// is false, it checks whether a gutter or scrollbar was clicked, +// and returns null if it was. forRect is used by rectangular +// selections, and tries to estimate a character position even for +// coordinates beyond the right of the text. +function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } + + var x, y, space = display.lineSpace.getBoundingClientRect() + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top } + catch (e) { return null } + var coords = coordsChar(cm, x, y), line + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) + } + return coords +} + +// Find the view element corresponding to a given line. Return null +// when the line isn't visible. +function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) { return null } + n -= cm.display.viewFrom + if (n < 0) { return null } + var view = cm.display.view + for (var i = 0; i < view.length; i++) { + n -= view[i].size + if (n < 0) { return i } + } +} + +function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()) +} + +function prepareSelection(cm, primary) { + var doc = cm.doc, result = {} + var curFragment = result.cursors = document.createDocumentFragment() + var selFragment = result.selection = document.createDocumentFragment() + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) { continue } + var range = doc.sel.ranges[i] + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } + var collapsed = range.empty() + if (collapsed || cm.options.showCursorWhenSelecting) + { drawSelectionCursor(cm, range.head, curFragment) } + if (!collapsed) + { drawSelectionRange(cm, range, selFragment) } + } + return result +} + +// Draws a cursor for the given range +function drawSelectionCursor(cm, head, output) { + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) + cursor.style.left = pos.left + "px" + cursor.style.top = pos.top + "px" + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) + otherCursor.style.display = "" + otherCursor.style.left = pos.other.left + "px" + otherCursor.style.top = pos.other.top + "px" + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" + } +} + +// Draws the given range as a highlighted selection +function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc + var fragment = document.createDocumentFragment() + var padding = paddingH(cm.display), leftSide = padding.left + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right + + function add(left, top, width, bottom) { + if (top < 0) { top = 0 } + top = Math.round(top) + bottom = Math.round(bottom) + fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))) + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line) + var lineLen = lineObj.text.length + var start, end + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right + if (from == to) { + rightPos = leftPos + left = right = leftPos.left + } else { + rightPos = coords(to - 1, "right") + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp } + left = leftPos.left + right = rightPos.right + } + if (fromArg == null && from == 0) { left = leftSide } + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom) + left = leftSide + if (leftPos.bottom < rightPos.top) { add(left, leftPos.bottom, null, rightPos.top) } + } + if (toArg == null && to == lineLen) { right = rightSide } + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + { start = leftPos } + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + { end = rightPos } + if (left < leftSide + 1) { left = leftSide } + add(left, rightPos.top, right - left, rightPos.bottom) + }) + return {start: start, end: end} + } + + var sFrom = range.from(), sTo = range.to() + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch) + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) + var singleVLine = visualLine(fromLine) == visualLine(toLine) + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) + } + } + if (leftEnd.bottom < rightStart.top) + { add(leftSide, leftEnd.bottom, null, rightStart.top) } + } + + output.appendChild(fragment) +} + +// Cursor-blinking +function restartBlink(cm) { + if (!cm.state.focused) { return } + var display = cm.display + clearInterval(display.blinker) + var on = true + display.cursorDiv.style.visibility = "" + if (cm.options.cursorBlinkRate > 0) + { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, + cm.options.cursorBlinkRate) } + else if (cm.options.cursorBlinkRate < 0) + { display.cursorDiv.style.visibility = "hidden" } +} + +function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } +} + +function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true + setTimeout(function () { if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false + onBlur(cm) + } }, 100) +} + +function onFocus(cm, e) { + if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false } + + if (cm.options.readOnly == "nocursor") { return } + if (!cm.state.focused) { + signal(cm, "focus", cm, e) + cm.state.focused = true + addClass(cm.display.wrapper, "CodeMirror-focused") + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset() + if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20) } // Issue #1730 + } + cm.display.input.receivedFocus() + } + restartBlink(cm) +} +function onBlur(cm, e) { + if (cm.state.delayingBlurEvent) { return } + + if (cm.state.focused) { + signal(cm, "blur", cm, e) + cm.state.focused = false + rmClass(cm.display.wrapper, "CodeMirror-focused") + } + clearInterval(cm.display.blinker) + setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150) +} + +// Re-align line numbers and gutter marks to compensate for +// horizontal scrolling. +function alignHorizontally(cm) { + var display = cm.display, view = display.view + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft + var gutterW = display.gutters.offsetWidth, left = comp + "px" + for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { + if (cm.options.fixedGutter) { + if (view[i].gutter) + { view[i].gutter.style.left = left } + if (view[i].gutterBackground) + { view[i].gutterBackground.style.left = left } + } + var align = view[i].alignable + if (align) { for (var j = 0; j < align.length; j++) + { align[j].style.left = left } } + } } + if (cm.options.fixedGutter) + { display.gutters.style.left = (comp + gutterW) + "px" } +} + +// Used to ensure that the line number gutter is still the right +// size for the current document size. Returns true when an update +// is needed. +function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) { return false } + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")) + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW + display.lineGutter.style.width = "" + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 + display.lineNumWidth = display.lineNumInnerWidth + padding + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 + display.lineGutter.style.width = display.lineNumWidth + "px" + updateGutterSpace(cm) + return true + } + return false +} + +// Read the actual heights of the rendered lines, and update their +// stored heights to match. +function updateHeightsInViewport(cm) { + var display = cm.display + var prevBottom = display.lineDiv.offsetTop + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height = (void 0) + if (cur.hidden) { continue } + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight + height = bot - prevBottom + prevBottom = bot + } else { + var box = cur.node.getBoundingClientRect() + height = box.bottom - box.top + } + var diff = cur.line.height - height + if (height < 2) { height = textHeight(display) } + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height) + updateWidgetHeight(cur.line) + if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) + { updateWidgetHeight(cur.rest[j]) } } + } + } +} + +// Read and store the height of line widgets associated with the +// given line. +function updateWidgetHeight(line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) + { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } } +} + +// Compute the lines that are visible in a given viewport (defaults +// the the current scroll position). viewport may contain top, +// height, and ensure (see op.scrollToPos) properties. +function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop + top = Math.floor(top - paddingTop(display)) + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line + if (ensureFrom < from) { + from = ensureFrom + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) + to = ensureTo + } + } + return {from: from, to: Math.max(to, from + 1)} +} + +// Sync the scrollable area and scrollbars, ensure the viewport +// covers the visible area. +function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) { return } + cm.doc.scrollTop = val + if (!gecko) { updateDisplaySimple(cm, {top: val}) } + if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } + cm.display.scrollbars.setScrollTop(val) + if (gecko) { updateDisplaySimple(cm) } + startWorker(cm, 100) +} +// Sync scroller and scrollbar, ensure the gutter elements are +// aligned. +function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) { return } + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) + cm.doc.scrollLeft = val + alignHorizontally(cm) + if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val } + cm.display.scrollbars.setScrollLeft(val) +} + +// Since the delta values reported on mouse wheel events are +// unstandardized between browsers and even browser versions, and +// generally horribly unpredictable, this code starts by measuring +// the scroll effect that the first few mouse wheel events have, +// and, from that, detects the way it can convert deltas to pixel +// offsets afterwards. +// +// The reason we want to know the amount a wheel event will scroll +// is that it gives us a chance to update the display before the +// actual scrolling happens, reducing flickering. + +var wheelSamples = 0; +var wheelPixelsPerUnit = null; +// Fill in a browser-detected starting value on browsers where we +// know one. These don't have to be accurate -- the result of them +// being wrong would just be a slight flicker on the first wheel +// scroll (if it is large enough). +if (ie) { wheelPixelsPerUnit = -.53 } +else if (gecko) { wheelPixelsPerUnit = 15 } +else if (chrome) { wheelPixelsPerUnit = -.7 } +else if (safari) { wheelPixelsPerUnit = -1/3 } + +function wheelEventDelta(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } + else if (dy == null) { dy = e.wheelDelta } + return {x: dx, y: dy} +} +function wheelEventPixels(e) { + var delta = wheelEventDelta(e) + delta.x *= wheelPixelsPerUnit + delta.y *= wheelPixelsPerUnit + return delta +} + +function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y + + var display = cm.display, scroll = display.scroller + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth + var canScrollY = scroll.scrollHeight > scroll.clientHeight + if (!(dx && canScrollX || dy && canScrollY)) { return } + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + { setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))) } + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))) + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + { e_preventDefault(e) } + display.wheelStartX = null // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight + if (pixels < 0) { top = Math.max(0, top + pixels - 50) } + else { bot = Math.min(cm.doc.height, bot + pixels + 50) } + updateDisplaySimple(cm, {top: top, bottom: bot}) + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop + display.wheelDX = dx; display.wheelDY = dy + setTimeout(function () { + if (display.wheelStartX == null) { return } + var movedX = scroll.scrollLeft - display.wheelStartX + var movedY = scroll.scrollTop - display.wheelStartY + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX) + display.wheelStartX = display.wheelStartY = null + if (!sample) { return } + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) + ++wheelSamples + }, 200) + } else { + display.wheelDX += dx; display.wheelDY += dy + } + } +} + +// SCROLLBARS + +// Prepare DOM reads needed to update the scrollbars. Done in one +// shot to minimize update/measure roundtrips. +function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth + var docH = Math.round(cm.doc.height + paddingVert(cm.display)) + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + } +} + +var NativeScrollbars = function(place, scroll, cm) { + this.cm = cm + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") + place(vert); place(horiz) + + on(vert, "scroll", function () { + if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") } + }) + on(horiz, "scroll", function () { + if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") } + }) + + this.checkedZeroWidth = false + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" } +}; + +NativeScrollbars.prototype.update = function (measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1 + var needsV = measure.scrollHeight > measure.clientHeight + 1 + var sWidth = measure.nativeBarWidth + + if (needsV) { + this.vert.style.display = "block" + this.vert.style.bottom = needsH ? sWidth + "px" : "0" + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0) + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" + } else { + this.vert.style.display = "" + this.vert.firstChild.style.height = "0" + } + + if (needsH) { + this.horiz.style.display = "block" + this.horiz.style.right = needsV ? sWidth + "px" : "0" + this.horiz.style.left = measure.barLeft + "px" + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) + this.horiz.firstChild.style.width = + Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" + } else { + this.horiz.style.display = "" + this.horiz.firstChild.style.width = "0" + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) { this.zeroWidthHack() } + this.checkedZeroWidth = true + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} +}; + +NativeScrollbars.prototype.setScrollLeft = function (pos) { + if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } + if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") } +}; + +NativeScrollbars.prototype.setScrollTop = function (pos) { + if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } + if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert") } +}; + +NativeScrollbars.prototype.zeroWidthHack = function () { + var w = mac && !mac_geMountainLion ? "12px" : "18px" + this.horiz.style.height = this.vert.style.width = w + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" + this.disableHoriz = new Delayed + this.disableVert = new Delayed +}; + +NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { + bar.style.pointerEvents = "auto" + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // right corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect() + var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) + : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) + if (elt != bar) { bar.style.pointerEvents = "none" } + else { delay.set(1000, maybeDisable) } + } + delay.set(1000, maybeDisable) +}; + +NativeScrollbars.prototype.clear = function () { + var parent = this.horiz.parentNode + parent.removeChild(this.horiz) + parent.removeChild(this.vert) +}; + +var NullScrollbars = function () {}; + +NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; +NullScrollbars.prototype.setScrollLeft = function () {}; +NullScrollbars.prototype.setScrollTop = function () {}; +NullScrollbars.prototype.clear = function () {}; + +function updateScrollbars(cm, measure) { + if (!measure) { measure = measureForScrollbars(cm) } + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight + updateScrollbarsInner(cm, measure) + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + { updateHeightsInViewport(cm) } + updateScrollbarsInner(cm, measureForScrollbars(cm)) + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight + } +} + +// Re-synchronize the fake scrollbars with the actual size of the +// content. +function updateScrollbarsInner(cm, measure) { + var d = cm.display + var sizes = d.scrollbars.update(measure) + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block" + d.scrollbarFiller.style.height = sizes.bottom + "px" + d.scrollbarFiller.style.width = sizes.right + "px" + } else { d.scrollbarFiller.style.display = "" } + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block" + d.gutterFiller.style.height = sizes.bottom + "px" + d.gutterFiller.style.width = measure.gutterWidth + "px" + } else { d.gutterFiller.style.display = "" } +} + +var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} + +function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear() + if (cm.display.scrollbars.addClass) + { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } + } + + cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function () { + if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0) } + }) + node.setAttribute("cm-not-content", "true") + }, function (pos, axis) { + if (axis == "horizontal") { setScrollLeft(cm, pos) } + else { setScrollTop(cm, pos) } + }, cm) + if (cm.display.scrollbars.addClass) + { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } +} + +// SCROLLING THINGS INTO VIEW + +// If an editor sits on the top or bottom of the window, partially +// scrolled out of view, this ensures that the cursor is visible. +function maybeScrollWindow(cm, rect) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null + if (rect.top + box.top < 0) { doScroll = true } + else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")) + cm.display.lineSpace.appendChild(scrollNode) + scrollNode.scrollIntoView(doScroll) + cm.display.lineSpace.removeChild(scrollNode) + } +} + +// Scroll a given position into view (immediately), verifying that +// it actually became visible (as line heights are accurately +// measured, the position of something may 'drift' during drawing). +function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) { margin = 0 } + var rect + for (var limit = 0; limit < 5; limit++) { + var changed = false + var coords = cursorCoords(cm, pos) + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) + rect = {left: Math.min(coords.left, endCoords.left), + top: Math.min(coords.top, endCoords.top) - margin, + right: Math.max(coords.left, endCoords.left), + bottom: Math.max(coords.bottom, endCoords.bottom) + margin} + var scrollPos = calculateScrollPos(cm, rect) + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop) + if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft) + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } + } + if (!changed) { break } + } + return rect +} + +// Scroll a given set of coordinates into view (immediately). +function scrollIntoView(cm, rect) { + var scrollPos = calculateScrollPos(cm, rect) + if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop) } + if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } +} + +// Calculate a new scroll position needed to scroll the given +// rectangle into view. Returns an object with scrollTop and +// scrollLeft properties. When these are undefined, the +// vertical/horizontal position does not need to be adjusted. +function calculateScrollPos(cm, rect) { + var display = cm.display, snapMargin = textHeight(cm.display) + if (rect.top < 0) { rect.top = 0 } + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop + var screen = displayHeight(cm), result = {} + if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } + var docBottom = cm.doc.height + paddingVert(display) + var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin + if (rect.top < screentop) { + result.scrollTop = atTop ? 0 : rect.top + } else if (rect.bottom > screentop + screen) { + var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) + if (newTop != screentop) { result.scrollTop = newTop } + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) + var tooWide = rect.right - rect.left > screenw + if (tooWide) { rect.right = rect.left + screenw } + if (rect.left < 10) + { result.scrollLeft = 0 } + else if (rect.left < screenleft) + { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } + else if (rect.right > screenw + screenleft - 3) + { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } + return result +} + +// Store a relative adjustment to the scroll position in the current +// operation (to be applied when the operation finishes). +function addToScrollPos(cm, left, top) { + if (left != null || top != null) { resolveScrollToPos(cm) } + if (left != null) + { cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left } + if (top != null) + { cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top } +} + +// Make sure that at the end of the operation the current cursor is +// shown. +function ensureCursorVisible(cm) { + resolveScrollToPos(cm) + var cur = cm.getCursor(), from = cur, to = cur + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur + to = Pos(cur.line, cur.ch + 1) + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin} +} + +// When an operation has its scrollToPos property set, and another +// scroll action is applied before the end of the operation, this +// 'simulates' scrolling that position into view in a cheap way, so +// that the effect of intermediate scroll commands is not ignored. +function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos + if (range) { + cm.curOp.scrollToPos = null + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) + var sPos = calculateScrollPos(cm, { + left: Math.min(from.left, to.left), + top: Math.min(from.top, to.top) - range.margin, + right: Math.max(from.right, to.right), + bottom: Math.max(from.bottom, to.bottom) + range.margin + }) + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop) + } +} + +// Operations are used to wrap a series of changes to the editor +// state in such a way that each change won't have to update the +// cursor and display (which would be awkward, slow, and +// error-prone). Instead, display updates are batched and then all +// combined and executed at once. + +var nextOpId = 0 +// Start a new operation. +function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + } + pushOperation(cm.curOp) +} + +// Finish an operation, updating the display and signalling delayed events +function endOperation(cm) { + var op = cm.curOp + finishOperation(op, function (group) { + for (var i = 0; i < group.ops.length; i++) + { group.ops[i].cm.curOp = null } + endOperations(group) + }) +} + +// The DOM updates done when an operation finishes are batched so +// that the minimum number of relayouts are required. +function endOperations(group) { + var ops = group.ops + for (var i = 0; i < ops.length; i++) // Read DOM + { endOperation_R1(ops[i]) } + for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) + { endOperation_W1(ops[i$1]) } + for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM + { endOperation_R2(ops[i$2]) } + for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) + { endOperation_W2(ops[i$3]) } + for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM + { endOperation_finish(ops[i$4]) } +} + +function endOperation_R1(op) { + var cm = op.cm, display = cm.display + maybeClipScrollbars(cm) + if (op.updateMaxLine) { findMaxLine(cm) } + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) +} + +function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) +} + +function endOperation_R2(op) { + var cm = op.cm, display = cm.display + if (op.updatedDisplay) { updateHeightsInViewport(cm) } + + op.barMeasure = measureForScrollbars(cm) + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 + cm.display.sizerWidth = op.adjustWidthTo + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) + } + + if (op.updatedDisplay || op.selectionChanged) + { op.preparedSelection = display.input.prepareSelection(op.focus) } +} + +function endOperation_W2(op) { + var cm = op.cm + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" + if (op.maxScrollLeft < cm.doc.scrollLeft) + { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) } + cm.display.maxLineChanged = false + } + + var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()) + if (op.preparedSelection) + { cm.display.input.showSelection(op.preparedSelection, takeFocus) } + if (op.updatedDisplay || op.startHeight != cm.doc.height) + { updateScrollbars(cm, op.barMeasure) } + if (op.updatedDisplay) + { setDocumentHeight(cm, op.barMeasure) } + + if (op.selectionChanged) { restartBlink(cm) } + + if (cm.state.focused && op.updateInput) + { cm.display.input.reset(op.typing) } + if (takeFocus) { ensureFocus(op.cm) } +} + +function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc + + if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) } + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + { display.wheelStartX = display.wheelStartY = null } + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)) + display.scrollbars.setScrollTop(doc.scrollTop) + display.scroller.scrollTop = doc.scrollTop + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)) + display.scrollbars.setScrollLeft(doc.scrollLeft) + display.scroller.scrollLeft = doc.scrollLeft + alignHorizontally(cm) + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) + maybeScrollWindow(cm, rect) + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers + if (hidden) { for (var i = 0; i < hidden.length; ++i) + { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } } + if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) + { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } } + + if (display.wrapper.offsetHeight) + { doc.scrollTop = cm.display.scroller.scrollTop } + + // Fire change events, and delayed event handlers + if (op.changeObjs) + { signal(cm, "changes", cm, op.changeObjs) } + if (op.update) + { op.update.finish() } +} + +// Run the given function in an operation +function runInOp(cm, f) { + if (cm.curOp) { return f() } + startOperation(cm) + try { return f() } + finally { endOperation(cm) } +} +// Wraps a function in an operation. Returns the wrapped function. +function operation(cm, f) { + return function() { + if (cm.curOp) { return f.apply(cm, arguments) } + startOperation(cm) + try { return f.apply(cm, arguments) } + finally { endOperation(cm) } + } +} +// Used to add methods to editor and doc instances, wrapping them in +// operations. +function methodOp(f) { + return function() { + if (this.curOp) { return f.apply(this, arguments) } + startOperation(this) + try { return f.apply(this, arguments) } + finally { endOperation(this) } + } +} +function docMethodOp(f) { + return function() { + var cm = this.cm + if (!cm || cm.curOp) { return f.apply(this, arguments) } + startOperation(cm) + try { return f.apply(this, arguments) } + finally { endOperation(cm) } + } +} + +// Updates the display.view data structure for a given change to the +// document. From and to are in pre-change coordinates. Lendiff is +// the amount of lines added or subtracted by the change. This is +// used for changes that span multiple lines, or change the way +// lines are divided into visual lines. regLineChange (below) +// registers single-line changes. +function regChange(cm, from, to, lendiff) { + if (from == null) { from = cm.doc.first } + if (to == null) { to = cm.doc.first + cm.doc.size } + if (!lendiff) { lendiff = 0 } + + var display = cm.display + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + { display.updateLineNumbers = from } + + cm.curOp.viewChanged = true + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + { resetView(cm) } + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm) + } else { + display.viewFrom += lendiff + display.viewTo += lendiff + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm) + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1) + if (cut) { + display.view = display.view.slice(cut.index) + display.viewFrom = cut.lineN + display.viewTo += lendiff + } else { + resetView(cm) + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut$1 = viewCuttingPoint(cm, from, from, -1) + if (cut$1) { + display.view = display.view.slice(0, cut$1.index) + display.viewTo = cut$1.lineN + } else { + resetView(cm) + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1) + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)) + display.viewTo += lendiff + } else { + resetView(cm) + } + } + + var ext = display.externalMeasured + if (ext) { + if (to < ext.lineN) + { ext.lineN += lendiff } + else if (from < ext.lineN + ext.size) + { display.externalMeasured = null } + } +} + +// Register a change to a single line. Type must be one of "text", +// "gutter", "class", "widget" +function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true + var display = cm.display, ext = cm.display.externalMeasured + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + { display.externalMeasured = null } + + if (line < display.viewFrom || line >= display.viewTo) { return } + var lineView = display.view[findViewIndex(cm, line)] + if (lineView.node == null) { return } + var arr = lineView.changes || (lineView.changes = []) + if (indexOf(arr, type) == -1) { arr.push(type) } +} + +// Clear the view. +function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first + cm.display.view = [] + cm.display.viewOffset = 0 +} + +function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + { return {index: index, lineN: newN} } + var n = cm.display.viewFrom + for (var i = 0; i < index; i++) + { n += view[i].size } + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) { return null } + diff = (n + view[index].size) - oldN + index++ + } else { + diff = n - oldN + } + oldN += diff; newN += diff + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) { return null } + newN += dir * view[index - (dir < 0 ? 1 : 0)].size + index += dir + } + return {index: index, lineN: newN} +} + +// Force the view to cover a given range, adding empty view element +// or clipping off existing ones as needed. +function adjustView(cm, from, to) { + var display = cm.display, view = display.view + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to) + display.viewFrom = from + } else { + if (display.viewFrom > from) + { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) } + else if (display.viewFrom < from) + { display.view = display.view.slice(findViewIndex(cm, from)) } + display.viewFrom = from + if (display.viewTo < to) + { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) } + else if (display.viewTo > to) + { display.view = display.view.slice(0, findViewIndex(cm, to)) } + } + display.viewTo = to +} + +// Count the number of lines in the view whose DOM representation is +// out of date (or nonexistent). +function countDirtyView(cm) { + var view = cm.display.view, dirty = 0 + for (var i = 0; i < view.length; i++) { + var lineView = view[i] + if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty } + } + return dirty +} + +// HIGHLIGHT WORKER + +function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + { cm.state.highlight.set(time, bind(highlightWorker, cm)) } +} + +function highlightWorker(cm) { + var doc = cm.doc + if (doc.frontier < doc.first) { doc.frontier = doc.first } + if (doc.frontier >= cm.display.viewTo) { return } + var end = +new Date + cm.options.workTime + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)) + var changedLines = [] + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength + var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true) + line.styles = highlighted.styles + var oldCls = line.styleClasses, newCls = highlighted.classes + if (newCls) { line.styleClasses = newCls } + else if (oldCls) { line.styleClasses = null } + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) + for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] } + if (ischange) { changedLines.push(doc.frontier) } + line.stateAfter = tooLong ? state : copyState(doc.mode, state) + } else { + if (line.text.length <= cm.options.maxHighlightLength) + { processLine(cm, line.text, state) } + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null + } + ++doc.frontier + if (+new Date > end) { + startWorker(cm, cm.options.workDelay) + return true + } + }) + if (changedLines.length) { runInOp(cm, function () { + for (var i = 0; i < changedLines.length; i++) + { regLineChange(cm, changedLines[i], "text") } + }) } +} + +// DISPLAY DRAWING + +var DisplayUpdate = function(cm, viewport, force) { + var display = cm.display + + this.viewport = viewport + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport) + this.editorIsHidden = !display.wrapper.offsetWidth + this.wrapperHeight = display.wrapper.clientHeight + this.wrapperWidth = display.wrapper.clientWidth + this.oldDisplayWidth = displayWidth(cm) + this.force = force + this.dims = getDimensions(cm) + this.events = [] +}; + +DisplayUpdate.prototype.signal = function (emitter, type) { + if (hasHandler(emitter, type)) + { this.events.push(arguments) } +}; +DisplayUpdate.prototype.finish = function () { + var this$1 = this; + + for (var i = 0; i < this.events.length; i++) + { signal.apply(null, this$1.events[i]) } +}; + +function maybeClipScrollbars(cm) { + var display = cm.display + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth + display.heightForcer.style.height = scrollGap(cm) + "px" + display.sizer.style.marginBottom = -display.nativeBarWidth + "px" + display.sizer.style.borderRightWidth = scrollGap(cm) + "px" + display.scrollbarsClipped = true + } +} + +// Does the actual updating of the line display. Bails out +// (returning false) when there is nothing to be done and forced is +// false. +function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc + + if (update.editorIsHidden) { + resetView(cm) + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + { return false } + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm) + update.dims = getDimensions(cm) + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) + var to = Math.min(end, update.visible.to + cm.options.viewportMargin) + if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom) } + if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo) } + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from) + to = visualLineEndNo(cm.doc, to) + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth + adjustView(cm, from, to) + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px" + + var toUpdate = countDirtyView(cm) + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + { return false } + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt() + if (toUpdate > 4) { display.lineDiv.style.display = "none" } + patchDisplay(cm, display.updateLineNumbers, update.dims) + if (toUpdate > 4) { display.lineDiv.style.display = "" } + display.renderedView = display.view + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) { focused.focus() } + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv) + removeChildren(display.selectionDiv) + display.gutters.style.height = display.sizer.style.minHeight = 0 + + if (different) { + display.lastWrapHeight = update.wrapperHeight + display.lastWrapWidth = update.wrapperWidth + startWorker(cm, 400) + } + + display.updateLineNumbers = null + + return true +} + +function postUpdateDisplay(cm, update) { + var viewport = update.viewport + + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} } + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport) + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + { break } + } + if (!updateDisplayIfNeeded(cm, update)) { break } + updateHeightsInViewport(cm) + var barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + } + + update.signal(cm, "update", cm) + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo + } +} + +function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport) + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm) + postUpdateDisplay(cm, update) + var barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + update.finish() + } +} + +// Sync the actual display DOM structure with display.view, removing +// nodes for lines that are no longer in view, and creating the ones +// that are not there yet, and updating the ones that are out of +// date. +function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers + var container = display.lineDiv, cur = container.firstChild + + function rm(node) { + var next = node.nextSibling + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + { node.style.display = "none" } + else + { node.parentNode.removeChild(node) } + return next + } + + var view = display.view, lineN = display.viewFrom + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i] + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims) + container.insertBefore(node, cur) + } else { // Already drawn + while (cur != lineView.node) { cur = rm(cur) } + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false } + updateLineForChanges(cm, lineView, lineN, dims) + } + if (updateNumber) { + removeChildren(lineView.lineNumber) + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) + } + cur = lineView.node.nextSibling + } + lineN += lineView.size + } + while (cur) { cur = rm(cur) } +} + +function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth + cm.display.sizer.style.marginLeft = width + "px" +} + +function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px" + cm.display.heightForcer.style.top = measure.docHeight + "px" + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" +} + +// Rebuild the gutter elements, ensure the margin to the left of the +// code matches their width. +function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters + removeChildren(gutters) + var i = 0 + for (; i < specs.length; ++i) { + var gutterClass = specs[i] + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)) + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt + gElt.style.width = (cm.display.lineNumWidth || 1) + "px" + } + } + gutters.style.display = i ? "" : "none" + updateGutterSpace(cm) +} + +// Make sure the gutters options contains the element +// "CodeMirror-linenumbers" when the lineNumbers option is true. +function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers") + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0) + options.gutters.splice(found, 1) + } +} + +// Selection objects are immutable. A new one is created every time +// the selection changes. A selection is one or more non-overlapping +// (and non-touching) ranges, sorted, and an integer that indicates +// which one is the primary selection (the one that's scrolled into +// view, that getCursor returns, etc). +var Selection = function(ranges, primIndex) { + this.ranges = ranges + this.primIndex = primIndex +}; + +Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; + +Selection.prototype.equals = function (other) { + var this$1 = this; + + if (other == this) { return true } + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } + for (var i = 0; i < this.ranges.length; i++) { + var here = this$1.ranges[i], there = other.ranges[i] + if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } + } + return true +}; + +Selection.prototype.deepCopy = function () { + var this$1 = this; + + var out = [] + for (var i = 0; i < this.ranges.length; i++) + { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)) } + return new Selection(out, this.primIndex) +}; + +Selection.prototype.somethingSelected = function () { + var this$1 = this; + + for (var i = 0; i < this.ranges.length; i++) + { if (!this$1.ranges[i].empty()) { return true } } + return false +}; + +Selection.prototype.contains = function (pos, end) { + var this$1 = this; + + if (!end) { end = pos } + for (var i = 0; i < this.ranges.length; i++) { + var range = this$1.ranges[i] + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + { return i } + } + return -1 +}; + +var Range = function(anchor, head) { + this.anchor = anchor; this.head = head +}; + +Range.prototype.from = function () { return minPos(this.anchor, this.head) }; +Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; +Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; + +// Take an unsorted, potentially overlapping set of ranges, and +// build a selection out of it. 'Consumes' ranges array (modifying +// it). +function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex] + ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }) + primIndex = indexOf(ranges, prim) + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1] + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head + if (i <= primIndex) { --primIndex } + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) + } + } + return new Selection(ranges, primIndex) +} + +function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0) +} + +// Compute the position of the end of a change (its 'to' property +// refers to the pre-change end). +function changeEnd(change) { + if (!change.text) { return change.to } + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) +} + +// Adjust a position to refer to the post-change position of the +// same text, or the end of the change if the change covers it. +function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) { return pos } + if (cmp(pos, change.to) <= 0) { return changeEnd(change) } + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch + if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch } + return Pos(line, ch) +} + +function computeSelAfterChange(doc, change) { + var out = [] + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i] + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))) + } + return normalizeSelection(out, doc.sel.primIndex) +} + +function offsetPos(pos, old, nw) { + if (pos.line == old.line) + { return Pos(nw.line, pos.ch - old.ch + nw.ch) } + else + { return Pos(nw.line + (pos.line - old.line), pos.ch) } +} + +// Used by replaceSelections to allow moving the selection to the +// start or around the replaced test. Hint may be "start" or "around". +function computeReplacedSel(doc, changes, hint) { + var out = [] + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev + for (var i = 0; i < changes.length; i++) { + var change = changes[i] + var from = offsetPos(change.from, oldPrev, newPrev) + var to = offsetPos(changeEnd(change), oldPrev, newPrev) + oldPrev = change.to + newPrev = to + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 + out[i] = new Range(inv ? to : from, inv ? from : to) + } else { + out[i] = new Range(from, from) + } + } + return new Selection(out, doc.sel.primIndex) +} + +// Used to get the editor into a consistent state again when options change. + +function loadMode(cm) { + cm.doc.mode = getMode(cm.options, cm.doc.modeOption) + resetModeState(cm) +} + +function resetModeState(cm) { + cm.doc.iter(function (line) { + if (line.stateAfter) { line.stateAfter = null } + if (line.styles) { line.styles = null } + }) + cm.doc.frontier = cm.doc.first + startWorker(cm, 100) + cm.state.modeGen++ + if (cm.curOp) { regChange(cm) } +} + +// DOCUMENT DATA STRUCTURE + +// By default, updates that start and end at the beginning of a line +// are treated specially, in order to make the association of line +// widgets and marker elements with the text behave more intuitive. +function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) +} + +// Perform a change on the document data structure. +function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight) + signalLater(line, "change", line, change) + } + function linesFor(start, end) { + var result = [] + for (var i = start; i < end; ++i) + { result.push(new Line(text[i], spansFor(i), estimateHeight)) } + return result + } + + var from = change.from, to = change.to, text = change.text + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)) + doc.remove(text.length, doc.size - text.length) + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1) + update(lastLine, lastLine.text, lastSpans) + if (nlines) { doc.remove(from.line, nlines) } + if (added.length) { doc.insert(from.line, added) } + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) + } else { + var added$1 = linesFor(1, text.length - 1) + added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + doc.insert(from.line + 1, added$1) + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) + doc.remove(from.line + 1, nlines) + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) + var added$2 = linesFor(1, text.length - 1) + if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) } + doc.insert(from.line + 1, added$2) + } + + signalLater(doc, "change", doc, change) +} + +// Call f for all linked documents. +function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i] + if (rel.doc == skip) { continue } + var shared = sharedHist && rel.sharedHist + if (sharedHistOnly && !shared) { continue } + f(rel.doc, shared) + propagate(rel.doc, doc, shared) + } } + } + propagate(doc, null, true) +} + +// Attach a document to an editor. +function attachDoc(cm, doc) { + if (doc.cm) { throw new Error("This document is already in use.") } + cm.doc = doc + doc.cm = cm + estimateLineHeights(cm) + loadMode(cm) + setDirectionClass(cm) + if (!cm.options.lineWrapping) { findMaxLine(cm) } + cm.options.mode = doc.modeOption + regChange(cm) +} + +function setDirectionClass(cm) { + ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") +} + +function directionChanged(cm) { + runInOp(cm, function () { + setDirectionClass(cm) + regChange(cm) + }) +} + +function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = [] + this.undoDepth = Infinity + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0 + this.lastOp = this.lastSelOp = null + this.lastOrigin = this.lastSelOrigin = null + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1 +} + +// Create a history change event from an updateDoc-style change +// object. +function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) + linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true) + return histChange +} + +// Pop all selection events off the end of a history array. Stop at +// a change event. +function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array) + if (last.ranges) { array.pop() } + else { break } + } +} + +// Find the top change event in the history. Pop off selection +// events that are in the way. +function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done) + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop() + return lst(hist.done) + } +} + +// Register a change in the history. Merges changes that are within +// a single operation, or are close together with an origin that +// allows merging (starting with "+") into a single event. +function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history + hist.undone.length = 0 + var time = +new Date, cur + var last + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes) + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change) + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)) + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done) + if (!before || !before.ranges) + { pushSelectionToHistory(doc.sel, hist.done) } + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation} + hist.done.push(cur) + while (hist.done.length > hist.undoDepth) { + hist.done.shift() + if (!hist.done[0].ranges) { hist.done.shift() } + } + } + hist.done.push(selAfter) + hist.generation = ++hist.maxGeneration + hist.lastModTime = hist.lastSelTime = time + hist.lastOp = hist.lastSelOp = opId + hist.lastOrigin = hist.lastSelOrigin = change.origin + + if (!last) { signal(doc, "historyAdded") } +} + +function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0) + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) +} + +// Called whenever the selection changes, sets the new selection as +// the pending selection in the history, and pushes the old pending +// selection into the 'done' array when it was significantly +// different (in number of selected ranges, emptiness, or time). +function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + { hist.done[hist.done.length - 1] = sel } + else + { pushSelectionToHistory(sel, hist.done) } + + hist.lastSelTime = +new Date + hist.lastSelOrigin = origin + hist.lastSelOp = opId + if (options && options.clearRedo !== false) + { clearSelectionEvents(hist.undone) } +} + +function pushSelectionToHistory(sel, dest) { + var top = lst(dest) + if (!(top && top.ranges && top.equals(sel))) + { dest.push(sel) } +} + +// Used to store marked span information in the history. +function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0 + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { + if (line.markedSpans) + { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans } + ++n + }) +} + +// When un/re-doing restores text containing marked spans, those +// that have been explicitly cleared should not be restored. +function removeClearedSpans(spans) { + if (!spans) { return null } + var out + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } } + else if (out) { out.push(spans[i]) } + } + return !out ? spans : out.length ? out : null +} + +// Retrieve and filter the old marked spans stored in a change event. +function getOldSpans(doc, change) { + var found = change["spans_" + doc.id] + if (!found) { return null } + var nw = [] + for (var i = 0; i < change.text.length; ++i) + { nw.push(removeClearedSpans(found[i])) } + return nw +} + +// Used for un/re-doing changes from the history. Combines the +// result of computing the existing spans with the set of spans that +// existed in the history (so that deleting around a span and then +// undoing brings back the span). +function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change) + var stretched = stretchSpansOverChange(doc, change) + if (!old) { return stretched } + if (!stretched) { return old } + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i] + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j] + for (var k = 0; k < oldCur.length; ++k) + { if (oldCur[k].marker == span.marker) { continue spans } } + oldCur.push(span) + } + } else if (stretchCur) { + old[i] = stretchCur + } + } + return old +} + +// Used both to provide a JSON-safe object in .getHistory, and, when +// detaching a document, to split the history in two +function copyHistoryArray(events, newGroup, instantiateSel) { + var copy = [] + for (var i = 0; i < events.length; ++i) { + var event = events[i] + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) + continue + } + var changes = event.changes, newChanges = [] + copy.push({changes: newChanges}) + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m = (void 0) + newChanges.push({from: change.from, to: change.to, text: change.text}) + if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop] + delete change[prop] + } + } } } + } + } + return copy +} + +// The 'scroll' parameter given to many of these indicated whether +// the new cursor position should be scrolled into view after +// modifying the selection. + +// If shift is held or the extend flag is set, extends a range to +// include a given position (and optionally a second position). +// Otherwise, simply returns the range between the given positions. +// Used for cursor motion and such. +function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor + if (other) { + var posBefore = cmp(head, anchor) < 0 + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head + head = other + } else if (posBefore != (cmp(head, other) < 0)) { + head = other + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } +} + +// Extend the primary selection range, discard the rest. +function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options) +} + +// Extend all selections (pos is an array of selections with length +// equal the number of selections) +function extendSelections(doc, heads, options) { + var out = [] + for (var i = 0; i < doc.sel.ranges.length; i++) + { out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null) } + var newSel = normalizeSelection(out, doc.sel.primIndex) + setSelection(doc, newSel, options) +} + +// Updates a single range in the selection. +function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0) + ranges[i] = range + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) +} + +// Reset the selection to a single range. +function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options) +} + +// Give beforeSelectionChange handlers a change to influence a +// selection update. +function filterSelectionChange(doc, sel, options) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + var this$1 = this; + + this.ranges = [] + for (var i = 0; i < ranges.length; i++) + { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)) } + }, + origin: options && options.origin + } + signal(doc, "beforeSelectionChange", doc, obj) + if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) } + if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) } + else { return sel } +} + +function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done) + if (last && last.ranges) { + done[done.length - 1] = sel + setSelectionNoUndo(doc, sel, options) + } else { + setSelection(doc, sel, options) + } +} + +// Set a new selection. +function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options) + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) +} + +function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + { sel = filterSelectionChange(doc, sel, options) } + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) + + if (!(options && options.scroll === false) && doc.cm) + { ensureCursorVisible(doc.cm) } +} + +function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) { return } + + doc.sel = sel + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true + signalCursorActivity(doc.cm) + } + signalLater(doc, "cursorActivity", doc) +} + +// Verify that the selection does not partially select any atomic +// marked ranges. +function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll) +} + +// Return a selection that does not partially select any atomic +// ranges. +function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i] + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) { out = sel.ranges.slice(0, i) } + out[i] = new Range(newAnchor, newHead) + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel +} + +function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line) + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter") + if (m.explicitlyCleared) { + if (!line.markedSpans) { break } + else {--i; continue} + } + } + if (!m.atomic) { continue } + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff = (void 0) + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) } + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + { return skipAtomicInner(doc, near, pos, dir, mayClear) } + } + + var far = m.find(dir < 0 ? -1 : 1) + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + { far = movePos(doc, far, dir, far.line == pos.line ? line : null) } + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } } + return pos +} + +// Ensure a given position is not inside an atomic range. +function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1 + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) + if (!found) { + doc.cantEdit = true + return Pos(doc.first, 0) + } + return found +} + +function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } + else { return null } + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } + else { return null } + } else { + return new Pos(pos.line, pos.ch + dir) + } +} + +function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) +} + +// UPDATING + +// Allow "beforeChange" event handlers to influence a change +function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function () { return obj.canceled = true; } + } + if (update) { obj.update = function (from, to, text, origin) { + if (from) { obj.from = clipPos(doc, from) } + if (to) { obj.to = clipPos(doc, to) } + if (text) { obj.text = text } + if (origin !== undefined) { obj.origin = origin } + } } + signal(doc, "beforeChange", doc, obj) + if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) } + + if (obj.canceled) { return null } + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} +} + +// Apply a change to a document, and add it to the document's +// history, and propagating it to all linked documents. +function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } + if (doc.cm.state.suppressEdits) { return } + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true) + if (!change) { return } + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) + if (split) { + for (var i = split.length - 1; i >= 0; --i) + { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}) } + } else { + makeChangeInner(doc, change) + } +} + +function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } + var selAfter = computeSelAfterChange(doc, change) + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) + var rebased = [] + + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change) + rebased.push(doc.history) + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) + }) +} + +// Revert a change stored in a document's history. +function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return } + + var hist = doc.history, event, selAfter = doc.sel + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + var i = 0 + for (; i < source.length; i++) { + event = source[i] + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + { break } + } + if (i == source.length) { return } + hist.lastOrigin = hist.lastSelOrigin = null + + for (;;) { + event = source.pop() + if (event.ranges) { + pushSelectionToHistory(event, dest) + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}) + return + } + selAfter = event + } + else { break } + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = [] + pushSelectionToHistory(selAfter, dest) + dest.push({changes: antiChanges, generation: hist.generation}) + hist.generation = event.generation || ++hist.maxGeneration + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") + + var loop = function ( i ) { + var change = event.changes[i] + change.origin = type + if (filter && !filterChange(doc, change, false)) { + source.length = 0 + return {} + } + + antiChanges.push(historyChangeFromChange(doc, change)) + + var after = i ? computeSelAfterChange(doc, change) : lst(source) + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) + if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) } + var rebased = [] + + // Propagate to the linked documents + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change) + rebased.push(doc.history) + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) + }) + }; + + for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { + var returned = loop( i$1 ); + + if ( returned ) return returned.v; + } +} + +// Sub-views need their line numbers shifted when text is added +// above or below them in the parent document. +function shiftDoc(doc, distance) { + if (distance == 0) { return } + doc.first += distance + doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( + Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch) + ); }), doc.sel.primIndex) + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance) + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + { regLineChange(doc.cm, l, "gutter") } + } +} + +// More lower-level change function, handling only a single document +// (not linked ones). +function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) + return + } + if (change.from.line > doc.lastLine()) { return } + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line) + shiftDoc(doc, shift) + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin} + } + var last = doc.lastLine() + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin} + } + + change.removed = getBetween(doc, change.from, change.to) + + if (!selAfter) { selAfter = computeSelAfterChange(doc, change) } + if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) } + else { updateDoc(doc, change, spans) } + setSelectionNoUndo(doc, selAfter, sel_dontScroll) +} + +// Handle the interaction of a change to a document with the editor +// that this document is part of. +function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to + + var recomputeMaxLength = false, checkWidthStart = from.line + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) + doc.iter(checkWidthStart, to.line + 1, function (line) { + if (line == display.maxLine) { + recomputeMaxLength = true + return true + } + }) + } + + if (doc.sel.contains(change.from, change.to) > -1) + { signalCursorActivity(cm) } + + updateDoc(doc, change, spans, estimateHeight(cm)) + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function (line) { + var len = lineLength(line) + if (len > display.maxLineLength) { + display.maxLine = line + display.maxLineLength = len + display.maxLineChanged = true + recomputeMaxLength = false + } + }) + if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line) + startWorker(cm, 400) + + var lendiff = change.text.length - (to.line - from.line) - 1 + // Remember that these lines changed, for updating the display + if (change.full) + { regChange(cm) } + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + { regLineChange(cm, from.line, "text") } + else + { regChange(cm, from.line, to.line + 1, lendiff) } + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + } + if (changeHandler) { signalLater(cm, "change", cm, obj) } + if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) } + } + cm.display.selForContextMenu = null +} + +function replaceRange(doc, code, from, to, origin) { + if (!to) { to = from } + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp } + if (typeof code == "string") { code = doc.splitLines(code) } + makeChange(doc, {from: from, to: to, text: code, origin: origin}) +} + +// Rebasing/resetting history to deal with externally-sourced changes + +function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff + } else if (from < pos.line) { + pos.line = from + pos.ch = 0 + } +} + +// Tries to rebase an array of history events given a change in the +// document. If the change touches the same lines as the event, the +// event, and everything 'behind' it, is discarded. If the change is +// before the event, the event's positions are updated. Uses a +// copy-on-write scheme for the positions, to avoid having to +// reallocate them all on every rebase, but also avoid problems with +// shared position objects being unsafely updated. +function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) + } + continue + } + for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { + var cur = sub.changes[j$1] + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch) + cur.to = Pos(cur.to.line + diff, cur.to.ch) + } else if (from <= cur.to.line) { + ok = false + break + } + } + if (!ok) { + array.splice(0, i + 1) + i = 0 + } + } +} + +function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 + rebaseHistArray(hist.done, from, to, diff) + rebaseHistArray(hist.undone, from, to, diff) +} + +// Utility for applying a change to a line by handle or number, +// returning the number and optionally registering the line as +// changed. +function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle + if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) } + else { no = lineNo(handle) } + if (no == null) { return null } + if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) } + return line +} + +// The document is represented as a BTree consisting of leaves, with +// chunk of lines in them, and branches, with up to ten leaves or +// other branch nodes below them. The top node is always a branch +// node, and is the document object itself (meaning it has +// additional methods and properties). +// +// All nodes have parent links. The tree is used both to go from +// line numbers to line objects, and to go from objects to numbers. +// It also indexes by height, and is used to convert between height +// and line object, and to find the total height of the document. +// +// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + +var LeafChunk = function(lines) { + var this$1 = this; + + this.lines = lines + this.parent = null + var height = 0 + for (var i = 0; i < lines.length; ++i) { + lines[i].parent = this$1 + height += lines[i].height + } + this.height = height +}; + +LeafChunk.prototype.chunkSize = function () { return this.lines.length }; + +// Remove the n lines at offset 'at'. +LeafChunk.prototype.removeInner = function (at, n) { + var this$1 = this; + + for (var i = at, e = at + n; i < e; ++i) { + var line = this$1.lines[i] + this$1.height -= line.height + cleanUpLine(line) + signalLater(line, "delete") + } + this.lines.splice(at, n) +}; + +// Helper used to collapse a small branch into a single leaf. +LeafChunk.prototype.collapse = function (lines) { + lines.push.apply(lines, this.lines) +}; + +// Insert the given array of lines at offset 'at', count them as +// having the given height. +LeafChunk.prototype.insertInner = function (at, lines, height) { + var this$1 = this; + + this.height += height + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) + for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } +}; + +// Used to iterate over a part of the tree. +LeafChunk.prototype.iterN = function (at, n, op) { + var this$1 = this; + + for (var e = at + n; at < e; ++at) + { if (op(this$1.lines[at])) { return true } } +}; + +var BranchChunk = function(children) { + var this$1 = this; + + this.children = children + var size = 0, height = 0 + for (var i = 0; i < children.length; ++i) { + var ch = children[i] + size += ch.chunkSize(); height += ch.height + ch.parent = this$1 + } + this.size = size + this.height = height + this.parent = null +}; + +BranchChunk.prototype.chunkSize = function () { return this.size }; + +BranchChunk.prototype.removeInner = function (at, n) { + var this$1 = this; + + this.size -= n + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height + child.removeInner(at, rm) + this$1.height -= oldHeight - child.height + if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } + if ((n -= rm) == 0) { break } + at = 0 + } else { at -= sz } + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = [] + this.collapse(lines) + this.children = [new LeafChunk(lines)] + this.children[0].parent = this + } +}; + +BranchChunk.prototype.collapse = function (lines) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } +}; + +BranchChunk.prototype.insertInner = function (at, lines, height) { + var this$1 = this; + + this.size += lines.length + this.height += height + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at <= sz) { + child.insertInner(at, lines, height) + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25 + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) + child.height -= leaf.height + this$1.children.splice(++i, 0, leaf) + leaf.parent = this$1 + } + child.lines = child.lines.slice(0, remaining) + this$1.maybeSpill() + } + break + } + at -= sz + } +}; + +// When a node has grown, check whether it should be split. +BranchChunk.prototype.maybeSpill = function () { + if (this.children.length <= 10) { return } + var me = this + do { + var spilled = me.children.splice(me.children.length - 5, 5) + var sibling = new BranchChunk(spilled) + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children) + copy.parent = me + me.children = [copy, sibling] + me = copy + } else { + me.size -= sibling.size + me.height -= sibling.height + var myIndex = indexOf(me.parent.children, me) + me.parent.children.splice(myIndex + 1, 0, sibling) + } + sibling.parent = me.parent + } while (me.children.length > 10) + me.parent.maybeSpill() +}; + +BranchChunk.prototype.iterN = function (at, n, op) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at < sz) { + var used = Math.min(n, sz - at) + if (child.iterN(at, used, op)) { return true } + if ((n -= used) == 0) { break } + at = 0 + } else { at -= sz } + } +}; + +// Line widgets are block elements displayed above or below a line. + +var LineWidget = function(doc, node, options) { + var this$1 = this; + + if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) + { this$1[opt] = options[opt] } } } + this.doc = doc + this.node = node +}; + +LineWidget.prototype.clear = function () { + var this$1 = this; + + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) + if (no == null || !ws) { return } + for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } } + if (!ws.length) { line.widgets = null } + var height = widgetHeight(this) + updateLineHeight(line, Math.max(0, line.height - height)) + if (cm) { + runInOp(cm, function () { + adjustScrollWhenAboveVisible(cm, line, -height) + regLineChange(cm, no, "widget") + }) + signalLater(cm, "lineWidgetCleared", cm, this, no) + } +}; + +LineWidget.prototype.changed = function () { + var this$1 = this; + + var oldH = this.height, cm = this.doc.cm, line = this.line + this.height = null + var diff = widgetHeight(this) - oldH + if (!diff) { return } + updateLineHeight(line, line.height + diff) + if (cm) { + runInOp(cm, function () { + cm.curOp.forceUpdate = true + adjustScrollWhenAboveVisible(cm, line, diff) + signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)) + }) + } +}; +eventMixin(LineWidget) + +function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + { addToScrollPos(cm, null, diff) } +} + +function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options) + var cm = doc.cm + if (cm && widget.noHScroll) { cm.display.alignWidgets = true } + changeLine(doc, handle, "widget", function (line) { + var widgets = line.widgets || (line.widgets = []) + if (widget.insertAt == null) { widgets.push(widget) } + else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) } + widget.line = line + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop + updateLineHeight(line, line.height + widgetHeight(widget)) + if (aboveVisible) { addToScrollPos(cm, null, widget.height) } + cm.curOp.forceUpdate = true + } + return true + }) + signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) + return widget +} + +// TEXTMARKERS + +// Created with markText and setBookmark methods. A TextMarker is a +// handle that can be used to clear or find a marked position in the +// document. Line objects hold arrays (markedSpans) containing +// {from, to, marker} object pointing to such marker objects, and +// indicating that such a marker is present on that line. Multiple +// lines may point to the same marker when it spans across lines. +// The spans will have null for their from/to properties when the +// marker continues beyond the start/end of the line. Markers have +// links back to the lines they currently touch. + +// Collapsed markers have unique ids, in order to be able to order +// them, which is needed for uniquely determining an outer marker +// when they overlap (they may nest, but not partially overlap). +var nextMarkerId = 0 + +var TextMarker = function(doc, type) { + this.lines = [] + this.type = type + this.doc = doc + this.id = ++nextMarkerId +}; + +// Clear the marker. +TextMarker.prototype.clear = function () { + var this$1 = this; + + if (this.explicitlyCleared) { return } + var cm = this.doc.cm, withOp = cm && !cm.curOp + if (withOp) { startOperation(cm) } + if (hasHandler(this, "clear")) { + var found = this.find() + if (found) { signalLater(this, "clear", found.from, found.to) } + } + var min = null, max = null + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i] + var span = getMarkedSpanFor(line.markedSpans, this$1) + if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") } + else if (cm) { + if (span.to != null) { max = lineNo(line) } + if (span.from != null) { min = lineNo(line) } + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span) + if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) + { updateLineHeight(line, textHeight(cm.display)) } + } + if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { + var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual) + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual + cm.display.maxLineLength = len + cm.display.maxLineChanged = true + } + } } + + if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) } + this.lines.length = 0 + this.explicitlyCleared = true + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false + if (cm) { reCheckSelection(cm.doc) } + } + if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) } + if (withOp) { endOperation(cm) } + if (this.parent) { this.parent.clear() } +}; + +// Find the position of the marker in the document. Returns a {from, +// to} object by default. Side can be passed to get a specific side +// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the +// Pos objects returned contain a line object, rather than a line +// number (used to prevent looking up the same line twice). +TextMarker.prototype.find = function (side, lineObj) { + var this$1 = this; + + if (side == null && this.type == "bookmark") { side = 1 } + var from, to + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i] + var span = getMarkedSpanFor(line.markedSpans, this$1) + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from) + if (side == -1) { return from } + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to) + if (side == 1) { return to } + } + } + return from && {from: from, to: to} +}; + +// Signals that the marker's widget changed, and surrounding layout +// should be recomputed. +TextMarker.prototype.changed = function () { + var this$1 = this; + + var pos = this.find(-1, true), widget = this, cm = this.doc.cm + if (!pos || !cm) { return } + runInOp(cm, function () { + var line = pos.line, lineN = lineNo(pos.line) + var view = findViewForLine(cm, lineN) + if (view) { + clearLineMeasurementCacheFor(view) + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true + } + cm.curOp.updateMaxLine = true + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height + widget.height = null + var dHeight = widgetHeight(widget) - oldHeight + if (dHeight) + { updateLineHeight(line, line.height + dHeight) } + } + signalLater(cm, "markerChanged", cm, this$1) + }) +}; + +TextMarker.prototype.attachLine = function (line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } + } + this.lines.push(line) +}; + +TextMarker.prototype.detachLine = function (line) { + this.lines.splice(indexOf(this.lines, line), 1) + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) + } +}; +eventMixin(TextMarker) + +// Create a marker, wire it up to the right lines, and +function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) { return markTextShared(doc, from, to, options, type) } + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } + + var marker = new TextMarker(doc, type), diff = cmp(from, to) + if (options) { copyObj(options, marker, false) } + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + { return marker } + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true + marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") + if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") } + if (options.insertLeft) { marker.widgetNode.insertLeft = true } + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + { throw new Error("Inserting collapsed marker partially overlapping an existing one") } + seeCollapsedSpans() + } + + if (marker.addToHistory) + { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) } + + var curLine = from.line, cm = doc.cm, updateMaxLine + doc.iter(curLine, to.line + 1, function (line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + { updateMaxLine = true } + if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) } + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)) + ++curLine + }) + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { + if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) } + }) } + + if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }) } + + if (marker.readOnly) { + seeReadOnlySpans() + if (doc.history.done.length || doc.history.undone.length) + { doc.clearHistory() } + } + if (marker.collapsed) { + marker.id = ++nextMarkerId + marker.atomic = true + } + if (cm) { + // Sync editor state + if (updateMaxLine) { cm.curOp.updateMaxLine = true } + if (marker.collapsed) + { regChange(cm, from.line, to.line + 1) } + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text") } } + if (marker.atomic) { reCheckSelection(cm.doc) } + signalLater(cm, "markerAdded", cm, marker) + } + return marker +} + +// SHARED TEXTMARKERS + +// A shared marker spans multiple linked documents. It is +// implemented as a meta-marker-object controlling multiple normal +// markers. +var SharedTextMarker = function(markers, primary) { + var this$1 = this; + + this.markers = markers + this.primary = primary + for (var i = 0; i < markers.length; ++i) + { markers[i].parent = this$1 } +}; + +SharedTextMarker.prototype.clear = function () { + var this$1 = this; + + if (this.explicitlyCleared) { return } + this.explicitlyCleared = true + for (var i = 0; i < this.markers.length; ++i) + { this$1.markers[i].clear() } + signalLater(this, "clear") +}; + +SharedTextMarker.prototype.find = function (side, lineObj) { + return this.primary.find(side, lineObj) +}; +eventMixin(SharedTextMarker) + +function markTextShared(doc, from, to, options, type) { + options = copyObj(options) + options.shared = false + var markers = [markText(doc, from, to, options, type)], primary = markers[0] + var widget = options.widgetNode + linkedDocs(doc, function (doc) { + if (widget) { options.widgetNode = widget.cloneNode(true) } + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) + for (var i = 0; i < doc.linked.length; ++i) + { if (doc.linked[i].isParent) { return } } + primary = lst(markers) + }) + return new SharedTextMarker(markers, primary) +} + +function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) +} + +function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find() + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) + marker.markers.push(subMark) + subMark.parent = marker + } + } +} + +function detachSharedMarkers(markers) { + var loop = function ( i ) { + var marker = markers[i], linked = [marker.primary.doc] + linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }) + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j] + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null + marker.markers.splice(j--, 1) + } + } + }; + + for (var i = 0; i < markers.length; i++) loop( i ); +} + +var nextDocId = 0 +var Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } + if (firstLine == null) { firstLine = 0 } + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) + this.first = firstLine + this.scrollTop = this.scrollLeft = 0 + this.cantEdit = false + this.cleanGeneration = 1 + this.frontier = firstLine + var start = Pos(firstLine, 0) + this.sel = simpleSelection(start) + this.history = new History(null) + this.id = ++nextDocId + this.modeOption = mode + this.lineSep = lineSep + this.direction = (direction == "rtl") ? "rtl" : "ltr" + this.extend = false + + if (typeof text == "string") { text = this.splitLines(text) } + updateDoc(this, {from: start, to: start, text: text}) + setSelection(this, simpleSelection(start), sel_dontScroll) +} + +Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) { this.iterN(from - this.first, to - from, op) } + else { this.iterN(this.first, this.first + this.size, from) } + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0 + for (var i = 0; i < lines.length; ++i) { height += lines[i].height } + this.insertInner(at - this.first, lines, height) + }, + remove: function(at, n) { this.removeInner(at - this.first, n) }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size) + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1 + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true) + if (this.cm) { this.cm.scrollTo(0, 0) } + setSelection(this, simpleSelection(top), sel_dontScroll) + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from) + to = to ? clipPos(this, to) : from + replaceRange(this, code, from, to, origin) + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)) + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, + + getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, + getLineNumber: function(line) {return lineNo(line)}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") { line = getLine(this, line) } + return visualLine(line) + }, + + lineCount: function() {return this.size}, + firstLine: function() {return this.first}, + lastLine: function() {return this.first + this.size - 1}, + + clipPos: function(pos) {return clipPos(this, pos)}, + + getCursor: function(start) { + var range = this.sel.primary(), pos + if (start == null || start == "head") { pos = range.head } + else if (start == "anchor") { pos = range.anchor } + else if (start == "end" || start == "to" || start === false) { pos = range.to() } + else { pos = range.from() } + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options) + }), + extendSelectionsBy: docMethodOp(function(f, options) { + var heads = map(this.sel.ranges, f) + extendSelections(this, clipPosArray(this, heads), options) + }), + setSelections: docMethodOp(function(ranges, primary, options) { + var this$1 = this; + + if (!ranges.length) { return } + var out = [] + for (var i = 0; i < ranges.length; i++) + { out[i] = new Range(clipPos(this$1, ranges[i].anchor), + clipPos(this$1, ranges[i].head)) } + if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex) } + setSelection(this, normalizeSelection(out, primary), options) + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0) + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) + }), + + getSelection: function(lineSep) { + var this$1 = this; + + var ranges = this.sel.ranges, lines + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) + lines = lines ? lines.concat(sel) : sel + } + if (lineSep === false) { return lines } + else { return lines.join(lineSep || this.lineSeparator()) } + }, + getSelections: function(lineSep) { + var this$1 = this; + + var parts = [], ranges = this.sel.ranges + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) + if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) } + parts[i] = sel + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + var dup = [] + for (var i = 0; i < this.sel.ranges.length; i++) + { dup[i] = code } + this.replaceSelections(dup, collapse, origin || "+input") + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var this$1 = this; + + var changes = [], sel = this.sel + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i] + changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(code[i]), origin: origin} + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) + for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) + { makeChange(this$1, changes[i$1]) } + if (newSel) { setSelectionReplaceHistory(this, newSel) } + else if (this.cm) { ensureCursorVisible(this.cm) } + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), + + setExtending: function(val) {this.extend = val}, + getExtending: function() {return this.extend}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0 + for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done } } + for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone } } + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration)}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true) + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null } + return this.history.generation + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration) + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)} + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration) + hist.done = copyHistoryArray(histData.done.slice(0), null, true) + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", function (line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}) + markers[gutterID] = value + if (!value && isEmpty(markers)) { line.gutterMarkers = null } + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + var this$1 = this; + + this.iter(function (line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this$1, line, "gutter", function () { + line.gutterMarkers[gutterID] = null + if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null } + return true + }) + } + }) + }), + + lineInfo: function(line) { + var n + if (typeof line == "number") { + if (!isLine(this, line)) { return null } + n = line + line = getLine(this, line) + if (!line) { return null } + } else { + n = lineNo(line) + if (n == null) { return null } + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + if (!line[prop]) { line[prop] = cls } + else if (classTest(cls).test(line[prop])) { return false } + else { line[prop] += " " + cls } + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + var cur = line[prop] + if (!cur) { return false } + else if (cls == null) { line[prop] = null } + else { + var found = cur.match(classTest(cls)) + if (!found) { return false } + var end = found.index + found[0].length + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null + } + return true + }) + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear() }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents} + pos = clipPos(this, pos) + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos) + var markers = [], spans = getLine(this, pos.line).markedSpans + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i] + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + { markers.push(span.marker.parent || span.marker) } + } } + return markers + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to) + var found = [], lineNo = from.line + this.iter(from.line, to.line + 1, function (line) { + var spans = line.markedSpans + if (spans) { for (var i = 0; i < spans.length; i++) { + var span = spans[i] + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + { found.push(span.marker.parent || span.marker) } + } } + ++lineNo + }) + return found + }, + getAllMarks: function() { + var markers = [] + this.iter(function (line) { + var sps = line.markedSpans + if (sps) { for (var i = 0; i < sps.length; ++i) + { if (sps[i].from != null) { markers.push(sps[i].marker) } } } + }) + return markers + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first, sepSize = this.lineSeparator().length + this.iter(function (line) { + var sz = line.text.length + sepSize + if (sz > off) { ch = off; return true } + off -= sz + ++lineNo + }) + return clipPos(this, Pos(lineNo, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords) + var index = coords.ch + if (coords.line < this.first || coords.ch < 0) { return 0 } + var sepSize = this.lineSeparator().length + this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize + }) + return index + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction) + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft + doc.sel = this.sel + doc.extend = false + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth + doc.setHistory(this.getHistory()) + } + return doc + }, + + linkedDoc: function(options) { + if (!options) { options = {} } + var from = this.first, to = this.first + this.size + if (options.from != null && options.from > from) { from = options.from } + if (options.to != null && options.to < to) { to = options.to } + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) + if (options.sharedHist) { copy.history = this.history + ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] + copySharedMarkers(copy, findSharedMarkers(this)) + return copy + }, + unlinkDoc: function(other) { + var this$1 = this; + + if (other instanceof CodeMirror) { other = other.doc } + if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { + var link = this$1.linked[i] + if (link.doc != other) { continue } + this$1.linked.splice(i, 1) + other.unlinkDoc(this$1) + detachSharedMarkers(findSharedMarkers(this$1)) + break + } } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id] + linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true) + other.history = new History(null) + other.history.done = copyHistoryArray(this.history.done, splitIds) + other.history.undone = copyHistoryArray(this.history.undone, splitIds) + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f)}, + + getMode: function() {return this.mode}, + getEditor: function() {return this.cm}, + + splitLines: function(str) { + if (this.lineSep) { return str.split(this.lineSep) } + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") { dir = "ltr" } + if (dir == this.direction) { return } + this.direction = dir + this.iter(function (line) { return line.order = null; }) + if (this.cm) { directionChanged(this.cm) } + }) +}) + +// Public alias. +Doc.prototype.eachLine = Doc.prototype.iter + +// Kludge to work around strange IE behavior where it'll sometimes +// re-fire a series of drag-related events right after the drop (#1551) +var lastDrop = 0 + +function onDrop(e) { + var cm = this + clearDragCursor(cm) + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + { return } + e_preventDefault(e) + if (ie) { lastDrop = +new Date } + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files + if (!pos || cm.isReadOnly()) { return } + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0 + var loadFile = function (file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + { return } + + var reader = new FileReader + reader.onload = operation(cm, function () { + var content = reader.result + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" } + text[i] = content + if (++read == n) { + pos = clipPos(cm.doc, pos) + var change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"} + makeChange(cm.doc, change) + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) + } + }) + reader.readAsText(file) + } + for (var i = 0; i < n; ++i) { loadFile(files[i], i) } + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e) + // Ensure the editor is re-focused + setTimeout(function () { return cm.display.input.focus(); }, 20) + return + } + try { + var text$1 = e.dataTransfer.getData("Text") + if (text$1) { + var selected + if (cm.state.draggingText && !cm.state.draggingText.copy) + { selected = cm.listSelections() } + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) + if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) + { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag") } } + cm.replaceSelection(text$1, "around", "paste") + cm.display.input.focus() + } + } + catch(e){} + } +} + +function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } + + e.dataTransfer.setData("Text", cm.getSelection()) + e.dataTransfer.effectAllowed = "copyMove" + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;") + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (presto) { + img.width = img.height = 1 + cm.display.wrapper.appendChild(img) + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop + } + e.dataTransfer.setDragImage(img, 0, 0) + if (presto) { img.parentNode.removeChild(img) } + } +} + +function onDragOver(cm, e) { + var pos = posFromMouse(cm, e) + if (!pos) { return } + var frag = document.createDocumentFragment() + drawSelectionCursor(cm, pos, frag) + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) + } + removeChildrenAndAdd(cm.display.dragCursor, frag) +} + +function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor) + cm.display.dragCursor = null + } +} + +// These must be handled carefully, because naively registering a +// handler for each editor will cause the editors to never be +// garbage collected. + +function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) { return } + var byClass = document.body.getElementsByClassName("CodeMirror") + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror + if (cm) { f(cm) } + } +} + +var globalsRegistered = false +function ensureGlobalHandlers() { + if (globalsRegistered) { return } + registerGlobalHandlers() + globalsRegistered = true +} +function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer + on(window, "resize", function () { + if (resizeTimer == null) { resizeTimer = setTimeout(function () { + resizeTimer = null + forEachCodeMirror(onResize) + }, 100) } + }) + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function () { return forEachCodeMirror(onBlur); }) +} +// Called when the window resizes +function onResize(cm) { + var d = cm.display + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + { return } + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + d.scrollbarsClipped = false + cm.setSize() +} + +var keyNames = { + 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" +} + +// Number keys +for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) } +// Alphabetic keys +for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) } +// Function keys +for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2 } + +var keyMap = {} + +keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" +} +// Note that the save and find-related commands aren't defined by +// default. User code or addons can define them. Unknown commands +// are simply ignored. +keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" +} +// Very basic readline/emacs-style bindings, which are standard on Mac. +keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" +} +keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] +} +keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault + +// KEYMAP DISPATCH + +function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/) + name = parts[parts.length - 1] + var alt, ctrl, shift, cmd + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i] + if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true } + else if (/^a(lt)?$/i.test(mod)) { alt = true } + else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } + else if (/^s(hift)?$/i.test(mod)) { shift = true } + else { throw new Error("Unrecognized modifier name: " + mod) } + } + if (alt) { name = "Alt-" + name } + if (ctrl) { name = "Ctrl-" + name } + if (cmd) { name = "Cmd-" + name } + if (shift) { name = "Shift-" + name } + return name +} + +// This is a kludge to keep keymaps mostly working as raw objects +// (backwards compatibility) while at the same time support features +// like normalization and multi-stroke key bindings. It compiles a +// new normalized keymap, and then updates the old object to reflect +// this. +function normalizeKeyMap(keymap) { + var copy = {} + for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname] + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } + if (value == "...") { delete keymap[keyname]; continue } + + var keys = map(keyname.split(" "), normalizeKeyName) + for (var i = 0; i < keys.length; i++) { + var val = (void 0), name = (void 0) + if (i == keys.length - 1) { + name = keys.join(" ") + val = value + } else { + name = keys.slice(0, i + 1).join(" ") + val = "..." + } + var prev = copy[name] + if (!prev) { copy[name] = val } + else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } + } + delete keymap[keyname] + } } + for (var prop in copy) { keymap[prop] = copy[prop] } + return keymap +} + +function lookupKey(key, map, handle, context) { + map = getKeyMap(map) + var found = map.call ? map.call(key, context) : map[key] + if (found === false) { return "nothing" } + if (found === "...") { return "multi" } + if (found != null && handle(found)) { return "handled" } + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + { return lookupKey(key, map.fallthrough, handle, context) } + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context) + if (result) { return result } + } + } +} + +// Modifier key presses don't count as 'real' key presses for the +// purpose of keymap fallthrough. +function isModifierKey(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode] + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" +} + +// Look up the name of a key as indicated by an event object. +function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) { return false } + var base = keyNames[event.keyCode], name = base + if (name == null || event.altGraphKey) { return false } + if (event.altKey && base != "Alt") { name = "Alt-" + name } + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name } + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name } + if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name } + return name +} + +function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val +} + +// Helper for deleting text near the selection(s), used to implement +// backspace, delete, and similar functionality. +function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = [] + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]) + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop() + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from + break + } + } + kill.push(toKill) + } + // Next, remove those actual ranges. + runInOp(cm, function () { + for (var i = kill.length - 1; i >= 0; i--) + { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") } + ensureCursorVisible(cm) + }) +} + +// Commands are parameter-less actions that can be performed on an +// editor, mostly used for keybindings. +var commands = { + selectAll: selectAll, + singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, + killLine: function (cm) { return deleteNearSelection(cm, function (range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length + if (range.head.ch == len && range.head.line < cm.lastLine()) + { return {from: range.head, to: Pos(range.head.line + 1, 0)} } + else + { return {from: range.head, to: Pos(range.head.line, len)} } + } else { + return {from: range.from(), to: range.to()} + } + }); }, + deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) + }); }); }, + delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), to: range.from() + }); }); }, + delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + var leftPos = cm.coordsChar({left: 0, top: top}, "div") + return {from: leftPos, to: range.from()} + }); }, + delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + return {from: range.from(), to: rightPos } + }); }, + undo: function (cm) { return cm.undo(); }, + redo: function (cm) { return cm.redo(); }, + undoSelection: function (cm) { return cm.undoSelection(); }, + redoSelection: function (cm) { return cm.redoSelection(); }, + goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, + goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, + goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1} + ); }, + goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, + {origin: "+move", bias: 1} + ); }, + goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1} + ); }, + goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + }, sel_move); }, + goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + return cm.coordsChar({left: 0, top: top}, "div") + }, sel_move); }, + goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + var pos = cm.coordsChar({left: 0, top: top}, "div") + if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } + return pos + }, sel_move); }, + goLineUp: function (cm) { return cm.moveV(-1, "line"); }, + goLineDown: function (cm) { return cm.moveV(1, "line"); }, + goPageUp: function (cm) { return cm.moveV(-1, "page"); }, + goPageDown: function (cm) { return cm.moveV(1, "page"); }, + goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, + goCharRight: function (cm) { return cm.moveH(1, "char"); }, + goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, + goColumnRight: function (cm) { return cm.moveH(1, "column"); }, + goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, + goGroupRight: function (cm) { return cm.moveH(1, "group"); }, + goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, + goWordRight: function (cm) { return cm.moveH(1, "word"); }, + delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, + delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, + delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, + delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, + delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, + delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, + indentAuto: function (cm) { return cm.indentSelection("smart"); }, + indentMore: function (cm) { return cm.indentSelection("add"); }, + indentLess: function (cm) { return cm.indentSelection("subtract"); }, + insertTab: function (cm) { return cm.replaceSelection("\t"); }, + insertSoftTab: function (cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from() + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) + spaces.push(spaceStr(tabSize - col % tabSize)) + } + cm.replaceSelections(spaces) + }, + defaultTab: function (cm) { + if (cm.somethingSelected()) { cm.indentSelection("add") } + else { cm.execCommand("insertTab") } + }, + // Swap the two chars left and right of each selection's head. + // Move cursor behind the two swapped characters afterwards. + // + // Doesn't consider line feeds a character. + // Doesn't scan more than one line above to find a character. + // Doesn't do anything on an empty line. + // Doesn't do anything with non-empty selections. + transposeChars: function (cm) { return runInOp(cm, function () { + var ranges = cm.listSelections(), newSel = [] + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) { continue } + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text + if (line) { + if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) } + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1) + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose") + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text + if (prev) { + cur = new Pos(cur.line, 1) + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), cur, "+transpose") + } + } + } + newSel.push(new Range(cur, cur)) + } + cm.setSelections(newSel) + }); }, + newlineAndIndent: function (cm) { return runInOp(cm, function () { + var sels = cm.listSelections() + for (var i = sels.length - 1; i >= 0; i--) + { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") } + sels = cm.listSelections() + for (var i$1 = 0; i$1 < sels.length; i$1++) + { cm.indentLine(sels[i$1].from().line, null, true) } + ensureCursorVisible(cm) + }); }, + openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, + toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } +} + + +function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN) + var visual = visualLine(line) + if (visual != line) { lineN = lineNo(visual) } + return endOfLine(true, cm, visual, lineN, 1) +} +function lineEnd(cm, lineN) { + var line = getLine(cm.doc, lineN) + var visual = visualLineEnd(line) + if (visual != line) { lineN = lineNo(visual) } + return endOfLine(true, cm, line, lineN, -1) +} +function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line) + var line = getLine(cm.doc, start.line) + var order = getOrder(line, cm.doc.direction) + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)) + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch + return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) + } + return start +} + +// Run a handler that was bound to a key. +function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound] + if (!bound) { return false } + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled() + var prevShift = cm.display.shift, done = false + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true } + if (dropShift) { cm.display.shift = false } + done = bound(cm) != Pass + } finally { + cm.display.shift = prevShift + cm.state.suppressEdits = false + } + return done +} + +function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) + if (result) { return result } + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm) +} + +var stopSeq = new Delayed +function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq + if (seq) { + if (isModifierKey(name)) { return "handled" } + stopSeq.set(50, function () { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null + cm.display.input.reset() + } + }) + name = seq + " " + name + } + var result = lookupKeyForEditor(cm, name, handle) + + if (result == "multi") + { cm.state.keySeq = name } + if (result == "handled") + { signalLater(cm, "keyHandled", cm, name, e) } + + if (result == "handled" || result == "multi") { + e_preventDefault(e) + restartBlink(cm) + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e) + return true + } + return !!result +} + +// Handle a key from the keydown event. +function handleKeyBinding(cm, e) { + var name = keyName(e, true) + if (!name) { return false } + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) + || dispatchKey(cm, name, e, function (b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + { return doHandleBinding(cm, b) } + }) + } else { + return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) + } +} + +// Handle a key from the keypress event +function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) +} + +var lastStoppedKey = null +function onKeyDown(e) { + var cm = this + cm.curOp.focus = activeElt() + if (signalDOMEvent(cm, e)) { return } + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } + var code = e.keyCode + cm.display.shift = code == 16 || e.shiftKey + var handled = handleKeyBinding(cm, e) + if (presto) { + lastStoppedKey = handled ? code : null + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + { cm.replaceSelection("", null, "cut") } + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + { showCrossHair(cm) } +} + +function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv + addClass(lineDiv, "CodeMirror-crosshair") + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair") + off(document, "keyup", up) + off(document, "mouseover", up) + } + } + on(document, "keyup", up) + on(document, "mouseover", up) +} + +function onKeyUp(e) { + if (e.keyCode == 16) { this.doc.sel.shift = false } + signalDOMEvent(this, e) +} + +function onKeyPress(e) { + var cm = this + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } + var keyCode = e.keyCode, charCode = e.charCode + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } + var ch = String.fromCharCode(charCode == null ? keyCode : charCode) + // Some browsers fire keypress events for backspace + if (ch == "\x08") { return } + if (handleCharBinding(cm, e, ch)) { return } + cm.display.input.onKeyPress(e) +} + +// A mouse down can be a single click, double click, triple click, +// start of selection drag, start of text drag, new cursor +// (ctrl-click), rectangle drag (alt-drag), or xwin +// middle-click-paste. Or it might be a click on something we should +// not interfere with, such as a scrollbar or widget. +function onMouseDown(e) { + var cm = this, display = cm.display + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } + display.input.ensurePolled() + display.shift = e.shiftKey + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false + setTimeout(function () { return display.scroller.draggable = true; }, 100) + } + return + } + if (clickInGutter(cm, e)) { return } + var start = posFromMouse(cm, e) + window.focus() + + switch (e_button(e)) { + case 1: + // #3261: make sure, that we're not starting a second selection + if (cm.state.selectingText) + { cm.state.selectingText(e) } + else if (start) + { leftButtonDown(cm, e, start) } + else if (e_target(e) == display.scroller) + { e_preventDefault(e) } + break + case 2: + if (webkit) { cm.state.lastMiddleDown = +new Date } + if (start) { extendSelection(cm.doc, start) } + setTimeout(function () { return display.input.focus(); }, 20) + e_preventDefault(e) + break + case 3: + if (captureRightClick) { onContextMenu(cm, e) } + else { delayBlurEvent(cm) } + break + } +} + +var lastClick; +var lastDoubleClick; +function leftButtonDown(cm, e, start) { + if (ie) { setTimeout(bind(ensureFocus, cm), 0) } + else { cm.curOp.focus = activeElt() } + + var now = +new Date, type + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple" + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double" + lastDoubleClick = {time: now, pos: start} + } else { + type = "single" + lastClick = {time: now, pos: start} + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + type == "single" && (contained = sel.contains(start)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && + (cmp(contained.to(), start) > 0 || start.xRel < 0)) + { leftButtonStartDrag(cm, e, start, modifier) } + else + { leftButtonSelect(cm, e, start, type, modifier) } +} + +// Start a text drag. When it ends, see if any dragging actually +// happen, and treat as a click if it didn't. +function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display, moved = false + var dragEnd = operation(cm, function (e) { + if (webkit) { display.scroller.draggable = false } + cm.state.draggingText = false + off(document, "mouseup", dragEnd) + off(document, "mousemove", mouseMove) + off(display.scroller, "dragstart", dragStart) + off(display.scroller, "drop", dragEnd) + if (!moved) { + e_preventDefault(e) + if (!modifier) + { extendSelection(cm.doc, start) } + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) } + else + { display.input.focus() } + } + }) + var mouseMove = function(e2) { + moved = moved || Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) >= 10 + } + var dragStart = function () { return moved = true; } + // Let the drag handler handle this. + if (webkit) { display.scroller.draggable = true } + cm.state.draggingText = dragEnd + dragEnd.copy = mac ? e.altKey : e.ctrlKey + // IE's approach to draggable + if (display.scroller.dragDrop) { display.scroller.dragDrop() } + on(document, "mouseup", dragEnd) + on(document, "mousemove", mouseMove) + on(display.scroller, "dragstart", dragStart) + on(display.scroller, "drop", dragEnd) + + delayBlurEvent(cm) + setTimeout(function () { return display.input.focus(); }, 20) +} + +// Normal selection, as opposed to text dragging. +function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc + e_preventDefault(e) + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start) + if (ourIndex > -1) + { ourRange = ranges[ourIndex] } + else + { ourRange = new Range(start, start) } + } else { + ourRange = doc.sel.primary() + ourIndex = doc.sel.primIndex + } + + if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { + type = "rect" + if (!addNew) { ourRange = new Range(start, start) } + start = posFromMouse(cm, e, true, true) + ourIndex = -1 + } else if (type == "double") { + var word = cm.findWordAt(start) + if (cm.display.shift || doc.extend) + { ourRange = extendRange(doc, ourRange, word.anchor, word.head) } + else + { ourRange = word } + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))) + if (cm.display.shift || doc.extend) + { ourRange = extendRange(doc, ourRange, line.anchor, line.head) } + else + { ourRange = line } + } else { + ourRange = extendRange(doc, ourRange, start) + } + + if (!addNew) { + ourIndex = 0 + setSelection(doc, new Selection([ourRange], 0), sel_mouse) + startSel = doc.sel + } else if (ourIndex == -1) { + ourIndex = ranges.length + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}) + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}) + startSel = doc.sel + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) + } + + var lastPos = start + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) { return } + lastPos = pos + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) + if (left == right) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } + else if (text.length > leftPos) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } + } + if (!ranges.length) { ranges.push(new Range(start, start)) } + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}) + cm.scrollIntoView(pos) + } else { + var oldRange = ourRange + var anchor = oldRange.anchor, head = pos + if (type != "single") { + var range + if (type == "double") + { range = cm.findWordAt(pos) } + else + { range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))) } + if (cmp(range.anchor, anchor) > 0) { + head = range.head + anchor = minPos(oldRange.from(), range.anchor) + } else { + head = range.anchor + anchor = maxPos(oldRange.to(), range.head) + } + } + var ranges$1 = startSel.ranges.slice(0) + ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head) + setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) + } + } + + var editorSize = display.wrapper.getBoundingClientRect() + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0 + + function extend(e) { + var curCount = ++counter + var cur = posFromMouse(cm, e, true, type == "rect") + if (!cur) { return } + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt() + extendTo(cur) + var visible = visibleLines(display, doc) + if (cur.line >= visible.to || cur.line < visible.from) + { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e) }}), 150) } + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 + if (outside) { setTimeout(operation(cm, function () { + if (counter != curCount) { return } + display.scroller.scrollTop += outside + extend(e) + }), 50) } + } + } + + function done(e) { + cm.state.selectingText = false + counter = Infinity + e_preventDefault(e) + display.input.focus() + off(document, "mousemove", move) + off(document, "mouseup", up) + doc.history.lastSelOrigin = null + } + + var move = operation(cm, function (e) { + if (!e_button(e)) { done(e) } + else { extend(e) } + }) + var up = operation(cm, done) + cm.state.selectingText = up + on(document, "mousemove", move) + on(document, "mouseup", up) +} + + +// Determines whether an event happened in the gutter, and fires the +// handlers for the corresponding event. +function gutterEvent(cm, e, type, prevent) { + var mX, mY + try { mX = e.clientX; mY = e.clientY } + catch(e) { return false } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } + if (prevent) { e_preventDefault(e) } + + var display = cm.display + var lineBox = display.lineDiv.getBoundingClientRect() + + if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } + mY -= lineBox.top - display.viewOffset + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i] + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY) + var gutter = cm.options.gutters[i] + signal(cm, type, cm, line, gutter, e) + return e_defaultPrevented(e) + } + } +} + +function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true) +} + +// CONTEXT MENU HANDLING + +// To make the context menu work, we need to briefly unhide the +// textarea (making it as unobtrusive as possible) to let the +// right-click take effect on it. +function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } + if (signalDOMEvent(cm, e, "contextmenu")) { return } + cm.display.input.onContextMenu(e) +} + +function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) { return false } + return gutterEvent(cm, e, "gutterContextMenu", false) +} + +function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") + clearCaches(cm) +} + +var Init = {toString: function(){return "CodeMirror.Init"}} + +var defaults = {} +var optionHandlers = {} + +function defineOptions(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt + if (handle) { optionHandlers[name] = + notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old) }} : handle } + } + + CodeMirror.defineOption = option + + // Passed to option handlers when there is no old value. + CodeMirror.Init = Init + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function (cm, val) { return cm.setValue(val); }, true) + option("mode", null, function (cm, val) { + cm.doc.modeOption = val + loadMode(cm) + }, true) + + option("indentUnit", 2, loadMode, true) + option("indentWithTabs", false) + option("smartIndent", true) + option("tabSize", 4, function (cm) { + resetModeState(cm) + clearCaches(cm) + regChange(cm) + }, true) + option("lineSeparator", null, function (cm, val) { + cm.doc.lineSep = val + if (!val) { return } + var newBreaks = [], lineNo = cm.doc.first + cm.doc.iter(function (line) { + for (var pos = 0;;) { + var found = line.text.indexOf(val, pos) + if (found == -1) { break } + pos = found + val.length + newBreaks.push(Pos(lineNo, found)) + } + lineNo++ + }) + for (var i = newBreaks.length - 1; i >= 0; i--) + { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) } + }) + option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") + if (old != Init) { cm.refresh() } + }) + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true) + option("electricChars", true) + option("inputStyle", mobile ? "contenteditable" : "textarea", function () { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true) + option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true) + option("rtlMoveVisually", !windows) + option("wholeLineUpdateBefore", true) + + option("theme", "default", function (cm) { + themeChanged(cm) + guttersChanged(cm) + }, true) + option("keyMap", "default", function (cm, val, old) { + var next = getKeyMap(val) + var prev = old != Init && getKeyMap(old) + if (prev && prev.detach) { prev.detach(cm, next) } + if (next.attach) { next.attach(cm, prev || null) } + }) + option("extraKeys", null) + + option("lineWrapping", false, wrappingChanged, true) + option("gutters", [], function (cm) { + setGuttersForLineNumbers(cm.options) + guttersChanged(cm) + }, true) + option("fixedGutter", true, function (cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" + cm.refresh() + }, true) + option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true) + option("scrollbarStyle", "native", function (cm) { + initScrollbars(cm) + updateScrollbars(cm) + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) + }, true) + option("lineNumbers", false, function (cm) { + setGuttersForLineNumbers(cm.options) + guttersChanged(cm) + }, true) + option("firstLineNumber", 1, guttersChanged, true) + option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true) + option("showCursorWhenSelecting", false, updateSelection, true) + + option("resetSelectionOnContextMenu", true) + option("lineWiseCopyCut", true) + + option("readOnly", false, function (cm, val) { + if (val == "nocursor") { + onBlur(cm) + cm.display.input.blur() + cm.display.disabled = true + } else { + cm.display.disabled = false + } + cm.display.input.readOnlyChanged(val) + }) + option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset() }}, true) + option("dragDrop", true, dragDropChanged) + option("allowDropFileTypes", null) + + option("cursorBlinkRate", 530) + option("cursorScrollMargin", 0) + option("cursorHeight", 1, updateSelection, true) + option("singleCursorHeightPerLine", true, updateSelection, true) + option("workTime", 100) + option("workDelay", 100) + option("flattenSpans", true, resetModeState, true) + option("addModeClass", false, resetModeState, true) + option("pollInterval", 100) + option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }) + option("historyEventDelay", 1250) + option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true) + option("maxHighlightLength", 10000, resetModeState, true) + option("moveInputWithCursor", true, function (cm, val) { + if (!val) { cm.display.input.resetPosition() } + }) + + option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }) + option("autofocus", null) + option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true) +} + +function guttersChanged(cm) { + updateGutters(cm) + regChange(cm) + alignHorizontally(cm) +} + +function dragDropChanged(cm, value, old) { + var wasOn = old && old != Init + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions + var toggle = value ? on : off + toggle(cm.display.scroller, "dragstart", funcs.start) + toggle(cm.display.scroller, "dragenter", funcs.enter) + toggle(cm.display.scroller, "dragover", funcs.over) + toggle(cm.display.scroller, "dragleave", funcs.leave) + toggle(cm.display.scroller, "drop", funcs.drop) + } +} + +function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap") + cm.display.sizer.style.minWidth = "" + cm.display.sizerWidth = null + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap") + findMaxLine(cm) + } + estimateLineHeights(cm) + regChange(cm) + clearCaches(cm) + setTimeout(function () { return updateScrollbars(cm); }, 100) +} + +// A CodeMirror instance represents an editor. This is the object +// that user code is usually dealing with. + +function CodeMirror(place, options) { + var this$1 = this; + + if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } + + this.options = options = options ? copyObj(options) : {} + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false) + setGuttersForLineNumbers(options) + + var doc = options.value + if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) } + this.doc = doc + + var input = new CodeMirror.inputStyles[options.inputStyle](this) + var display = this.display = new Display(place, doc, input) + display.wrapper.CodeMirror = this + updateGutters(this) + themeChanged(this) + if (options.lineWrapping) + { this.display.wrapper.className += " CodeMirror-wrap" } + initScrollbars(this) + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + } + + if (options.autofocus && !mobile) { display.input.focus() } + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) } + + registerEventHandlers(this) + ensureGlobalHandlers() + + startOperation(this) + this.curOp.forceUpdate = true + attachDoc(this, doc) + + if ((options.autofocus && !mobile) || this.hasFocus()) + { setTimeout(bind(onFocus, this), 20) } + else + { onBlur(this) } + + for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) + { optionHandlers[opt](this$1, options[opt], Init) } } + maybeUpdateLineNumberWidth(this) + if (options.finishInit) { options.finishInit(this) } + for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) } + endOperation(this) + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + { display.lineDiv.style.textRendering = "auto" } +} + +// The default configuration options. +CodeMirror.defaults = defaults +// Functions to run when options are changed. +CodeMirror.optionHandlers = optionHandlers + +// Attach the necessary event handlers when initializing the editor +function registerEventHandlers(cm) { + var d = cm.display + on(d.scroller, "mousedown", operation(cm, onMouseDown)) + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + { on(d.scroller, "dblclick", operation(cm, function (e) { + if (signalDOMEvent(cm, e)) { return } + var pos = posFromMouse(cm, e) + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } + e_preventDefault(e) + var word = cm.findWordAt(pos) + extendSelection(cm.doc, word.anchor, word.head) + })) } + else + { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) } + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) } + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0} + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000) + prevTouch = d.activeTouch + prevTouch.end = +new Date + } + } + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) { return false } + var touch = e.touches[0] + return touch.radiusX <= 1 && touch.radiusY <= 1 + } + function farAway(touch, other) { + if (other.left == null) { return true } + var dx = other.left - touch.left, dy = other.top - touch.top + return dx * dx + dy * dy > 20 * 20 + } + on(d.scroller, "touchstart", function (e) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { + d.input.ensurePolled() + clearTimeout(touchFinished) + var now = +new Date + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null} + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX + d.activeTouch.top = e.touches[0].pageY + } + } + }) + on(d.scroller, "touchmove", function () { + if (d.activeTouch) { d.activeTouch.moved = true } + }) + on(d.scroller, "touchend", function (e) { + var touch = d.activeTouch + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + { range = new Range(pos, pos) } + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + { range = cm.findWordAt(pos) } + else // Triple tap + { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } + cm.setSelection(range.anchor, range.head) + cm.focus() + e_preventDefault(e) + } + finishTouch() + }) + on(d.scroller, "touchcancel", finishTouch) + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function () { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop) + setScrollLeft(cm, d.scroller.scrollLeft, true) + signal(cm, "scroll", cm) + } + }) + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }) + on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }) + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }) + + d.dragFunctions = { + enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }}, + over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, + start: function (e) { return onDragStart(cm, e); }, + drop: operation(cm, onDrop), + leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} + } + + var inp = d.input.getField() + on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }) + on(inp, "keydown", operation(cm, onKeyDown)) + on(inp, "keypress", operation(cm, onKeyPress)) + on(inp, "focus", function (e) { return onFocus(cm, e); }) + on(inp, "blur", function (e) { return onBlur(cm, e); }) +} + +var initHooks = [] +CodeMirror.defineInitHook = function (f) { return initHooks.push(f); } + +// Indent the given line. The how parameter can be "smart", +// "add"/null, "subtract", or "prev". When aggressive is false +// (typically set to true for forced single-line indents), empty +// lines are not indented, and places where the mode returns Pass +// are left alone. +function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state + if (how == null) { how = "add" } + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) { how = "prev" } + else { state = getStateBefore(cm, n) } + } + + var tabSize = cm.options.tabSize + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) + if (line.stateAfter) { line.stateAfter = null } + var curSpaceString = line.text.match(/^\s*/)[0], indentation + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0 + how = "not" + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) + if (indentation == Pass || indentation > 150) { + if (!aggressive) { return } + how = "prev" + } + } + if (how == "prev") { + if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) } + else { indentation = 0 } + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit + } else if (typeof how == "number") { + indentation = curSpace + how + } + indentation = Math.max(0, indentation) + + var indentString = "", pos = 0 + if (cm.options.indentWithTabs) + { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} } + if (pos < indentation) { indentString += spaceStr(indentation - pos) } + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") + line.stateAfter = null + return true + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { + var range = doc.sel.ranges[i$1] + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos$1 = Pos(n, curSpaceString.length) + replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)) + break + } + } + } +} + +// This will be set to a {lineWise: bool, text: [string]} object, so +// that, when pasting, we know what kind of selections the copied +// text was made out of. +var lastCopied = null + +function setLastCopied(newLastCopied) { + lastCopied = newLastCopied +} + +function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc + cm.display.shift = false + if (!sel) { sel = doc.sel } + + var paste = cm.state.pasteIncoming || origin == "paste" + var textLines = splitLinesAuto(inserted), multiPaste = null + // When pasing N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = [] + for (var i = 0; i < lastCopied.text.length; i++) + { multiPaste.push(doc.splitLines(lastCopied.text[i])) } + } + } else if (textLines.length == sel.ranges.length) { + multiPaste = map(textLines, function (l) { return [l]; }) + } + } + + var updateInput + // Normal behavior is to insert the new text into every selection + for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { + var range = sel.ranges[i$1] + var from = range.from(), to = range.to() + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + { from = Pos(from.line, from.ch - deleted) } + else if (cm.state.overwrite && !paste) // Handle overwrite + { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) } + else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) + { from = to = Pos(from.line, 0) } + } + updateInput = cm.curOp.updateInput + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")} + makeChange(cm.doc, changeEvent) + signalLater(cm, "inputRead", cm, changeEvent) + } + if (inserted && !paste) + { triggerElectric(cm, inserted) } + + ensureCursorVisible(cm) + cm.curOp.updateInput = updateInput + cm.curOp.typing = true + cm.state.pasteIncoming = cm.state.cutIncoming = false +} + +function handlePaste(e, cm) { + var pasted = e.clipboardData && e.clipboardData.getData("Text") + if (pasted) { + e.preventDefault() + if (!cm.isReadOnly() && !cm.options.disableInput) + { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }) } + return true + } +} + +function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) { return } + var sel = cm.doc.sel + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i] + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } + var mode = cm.getModeAt(range.head) + var indented = false + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range.head.line, "smart") + break + } } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) + { indented = indentLine(cm, range.head.line, "smart") } + } + if (indented) { signalLater(cm, "electricInput", cm, range.head.line) } + } +} + +function copyableRanges(cm) { + var text = [], ranges = [] + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} + ranges.push(lineRange) + text.push(cm.getRange(lineRange.anchor, lineRange.head)) + } + return {text: text, ranges: ranges} +} + +function disableBrowserMagic(field, spellcheck) { + field.setAttribute("autocorrect", "off") + field.setAttribute("autocapitalize", "off") + field.setAttribute("spellcheck", !!spellcheck) +} + +function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) { te.style.width = "1000px" } + else { te.setAttribute("wrap", "off") } + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) { te.style.border = "1px solid black" } + disableBrowserMagic(te) + return div +} + +// The publicly visible API. Note that methodOp(f) means +// 'wrap f in an operation, performed on its `this` parameter'. + +// This is not the complete set of editor methods. Most of the +// methods defined on the Doc type are also injected into +// CodeMirror.prototype, for backwards compatibility and +// convenience. + +function addEditorMethods(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers + + var helpers = CodeMirror.helpers = {} + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus()}, + + setOption: function(option, value) { + var options = this.options, old = options[option] + if (options[option] == value && option != "mode") { return } + options[option] = value + if (optionHandlers.hasOwnProperty(option)) + { operation(this, optionHandlers[option])(this, value, old) } + signal(this, "optionChange", this, option) + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps + for (var i = 0; i < maps.length; ++i) + { if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1) + return true + } } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) + if (mode.startState) { throw new Error("Overlays may not be stateful.") } + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + function (overlay) { return overlay.priority; }) + this.state.modeGen++ + regChange(this) + }), + removeOverlay: methodOp(function(spec) { + var this$1 = this; + + var overlays = this.state.overlays + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1) + this$1.state.modeGen++ + regChange(this$1) + return + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } + else { dir = dir ? "add" : "subtract" } + } + if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } + }), + indentSelection: methodOp(function(how) { + var this$1 = this; + + var ranges = this.doc.sel.ranges, end = -1 + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i] + if (!range.empty()) { + var from = range.from(), to = range.to() + var start = Math.max(end, from.line) + end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 + for (var j = start; j < end; ++j) + { indentLine(this$1, j, how) } + var newRanges = this$1.doc.sel.ranges + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) } + } else if (range.head.line > end) { + indentLine(this$1, range.head.line, how, true) + end = range.head.line + if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos) + var styles = getLineStyles(this, getLine(this.doc, pos.line)) + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch + var type + if (ch == 0) { type = styles[2] } + else { for (;;) { + var mid = (before + after) >> 1 + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } + else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } + else { type = styles[mid * 2 + 2]; break } + } } + var cut = type ? type.indexOf("overlay ") : -1 + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + var mode = this.doc.mode + if (!mode.innerMode) { return mode } + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + var this$1 = this; + + var found = [] + if (!helpers.hasOwnProperty(type)) { return found } + var help = helpers[type], mode = this.getModeAt(pos) + if (typeof mode[type] == "string") { + if (help[mode[type]]) { found.push(help[mode[type]]) } + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]] + if (val) { found.push(val) } + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]) + } else if (help[mode.name]) { + found.push(help[mode.name]) + } + for (var i$1 = 0; i$1 < help._global.length; i$1++) { + var cur = help._global[i$1] + if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) + { found.push(cur.val) } + } + return found + }, + + getStateAfter: function(line, precise) { + var doc = this.doc + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) + return getStateBefore(this, line + 1, precise) + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary() + if (start == null) { pos = range.head } + else if (typeof start == "object") { pos = clipPos(this.doc, start) } + else { pos = start ? range.from() : range.to() } + return cursorCoords(this, pos, mode || "page") + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page") + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page") + return coordsChar(this, coords.left, coords.top) + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top + return lineAtHeight(this.doc, height + this.display.viewOffset) + }, + heightAtLine: function(line, mode, includeWidgets) { + var end = false, lineObj + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1 + if (line < this.doc.first) { line = this.doc.first } + else if (line > last) { line = last; end = true } + lineObj = getLine(this.doc, line) + } else { + lineObj = line + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display + pos = cursorCoords(this, clipPos(this.doc, pos)) + var top = pos.bottom, left = pos.left + node.style.position = "absolute" + node.setAttribute("cm-ignore-events", "true") + this.display.input.setUneditable(node) + display.sizer.appendChild(node) + if (vert == "over") { + top = pos.top + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + { top = pos.top - node.offsetHeight } + else if (pos.bottom + node.offsetHeight <= vspace) + { top = pos.bottom } + if (left + node.offsetWidth > hspace) + { left = hspace - node.offsetWidth } + } + node.style.top = top + "px" + node.style.left = node.style.right = "" + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth + node.style.right = "0px" + } else { + if (horiz == "left") { left = 0 } + else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2 } + node.style.left = left + "px" + } + if (scroll) + { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) } + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + { return commands[cmd].call(null, this) } + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), + + findPosH: function(from, amount, unit, visually) { + var this$1 = this; + + var dir = 1 + if (amount < 0) { dir = -1; amount = -amount } + var cur = clipPos(this.doc, from) + for (var i = 0; i < amount; ++i) { + cur = findPosH(this$1.doc, cur, dir, unit, visually) + if (cur.hitSide) { break } + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + var this$1 = this; + + this.extendSelectionsBy(function (range) { + if (this$1.display.shift || this$1.doc.extend || range.empty()) + { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } + else + { return dir < 0 ? range.from() : range.to() } + }, sel_move) + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc + if (sel.somethingSelected()) + { doc.replaceSelection("", null, "+delete") } + else + { deleteNearSelection(this, function (range) { + var other = findPosH(doc, range.head, dir, unit, false) + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} + }) } + }), + + findPosV: function(from, amount, unit, goalColumn) { + var this$1 = this; + + var dir = 1, x = goalColumn + if (amount < 0) { dir = -1; amount = -amount } + var cur = clipPos(this.doc, from) + for (var i = 0; i < amount; ++i) { + var coords = cursorCoords(this$1, cur, "div") + if (x == null) { x = coords.left } + else { coords.left = x } + cur = findPosV(this$1, coords, dir, unit) + if (cur.hitSide) { break } + } + return cur + }, + + moveV: methodOp(function(dir, unit) { + var this$1 = this; + + var doc = this.doc, goals = [] + var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() + doc.extendSelectionsBy(function (range) { + if (collapse) + { return dir < 0 ? range.from() : range.to() } + var headPos = cursorCoords(this$1, range.head, "div") + if (range.goalColumn != null) { headPos.left = range.goalColumn } + goals.push(headPos.left) + var pos = findPosV(this$1, headPos, dir, unit) + if (unit == "page" && range == doc.sel.primary()) + { addToScrollPos(this$1, null, charCoords(this$1, pos, "div").top - headPos.top) } + return pos + }, sel_move) + if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) + { doc.sel.ranges[i].goalColumn = goals[i] } } + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text + var start = pos.ch, end = pos.ch + if (line) { + var helper = this.getHelper(pos, "wordChars") + if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end } + var startChar = line.charAt(start) + var check = isWordChar(startChar, helper) + ? function (ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } + : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } + while (start > 0 && check(line.charAt(start - 1))) { --start } + while (end < line.length && check(line.charAt(end))) { ++end } + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) { return } + if (this.state.overwrite = !this.state.overwrite) + { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } + else + { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } + + signal(this, "overwriteToggle", this, this.state.overwrite) + }, + hasFocus: function() { return this.display.input.getField() == activeElt() }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) { resolveScrollToPos(this) } + if (x != null) { this.curOp.scrollLeft = x } + if (y != null) { this.curOp.scrollTop = y } + }), + getScrollInfo: function() { + var scroller = this.display.scroller + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null} + if (margin == null) { margin = this.options.cursorScrollMargin } + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null} + } else if (range.from == null) { + range = {from: range, to: null} + } + if (!range.to) { range.to = range.from } + range.margin = margin || 0 + + if (range.from.line != null) { + resolveScrollToPos(this) + this.curOp.scrollToPos = range + } else { + var sPos = calculateScrollPos(this, { + left: Math.min(range.from.left, range.to.left), + top: Math.min(range.from.top, range.to.top) - range.margin, + right: Math.max(range.from.right, range.to.right), + bottom: Math.max(range.from.bottom, range.to.bottom) + range.margin + }) + this.scrollTo(sPos.scrollLeft, sPos.scrollTop) + } + }), + + setSize: methodOp(function(width, height) { + var this$1 = this; + + var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } + if (width != null) { this.display.wrapper.style.width = interpret(width) } + if (height != null) { this.display.wrapper.style.height = interpret(height) } + if (this.options.lineWrapping) { clearLineMeasurementCache(this) } + var lineNo = this.display.viewFrom + this.doc.iter(lineNo, this.display.viewTo, function (line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) + { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } + ++lineNo + }) + this.curOp.forceUpdate = true + signal(this, "refresh", this) + }), + + operation: function(f){return runInOp(this, f)}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight + regChange(this) + this.curOp.forceUpdate = true + clearCaches(this) + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop) + updateGutterSpace(this) + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + { estimateLineHeights(this) } + signal(this, "refresh", this) + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc + old.cm = null + attachDoc(this, doc) + clearCaches(this) + this.display.input.reset() + this.scrollTo(doc.scrollLeft, doc.scrollTop) + this.curOp.forceScroll = true + signalLater(this, "swapDoc", this, old) + return old + }), + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + } + eventMixin(CodeMirror) + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []} } + helpers[type][name] = value + } + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value) + helpers[type]._global.push({pred: predicate, val: value}) + } +} + +// Used for horizontal relative motion. Dir is -1 or 1 (left or +// right), unit can be "char", "column" (like char, but doesn't +// cross line boundaries), "word" (across next word), or "group" (to +// the start of next group of word or non-word-non-whitespace +// chars). The visually param controls whether, in right-to-left +// text, direction 1 means to move towards the next index in the +// string, or towards the character to the right of the current +// position. The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosH(doc, pos, dir, unit, visually) { + var oldPos = pos + var origDir = dir + var lineObj = getLine(doc, pos.line) + function findNextLine() { + var l = pos.line + dir + if (l < doc.first || l >= doc.first + doc.size) { return false } + pos = new Pos(l, pos.ch, pos.sticky) + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + var next + if (visually) { + next = moveVisually(doc.cm, lineObj, pos, dir) + } else { + next = moveLogically(lineObj, pos, dir) + } + if (next == null) { + if (!boundToLine && findNextLine()) + { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) } + else + { return false } + } else { + pos = next + } + return true + } + + if (unit == "char") { + moveOnce() + } else if (unit == "column") { + moveOnce(true) + } else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group" + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) { break } + var cur = lineObj.text.charAt(pos.ch) || "\n" + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p" + if (group && !first && !type) { type = "s" } + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} + break + } + + if (type) { sawType = type } + if (dir > 0 && !moveOnce(!first)) { break } + } + } + var result = skipAtomic(doc, pos, oldPos, origDir, true) + if (equalCursorPos(oldPos, result)) { result.hitSide = true } + return result +} + +// For relative vertical movement. Dir may be -1 or 1. Unit can be +// "page" or "line". The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) + var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3 + } + var target + for (;;) { + target = coordsChar(cm, x, y) + if (!target.outside) { break } + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } + y += dir * 5 + } + return target +} + +// CONTENTEDITABLE INPUT STYLE + +var ContentEditableInput = function(cm) { + this.cm = cm + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null + this.polling = new Delayed() + this.composing = null + this.gracePeriod = false + this.readDOMTimeout = null +}; + +ContentEditableInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = input.cm + var div = input.div = display.lineDiv + disableBrowserMagic(div, cm.options.spellcheck) + + on(div, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20) } + }) + + on(div, "compositionstart", function (e) { + this$1.composing = {data: e.data, done: false} + }) + on(div, "compositionupdate", function (e) { + if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } + }) + on(div, "compositionend", function (e) { + if (this$1.composing) { + if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } + this$1.composing.done = true + } + }) + + on(div, "touchstart", function () { return input.forceCompositionEnd(); }) + + on(div, "input", function () { + if (!this$1.composing) { this$1.readFromDOMSoon() } + }) + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + if (e.type == "cut") { cm.replaceSelection("", null, "cut") } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.operation(function () { + cm.setSelections(ranges.ranges, 0, sel_dontScroll) + cm.replaceSelection("", null, "cut") + }) + } + } + if (e.clipboardData) { + e.clipboardData.clearData() + var content = lastCopied.text.join("\n") + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content) + if (e.clipboardData.getData("Text") == content) { + e.preventDefault() + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) + te.value = lastCopied.text.join("\n") + var hadFocus = document.activeElement + selectInput(te) + setTimeout(function () { + cm.display.lineSpace.removeChild(kludge) + hadFocus.focus() + if (hadFocus == div) { input.showPrimarySelection() } + }, 50) + } + on(div, "copy", onCopyCut) + on(div, "cut", onCopyCut) +}; + +ContentEditableInput.prototype.prepareSelection = function () { + var result = prepareSelection(this.cm, false) + result.focus = this.cm.state.focused + return result +}; + +ContentEditableInput.prototype.showSelection = function (info, takeFocus) { + if (!info || !this.cm.display.view.length) { return } + if (info.focus || takeFocus) { this.showPrimarySelection() } + this.showMultipleSelections(info) +}; + +ContentEditableInput.prototype.showPrimarySelection = function () { + var sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() + var from = prim.from(), to = prim.to() + + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { + sel.removeAllRanges() + return + } + + var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + { return } + + var view = cm.display.view + var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0} + var end = to.line < cm.display.viewTo && posToDOM(cm, to) + if (!end) { + var measure = view[view.length - 1].measure + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} + } + + if (!start || !end) { + sel.removeAllRanges() + return + } + + var old = sel.rangeCount && sel.getRangeAt(0), rng + try { rng = range(start.node, start.offset, end.offset, end.node) } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && cm.state.focused) { + sel.collapse(start.node, start.offset) + if (!rng.collapsed) { + sel.removeAllRanges() + sel.addRange(rng) + } + } else { + sel.removeAllRanges() + sel.addRange(rng) + } + if (old && sel.anchorNode == null) { sel.addRange(old) } + else if (gecko) { this.startGracePeriod() } + } + this.rememberSelection() +}; + +ContentEditableInput.prototype.startGracePeriod = function () { + var this$1 = this; + + clearTimeout(this.gracePeriod) + this.gracePeriod = setTimeout(function () { + this$1.gracePeriod = false + if (this$1.selectionChanged()) + { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) } + }, 20) +}; + +ContentEditableInput.prototype.showMultipleSelections = function (info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) +}; + +ContentEditableInput.prototype.rememberSelection = function () { + var sel = window.getSelection() + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset +}; + +ContentEditableInput.prototype.selectionInEditor = function () { + var sel = window.getSelection() + if (!sel.rangeCount) { return false } + var node = sel.getRangeAt(0).commonAncestorContainer + return contains(this.div, node) +}; + +ContentEditableInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor()) + { this.showSelection(this.prepareSelection(), true) } + this.div.focus() + } +}; +ContentEditableInput.prototype.blur = function () { this.div.blur() }; +ContentEditableInput.prototype.getField = function () { return this.div }; + +ContentEditableInput.prototype.supportsTouch = function () { return true }; + +ContentEditableInput.prototype.receivedFocus = function () { + var input = this + if (this.selectionInEditor()) + { this.pollSelection() } + else + { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) } + + function poll() { + if (input.cm.state.focused) { + input.pollSelection() + input.polling.set(input.cm.options.pollInterval, poll) + } + } + this.polling.set(this.cm.options.pollInterval, poll) +}; + +ContentEditableInput.prototype.selectionChanged = function () { + var sel = window.getSelection() + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset +}; + +ContentEditableInput.prototype.pollSelection = function () { + if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } + var sel = window.getSelection(), cm = this.cm + // On Android Chrome (version 56, at least), backspacing into an + // uneditable block element will put the cursor in that element, + // and then, because it's not editable, hide the virtual keyboard. + // Because Android doesn't allow us to actually detect backspace + // presses in a sane way, this code checks for when that happens + // and simulates a backspace press in this case. + if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { + this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) + this.blur() + this.focus() + return + } + if (this.composing) { return } + this.rememberSelection() + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + var head = domToPos(cm, sel.focusNode, sel.focusOffset) + if (anchor && head) { runInOp(cm, function () { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) + if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } + }) } +}; + +ContentEditableInput.prototype.pollContent = function () { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout) + this.readDOMTimeout = null + } + + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() + var from = sel.from(), to = sel.to() + if (from.ch == 0 && from.line > cm.firstLine()) + { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + { to = Pos(to.line + 1, 0) } + if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } + + var fromIndex, fromLine, fromNode + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line) + fromNode = display.view[0].node + } else { + fromLine = lineNo(display.view[fromIndex].line) + fromNode = display.view[fromIndex - 1].node.nextSibling + } + var toIndex = findViewIndex(cm, to.line) + var toLine, toNode + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1 + toNode = display.lineDiv.lastChild + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1 + toNode = display.view[toIndex + 1].node.previousSibling + } + + if (!fromNode) { return false } + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } + else { break } + } + + var cutFront = 0, cutEnd = 0 + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + { ++cutFront } + var newBot = lst(newText), oldBot = lst(oldText) + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)) + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + { ++cutEnd } + // Try to move start of change to start of selection if ambiguous + if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { + while (cutFront && cutFront > from.ch && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { + cutFront-- + cutEnd++ + } + } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") + + var chFrom = Pos(fromLine, cutFront) + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input") + return true + } +}; + +ContentEditableInput.prototype.ensurePolled = function () { + this.forceCompositionEnd() +}; +ContentEditableInput.prototype.reset = function () { + this.forceCompositionEnd() +}; +ContentEditableInput.prototype.forceCompositionEnd = function () { + if (!this.composing) { return } + clearTimeout(this.readDOMTimeout) + this.composing = null + this.updateFromDOM() + this.div.blur() + this.div.focus() +}; +ContentEditableInput.prototype.readFromDOMSoon = function () { + var this$1 = this; + + if (this.readDOMTimeout != null) { return } + this.readDOMTimeout = setTimeout(function () { + this$1.readDOMTimeout = null + if (this$1.composing) { + if (this$1.composing.done) { this$1.composing = null } + else { return } + } + this$1.updateFromDOM() + }, 80) +}; + +ContentEditableInput.prototype.updateFromDOM = function () { + var this$1 = this; + + if (this.cm.isReadOnly() || !this.pollContent()) + { runInOp(this.cm, function () { return regChange(this$1.cm); }) } +}; + +ContentEditableInput.prototype.setUneditable = function (node) { + node.contentEditable = "false" +}; + +ContentEditableInput.prototype.onKeyPress = function (e) { + if (e.charCode == 0) { return } + e.preventDefault() + if (!this.cm.isReadOnly()) + { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } +}; + +ContentEditableInput.prototype.readOnlyChanged = function (val) { + this.div.contentEditable = String(val != "nocursor") +}; + +ContentEditableInput.prototype.onContextMenu = function () {}; +ContentEditableInput.prototype.resetPosition = function () {}; + +ContentEditableInput.prototype.needsContentAttribute = true + +function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line) + if (!view || view.hidden) { return null } + var line = getLine(cm.doc, pos.line) + var info = mapFromLineView(view, line, pos.line) + + var order = getOrder(line, cm.doc.direction), side = "left" + if (order) { + var partPos = getBidiPartAt(order, pos.ch) + side = partPos % 2 ? "right" : "left" + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) + result.offset = result.collapse == "right" ? result.end : result.start + return result +} + +function isInGutter(node) { + for (var scan = node; scan; scan = scan.parentNode) + { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } + return false +} + +function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } + +function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false, lineSep = cm.doc.lineSeparator() + function recognizeMarker(id) { return function (marker) { return marker.id == id; } } + function close() { + if (closing) { + text += lineSep + closing = false + } + } + function addText(str) { + if (str) { + close() + text += str + } + } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text") + if (cmText != null) { + addText(cmText || node.textContent.replace(/\u200b/g, "")) + return + } + var markerID = node.getAttribute("cm-marker"), range + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) + if (found.length && (range = found[0].find())) + { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } + return + } + if (node.getAttribute("contenteditable") == "false") { return } + var isBlock = /^(pre|div|p)$/i.test(node.nodeName) + if (isBlock) { close() } + for (var i = 0; i < node.childNodes.length; i++) + { walk(node.childNodes[i]) } + if (isBlock) { closing = true } + } else if (node.nodeType == 3) { + addText(node.nodeValue) + } + } + for (;;) { + walk(from) + if (from == to) { break } + from = from.nextSibling + } + return text +} + +function domToPos(cm, node, offset) { + var lineNode + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset] + if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } + node = null; offset = 0 + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) { return null } + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i] + if (lineView.node == lineNode) + { return locateNodeInLineView(lineView, node, offset) } + } +} + +function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false + if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } + if (node == wrapper) { + bad = true + node = wrapper.childNodes[offset] + offset = 0 + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild + if (offset) { offset = textNode.nodeValue.length } + } + while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } + var measure = lineView.measure, maps = measure.maps + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i] + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2] + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) + var ch = map[j] + offset + if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)] } + return Pos(line, ch) + } + } + } + } + var found = find(textNode, topNode, offset) + if (found) { return badPos(found, bad) } + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0) + if (found) + { return badPos(Pos(found.line, found.ch - dist), bad) } + else + { dist += after.textContent.length } + } + for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1) + if (found) + { return badPos(Pos(found.line, found.ch + dist$1), bad) } + else + { dist$1 += before.textContent.length } + } +} + +// TEXTAREA INPUT STYLE + +var TextareaInput = function(cm) { + this.cm = cm + // See input.poll and input.reset + this.prevInput = "" + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false + // Self-resetting timeout for the poller + this.polling = new Delayed() + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false + this.composing = null +}; + +TextareaInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = this.cm + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea() + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild + display.wrapper.insertBefore(div, display.wrapper.firstChild) + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) { te.style.width = "0px" } + + on(te, "input", function () { + if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null } + input.poll() + }) + + on(te, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + + cm.state.pasteIncoming = true + input.fastPoll() + }) + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + if (input.inaccurateSelection) { + input.prevInput = "" + input.inaccurateSelection = false + te.value = lastCopied.text.join("\n") + selectInput(te) + } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll) + } else { + input.prevInput = "" + te.value = ranges.text.join("\n") + selectInput(te) + } + } + if (e.type == "cut") { cm.state.cutIncoming = true } + } + on(te, "cut", prepareCopyCut) + on(te, "copy", prepareCopyCut) + + on(display.scroller, "paste", function (e) { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } + cm.state.pasteIncoming = true + input.focus() + }) + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function (e) { + if (!eventInWidget(display, e)) { e_preventDefault(e) } + }) + + on(te, "compositionstart", function () { + var start = cm.getCursor("from") + if (input.composing) { input.composing.range.clear() } + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + } + }) + on(te, "compositionend", function () { + if (input.composing) { + input.poll() + input.composing.range.clear() + input.composing = null + } + }) +}; + +TextareaInput.prototype.prepareSelection = function () { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc + var result = prepareSelection(cm) + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div") + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + } + + return result +}; + +TextareaInput.prototype.showSelection = function (drawn) { + var cm = this.cm, display = cm.display + removeChildrenAndAdd(display.cursorDiv, drawn.cursors) + removeChildrenAndAdd(display.selectionDiv, drawn.selection) + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px" + this.wrapper.style.left = drawn.teLeft + "px" + } +}; + +// Reset the input to correspond to the selection (or to be empty, +// when not typing and nothing is selected) +TextareaInput.prototype.reset = function (typing) { + if (this.contextMenuPending) { return } + var minimal, selected, cm = this.cm, doc = cm.doc + if (cm.somethingSelected()) { + this.prevInput = "" + var range = doc.sel.primary() + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000) + var content = minimal ? "-" : selected || cm.getSelection() + this.textarea.value = content + if (cm.state.focused) { selectInput(this.textarea) } + if (ie && ie_version >= 9) { this.hasSelection = content } + } else if (!typing) { + this.prevInput = this.textarea.value = "" + if (ie && ie_version >= 9) { this.hasSelection = null } + } + this.inaccurateSelection = minimal +}; + +TextareaInput.prototype.getField = function () { return this.textarea }; + +TextareaInput.prototype.supportsTouch = function () { return false }; + +TextareaInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus() } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } +}; + +TextareaInput.prototype.blur = function () { this.textarea.blur() }; + +TextareaInput.prototype.resetPosition = function () { + this.wrapper.style.top = this.wrapper.style.left = 0 +}; + +TextareaInput.prototype.receivedFocus = function () { this.slowPoll() }; + +// Poll for input changes, using the normal rate of polling. This +// runs as long as the editor is focused. +TextareaInput.prototype.slowPoll = function () { + var this$1 = this; + + if (this.pollingFast) { return } + this.polling.set(this.cm.options.pollInterval, function () { + this$1.poll() + if (this$1.cm.state.focused) { this$1.slowPoll() } + }) +}; + +// When an event has just come in that is likely to add or change +// something in the input textarea, we poll faster, to ensure that +// the change appears on the screen quickly. +TextareaInput.prototype.fastPoll = function () { + var missed = false, input = this + input.pollingFast = true + function p() { + var changed = input.poll() + if (!changed && !missed) {missed = true; input.polling.set(60, p)} + else {input.pollingFast = false; input.slowPoll()} + } + input.polling.set(20, p) +}; + +// Read input from the textarea, and update the document to match. +// When something is selected, it is present in the textarea, and +// selected (unless it is huge, in which case a placeholder is +// used). When nothing is selected, the cursor sits after previously +// seen text (can be empty), which is stored in prevInput (we must +// not reset the textarea when typing, because that breaks IME). +TextareaInput.prototype.poll = function () { + var this$1 = this; + + var cm = this.cm, input = this.textarea, prevInput = this.prevInput + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + { return false } + + var text = input.value + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) { return false } + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset() + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0) + if (first == 0x200b && !prevInput) { prevInput = "\u200b" } + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length) + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same } + + runInOp(cm, function () { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this$1.composing ? "*compose" : null) + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" } + else { this$1.prevInput = text } + + if (this$1.composing) { + this$1.composing.range.clear() + this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}) + } + }) + return true +}; + +TextareaInput.prototype.ensurePolled = function () { + if (this.pollingFast && this.poll()) { this.pollingFast = false } +}; + +TextareaInput.prototype.onKeyPress = function () { + if (ie && ie_version >= 9) { this.hasSelection = null } + this.fastPoll() +}; + +TextareaInput.prototype.onContextMenu = function (e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop + if (!pos || presto) { return } // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu + if (reset && cm.doc.sel.contains(pos) == -1) + { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) } + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText + input.wrapper.style.cssText = "position: absolute" + var wrapperBox = input.wrapper.getBoundingClientRect() + te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);" + var oldScrollY + if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712) + display.input.focus() + if (webkit) { window.scrollTo(null, oldScrollY) } + display.input.reset() + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) { te.value = input.prevInput = " " } + input.contextMenuPending = true + display.selForContextMenu = cm.doc.sel + clearTimeout(display.detectingSelectAll) + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected() + var extval = "\u200b" + (selected ? te.value : "") + te.value = "\u21da" // Used to catch context-menu undo + te.value = extval + input.prevInput = selected ? "" : "\u200b" + te.selectionStart = 1; te.selectionEnd = extval.length + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel + } + } + function rehide() { + input.contextMenuPending = false + input.wrapper.style.cssText = oldWrapperCSS + te.style.cssText = oldCSS + if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) } + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() } + var i = 0, poll = function () { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") { + operation(cm, selectAll)(cm) + } else if (i++ < 10) { + display.detectingSelectAll = setTimeout(poll, 500) + } else { + display.selForContextMenu = null + display.input.reset() + } + } + display.detectingSelectAll = setTimeout(poll, 200) + } + } + + if (ie && ie_version >= 9) { prepareSelectAllHack() } + if (captureRightClick) { + e_stop(e) + var mouseup = function () { + off(window, "mouseup", mouseup) + setTimeout(rehide, 20) + } + on(window, "mouseup", mouseup) + } else { + setTimeout(rehide, 50) + } +}; + +TextareaInput.prototype.readOnlyChanged = function (val) { + if (!val) { this.reset() } +}; + +TextareaInput.prototype.setUneditable = function () {}; + +TextareaInput.prototype.needsContentAttribute = false + +function fromTextArea(textarea, options) { + options = options ? copyObj(options) : {} + options.value = textarea.value + if (!options.tabindex && textarea.tabIndex) + { options.tabindex = textarea.tabIndex } + if (!options.placeholder && textarea.placeholder) + { options.placeholder = textarea.placeholder } + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt() + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body + } + + function save() {textarea.value = cm.getValue()} + + var realSubmit + if (textarea.form) { + on(textarea.form, "submit", save) + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form + realSubmit = form.submit + try { + var wrappedSubmit = form.submit = function () { + save() + form.submit = realSubmit + form.submit() + form.submit = wrappedSubmit + } + } catch(e) {} + } + } + + options.finishInit = function (cm) { + cm.save = save + cm.getTextArea = function () { return textarea; } + cm.toTextArea = function () { + cm.toTextArea = isNaN // Prevent this from being ran twice + save() + textarea.parentNode.removeChild(cm.getWrapperElement()) + textarea.style.display = "" + if (textarea.form) { + off(textarea.form, "submit", save) + if (typeof textarea.form.submit == "function") + { textarea.form.submit = realSubmit } + } + } + } + + textarea.style.display = "none" + var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, + options) + return cm +} + +function addLegacyProps(CodeMirror) { + CodeMirror.off = off + CodeMirror.on = on + CodeMirror.wheelEventPixels = wheelEventPixels + CodeMirror.Doc = Doc + CodeMirror.splitLines = splitLinesAuto + CodeMirror.countColumn = countColumn + CodeMirror.findColumn = findColumn + CodeMirror.isWordChar = isWordCharBasic + CodeMirror.Pass = Pass + CodeMirror.signal = signal + CodeMirror.Line = Line + CodeMirror.changeEnd = changeEnd + CodeMirror.scrollbarModel = scrollbarModel + CodeMirror.Pos = Pos + CodeMirror.cmpPos = cmp + CodeMirror.modes = modes + CodeMirror.mimeModes = mimeModes + CodeMirror.resolveMode = resolveMode + CodeMirror.getMode = getMode + CodeMirror.modeExtensions = modeExtensions + CodeMirror.extendMode = extendMode + CodeMirror.copyState = copyState + CodeMirror.startState = startState + CodeMirror.innerMode = innerMode + CodeMirror.commands = commands + CodeMirror.keyMap = keyMap + CodeMirror.keyName = keyName + CodeMirror.isModifierKey = isModifierKey + CodeMirror.lookupKey = lookupKey + CodeMirror.normalizeKeyMap = normalizeKeyMap + CodeMirror.StringStream = StringStream + CodeMirror.SharedTextMarker = SharedTextMarker + CodeMirror.TextMarker = TextMarker + CodeMirror.LineWidget = LineWidget + CodeMirror.e_preventDefault = e_preventDefault + CodeMirror.e_stopPropagation = e_stopPropagation + CodeMirror.e_stop = e_stop + CodeMirror.addClass = addClass + CodeMirror.contains = contains + CodeMirror.rmClass = rmClass + CodeMirror.keyNames = keyNames +} + +// EDITOR CONSTRUCTOR + +defineOptions(CodeMirror) + +addEditorMethods(CodeMirror) + +// Set up methods on CodeMirror's prototype to redirect to the editor's document. +var dontDelegate = "iter insert remove copy getEditor constructor".split(" ") +for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + { CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments)} + })(Doc.prototype[prop]) } } + +eventMixin(Doc) + +// INPUT HANDLING + +CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} + +// MODE DEFINITION AND QUERYING + +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +CodeMirror.defineMode = function(name/*, mode, …*/) { + if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name } + defineMode.apply(this, arguments) +} + +CodeMirror.defineMIME = defineMIME + +// Minimal default mode. +CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }) +CodeMirror.defineMIME("text/plain", "null") + +// EXTENSIONS + +CodeMirror.defineExtension = function (name, func) { + CodeMirror.prototype[name] = func +} +CodeMirror.defineDocExtension = function (name, func) { + Doc.prototype[name] = func +} + +CodeMirror.fromTextArea = fromTextArea + +addLegacyProps(CodeMirror) + +CodeMirror.version = "5.25.2" + +return CodeMirror; + +}))); \ No newline at end of file diff --git a/_content/tour/grc/static/lib/codemirror/mode/go/go.js b/_content/tour/grc/static/lib/codemirror/mode/go/go.js new file mode 100644 index 00000000..8896b57f --- /dev/null +++ b/_content/tour/grc/static/lib/codemirror/mode/go/go.js @@ -0,0 +1,187 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("go", function(config) { + var indentUnit = config.indentUnit; + + var keywords = { + "break":true, "case":true, "chan":true, "const":true, "continue":true, + "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, + "func":true, "go":true, "goto":true, "if":true, "import":true, + "interface":true, "map":true, "package":true, "range":true, "return":true, + "select":true, "struct":true, "switch":true, "type":true, "var":true, + "bool":true, "byte":true, "complex64":true, "complex128":true, + "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, + "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, + "uint64":true, "int":true, "uint":true, "uintptr":true, "error": true, + "rune":true + }; + + var atoms = { + "true":true, "false":true, "iota":true, "nil":true, "append":true, + "cap":true, "close":true, "complex":true, "copy":true, "delete":true, "imag":true, + "len":true, "make":true, "new":true, "panic":true, "print":true, + "println":true, "real":true, "recover":true + }; + + var isOperatorChar = /[+\-*&^%:=<>!|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'" || ch == "`") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\d\.]/.test(ch)) { + if (ch == ".") { + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + } else if (ch == "0") { + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + } else { + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + } + return "number"; + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (cur == "case" || cur == "default") curPunc = "case"; + return "keyword"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && quote != "`" && next == "\\"; + } + if (end || !(escaped || quote == "`")) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + if (!state.context.prev) return; + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + if (ctx.type == "case") ctx.type = "}"; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "case") ctx.type = "case"; + else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state); + else if (curPunc == ctx.type) popContext(state); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { + state.context.type = "}"; + return ctx.indented; + } + var closing = firstChar == ctx.type; + if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}):", + closeBrackets: "()[]{}''\"\"``", + fold: "brace", + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//" + }; +}); + +CodeMirror.defineMIME("text/x-go", "go"); + +}); diff --git a/_content/tour/grc/static/lib/jquery-ui.min.js b/_content/tour/grc/static/lib/jquery-ui.min.js new file mode 100644 index 00000000..115fdfdc --- /dev/null +++ b/_content/tour/grc/static/lib/jquery-ui.min.js @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.10.3 - 2013-09-26 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js +* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e,t){function i(t,i){var s,o,u,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,o=s.name,t.href&&o&&"map"===s.nodeName.toLowerCase()?(u=e("img[usemap=#"+o+"]")[0],!!u&&n(u)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,o=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.3",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,o=e(this[0]);o.length&&o[0]!==document;){if(n=o.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(o.css("zIndex"),10),!isNaN(s)&&0!==s))return s;o=o.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){o.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("
").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(o,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var o="Width"===n?["Left","Right"]:["Top","Bottom"],u=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(u,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(u,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,o=e.ui[t].prototype;for(s in n)o.plugins[s]=o.plugins[s]||[],o.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(e,t){var i=0,n=Array.prototype.slice,s=e.cleanData;e.cleanData=function(t){for(var i,n=0;null!=(i=t[n]);n++)try{e(i).triggerHandler("remove")}catch(o){}s(t)},e.widget=function(i,n,s){var o,r,a,u,l={},h=i.split(".")[0];i=i.split(".")[1],o=h+"-"+i,s||(s=n,n=e.Widget),e.expr[":"][o.toLowerCase()]=function(t){return!!e.data(t,o)},e[h]=e[h]||{},r=e[h][i],a=e[h][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new a(e,i)},e.extend(a,r,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),u=new n,u.options=e.widget.extend({},u.options),e.each(s,function(i,s){return e.isFunction(s)?(l[i]=function(){var e=function(){return n.prototype[i].apply(this,arguments)},t=function(e){return n.prototype[i].apply(this,e)};return function(){var i,n=this._super,o=this._superApply;return this._super=e,this._superApply=t,i=s.apply(this,arguments),this._super=n,this._superApply=o,i}}(),t):(l[i]=s,t)}),a.prototype=e.widget.extend(u,{widgetEventPrefix:r?u.widgetEventPrefix:i},l,{constructor:a,namespace:h,widgetName:i,widgetFullName:o}),r?(e.each(r._childConstructors,function(t,i){var n=i.prototype;e.widget(n.namespace+"."+n.widgetName,a,i._proto)}),delete r._childConstructors):n._childConstructors.push(a),e.widget.bridge(i,a)},e.widget.extend=function(i){for(var s,o,r=n.call(arguments,1),a=0,u=r.length;u>a;a++)for(s in r[a])o=r[a][s],r[a].hasOwnProperty(s)&&o!==t&&(i[s]=e.isPlainObject(o)?e.isPlainObject(i[s])?e.widget.extend({},i[s],o):e.widget.extend({},o):o);return i},e.widget.bridge=function(i,s){var o=s.prototype.widgetFullName||i;e.fn[i]=function(r){var a="string"==typeof r,u=n.call(arguments,1),l=this;return r=!a&&u.length?e.widget.extend.apply(null,[r].concat(u)):r,a?this.each(function(){var n,s=e.data(this,o);return s?e.isFunction(s[r])&&"_"!==r.charAt(0)?(n=s[r].apply(s,u),n!==s&&n!==t?(l=n&&n.jquery?l.pushStack(n.get()):n,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,o);t?t.option(r||{})._init():e.data(this,o,new s(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,n){n=e(n||this.defaultElement||this)[0],this.element=e(n),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),n!==this&&(e.data(n,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===n&&this.destroy()}}),this.document=e(n.style?n.ownerDocument:n.document||n),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,n){var s,o,r,a=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(a={},s=i.split("."),i=s.shift(),s.length){for(o=a[i]=e.widget.extend({},this.options[i]),r=0;s.length-1>r;r++)o[s[r]]=o[s[r]]||{},o=o[s[r]];if(i=s.pop(),n===t)return o[i]===t?null:o[i];o[i]=n}else{if(n===t)return this.options[i]===t?null:this.options[i];a[i]=n}return this._setOptions(a),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,n,s){var o,r=this;"boolean"!=typeof i&&(s=n,n=i,i=!1),s?(n=o=e(n),this.bindings=this.bindings.add(n)):(s=n,n=this.element,o=this.widget()),e.each(s,function(s,a){function u(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof a?r[a]:a).apply(r,arguments):t}"string"!=typeof a&&(u.guid=a.guid=a.guid||u.guid||e.guid++);var l=s.match(/^(\w+)\s*(.*)$/),h=l[1]+r.eventNamespace,c=l[2];c?o.delegate(c,h,u):n.bind(h,u)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?n[e]:e).apply(n,arguments)}var n=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,n){var s,o,r=this.options[t];if(n=n||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(s in o)s in i||(i[s]=o[s]);return this.element.trigger(i,n),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(n))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(n,s,o){"string"==typeof s&&(s={effect:s});var r,a=s?s===!0||"number"==typeof s?i:s.effect||i:t;s=s||{},"number"==typeof s&&(s={duration:s}),r=!e.isEmptyObject(s),s.complete=o,s.delay&&n.delay(s.delay),r&&e.effects&&e.effects.effect[a]?n[t](s):a!==t&&n[a]?n[a](s.duration,s.easing,o):n.queue(function(i){e(this)[t](),o&&o.call(n[0]),i()})}})})(jQuery);(function(e){var t=!1;e(document).mouseup(function(){t=!1}),e.widget("ui.mouse",{version:"1.10.3",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(a){return!0===e.data(a.target,t.widgetName+".preventClickEvent")?(e.removeData(a.target,t.widgetName+".preventClickEvent"),a.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(a){if(!t){this._mouseStarted&&this._mouseUp(a),this._mouseDownEvent=a;var i=this,r=1===a.which,n="string"==typeof this.options.cancel&&a.target.nodeName?e(a.target).closest(this.options.cancel).length:!1;return r&&!n&&this._mouseCapture(a)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(a)&&this._mouseDelayMet(a)&&(this._mouseStarted=this._mouseStart(a)!==!1,!this._mouseStarted)?(a.preventDefault(),!0):(!0===e.data(a.target,this.widgetName+".preventClickEvent")&&e.removeData(a.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),a.preventDefault(),t=!0,!0)):!0}},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e,t){function i(e,t,i){return[parseFloat(e[0])*(f.test(e[0])?t/100:1),parseFloat(e[1])*(f.test(e[1])?i/100:1)]}function n(t,i){return parseInt(e.css(t,i),10)||0}function s(t){var i=t[0];return 9===i.nodeType?{width:t.width(),height:t.height(),offset:{top:0,left:0}}:e.isWindow(i)?{width:t.width(),height:t.height(),offset:{top:t.scrollTop(),left:t.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:t.outerWidth(),height:t.outerHeight(),offset:t.offset()}}e.ui=e.ui||{};var o,r=Math.max,a=Math.abs,l=Math.round,u=/left|center|right/,h=/top|center|bottom/,c=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,f=/%$/,m=e.fn.position;e.position={scrollbarWidth:function(){if(o!==t)return o;var i,n,s=e("
"),r=s.children()[0];return e("body").append(s),i=r.offsetWidth,s.css("overflow","scroll"),n=r.offsetWidth,i===n&&(n=s[0].clientWidth),s.remove(),o=i-n},getScrollInfo:function(t){var i=t.isWindow?"":t.element.css("overflow-x"),n=t.isWindow?"":t.element.css("overflow-y"),s="scroll"===i||"auto"===i&&t.widthn?"left":i>0?"right":"center",vertical:0>o?"top":s>0?"bottom":"middle"};c>f&&f>a(i+n)&&(l.horizontal="center"),d>p&&p>a(s+o)&&(l.vertical="middle"),l.important=r(a(i),a(n))>r(a(s),a(o))?"horizontal":"vertical",t.using.call(this,e,l)}),h.offset(e.extend(E,{using:u}))})},e.ui.position={fit:{left:function(e,t){var i,n=t.within,s=n.isWindow?n.scrollLeft:n.offset.left,o=n.width,a=e.left-t.collisionPosition.marginLeft,l=s-a,u=a+t.collisionWidth-o-s;t.collisionWidth>o?l>0&&0>=u?(i=e.left+l+t.collisionWidth-o-s,e.left+=l-i):e.left=u>0&&0>=l?s:l>u?s+o-t.collisionWidth:s:l>0?e.left+=l:u>0?e.left-=u:e.left=r(e.left-a,e.left)},top:function(e,t){var i,n=t.within,s=n.isWindow?n.scrollTop:n.offset.top,o=t.within.height,a=e.top-t.collisionPosition.marginTop,l=s-a,u=a+t.collisionHeight-o-s;t.collisionHeight>o?l>0&&0>=u?(i=e.top+l+t.collisionHeight-o-s,e.top+=l-i):e.top=u>0&&0>=l?s:l>u?s+o-t.collisionHeight:s:l>0?e.top+=l:u>0?e.top-=u:e.top=r(e.top-a,e.top)}},flip:{left:function(e,t){var i,n,s=t.within,o=s.offset.left+s.scrollLeft,r=s.width,l=s.isWindow?s.scrollLeft:s.offset.left,u=e.left-t.collisionPosition.marginLeft,h=u-l,c=u+t.collisionWidth-r-l,d="left"===t.my[0]?-t.elemWidth:"right"===t.my[0]?t.elemWidth:0,f="left"===t.at[0]?t.targetWidth:"right"===t.at[0]?-t.targetWidth:0,m=-2*t.offset[0];0>h?(i=e.left+d+f+m+t.collisionWidth-r-o,(0>i||a(h)>i)&&(e.left+=d+f+m)):c>0&&(n=e.left-t.collisionPosition.marginLeft+d+f+m-l,(n>0||c>a(n))&&(e.left+=d+f+m))},top:function(e,t){var i,n,s=t.within,o=s.offset.top+s.scrollTop,r=s.height,l=s.isWindow?s.scrollTop:s.offset.top,u=e.top-t.collisionPosition.marginTop,h=u-l,c=u+t.collisionHeight-r-l,d="top"===t.my[1],f=d?-t.elemHeight:"bottom"===t.my[1]?t.elemHeight:0,m="top"===t.at[1]?t.targetHeight:"bottom"===t.at[1]?-t.targetHeight:0,p=-2*t.offset[1];0>h?(n=e.top+f+m+p+t.collisionHeight-r-o,e.top+f+m+p>h&&(0>n||a(h)>n)&&(e.top+=f+m+p)):c>0&&(i=e.top-t.collisionPosition.marginTop+f+m+p-l,e.top+f+m+p>c&&(i>0||c>a(i))&&(e.top+=f+m+p))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,i,n,s,o,r=document.getElementsByTagName("body")[0],a=document.createElement("div");t=document.createElement(r?"div":"body"),n={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},r&&e.extend(n,{position:"absolute",left:"-1000px",top:"-1000px"});for(o in n)t.style[o]=n[o];t.appendChild(a),i=r||document.documentElement,i.insertBefore(t,i.firstChild),a.style.cssText="position: absolute; left: 10.7432222px;",s=e(a).offset().left,e.support.offsetFractions=s>10&&11>s,t.innerHTML="",i.removeChild(t)}()})(jQuery);(function(e){e.widget("ui.draggable",e.ui.mouse,{version:"1.10.3",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"!==this.options.helper||/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var a=this.options;return this.helper||a.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(e(a.iframeFix===!0?"iframe":a.iframeFix).each(function(){e("
").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var a=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offsetParent=this.helper.offsetParent(),this.offsetParentCssPosition=this.offsetParent.css("position"),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.offset.scroll=!1,e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!a.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,a){if("fixed"===this.offsetParentCssPosition&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),!a){var i=this._uiHash();if(this._trigger("drag",t,i)===!1)return this._mouseUp({}),!1;this.position=i.position}return this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var a=this,i=!1;return e.ui.ddmanager&&!this.options.dropBehaviour&&(i=e.ui.ddmanager.drop(this,t)),this.dropped&&(i=this.dropped,this.dropped=!1),"original"!==this.options.helper||e.contains(this.element[0].ownerDocument,this.element[0])?("invalid"===this.options.revert&&!i||"valid"===this.options.revert&&i||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,i)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){a._trigger("stop",t)!==!1&&a._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1):!1},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){return this.options.handle?!!e(t.target).closest(this.element.find(this.options.handle)).length:!0},_createHelper:function(t){var a=this.options,i=e.isFunction(a.helper)?e(a.helper.apply(this.element[0],[t])):"clone"===a.helper?this.element.clone().removeAttr("id"):this.element;return i.parents("body").length||i.appendTo("parent"===a.appendTo?this.element[0].parentNode:a.appendTo),i[0]===this.element[0]||/(fixed|absolute)/.test(i.css("position"))||i.css("position","absolute"),i},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,a,i,r=this.options;return r.containment?"window"===r.containment?(this.containment=[e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,e(window).scrollLeft()+e(window).width()-this.helperProportions.width-this.margins.left,e(window).scrollTop()+(e(window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):"document"===r.containment?(this.containment=[0,0,e(document).width()-this.helperProportions.width-this.margins.left,(e(document).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):r.containment.constructor===Array?(this.containment=r.containment,undefined):("parent"===r.containment&&(r.containment=this.helper[0].parentNode),a=e(r.containment),i=a[0],i&&(t="hidden"!==a.css("overflow"),this.containment=[(parseInt(a.css("borderLeftWidth"),10)||0)+(parseInt(a.css("paddingLeft"),10)||0),(parseInt(a.css("borderTopWidth"),10)||0)+(parseInt(a.css("paddingTop"),10)||0),(t?Math.max(i.scrollWidth,i.offsetWidth):i.offsetWidth)-(parseInt(a.css("borderRightWidth"),10)||0)-(parseInt(a.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(i.scrollHeight,i.offsetHeight):i.offsetHeight)-(parseInt(a.css("borderBottomWidth"),10)||0)-(parseInt(a.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=a),undefined):(this.containment=null,undefined)},_convertPositionTo:function(t,a){a||(a=this.position);var i="absolute"===t?1:-1,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent;return this.offset.scroll||(this.offset.scroll={top:r.scrollTop(),left:r.scrollLeft()}),{top:a.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top)*i,left:a.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)*i}},_generatePosition:function(t){var a,i,r,n,s=this.options,o="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,d=t.pageX,u=t.pageY;return this.offset.scroll||(this.offset.scroll={top:o.scrollTop(),left:o.scrollLeft()}),this.originalPosition&&(this.containment&&(this.relative_container?(i=this.relative_container.offset(),a=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]):a=this.containment,t.pageX-this.offset.click.lefta[2]&&(d=a[2]+this.offset.click.left),t.pageY-this.offset.click.top>a[3]&&(u=a[3]+this.offset.click.top)),s.grid&&(r=s.grid[1]?this.originalPageY+Math.round((u-this.originalPageY)/s.grid[1])*s.grid[1]:this.originalPageY,u=a?r-this.offset.click.top>=a[1]||r-this.offset.click.top>a[3]?r:r-this.offset.click.top>=a[1]?r-s.grid[1]:r+s.grid[1]:r,n=s.grid[0]?this.originalPageX+Math.round((d-this.originalPageX)/s.grid[0])*s.grid[0]:this.originalPageX,d=a?n-this.offset.click.left>=a[0]||n-this.offset.click.left>a[2]?n:n-this.offset.click.left>=a[0]?n-s.grid[0]:n+s.grid[0]:n)),{top:u-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top),left:d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(t,a,i){return i=i||this._uiHash(),e.ui.plugin.call(this,t,[a,i]),"drag"===t&&(this.positionAbs=this._convertPositionTo("absolute")),e.Widget.prototype._trigger.call(this,t,a,i)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,a){var i=e(this).data("ui-draggable"),r=i.options,n=e.extend({},a,{item:i.element});i.sortables=[],e(r.connectToSortable).each(function(){var a=e.data(this,"ui-sortable");a&&!a.options.disabled&&(i.sortables.push({instance:a,shouldRevert:a.options.revert}),a.refreshPositions(),a._trigger("activate",t,n))})},stop:function(t,a){var i=e(this).data("ui-draggable"),r=e.extend({},a,{item:i.element});e.each(i.sortables,function(){this.instance.isOver?(this.instance.isOver=0,i.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=this.shouldRevert),this.instance._mouseStop(t),this.instance.options.helper=this.instance.options._helper,"original"===i.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",t,r))})},drag:function(t,a){var i=e(this).data("ui-draggable"),r=this;e.each(i.sortables,function(){var n=!1,s=this;this.instance.positionAbs=i.positionAbs,this.instance.helperProportions=i.helperProportions,this.instance.offset.click=i.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(n=!0,e.each(i.sortables,function(){return this.instance.positionAbs=i.positionAbs,this.instance.helperProportions=i.helperProportions,this.instance.offset.click=i.offset.click,this!==s&&this.instance._intersectsWith(this.instance.containerCache)&&e.contains(s.instance.element[0],this.instance.element[0])&&(n=!1),n})),n?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=e(r).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return a.helper[0]},t.target=this.instance.currentItem[0],this.instance._mouseCapture(t,!0),this.instance._mouseStart(t,!0,!0),this.instance.offset.click.top=i.offset.click.top,this.instance.offset.click.left=i.offset.click.left,this.instance.offset.parent.left-=i.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=i.offset.parent.top-this.instance.offset.parent.top,i._trigger("toSortable",t),i.dropped=this.instance.element,i.currentItem=i.element,this.instance.fromOutside=i),this.instance.currentItem&&this.instance._mouseDrag(t)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",t,this.instance._uiHash(this.instance)),this.instance._mouseStop(t,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),i._trigger("fromSortable",t),i.dropped=!1)})}}),e.ui.plugin.add("draggable","cursor",{start:function(){var t=e("body"),a=e(this).data("ui-draggable").options;t.css("cursor")&&(a._cursor=t.css("cursor")),t.css("cursor",a.cursor)},stop:function(){var t=e(this).data("ui-draggable").options;t._cursor&&e("body").css("cursor",t._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,a){var i=e(a.helper),r=e(this).data("ui-draggable").options;i.css("opacity")&&(r._opacity=i.css("opacity")),i.css("opacity",r.opacity)},stop:function(t,a){var i=e(this).data("ui-draggable").options;i._opacity&&e(a.helper).css("opacity",i._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(){var t=e(this).data("ui-draggable");t.scrollParent[0]!==document&&"HTML"!==t.scrollParent[0].tagName&&(t.overflowOffset=t.scrollParent.offset())},drag:function(t){var a=e(this).data("ui-draggable"),i=a.options,r=!1;a.scrollParent[0]!==document&&"HTML"!==a.scrollParent[0].tagName?(i.axis&&"x"===i.axis||(a.overflowOffset.top+a.scrollParent[0].offsetHeight-t.pageY=0;h--)o=m.snapElements[h].left,d=o+m.snapElements[h].width,u=m.snapElements[h].top,l=u+m.snapElements[h].height,o-f>y||g>d+f||u-f>x||v>l+f||!e.contains(m.snapElements[h].item.ownerDocument,m.snapElements[h].item)?(m.snapElements[h].snapping&&m.options.snap.release&&m.options.snap.release.call(m.element,t,e.extend(m._uiHash(),{snapItem:m.snapElements[h].item})),m.snapElements[h].snapping=!1):("inner"!==p.snapMode&&(i=f>=Math.abs(u-x),r=f>=Math.abs(l-v),n=f>=Math.abs(o-y),s=f>=Math.abs(d-g),i&&(a.position.top=m._convertPositionTo("relative",{top:u-m.helperProportions.height,left:0}).top-m.margins.top),r&&(a.position.top=m._convertPositionTo("relative",{top:l,left:0}).top-m.margins.top),n&&(a.position.left=m._convertPositionTo("relative",{top:0,left:o-m.helperProportions.width}).left-m.margins.left),s&&(a.position.left=m._convertPositionTo("relative",{top:0,left:d}).left-m.margins.left)),c=i||r||n||s,"outer"!==p.snapMode&&(i=f>=Math.abs(u-v),r=f>=Math.abs(l-x),n=f>=Math.abs(o-g),s=f>=Math.abs(d-y),i&&(a.position.top=m._convertPositionTo("relative",{top:u,left:0}).top-m.margins.top),r&&(a.position.top=m._convertPositionTo("relative",{top:l-m.helperProportions.height,left:0}).top-m.margins.top),n&&(a.position.left=m._convertPositionTo("relative",{top:0,left:o}).left-m.margins.left),s&&(a.position.left=m._convertPositionTo("relative",{top:0,left:d-m.helperProportions.width}).left-m.margins.left)),!m.snapElements[h].snapping&&(i||r||n||s||c)&&m.options.snap.snap&&m.options.snap.snap.call(m.element,t,e.extend(m._uiHash(),{snapItem:m.snapElements[h].item})),m.snapElements[h].snapping=i||r||n||s||c)}}),e.ui.plugin.add("draggable","stack",{start:function(){var t,a=this.data("ui-draggable").options,i=e.makeArray(e(a.stack)).sort(function(t,a){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(a).css("zIndex"),10)||0)});i.length&&(t=parseInt(e(i[0]).css("zIndex"),10)||0,e(i).each(function(a){e(this).css("zIndex",t+a)}),this.css("zIndex",t+i.length))}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,a){var i=e(a.helper),r=e(this).data("ui-draggable").options;i.css("zIndex")&&(r._zIndex=i.css("zIndex")),i.css("zIndex",r.zIndex)},stop:function(t,a){var i=e(this).data("ui-draggable").options;i._zIndex&&e(a.helper).css("zIndex",i._zIndex)}})})(jQuery);(function(e){function t(e,t,a){return e>t&&t+a>e}e.widget("ui.droppable",{version:"1.10.3",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var t=this.options,a=t.accept;this.isover=!1,this.isout=!0,this.accept=e.isFunction(a)?a:function(e){return e.is(a)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},e.ui.ddmanager.droppables[t.scope]=e.ui.ddmanager.droppables[t.scope]||[],e.ui.ddmanager.droppables[t.scope].push(this),t.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){for(var t=0,a=e.ui.ddmanager.droppables[this.options.scope];a.length>t;t++)a[t]===this&&a.splice(t,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,a){"accept"===t&&(this.accept=e.isFunction(a)?a:function(e){return e.is(a)}),e.Widget.prototype._setOption.apply(this,arguments)},_activate:function(t){var a=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),a&&this._trigger("activate",t,this.ui(a))},_deactivate:function(t){var a=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),a&&this._trigger("deactivate",t,this.ui(a))},_over:function(t){var a=e.ui.ddmanager.current;a&&(a.currentItem||a.element)[0]!==this.element[0]&&this.accept.call(this.element[0],a.currentItem||a.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(a)))},_out:function(t){var a=e.ui.ddmanager.current;a&&(a.currentItem||a.element)[0]!==this.element[0]&&this.accept.call(this.element[0],a.currentItem||a.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(a)))},_drop:function(t,a){var i=a||e.ui.ddmanager.current,r=!1;return i&&(i.currentItem||i.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var t=e.data(this,"ui-droppable");return t.options.greedy&&!t.options.disabled&&t.options.scope===i.options.scope&&t.accept.call(t.element[0],i.currentItem||i.element)&&e.ui.intersect(i,e.extend(t,{offset:t.element.offset()}),t.options.tolerance)?(r=!0,!1):undefined}),r?!1:this.accept.call(this.element[0],i.currentItem||i.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(i)),this.element):!1):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(e,a,i){if(!a.offset)return!1;var r,s,n=(e.positionAbs||e.position.absolute).left,o=n+e.helperProportions.width,d=(e.positionAbs||e.position.absolute).top,u=d+e.helperProportions.height,l=a.offset.left,h=l+a.proportions.width,c=a.offset.top,m=c+a.proportions.height;switch(i){case"fit":return n>=l&&h>=o&&d>=c&&m>=u;case"intersect":return n+e.helperProportions.width/2>l&&h>o-e.helperProportions.width/2&&d+e.helperProportions.height/2>c&&m>u-e.helperProportions.height/2;case"pointer":return r=(e.positionAbs||e.position.absolute).left+(e.clickOffset||e.offset.click).left,s=(e.positionAbs||e.position.absolute).top+(e.clickOffset||e.offset.click).top,t(s,c,a.proportions.height)&&t(r,l,a.proportions.width);case"touch":return(d>=c&&m>=d||u>=c&&m>=u||c>d&&u>m)&&(n>=l&&h>=n||o>=l&&h>=o||l>n&&o>h);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,a){var i,r,s=e.ui.ddmanager.droppables[t.options.scope]||[],n=a?a.type:null,o=(t.currentItem||t.element).find(":data(ui-droppable)").addBack();e:for(i=0;s.length>i;i++)if(!(s[i].options.disabled||t&&!s[i].accept.call(s[i].element[0],t.currentItem||t.element))){for(r=0;o.length>r;r++)if(o[r]===s[i].element[0]){s[i].proportions.height=0;continue e}s[i].visible="none"!==s[i].element.css("display"),s[i].visible&&("mousedown"===n&&s[i]._activate.call(s[i],a),s[i].offset=s[i].element.offset(),s[i].proportions={width:s[i].element[0].offsetWidth,height:s[i].element[0].offsetHeight})}},drop:function(t,a){var i=!1;return e.each((e.ui.ddmanager.droppables[t.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance)&&(i=this._drop.call(this,a)||i),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,a)))}),i},dragStart:function(t,a){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,a)})},drag:function(t,a){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,a),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var i,r,s,n=e.ui.intersect(t,this,this.options.tolerance),o=!n&&this.isover?"isout":n&&!this.isover?"isover":null;o&&(this.options.greedy&&(r=this.options.scope,s=this.element.parents(":data(ui-droppable)").filter(function(){return e.data(this,"ui-droppable").options.scope===r}),s.length&&(i=e.data(s[0],"ui-droppable"),i.greedyChild="isover"===o)),i&&"isover"===o&&(i.isover=!1,i.isout=!0,i._out.call(i,a)),this[o]=!0,this["isout"===o?"isover":"isout"]=!1,this["isover"===o?"_over":"_out"].call(this,a),i&&"isout"===o&&(i.isout=!1,i.isover=!0,i._over.call(i,a)))}})},dragStop:function(t,a){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,a)}}})(jQuery);(function(e){function t(e){return parseInt(e,10)||0}function i(e){return!isNaN(parseInt(e,10))}e.widget("ui.resizable",e.ui.mouse,{version:"1.10.3",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var t,i,a,r,s,n=this,o=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!o.aspectRatio,aspectRatio:o.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:o.helper||o.ghost||o.animate?o.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e("
").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=o.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)a=e.trim(t[i]),s="ui-resizable-"+a,r=e("
"),r.css({zIndex:o.zIndex}),"se"===a&&r.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[a]=".ui-resizable-"+a,this.element.append(r);this._renderAxis=function(t){var i,a,r,s;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=e(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(a=e(this.handles[i],this.element),s=/sw|ne|nw|se|n|s/.test(i)?a.outerHeight():a.outerWidth(),r=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(r,s),this._proportionallyResize()),e(this.handles[i]).length},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){n.resizing||(this.className&&(r=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),n.axis=r&&r[1]?r[1]:"se")}),o.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){o.disabled||(e(this).removeClass("ui-resizable-autohide"),n._handles.show())}).mouseleave(function(){o.disabled||n.resizing||(e(this).addClass("ui-resizable-autohide"),n._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,a,r=!1;for(i in this.handles)a=e(this.handles[i])[0],(a===t.target||e.contains(a,t.target))&&(r=!0);return!this.options.disabled&&r},_mouseStart:function(i){var a,r,s,n=this.options,o=this.element.position(),d=this.element;return this.resizing=!0,/absolute/.test(d.css("position"))?d.css({position:"absolute",top:d.css("top"),left:d.css("left")}):d.is(".ui-draggable")&&d.css({position:"absolute",top:o.top,left:o.left}),this._renderProxy(),a=t(this.helper.css("left")),r=t(this.helper.css("top")),n.containment&&(a+=e(n.containment).scrollLeft()||0,r+=e(n.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:a,top:r},this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()},this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()},this.originalPosition={left:a,top:r},this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof n.aspectRatio?n.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===s?this.axis+"-resize":s),d.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(t){var i,a=this.helper,r={},s=this.originalMousePosition,n=this.axis,o=this.position.top,d=this.position.left,u=this.size.width,h=this.size.height,l=t.pageX-s.left||0,c=t.pageY-s.top||0,m=this._change[n];return m?(i=m.apply(this,[t,l,c]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),this.position.top!==o&&(r.top=this.position.top+"px"),this.position.left!==d&&(r.left=this.position.left+"px"),this.size.width!==u&&(r.width=this.size.width+"px"),this.size.height!==h&&(r.height=this.size.height+"px"),a.css(r),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(r)||this._trigger("resize",t,this.ui()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,a,r,s,n,o,d,u=this.options,h=this;return this._helper&&(i=this._proportionallyResizeElements,a=i.length&&/textarea/i.test(i[0].nodeName),r=a&&e.ui.hasScroll(i[0],"left")?0:h.sizeDiff.height,s=a?0:h.sizeDiff.width,n={width:h.helper.width()-s,height:h.helper.height()-r},o=parseInt(h.element.css("left"),10)+(h.position.left-h.originalPosition.left)||null,d=parseInt(h.element.css("top"),10)+(h.position.top-h.originalPosition.top)||null,u.animate||this.element.css(e.extend(n,{top:d,left:o})),h.helper.height(h.size.height),h.helper.width(h.size.width),this._helper&&!u.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t,a,r,s,n,o=this.options;n={minWidth:i(o.minWidth)?o.minWidth:0,maxWidth:i(o.maxWidth)?o.maxWidth:1/0,minHeight:i(o.minHeight)?o.minHeight:0,maxHeight:i(o.maxHeight)?o.maxHeight:1/0},(this._aspectRatio||e)&&(t=n.minHeight*this.aspectRatio,r=n.minWidth/this.aspectRatio,a=n.maxHeight*this.aspectRatio,s=n.maxWidth/this.aspectRatio,t>n.minWidth&&(n.minWidth=t),r>n.minHeight&&(n.minHeight=r),n.maxWidth>a&&(n.maxWidth=a),n.maxHeight>s&&(n.maxHeight=s)),this._vBoundaries=n},_updateCache:function(e){this.offset=this.helper.offset(),i(e.left)&&(this.position.left=e.left),i(e.top)&&(this.position.top=e.top),i(e.height)&&(this.size.height=e.height),i(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,a=this.size,r=this.axis;return i(e.height)?e.width=e.height*this.aspectRatio:i(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===r&&(e.left=t.left+(a.width-e.width),e.top=null),"nw"===r&&(e.top=t.top+(a.height-e.height),e.left=t.left+(a.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,a=this.axis,r=i(e.width)&&t.maxWidth&&t.maxWidthe.width,o=i(e.height)&&t.minHeight&&t.minHeight>e.height,d=this.originalPosition.left+this.originalSize.width,u=this.position.top+this.size.height,h=/sw|nw|w/.test(a),l=/nw|ne|n/.test(a);return n&&(e.width=t.minWidth),o&&(e.height=t.minHeight),r&&(e.width=t.maxWidth),s&&(e.height=t.maxHeight),n&&h&&(e.left=d-t.minWidth),r&&h&&(e.left=d-t.maxWidth),o&&l&&(e.top=u-t.minHeight),s&&l&&(e.top=u-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var e,t,i,a,r,s=this.helper||this.element;for(e=0;this._proportionallyResizeElements.length>e;e++){if(r=this._proportionallyResizeElements[e],!this.borderDif)for(this.borderDif=[],i=[r.css("borderTopWidth"),r.css("borderRightWidth"),r.css("borderBottomWidth"),r.css("borderLeftWidth")],a=[r.css("paddingTop"),r.css("paddingRight"),r.css("paddingBottom"),r.css("paddingLeft")],t=0;i.length>t;t++)this.borderDif[t]=(parseInt(i[t],10)||0)+(parseInt(a[t],10)||0);r.css({height:s.height()-this.borderDif[0]-this.borderDif[2]||0,width:s.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("
"),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,a=this.originalPosition;return{left:a.left+t,width:i.width-t}},n:function(e,t,i){var a=this.originalSize,r=this.originalPosition;return{top:r.top+i,height:a.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,a){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,a]))},sw:function(t,i,a){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,a]))},ne:function(t,i,a){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,a]))},nw:function(t,i,a){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,a]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).data("ui-resizable"),a=i.options,r=i._proportionallyResizeElements,s=r.length&&/textarea/i.test(r[0].nodeName),n=s&&e.ui.hasScroll(r[0],"left")?0:i.sizeDiff.height,o=s?0:i.sizeDiff.width,d={width:i.size.width-o,height:i.size.height-n},u=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,h=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(d,h&&u?{top:h,left:u}:{}),{duration:a.animateDuration,easing:a.animateEasing,step:function(){var a={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};r&&r.length&&e(r[0]).css({width:a.width,height:a.height}),i._updateCache(a),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var i,a,r,s,n,o,d,u=e(this).data("ui-resizable"),h=u.options,l=u.element,c=h.containment,m=c instanceof e?c.get(0):/parent/.test(c)?l.parent().get(0):c;m&&(u.containerElement=e(m),/document/.test(c)||c===document?(u.containerOffset={left:0,top:0},u.containerPosition={left:0,top:0},u.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(i=e(m),a=[],e(["Top","Right","Left","Bottom"]).each(function(e,r){a[e]=t(i.css("padding"+r))}),u.containerOffset=i.offset(),u.containerPosition=i.position(),u.containerSize={height:i.innerHeight()-a[3],width:i.innerWidth()-a[1]},r=u.containerOffset,s=u.containerSize.height,n=u.containerSize.width,o=e.ui.hasScroll(m,"left")?m.scrollWidth:n,d=e.ui.hasScroll(m)?m.scrollHeight:s,u.parentData={element:m,left:r.left,top:r.top,width:o,height:d}))},resize:function(t){var i,a,r,s,n=e(this).data("ui-resizable"),o=n.options,d=n.containerOffset,u=n.position,h=n._aspectRatio||t.shiftKey,l={top:0,left:0},c=n.containerElement;c[0]!==document&&/static/.test(c.css("position"))&&(l=d),u.left<(n._helper?d.left:0)&&(n.size.width=n.size.width+(n._helper?n.position.left-d.left:n.position.left-l.left),h&&(n.size.height=n.size.width/n.aspectRatio),n.position.left=o.helper?d.left:0),u.top<(n._helper?d.top:0)&&(n.size.height=n.size.height+(n._helper?n.position.top-d.top:n.position.top),h&&(n.size.width=n.size.height*n.aspectRatio),n.position.top=n._helper?d.top:0),n.offset.left=n.parentData.left+n.position.left,n.offset.top=n.parentData.top+n.position.top,i=Math.abs((n._helper?n.offset.left-l.left:n.offset.left-l.left)+n.sizeDiff.width),a=Math.abs((n._helper?n.offset.top-l.top:n.offset.top-d.top)+n.sizeDiff.height),r=n.containerElement.get(0)===n.element.parent().get(0),s=/relative|absolute/.test(n.containerElement.css("position")),r&&s&&(i-=n.parentData.left),i+n.size.width>=n.parentData.width&&(n.size.width=n.parentData.width-i,h&&(n.size.height=n.size.width/n.aspectRatio)),a+n.size.height>=n.parentData.height&&(n.size.height=n.parentData.height-a,h&&(n.size.width=n.size.height*n.aspectRatio))},stop:function(){var t=e(this).data("ui-resizable"),i=t.options,a=t.containerOffset,r=t.containerPosition,s=t.containerElement,n=e(t.helper),o=n.offset(),d=n.outerWidth()-t.sizeDiff.width,u=n.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(s.css("position"))&&e(this).css({left:o.left-r.left-a.left,width:d,height:u}),t._helper&&!i.animate&&/static/.test(s.css("position"))&&e(this).css({left:o.left-r.left-a.left,width:d,height:u})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).data("ui-resizable"),i=t.options,a=function(t){e(t).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?a(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],a(i.alsoResize)):e.each(i.alsoResize,function(e){a(e)})},resize:function(t,i){var a=e(this).data("ui-resizable"),r=a.options,s=a.originalSize,n=a.originalPosition,o={height:a.size.height-s.height||0,width:a.size.width-s.width||0,top:a.position.top-n.top||0,left:a.position.left-n.left||0},d=function(t,a){e(t).each(function(){var t=e(this),r=e(this).data("ui-resizable-alsoresize"),s={},n=a&&a.length?a:t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(n,function(e,t){var i=(r[t]||0)+(o[t]||0);i&&i>=0&&(s[t]=i||null)}),t.css(s)})};"object"!=typeof r.alsoResize||r.alsoResize.nodeType?d(r.alsoResize):e.each(r.alsoResize,function(e,t){d(e,t)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).data("ui-resizable"),i=t.options,a=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:a.height,width:a.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).data("ui-resizable");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).data("ui-resizable");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t=e(this).data("ui-resizable"),i=t.options,a=t.size,r=t.originalSize,s=t.originalPosition,n=t.axis,o="number"==typeof i.grid?[i.grid,i.grid]:i.grid,d=o[0]||1,u=o[1]||1,h=Math.round((a.width-r.width)/d)*d,l=Math.round((a.height-r.height)/u)*u,c=r.width+h,m=r.height+l,p=i.maxWidth&&c>i.maxWidth,f=i.maxHeight&&m>i.maxHeight,g=i.minWidth&&i.minWidth>c,y=i.minHeight&&i.minHeight>m;i.grid=o,g&&(c+=d),y&&(m+=u),p&&(c-=d),f&&(m-=u),/^(se|s|e)$/.test(n)?(t.size.width=c,t.size.height=m):/^(ne)$/.test(n)?(t.size.width=c,t.size.height=m,t.position.top=s.top-l):/^(sw)$/.test(n)?(t.size.width=c,t.size.height=m,t.position.left=s.left-h):(t.size.width=c,t.size.height=m,t.position.top=s.top-l,t.position.left=s.left-h)}})})(jQuery);(function(e){e.widget("ui.selectable",e.ui.mouse,{version:"1.10.3",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var t,i=this;this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){t=e(i.options.filter,i.element[0]),t.addClass("ui-selectee"),t.each(function(){var t=e(this),i=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:i.left,top:i.top,right:i.left+t.outerWidth(),bottom:i.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=t.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var i=this,a=this.options;this.opos=[t.pageX,t.pageY],this.options.disabled||(this.selectees=e(a.filter,this.element[0]),this._trigger("start",t),e(a.appendTo).append(this.helper),this.helper.css({left:t.pageX,top:t.pageY,width:0,height:0}),a.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var a=e.data(this,"selectable-item");a.startselected=!0,t.metaKey||t.ctrlKey||(a.$element.removeClass("ui-selected"),a.selected=!1,a.$element.addClass("ui-unselecting"),a.unselecting=!0,i._trigger("unselecting",t,{unselecting:a.element}))}),e(t.target).parents().addBack().each(function(){var a,r=e.data(this,"selectable-item");return r?(a=!t.metaKey&&!t.ctrlKey||!r.$element.hasClass("ui-selected"),r.$element.removeClass(a?"ui-unselecting":"ui-selected").addClass(a?"ui-selecting":"ui-unselecting"),r.unselecting=!a,r.selecting=a,r.selected=a,a?i._trigger("selecting",t,{selecting:r.element}):i._trigger("unselecting",t,{unselecting:r.element}),!1):undefined}))},_mouseDrag:function(t){if(this.dragged=!0,!this.options.disabled){var i,a=this,r=this.options,s=this.opos[0],n=this.opos[1],o=t.pageX,d=t.pageY;return s>o&&(i=o,o=s,s=i),n>d&&(i=d,d=n,n=i),this.helper.css({left:s,top:n,width:o-s,height:d-n}),this.selectees.each(function(){var i=e.data(this,"selectable-item"),u=!1;i&&i.element!==a.element[0]&&("touch"===r.tolerance?u=!(i.left>o||s>i.right||i.top>d||n>i.bottom):"fit"===r.tolerance&&(u=i.left>s&&o>i.right&&i.top>n&&d>i.bottom),u?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,a._trigger("selecting",t,{selecting:i.element}))):(i.selecting&&((t.metaKey||t.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),a._trigger("unselecting",t,{unselecting:i.element}))),i.selected&&(t.metaKey||t.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,a._trigger("unselecting",t,{unselecting:i.element})))))}),!1}},_mouseStop:function(t){var i=this;return this.dragged=!1,e(".ui-unselecting",this.element[0]).each(function(){var a=e.data(this,"selectable-item");a.$element.removeClass("ui-unselecting"),a.unselecting=!1,a.startselected=!1,i._trigger("unselected",t,{unselected:a.element})}),e(".ui-selecting",this.element[0]).each(function(){var a=e.data(this,"selectable-item");a.$element.removeClass("ui-selecting").addClass("ui-selected"),a.selecting=!1,a.selected=!0,a.startselected=!0,i._trigger("selected",t,{selected:a.element})}),this._trigger("stop",t),this.helper.remove(),!1}})})(jQuery);(function(e){function t(e,t,i){return e>t&&t+i>e}function i(e){return/left|right/.test(e.css("float"))||/inline|table-cell/.test(e.css("display"))}e.widget("ui.sortable",e.ui.mouse,{version:"1.10.3",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===e.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,i){"disabled"===t?(this.options[t]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,i){var a=null,s=!1,r=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(t),e(t.target).parents().each(function(){return e.data(this,r.widgetName+"-item")===r?(a=e(this),!1):undefined}),e.data(t.target,r.widgetName+"-item")===r&&(a=e(t.target)),a?!this.options.handle||i||(e(this.options.handle,a).find("*").addBack().each(function(){this===t.target&&(s=!0)}),s)?(this.currentItem=a,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(t,i,a){var s,r,n=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),n.containment&&this._setContainment(),n.cursor&&"auto"!==n.cursor&&(r=this.document.find("body"),this.storedCursor=r.css("cursor"),r.css("cursor",n.cursor),this.storedStylesheet=e("").appendTo(r)),n.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",n.opacity)),n.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",n.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!a)for(s=this.containers.length-1;s>=0;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var i,a,s,r,n=this.options,o=!1;for(this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--)if(a=this.items[i],s=a.item[0],r=this._intersectsWithPointer(a),r&&a.instance===this.currentContainer&&s!==this.currentItem[0]&&this.placeholder[1===r?"next":"prev"]()[0]!==s&&!e.contains(this.placeholder[0],s)&&("semi-dynamic"===this.options.type?!e.contains(this.element[0],s):!0)){if(this.direction=1===r?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(a))break;this._rearrange(t,a),this._trigger("change",t,this._uiHash());break}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,i){if(t){if(e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t),this.options.revert){var a=this,s=this.placeholder.offset(),r=this.options.axis,n={};r&&"x"!==r||(n.left=s.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),r&&"y"!==r||(n.top=s.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,e(this.helper).animate(n,parseInt(this.options.revert,10)||500,function(){a._clear(t)})}else this._clear(t,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var i=this._getItemsAsjQuery(t&&t.connected),a=[];return t=t||{},e(i).each(function(){var i=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);i&&a.push((t.key||i[1]+"[]")+"="+(t.key&&t.expression?i[1]:i[2]))}),!a.length&&t.key&&a.push(t.key+"="),a.join("&")},toArray:function(t){var i=this._getItemsAsjQuery(t&&t.connected),a=[];return t=t||{},i.each(function(){a.push(e(t.item||this).attr(t.attribute||"id")||"")}),a},_intersectsWith:function(e){var t=this.positionAbs.left,i=t+this.helperProportions.width,a=this.positionAbs.top,s=a+this.helperProportions.height,r=e.left,n=r+e.width,o=e.top,h=o+e.height,l=this.offset.click.top,u=this.offset.click.left,d="x"===this.options.axis||a+l>o&&h>a+l,c="y"===this.options.axis||t+u>r&&n>t+u,p=d&&c;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?p:t+this.helperProportions.width/2>r&&n>i-this.helperProportions.width/2&&a+this.helperProportions.height/2>o&&h>s-this.helperProportions.height/2},_intersectsWithPointer:function(e){var i="x"===this.options.axis||t(this.positionAbs.top+this.offset.click.top,e.top,e.height),a="y"===this.options.axis||t(this.positionAbs.left+this.offset.click.left,e.left,e.width),s=i&&a,r=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return s?this.floating?n&&"right"===n||"down"===r?2:1:r&&("down"===r?2:1):!1},_intersectsWithSides:function(e){var i=t(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),a=t(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),s=this._getDragVerticalDirection(),r=this._getDragHorizontalDirection();return this.floating&&r?"right"===r&&a||"left"===r&&!a:s&&("down"===s&&i||"up"===s&&!i)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return 0!==e&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return 0!==e&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var i,a,s,r,n=[],o=[],h=this._connectWith();if(h&&t)for(i=h.length-1;i>=0;i--)for(s=e(h[i]),a=s.length-1;a>=0;a--)r=e.data(s[a],this.widgetFullName),r&&r!==this&&!r.options.disabled&&o.push([e.isFunction(r.options.items)?r.options.items.call(r.element):e(r.options.items,r.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),r]);for(o.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),i=o.length-1;i>=0;i--)o[i][0].each(function(){n.push(this)});return e(n)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var i=0;t.length>i;i++)if(t[i]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var i,a,s,r,n,o,h,l,u=this.items,d=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],c=this._connectWith();if(c&&this.ready)for(i=c.length-1;i>=0;i--)for(s=e(c[i]),a=s.length-1;a>=0;a--)r=e.data(s[a],this.widgetFullName),r&&r!==this&&!r.options.disabled&&(d.push([e.isFunction(r.options.items)?r.options.items.call(r.element[0],t,{item:this.currentItem}):e(r.options.items,r.element),r]),this.containers.push(r));for(i=d.length-1;i>=0;i--)for(n=d[i][1],o=d[i][0],a=0,l=o.length;l>a;a++)h=e(o[a]),h.data(this.widgetName+"-item",n),u.push({item:h,instance:n,width:0,height:0,left:0,top:0})},refreshPositions:function(t){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,a,s,r;for(i=this.items.length-1;i>=0;i--)a=this.items[i],a.instance!==this.currentContainer&&this.currentContainer&&a.item[0]!==this.currentItem[0]||(s=this.options.toleranceElement?e(this.options.toleranceElement,a.item):a.item,t||(a.width=s.outerWidth(),a.height=s.outerHeight()),r=s.offset(),a.left=r.left,a.top=r.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)r=this.containers[i].element.offset(),this.containers[i].containerCache.left=r.left,this.containers[i].containerCache.top=r.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var i,a=t.options;a.placeholder&&a.placeholder.constructor!==String||(i=a.placeholder,a.placeholder={element:function(){var a=t.currentItem[0].nodeName.toLowerCase(),s=e("<"+a+">",t.document[0]).addClass(i||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===a?t.currentItem.children().each(function(){e(" ",t.document[0]).attr("colspan",e(this).attr("colspan")||1).appendTo(s)}):"img"===a&&s.attr("src",t.currentItem.attr("src")),i||s.css("visibility","hidden"),s},update:function(e,s){(!i||a.forcePlaceholderSize)&&(s.height()||s.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),s.width()||s.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10)))}}),t.placeholder=e(a.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),a.placeholder.update(t,t.placeholder)},_contactContainers:function(a){var s,r,n,o,h,l,u,d,c,p,m=null,f=null;for(s=this.containers.length-1;s>=0;s--)if(!e.contains(this.currentItem[0],this.containers[s].element[0]))if(this._intersectsWith(this.containers[s].containerCache)){if(m&&e.contains(this.containers[s].element[0],m.element[0]))continue;m=this.containers[s],f=s}else this.containers[s].containerCache.over&&(this.containers[s]._trigger("out",a,this._uiHash(this)),this.containers[s].containerCache.over=0);if(m)if(1===this.containers.length)this.containers[f].containerCache.over||(this.containers[f]._trigger("over",a,this._uiHash(this)),this.containers[f].containerCache.over=1);else{for(n=1e4,o=null,p=m.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",u=this.positionAbs[h]+this.offset.click[h],r=this.items.length-1;r>=0;r--)e.contains(this.containers[f].element[0],this.items[r].item[0])&&this.items[r].item[0]!==this.currentItem[0]&&(!p||t(this.positionAbs.top+this.offset.click.top,this.items[r].top,this.items[r].height))&&(d=this.items[r].item.offset()[h],c=!1,Math.abs(d-u)>Math.abs(d+this.items[r][l]-u)&&(c=!0,d+=this.items[r][l]),n>Math.abs(d-u)&&(n=Math.abs(d-u),o=this.items[r],this.direction=c?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[f])return;o?this._rearrange(a,o,null,!0):this._rearrange(a,null,this.containers[f].element,!0),this._trigger("change",a,this._uiHash()),this.containers[f]._trigger("change",a,this._uiHash(this)),this.currentContainer=this.containers[f],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[f]._trigger("over",a,this._uiHash(this)),this.containers[f].containerCache.over=1}},_createHelper:function(t){var i=this.options,a=e.isFunction(i.helper)?e(i.helper.apply(this.element[0],[t,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return a.parents("body").length||e("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(a[0]),a[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!a[0].style.width||i.forceHelperSize)&&a.width(this.currentItem.width()),(!a[0].style.height||i.forceHelperSize)&&a.height(this.currentItem.height()),a},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,a,s=this.options;"parent"===s.containment&&(s.containment=this.helper[0].parentNode),("document"===s.containment||"window"===s.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,e("document"===s.containment?document:window).width()-this.helperProportions.width-this.margins.left,(e("document"===s.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(s.containment)||(t=e(s.containment)[0],i=e(s.containment).offset(),a="hidden"!==e(t).css("overflow"),this.containment=[i.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,i.left+(a?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(a?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(t,i){i||(i=this.position);var a="absolute"===t?1:-1,s="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,r=/(html|body)/i.test(s[0].tagName);return{top:i.top+this.offset.relative.top*a+this.offset.parent.top*a-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():r?0:s.scrollTop())*a,left:i.left+this.offset.relative.left*a+this.offset.parent.left*a-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():r?0:s.scrollLeft())*a}},_generatePosition:function(t){var i,a,s=this.options,r=t.pageX,n=t.pageY,o="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(o[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(t.pageX-this.offset.click.leftthis.containment[2]&&(r=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(n=this.containment[3]+this.offset.click.top)),s.grid&&(i=this.originalPageY+Math.round((n-this.originalPageY)/s.grid[1])*s.grid[1],n=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-s.grid[1]:i+s.grid[1]:i,a=this.originalPageX+Math.round((r-this.originalPageX)/s.grid[0])*s.grid[0],r=this.containment?a-this.offset.click.left>=this.containment[0]&&a-this.offset.click.left<=this.containment[2]?a:a-this.offset.click.left>=this.containment[0]?a-s.grid[0]:a+s.grid[0]:a)),{top:n-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:o.scrollTop()),left:r-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:o.scrollLeft())}},_rearrange:function(e,t,i,a){i?i[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var s=this.counter;this._delay(function(){s===this.counter&&this.refreshPositions(!a)})},_clear:function(e,t){this.reverting=!1;var i,a=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)("auto"===this._storedCSS[i]||"static"===this._storedCSS[i])&&(this._storedCSS[i]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!t&&a.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||t||a.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(t||(a.push(function(e){this._trigger("remove",e,this._uiHash())}),a.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),a.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;i>=0;i--)t||a.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(a.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!t){for(this._trigger("beforeStop",e,this._uiHash()),i=0;a.length>i;i++)a[i].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!1}if(t||this._trigger("beforeStop",e,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!t){for(i=0;a.length>i;i++)a[i].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var i=t||this;return{helper:i.helper,placeholder:i.placeholder||e([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:t?t.element:null}}})})(jQuery);(function(e){var t=0,a={},r={};a.height=a.paddingTop=a.paddingBottom=a.borderTopWidth=a.borderBottomWidth="hide",r.height=r.paddingTop=r.paddingBottom=r.borderTopWidth=r.borderBottomWidth="show",e.widget("ui.accordion",{version:"1.10.3",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e(),content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),undefined):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t),undefined)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var a=e.ui.keyCode,r=this.headers.length,i=this.headers.index(t.target),n=!1;switch(t.keyCode){case a.RIGHT:case a.DOWN:n=this.headers[(i+1)%r];break;case a.LEFT:case a.UP:n=this.headers[(i-1+r)%r];break;case a.SPACE:case a.ENTER:this._eventHandler(t);break;case a.HOME:n=this.headers[0];break;case a.END:n=this.headers[r-1]}n&&(e(t.target).attr("tabIndex",-1),e(n).attr("tabIndex",0),n.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide()},_refresh:function(){var a,r=this.options,i=r.heightStyle,n=this.element.parent(),s=this.accordionId="ui-accordion-"+(this.element.attr("id")||++t);this.active=this._findActive(r.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(t){var a=e(this),r=a.attr("id"),i=a.next(),n=i.attr("id");r||(r=s+"-header-"+t,a.attr("id",r)),n||(n=s+"-panel-"+t,i.attr("id",n)),a.attr("aria-controls",n),i.attr("aria-labelledby",r)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(r.event),"fill"===i?(a=n.height(),this.element.siblings(":visible").each(function(){var t=e(this),r=t.css("position");"absolute"!==r&&"fixed"!==r&&(a-=t.outerHeight(!0))}),this.headers.each(function(){a-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,a-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===i&&(a=0,this.headers.next().each(function(){a=Math.max(a,e(this).css("height","").height())}).height(a))},_activate:function(t){var a=this._findActive(t)[0];a!==this.active[0]&&(a=a||this.active[0],this._eventHandler({target:a,currentTarget:a,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var a={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){a[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,a),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var a=this.options,r=this.active,i=e(t.currentTarget),n=i[0]===r[0],s=n&&a.collapsible,o=s?e():i.next(),d=r.next(),u={oldHeader:r,oldPanel:d,newHeader:s?e():i,newPanel:o};t.preventDefault(),n&&!a.collapsible||this._trigger("beforeActivate",t,u)===!1||(a.active=s?!1:this.headers.index(i),this.active=n?e():i,this._toggle(u),r.removeClass("ui-accordion-header-active ui-state-active"),a.icons&&r.children(".ui-accordion-header-icon").removeClass(a.icons.activeHeader).addClass(a.icons.header),n||(i.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),a.icons&&i.children(".ui-accordion-header-icon").removeClass(a.icons.header).addClass(a.icons.activeHeader),i.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var a=t.newPanel,r=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=a,this.prevHide=r,this.options.animate?this._animate(a,r,t):(r.hide(),a.show(),this._toggleComplete(t)),r.attr({"aria-expanded":"false","aria-hidden":"true"}),r.prev().attr("aria-selected","false"),a.length&&r.length?r.prev().attr("tabIndex",-1):a.length&&this.headers.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),a.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(e,t,i){var n,s,o,d=this,u=0,c=e.length&&(!t.length||e.index()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,i,n,s=this.element[0].nodeName.toLowerCase(),u="textarea"===s,o="input"===s;this.isMultiLine=u?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[u||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(s){if(this.element.prop("readOnly"))return t=!0,n=!0,i=!0,undefined;t=!1,n=!1,i=!1;var u=e.ui.keyCode;switch(s.keyCode){case u.PAGE_UP:t=!0,this._move("previousPage",s);break;case u.PAGE_DOWN:t=!0,this._move("nextPage",s);break;case u.UP:t=!0,this._keyEvent("previous",s);break;case u.DOWN:t=!0,this._keyEvent("next",s);break;case u.ENTER:case u.NUMPAD_ENTER:this.menu.active&&(t=!0,s.preventDefault(),this.menu.select(s));break;case u.TAB:this.menu.active&&this.menu.select(s);break;case u.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(s),s.preventDefault());break;default:i=!0,this._searchTimeout(s)}},keypress:function(n){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&n.preventDefault(),undefined;if(!i){var s=e.ui.keyCode;switch(n.keyCode){case s.PAGE_UP:this._move("previousPage",n);break;case s.PAGE_DOWN:this._move("nextPage",n);break;case s.UP:this._keyEvent("previous",n);break;case s.DOWN:this._keyEvent("next",n)}}},input:function(e){return n?(n=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("