├── .github ├── dependabot.yml └── workflows │ ├── publish.yaml │ └── test-package.yml ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── benchmark └── benchmark.dart ├── example └── main.dart ├── lib ├── characters.dart └── src │ ├── characters.dart │ ├── characters_impl.dart │ ├── extensions.dart │ └── grapheme_clusters │ ├── breaks.dart │ ├── constants.dart │ └── table.dart ├── pubspec.yaml ├── test ├── characters_test.dart └── src │ ├── text_samples.dart │ ├── unicode_grapheme_tests.dart │ ├── unicode_tests.dart │ └── various_tests.dart ├── third_party ├── Unicode_Consortium │ ├── GraphemeBreakProperty.txt │ ├── GraphemeBreakTest.txt │ ├── UNICODE_LICENSE.txt │ ├── emoji_data.txt │ └── emoji_test.txt └── Wikipedia │ ├── License CC BY-SA 3.0.txt │ └── hangul.txt └── tool ├── README.txt ├── benchmark.dart ├── bin ├── gentable.dart └── gentest.dart ├── generate.dart └── src ├── args.dart ├── atsp.dart ├── automaton_builder.dart ├── data_files.dart ├── grapheme_category_loader.dart ├── indirect_table.dart ├── list_overlap.dart ├── shared.dart ├── string_literal_writer.dart └── table_builder.dart /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | labels: 11 | - autosubmit 12 | groups: 13 | github-actions: 14 | patterns: 15 | - "*" 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # A CI configuration to auto-publish pub packages. 2 | 3 | name: Publish 4 | 5 | on: 6 | pull_request: 7 | branches: [ master ] 8 | push: 9 | tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ] 10 | 11 | jobs: 12 | publish: 13 | if: ${{ github.repository_owner == 'dart-lang' }} 14 | uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main 15 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against dev, stable, and 2.19.0 (the package's lower bound). 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev, stable, 3.4] 24 | steps: 25 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 26 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: matrix.sdk == 'dev' && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest 41 | # 2. Release channel: dev, stable, and 2.19.0 (the package's lower bound) 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | os: [ubuntu-latest] 49 | sdk: [dev, stable, 3.4] 50 | steps: 51 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 52 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 53 | with: 54 | sdk: ${{ matrix.sdk }} 55 | - id: install 56 | name: Install dependencies 57 | run: dart pub get 58 | - name: Run VM tests 59 | run: dart test --platform vm 60 | if: always() && steps.install.outcome == 'success' 61 | - name: Run Chrome tests 62 | run: dart test --platform chrome 63 | if: always() && steps.install.outcome == 'success' 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool/ 2 | .packages 3 | pubspec.lock 4 | doc/api/ 5 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Dart project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google LLC 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.3.1-wip 2 | 3 | * Fixed README rendering on pub.dev and API docs. 4 | * Require Dart `^3.4.0` 5 | 6 | ## 1.3.0 7 | 8 | * Updated to use Unicode 15.0.0. 9 | 10 | ## 1.2.1 11 | 12 | * Update the value of the pubspec `repository` field. 13 | 14 | ## 1.2.0 15 | 16 | * Fix `Characters.where` which unnecessarily did the iteration and test twice. 17 | * Adds `Characters.empty` constant and makes `Characters("")` return it. 18 | * Changes the argument type of `Characters.contains` to (covariant) `String`. 19 | The implementation still accepts `Object?`, so it can be cast to 20 | `Iterable`, but you get warned if you try to call directly with a 21 | non-`String`. 22 | 23 | ## 1.1.0 24 | 25 | * Stable release for null safety. 26 | * Added `stringBeforeLength` and `stringAfterLength` to `CharacterRange`. 27 | * Added `CharacterRange.at` constructor. 28 | * Added `getRange(start, end)` and `characterAt(pos)` to `Characters` 29 | as alternative to `.take(end).skip(start)` and `getRange(pos, pos + 1)`. 30 | * Change some positional parameter names from `other` to `characters`. 31 | 32 | ## 1.0.0 33 | 34 | * Core APIs deemed stable; package version set to 1.0.0. 35 | * Added `split` methods on `Characters` and `CharacterRange`. 36 | 37 | ## 0.5.0 38 | 39 | * Change [codeUnits] getter to [utf16CodeUnits] which returns an iterable. 40 | This avoids leaking that the underlying string has efficient UTF-16 41 | code unit access in the API, and allows the same interface to be 42 | just as efficiently implemented on top of UTF-8. 43 | 44 | ## 0.4.0 45 | 46 | * Added an extension method on `String` to allow easy access to the `Characters` 47 | of the string: 48 | 49 | ```dart 50 | print('The first character is: ' + myString.characters.first) 51 | ``` 52 | 53 | * Updated Dart SDK dependency to Dart 2.6.0 54 | 55 | ## 0.3.1 56 | 57 | * Added small example in `example/main.dart` 58 | * Enabled pedantic lints and updated code to resolve issues. 59 | 60 | ## 0.3.0 61 | 62 | * Updated API which does not expose the underlying string indices. 63 | 64 | ## 0.1.0 65 | 66 | * Initial release 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This repo has moved to https://github.com/dart-lang/core/tree/main/pkgs/characters 3 | 4 | [![Build Status](https://github.com/dart-lang/characters/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/characters/actions?query=workflow%3A"Dart+CI"+branch%3Amaster) 5 | [![pub package](https://img.shields.io/pub/v/characters.svg)](https://pub.dev/packages/characters) 6 | [![package publisher](https://img.shields.io/pub/publisher/characters.svg)](https://pub.dev/packages/characters/publisher) 7 | 8 | [`Characters`][Characters] are strings viewed as 9 | sequences of **user-perceived character**s, 10 | also known as [Unicode (extended) grapheme clusters][Grapheme Clusters]. 11 | 12 | The [`Characters`][Characters] class allows access to 13 | the individual characters of a string, 14 | and a way to navigate back and forth between them 15 | using a [`CharacterRange`][CharacterRange]. 16 | 17 | ## Unicode characters and representations 18 | 19 | There is no such thing as plain text. 20 | 21 | Computers only know numbers, 22 | so any "text" on a computer is represented by numbers, 23 | which are again stored as bytes in memory. 24 | 25 | The meaning of those bytes are provided by layers of interpretation, 26 | building up to the *glyph*s that the computer displays on the screen. 27 | 28 | | Abstraction | Dart Type | Usage | Example | 29 | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 30 | | Bytes | [`ByteBuffer`][ByteBuffer],
[`Uint8List`][Uint8List] | Physical layout: Memory or network communication. | `file.readAsBytesSync()` | 31 | | [Code units][] | [`Uint8List`][Uint8List] (UTF‑8)
[`Uint16List`][Uint16List], [`String`][String] (UTF‑16) | Standard formats for
encoding code points in memory.
Stored in memory using one (UTF‑8) or more (UTF‑16) bytes. One or more code units encode a code point. | `string.codeUnits`
`string.codeUnitAt(index)`
`utf8.encode(string)` | 32 | | [Code points][] | [`Runes`][Runes] | The Unicode unit of meaning. | `string.runes` | 33 | | [Grapheme Clusters][] | [`Characters`][Characters] | Human perceived character. One or more code points. | `string.characters` | 34 | | [Glyphs][] | | Visual rendering of grapheme clusters. | `print(string)` | 35 | 36 | A Dart `String` is a sequence of UTF-16 code units, 37 | just like strings in JavaScript and Java. 38 | The runtime system decides on the underlying physical representation. 39 | 40 | That makes plain strings inadequate 41 | when needing to manipulate the text that a user is viewing, or entering, 42 | because string operations are not working at the grapheme cluster level. 43 | 44 | For example, to abbreviate a text to, say, the 15 first characters or glyphs, 45 | a string like "A 🇬🇧 text in English" 46 | should abbreviate to "A 🇬🇧 text in Eng… when counting characters, 47 | but will become "A 🇬🇧 text in …" 48 | if counting code units using [`String`][String] operations. 49 | 50 | Whenever you need to manipulate strings at the character level, 51 | you should be using the [`Characters`][Characters] type, 52 | not the methods of the [`String`][String] class. 53 | 54 | ## The Characters class 55 | 56 | The [`Characters`][Characters] class exposes a string 57 | as a sequence of grapheme clusters. 58 | All operations on [`Characters`][Characters] operate 59 | on entire grapheme clusters, 60 | so it removes the risk of splitting combined characters or emojis 61 | that are inherent in the code-unit based [`String`][String] operations. 62 | 63 | You can get a [`Characters`][Characters] object for a string using either 64 | the constructor [`Characters(string)`][Characters constructor] 65 | or the extension getter `string.characters`. 66 | 67 | At its core, the class is an [`Iterable`][Iterable] 68 | where the element strings are single grapheme clusters. 69 | This allows sequential access to the individual grapheme clusters 70 | of the original string. 71 | 72 | On top of that, there are operations mirroring the operations 73 | of [`String`][String] that are not index, code-unit or code-point based, 74 | like [`startsWith`][Characters.startsWith] 75 | or [`replaceAll`][Characters.replaceAll]. 76 | There are some differences between these and the [`String`][String] operations. 77 | For example the replace methods only accept characters as pattern. 78 | Regular expressions are not grapheme cluster aware, 79 | so they cannot be used safely on a sequence of characters. 80 | 81 | Grapheme clusters have varying length in the underlying representation, 82 | so operations on a [`Characters`][Characters] sequence cannot be index based. 83 | Instead, the [`CharacterRange`][CharacterRange] *iterator* 84 | provided by [`Characters.iterator`][Characters.iterator] 85 | has been greatly enhanced. 86 | It can move both forwards and backwards, 87 | and it can span a *range* of grapheme cluster. 88 | Most operations that can be performed on a full [`Characters`][Characters] 89 | can also be performed on the grapheme clusters 90 | in the range of a [`CharacterRange`][CharacterRange]. 91 | The range can be contracted, expanded or moved in various ways, 92 | not restricted to using [`moveNext`][CharacterRange.moveNext], 93 | to move to the next grapheme cluster. 94 | 95 | Example: 96 | 97 | ```dart 98 | // Using String indices. 99 | String? firstTagString(String source) { 100 | var start = source.indexOf('<') + 1; 101 | if (start > 0) { 102 | var end = source.indexOf('>', start); 103 | if (end >= 0) { 104 | return source.substring(start, end); 105 | } 106 | } 107 | return null; 108 | } 109 | 110 | // Using CharacterRange operations. 111 | Characters? firstTagCharacters(Characters source) { 112 | var range = source.findFirst('<'.characters); 113 | if (range != null && range.moveUntil('>'.characters)) { 114 | return range.currentCharacters; 115 | } 116 | return null; 117 | } 118 | ``` 119 | 120 | [ByteBuffer]: https://api.dart.dev/dart-typed_data/ByteBuffer-class.html "ByteBuffer class" 121 | [CharacterRange.moveNext]: https://pub.dev/documentation/characters/latest/characters/CharacterRange/moveNext.html "CharacterRange.moveNext" 122 | [CharacterRange]: https://pub.dev/documentation/characters/latest/characters/CharacterRange-class.html "CharacterRange class" 123 | [Characters constructor]: https://pub.dev/documentation/characters/latest/characters/Characters/Characters.html "Characters constructor" 124 | [Characters.iterator]: https://pub.dev/documentation/characters/latest/characters/Characters/iterator.html "CharactersRange get iterator" 125 | [Characters.replaceAll]: https://pub.dev/documentation/characters/latest/characters/Characters/replaceAll.html "Characters.replaceAlle" 126 | [Characters.startsWith]: https://pub.dev/documentation/characters/latest/characters/Characters/startsWith.html "Characters.startsWith" 127 | [Characters]: https://pub.dev/documentation/characters/latest/characters/Characters-class.html "Characters class" 128 | [Code Points]: https://unicode.org/glossary/#code_point "Unicode Code Point" 129 | [Code Units]: https://unicode.org/glossary/#code_unit "Unicode Code Units" 130 | [Glyphs]: https://unicode.org/glossary/#glyph "Unicode Glyphs" 131 | [Grapheme Clusters]: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries "Unicode (Extended) Grapheme Cluster" 132 | [Iterable]: https://api.dart.dev/dart-core/Iterable-class.html "Iterable class" 133 | [Runes]: https://api.dart.dev/dart-core/Runes-class.html "Runes class" 134 | [String]: https://api.dart.dev/dart-core/String-class.html "String class" 135 | [Uint16List]: https://api.dart.dev/dart-typed_data/Uint16List-class.html "Uint16List class" 136 | [Uint8List]: https://api.dart.dev/dart-typed_data/Uint8List-class.html "Uint8List class" 137 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | prefer_single_quotes: ignore 6 | -------------------------------------------------------------------------------- /benchmark/benchmark.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Benchmark of efficiency of grapheme cluster operations. 6 | 7 | import "package:characters/characters.dart"; 8 | 9 | import "../test/src/text_samples.dart"; 10 | 11 | double bench(int Function() action, int ms) { 12 | var elapsed = 0; 13 | var count = 0; 14 | var stopwatch = Stopwatch()..start(); 15 | do { 16 | count += action(); 17 | elapsed = stopwatch.elapsedMilliseconds; 18 | } while (elapsed < ms); 19 | return count / elapsed; 20 | } 21 | 22 | int iterateIndicesOnly() { 23 | var graphemeClusters = 0; 24 | var char = Characters(hangul).iterator; 25 | while (char.moveNext()) { 26 | graphemeClusters++; 27 | } 28 | char = Characters(genesis).iterator; 29 | while (char.moveNext()) { 30 | graphemeClusters++; 31 | } 32 | return graphemeClusters; 33 | } 34 | 35 | int iterateStrings() { 36 | var codeUnits = 0; 37 | var char = Characters(hangul).iterator; 38 | while (char.moveNext()) { 39 | codeUnits += char.current.length; 40 | } 41 | char = Characters(genesis).iterator; 42 | while (char.moveNext()) { 43 | codeUnits += char.current.length; 44 | } 45 | return codeUnits; 46 | } 47 | 48 | int reverseStrings() { 49 | var revHangul = reverse(hangul); 50 | var rev2Hangul = reverse(revHangul); 51 | if (hangul != rev2Hangul || hangul == revHangul) { 52 | throw AssertionError("Bad reverse"); 53 | } 54 | var revGenesis = reverse(genesis); 55 | var rev2Genesis = reverse(revGenesis); 56 | if (genesis != rev2Genesis || genesis == revGenesis) { 57 | throw AssertionError("Bad reverse"); 58 | } 59 | 60 | return (hangul.length + genesis.length) * 2; 61 | } 62 | 63 | int replaceStrings() { 64 | var count = 0; 65 | { 66 | const language = "한글"; 67 | assert(language.length == 6); 68 | var chars = Characters(hangul); 69 | var replaced = 70 | chars.replaceAll(Characters(language), Characters("Hangul!")); 71 | count += replaced.string.length - hangul.length; 72 | } 73 | { 74 | var chars = Characters(genesis); 75 | var replaced = chars.replaceAll(Characters("And"), Characters("Also")); 76 | count += replaced.string.length - genesis.length; 77 | } 78 | return count; 79 | } 80 | 81 | String reverse(String input) { 82 | var chars = Characters(input); 83 | var buffer = StringBuffer(); 84 | for (var it = chars.iteratorAtEnd; it.moveBack();) { 85 | buffer.write(it.current); 86 | } 87 | return buffer.toString(); 88 | } 89 | 90 | void main(List args) { 91 | var count = 1; 92 | if (args.isNotEmpty) count = int.tryParse(args[0]) ?? 1; 93 | 94 | // Warmup. 95 | bench(iterateIndicesOnly, 250); 96 | bench(iterateStrings, 250); 97 | bench(reverseStrings, 250); 98 | bench(replaceStrings, 250); 99 | 100 | var bestIterateIndices = 0.0; 101 | var bestIterateStrings = 0.0; 102 | var bestReverseStrings = 0.0; 103 | var bestReplaceStrings = 0.0; 104 | 105 | String toDigits(double d) { 106 | const n = 5; 107 | var s = d.round().toString(); 108 | if (s.length >= n) return s; 109 | return d.toStringAsFixed(n - s.length); 110 | } 111 | 112 | for (var i = 0; i < count; i++) { 113 | var performance = bench(iterateIndicesOnly, 2000); 114 | print("Index Iteration: ${toDigits(performance)} gc/ms"); 115 | if (performance > bestIterateIndices) bestIterateIndices = performance; 116 | 117 | performance = bench(iterateStrings, 2000); 118 | print("String Iteration: ${toDigits(performance)} cu/ms"); 119 | if (performance > bestIterateStrings) bestIterateStrings = performance; 120 | 121 | performance = bench(reverseStrings, 2000); 122 | print("String Reversing: ${toDigits(performance)} cu/ms"); 123 | if (performance > bestReverseStrings) bestReverseStrings = performance; 124 | 125 | performance = bench(replaceStrings, 2000); 126 | print("String Replacing: ${toDigits(performance)} changes/ms"); 127 | if (performance > bestReplaceStrings) bestReplaceStrings = performance; 128 | } 129 | 130 | if (count > 1) { 131 | print("Best: "); 132 | print("Index Iteration: ${toDigits(bestIterateIndices)} gc/ms"); 133 | print("String Iteration: ${toDigits(bestIterateStrings)} cu/ms"); 134 | print("String Reversing: ${toDigits(bestReverseStrings)} cu/ms"); 135 | print("String Replacing: ${toDigits(bestReplaceStrings)} changes/ms"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:characters/characters.dart'; 6 | 7 | // Small API examples. For full API docs see: 8 | // https://pub.dev/documentation/characters/latest/characters/characters-library.html 9 | void main() { 10 | var hi = 'Hi 🇩🇰'; 11 | print('String is "$hi"\n'); 12 | 13 | // Length. 14 | print('String.length: ${hi.length}'); 15 | print('Characters.length: ${hi.characters.length}\n'); 16 | 17 | // Last character. 18 | print('The string ends with: ${hi.substring(hi.length - 1)}'); 19 | print('The last character: ${hi.characters.last}\n'); 20 | 21 | // Skip last character. 22 | print('Substring -1: "${hi.substring(0, hi.length - 1)}"'); 23 | print('Skipping last character: "${hi.characters.skipLast(1)}"\n'); 24 | 25 | // Replace characters. 26 | var newHi = hi.characters.replaceAll('🇩🇰'.characters, '🇺🇸'.characters); 27 | print('Change flag: "$newHi"'); 28 | } 29 | -------------------------------------------------------------------------------- /lib/characters.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// String operations based on characters (Unicode grapheme clusters). 6 | library; 7 | 8 | export "src/characters.dart"; 9 | export "src/extensions.dart"; 10 | -------------------------------------------------------------------------------- /lib/src/extensions.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'characters.dart'; 6 | 7 | extension StringCharacters on String { 8 | /// The [Characters] of this string. 9 | Characters get characters => Characters(this); 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/grapheme_clusters/breaks.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "constants.dart"; 6 | import "table.dart"; 7 | 8 | /// Iterates grapheme cluster breaks of a string. 9 | /// 10 | /// Iterates the grapheme cluster breaks of the substring of 11 | /// [base] from [cursor] to [end]. 12 | /// 13 | /// To iterate a substring, use: 14 | /// ```dart 15 | /// var breaks = Breaks(string, start, end, stateSoT); 16 | /// int brk = 0; 17 | /// while((brk = breaks.nextBreak) >= 0) { 18 | /// print("Break at index $brk"); 19 | /// } 20 | /// ``` 21 | /// If you use [stateSoTNoBreak] instead of [stateSoT], the 22 | /// initial break between the start-of-text and the first grapheme 23 | /// is suppressed. 24 | class Breaks { 25 | /// Text being iterated. 26 | final String base; 27 | 28 | /// end of substring of [base] being iterated. 29 | final int end; 30 | 31 | /// Position of the first yet-unprocessed code point. 32 | int cursor; 33 | 34 | /// Current state based on code points processed so far. 35 | int state; 36 | 37 | Breaks(this.base, this.cursor, this.end, this.state); 38 | 39 | /// Creates a copy of the current iteration, at the exact same state. 40 | Breaks copy() => Breaks(base, cursor, end, state); 41 | 42 | /// The index of the next grapheme cluster break in last-to-first index order. 43 | /// 44 | /// Returns a negative number if there are no further breaks, 45 | /// which means that [cursor] has reached [end]. 46 | int nextBreak() { 47 | while (cursor < end) { 48 | var breakAt = cursor; 49 | var char = base.codeUnitAt(cursor++); 50 | if (char & 0xFC00 != 0xD800) { 51 | state = move(state, low(char)); 52 | if (state & stateNoBreak == 0) { 53 | return breakAt; 54 | } 55 | continue; 56 | } 57 | // The category of an unpaired lead surrogate is Control. 58 | var category = categoryControl; 59 | if (cursor < end) { 60 | var nextChar = base.codeUnitAt(cursor); 61 | if (nextChar & 0xFC00 == 0xDC00) { 62 | category = high(char, nextChar); 63 | cursor++; 64 | } 65 | } 66 | state = move(state, category); 67 | if (state & stateNoBreak == 0) { 68 | return breakAt; 69 | } 70 | } 71 | state = move(state, categoryEoT); 72 | if (state & stateNoBreak == 0) return cursor; 73 | return -1; 74 | } 75 | } 76 | 77 | /// Iterates grapheme cluster breaks backwards. 78 | /// 79 | /// Given a substring of a [base] string from [start] to [cursor], 80 | /// iterates the grapheme cluster breaks from [cursor] to [start]. 81 | /// 82 | /// To iterate a substring, do 83 | /// ```dart 84 | /// var breaks = BackBreaks(string, start, end, stateEoT); 85 | /// int brk = 0; 86 | /// while ((brk = breaks.nextBreak()) >= 0) { 87 | /// print("Break at index $brk"); 88 | /// } 89 | /// ``` 90 | /// If the initial [state] is [stateEoTNoBreak] instead of [stateEoT], 91 | /// the initial break between the last grapheme and the end-of-text 92 | /// is suppressed. 93 | class BackBreaks { 94 | /// Text being iterated. 95 | final String base; 96 | 97 | /// Start of substring of [base] being iterated. 98 | final int start; 99 | 100 | /// Position after the last yet-unprocessed code point. 101 | int cursor; 102 | 103 | /// Current state based on code points processed so far. 104 | int state; 105 | BackBreaks(this.base, this.cursor, this.start, this.state); 106 | 107 | BackBreaks copy() => BackBreaks(base, cursor, start, state); 108 | 109 | /// The index of the next grapheme cluster break in first-to-last index order. 110 | /// 111 | /// Returns a negative number if there are no further breaks, 112 | /// which means that [cursor] has reached [start]. 113 | int nextBreak() { 114 | while (cursor > start) { 115 | var breakAt = cursor; 116 | var char = base.codeUnitAt(--cursor); 117 | if (char & 0xFC00 != 0xDC00) { 118 | state = moveBack(state, low(char)); 119 | if (state >= stateLookaheadMin) state = _lookAhead(state); 120 | if (state & stateNoBreak == 0) { 121 | return breakAt; 122 | } 123 | continue; 124 | } 125 | // The category of an unpaired tail surrogate is Control. 126 | var category = categoryControl; 127 | if (cursor >= start) { 128 | var prevChar = base.codeUnitAt(cursor - 1); 129 | if (prevChar & 0xFC00 == 0xD800) { 130 | category = high(prevChar, char); 131 | cursor -= 1; 132 | } 133 | } 134 | state = moveBack(state, category); 135 | if (state >= stateLookaheadMin) state = _lookAhead(state); 136 | if (state & stateNoBreak == 0) { 137 | return breakAt; 138 | } 139 | } 140 | state = moveBack(state, categoryEoT); 141 | if (state >= stateLookaheadMin) state = _lookAhead(state); 142 | if (state & stateNoBreak == 0) return cursor; 143 | return -1; 144 | } 145 | 146 | int _lookAhead(int state) => lookAhead(base, start, cursor, state); 147 | } 148 | 149 | /// Request a lookahead for [state]. 150 | /// 151 | /// The [state] was output by the backwards grapheme cluster state 152 | /// machine and is above [stateLookaheadMin]. 153 | /// The lookahead looks at the [base] string from just before [cursor] 154 | /// back to [start], to detect which actual state to enter. 155 | int lookAhead(String base, int start, int cursor, int state) { 156 | assert(state >= stateLookaheadMin); 157 | if (state == stateRegionalLookahead) { 158 | return lookAheadRegional(base, start, cursor); 159 | } 160 | if (state == stateZWJPictographicLookahead) { 161 | var prevPic = lookAheadPictorgraphicExtend(base, start, cursor); 162 | if (prevPic >= 0) return stateZWJPictographic | stateNoBreak; 163 | return stateExtend; // State for break before seeing ZWJ. 164 | } 165 | throw StateError("Unexpected state: ${state.toRadixString(16)}"); 166 | } 167 | 168 | /// Counts preceding regional indicators. 169 | /// 170 | /// The look-ahead for the backwards moving grapheme cluster 171 | /// state machine is called when two RIs are found in a row. 172 | /// The [cursor] points to the first code unit of the former of those RIs, 173 | /// and it preceding RIs until [start]. 174 | /// If that count is even, there should not be a break before 175 | /// the second of the original RIs. 176 | /// If the count is odd, there should be a break, because that RI 177 | /// is combined with a prior RI in the string. 178 | int lookAheadRegional(String base, int start, int cursor) { 179 | // Has just seen second regional indicator. 180 | // Figure out if there are an odd or even number of preceding RIs. 181 | // ALL REGIONAL INDICATORS ARE NON-BMP CHARACTERS. 182 | var count = 0; 183 | var index = cursor; 184 | while (index - 2 >= start) { 185 | var tail = base.codeUnitAt(index - 1); 186 | if (tail & 0xFC00 != 0xDC00) break; 187 | var lead = base.codeUnitAt(index - 2); 188 | if (lead & 0xFC00 != 0xD800) break; 189 | var category = high(lead, tail); 190 | if (category != categoryRegionalIndicator) break; 191 | index -= 2; 192 | count ^= 1; 193 | } 194 | if (count == 0) { 195 | return stateRegionalEven | stateNoBreak; 196 | } else { 197 | return stateRegionalOdd; 198 | } 199 | } 200 | 201 | /// Checks if a ZWJ+Pictographic token sequence should be broken. 202 | /// 203 | /// Checks whether the characters preceeding [cursor] are Pic Ext*. 204 | /// Only the [base] string from [start] to [cursor] is checked. 205 | /// 206 | /// Returns the index of the Pic character if preceeded by Pic Ext*, 207 | /// and negative if not. 208 | int lookAheadPictorgraphicExtend(String base, int start, int cursor) { 209 | // Has just seen ZWJ+Pictographic. Check if preceeding is Pic Ext*. 210 | // (If so, just move cursor back to the Pic). 211 | var index = cursor; 212 | while (index > start) { 213 | var char = base.codeUnitAt(--index); 214 | var prevChar = 0; 215 | var category = categoryControl; 216 | if (char & 0xFC00 != 0xDC00) { 217 | category = low(char); 218 | } else if (index > start && 219 | (prevChar = base.codeUnitAt(--index)) & 0xFC00 == 0xD800) { 220 | category = high(prevChar, char); 221 | } else { 222 | break; 223 | } 224 | if (category == categoryPictographic) { 225 | return index; 226 | } 227 | if (category != categoryExtend) break; 228 | } 229 | return -1; 230 | } 231 | 232 | /// Whether there is a grapheme cluster boundary before [index] in [text]. 233 | /// 234 | /// This is a low-level function. There is no validation of the arguments. 235 | /// They should satisfy `0 <= start <= index <= end <= text.length`. 236 | bool isGraphemeClusterBoundary(String text, int start, int end, int index) { 237 | assert(0 <= start); 238 | assert(start <= index); 239 | assert(index <= end); 240 | assert(end <= text.length); 241 | // Uses the backwards automaton because answering the question 242 | // might be answered by looking only at the code points around the 243 | // index, but it may also require looking further back. It never 244 | // requires looking further ahead, though. 245 | // The backwards automaton is built for this use case. 246 | // Most of the apparent complication in this function is merely dealing with 247 | // surrogates. 248 | if (start < index && index < end) { 249 | // Something on both sides of index. 250 | var char = text.codeUnitAt(index); 251 | var prevChar = text.codeUnitAt(index - 1); 252 | var catAfter = categoryControl; 253 | if (char & 0xF800 != 0xD800) { 254 | catAfter = low(char); 255 | } else if (char & 0xFC00 == 0xD800) { 256 | // Lead surrogate. Combine with following tail surrogate, 257 | // otherwise it's a control and always a boundary. 258 | if (index + 1 >= end) return true; 259 | var nextChar = text.codeUnitAt(index + 1); 260 | if (nextChar & 0xFC00 != 0xDC00) return true; 261 | catAfter = high(char, nextChar); 262 | } else { 263 | // Tail surrogate after index. Either combines with lead surrogate 264 | // before or is always a bundary. 265 | return prevChar & 0xFC00 != 0xD800; 266 | } 267 | var catBefore = categoryControl; 268 | if (prevChar & 0xFC00 != 0xDC00) { 269 | catBefore = low(prevChar); 270 | index -= 1; 271 | } else { 272 | // If no prior lead surrogate, it's a control and always a boundary. 273 | index -= 2; 274 | if (start <= index) { 275 | var prevPrevChar = text.codeUnitAt(index); 276 | if (prevPrevChar & 0xFC00 != 0xD800) { 277 | return true; 278 | } 279 | catBefore = high(prevPrevChar, prevChar); 280 | } else { 281 | return true; 282 | } 283 | } 284 | var state = moveBack(stateEoTNoBreak, catAfter); 285 | // It requires at least two moves from EoT to trigger a lookahead, 286 | // either ZWJ+Pic or RI+RI. 287 | assert(state < stateLookaheadMin); 288 | state = moveBack(state, catBefore); 289 | if (state >= stateLookaheadMin) { 290 | state = lookAhead(text, start, index, state); 291 | } 292 | return state & stateNoBreak == 0; 293 | } 294 | // Always boundary at EoT or SoT, unless there is nothing between them. 295 | return start != end; 296 | } 297 | 298 | /// The most recent break no later than [index] in 299 | /// `string.substring(start, end)`. 300 | int previousBreak(String text, int start, int end, int index) { 301 | assert(0 <= start); 302 | assert(start <= index); 303 | assert(index <= end); 304 | assert(end <= text.length); 305 | if (index == start || index == end) return index; 306 | var indexBefore = index; 307 | var nextChar = text.codeUnitAt(index); 308 | var category = categoryControl; 309 | if (nextChar & 0xF800 != 0xD800) { 310 | category = low(nextChar); 311 | } else if (nextChar & 0xFC00 == 0xD800) { 312 | var indexAfter = index + 1; 313 | if (indexAfter < end) { 314 | var secondChar = text.codeUnitAt(indexAfter); 315 | if (secondChar & 0xFC00 == 0xDC00) { 316 | category = high(nextChar, secondChar); 317 | } 318 | } 319 | } else { 320 | var prevChar = text.codeUnitAt(index - 1); 321 | if (prevChar & 0xFC00 == 0xD800) { 322 | category = high(prevChar, nextChar); 323 | indexBefore -= 1; 324 | } 325 | } 326 | return BackBreaks( 327 | text, indexBefore, start, moveBack(stateEoTNoBreak, category)) 328 | .nextBreak(); 329 | } 330 | 331 | /// The next break no earlier than [index] in `string.substring(start, end)`. 332 | /// 333 | /// The index need not be at a grapheme cluster boundary. 334 | int nextBreak(String text, int start, int end, int index) { 335 | assert(0 <= start); 336 | assert(start <= index); 337 | assert(index <= end); 338 | assert(end <= text.length); 339 | if (index == start || index == end) return index; 340 | var indexBefore = index - 1; 341 | var prevChar = text.codeUnitAt(indexBefore); 342 | var prevCategory = categoryControl; 343 | if (prevChar & 0xF800 != 0xD800) { 344 | prevCategory = low(prevChar); 345 | } else if (prevChar & 0xFC00 == 0xD800) { 346 | var nextChar = text.codeUnitAt(index); 347 | if (nextChar & 0xFC00 == 0xDC00) { 348 | index += 1; 349 | if (index == end) return end; 350 | prevCategory = high(prevChar, nextChar); 351 | } 352 | } else if (indexBefore > start) { 353 | var secondCharIndex = indexBefore - 1; 354 | var secondChar = text.codeUnitAt(secondCharIndex); 355 | if (secondChar & 0xFC00 == 0xD800) { 356 | indexBefore = secondCharIndex; 357 | prevCategory = high(secondChar, prevChar); 358 | } 359 | } 360 | // The only boundaries which depend on more information than 361 | // the previous character are the [^RI] (RI RI)* RI x RI and 362 | // Pic Ext* ZWJ x Pic breaks. In all other cases, all the necessary 363 | // information is in the last seen category. 364 | var state = stateOther; 365 | if (prevCategory == categoryRegionalIndicator) { 366 | var prevState = lookAheadRegional(text, start, indexBefore); 367 | if (prevState != stateRegionalOdd) { 368 | state = stateRegionalSingle; 369 | } 370 | } else if (prevCategory == categoryZWJ || prevCategory == categoryExtend) { 371 | var prevPic = lookAheadPictorgraphicExtend(text, start, indexBefore); 372 | if (prevPic >= 0) { 373 | state = prevCategory == categoryZWJ 374 | ? statePictographicZWJ 375 | : statePictographic; 376 | } 377 | } else { 378 | state = move(stateSoTNoBreak, prevCategory); 379 | } 380 | return Breaks(text, index, text.length, state).nextBreak(); 381 | } 382 | -------------------------------------------------------------------------------- /lib/src/grapheme_clusters/constants.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Unicode Grapheme Breaking Algorithm Character Categories. 6 | /// (Order is irrelevant to correctness, so it is chosen 7 | /// to minimize the size of the generated table strings 8 | /// by avoiding many bytes that need escapes). 9 | const int categoryCR = 0; 10 | const int categoryZWJ = 1; 11 | const int categoryControl = 2; 12 | const int categoryOther = 3; // Any character not in any other category. 13 | const int categoryExtend = 4; 14 | const int categorySpacingMark = 5; 15 | const int categoryRegionalIndicator = 6; 16 | const int categoryPictographic = 7; 17 | const int categoryLF = 8; 18 | const int categoryPrepend = 9; 19 | const int categoryL = 10; 20 | const int categoryV = 11; 21 | const int categoryT = 12; 22 | const int categoryLV = 13; 23 | const int categoryLVT = 14; 24 | const int categoryEoT = 15; // End of Text (synthetic input) 25 | 26 | // Automaton states for forwards automaton. 27 | 28 | const int stateSoT = 0; // Start of text (or grapheme). 29 | const int stateBreak = 0x10; // Always break before next. 30 | const int stateCR = 0x20; // Break unless next is LF. 31 | const int stateOther = 0x30; // Break unless next is Extend, ZWJ, SpacingMark. 32 | const int statePrepend = 0x40; // Only break if next is Control/CR/LF/eot. 33 | const int stateL = 0x50; // As Other unless next is L, V, LV, LVT. 34 | const int stateV = 0x60; // As Other unless next is V, T. 35 | const int stateT = 0x70; // As Other unless next is T. 36 | const int statePictographic = 0x80; // As Other unless followed by Ext* ZWJ Pic. 37 | const int statePictographicZWJ = 0x90; // As Other unless followed by Pic. 38 | const int stateRegionalSingle = 0xA0; // As Other unless followed by RI 39 | const int stateSoTNoBreak = 0xB0; // As SoT but never cause break before next. 40 | 41 | /// Bit flag or'ed to the automaton output if there should not be a break 42 | /// before the most recent input character. 43 | const int stateNoBreak = 1; 44 | 45 | // Backwards Automaton extra/alternative states and categories. 46 | 47 | const int categorySoT = 15; // Start of Text (synthetic input) 48 | 49 | const int stateEoT = 0; // Start of text (or grapheme). 50 | const int stateLF = 0x20; // Break unless prev is CR. 51 | const int stateExtend = 0x40; // Only break if prev is Control/CR/LF/sot. 52 | const int stateZWJPictographic = 0x90; // Preceded by Pic Ext*. 53 | const int stateEoTNoBreak = 0xB0; // As EoT but never cause break before. 54 | const int stateRegionalEven = 0xC0; // There is an even number of RIs before. 55 | const int stateRegionalOdd = 56 | stateZWJPictographic; // There is an odd (non-zero!) number of RIs before. 57 | 58 | /// Minimum state requesting a look-ahead. 59 | const int stateLookaheadMin = stateRegionalLookahead; 60 | 61 | /// State requesting a look-ahead for an even or odd number of RIs. 62 | const int stateRegionalLookahead = 0xD0; 63 | 64 | /// State requesting a look-ahead for Pic Ext*. 65 | const int stateZWJPictographicLookahead = 0xE0; 66 | -------------------------------------------------------------------------------- /lib/src/grapheme_clusters/table.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Generated code. Do not edit. 6 | // Generated from [https://unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt](../../third_party/Unicode_Consortium/GraphemeBreakProperty.txt) 7 | // and [https://unicode.org/Public/15.0.0/ucd/emoji/emoji-data.txt](../../third_party/Unicode_Consortium/emoji_data.txt). 8 | // Licensed under the Unicode Inc. License Agreement 9 | // (https://www.unicode.org/license.txt, ../../third_party/third_party/Unicode_Consortium/UNICODE_LICENSE.txt) 10 | 11 | const String _data = 'E533333333333333333333333333DDDDDDD4333333333333333333334' 12 | 'C43333CD53333333333333333333333UEDTE4\x933343333\x933333333333333333333333' 13 | '333D433333333333333333CDDEDDD43333333S5333333333333333333333C333333D533333' 14 | '333333333333333333SUDDDDT5\x9933CD4E333333333333333333333333UEDDDDE4333333' 15 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 16 | '333333333333333333333333333333333333333333TUUS5CT\x94\x95E3333333333333333' 17 | '333333333333333333333333333333333333333333333333333333SUDD3DUU435333333333' 18 | '33333333C3333333333333w733337333333s3333333w7333333333w3333333333333333333' 19 | '3CDDTETE43333ED4S5SE3333C33333D33333333333334E433C3333333C3333333333333333' 20 | '3333333333333CETUTDT533333CDDDDDDDDDD3333333343333333D\x244333333333333333' 21 | '33333333SUDTEE433C34333333333333333333333333333333333333333333333333333333' 22 | '333333333333333333333333TUDDDD3333333333CT5333333333333333333333333333DCEU' 23 | 'U3U3U5333343333S5CDDD3CDD3333333333333333333333333333333333333333333333333' 24 | '33333333333333333333s73333s33333333333""""""""333333339433333333333333CDDD' 25 | 'DDDDDDDDDDDDD3333333CDDDDDDDDDDD\x94DDDDDDDDDDDDDDDDDDDDDDDD33333333DDDDDD' 26 | 'DD3333333373s333333333333333333333333333333CDTDDDCTE43C4CD3C33333333333333' 27 | '3D3C33333îîíîîîîîîîîîîîîîíîîîîîîîîîîîîîíîîîîîîîîîîîîî333333»»»»»»»»33ÌÌÌÌÌ' 28 | 'ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ<3sww73333swwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 29 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 30 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 31 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7333swwwwww' 32 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 33 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww7333333w7333333333333333733' 34 | '333333333333333333333333333sww733333s7333333s3wwwww333333333wwwwwwwwwwwwww' 35 | 'wwwwwwwwwwwwwwgffffffffffffvww7wwwwwwswwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 36 | 'www733333333333333333333333swwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 37 | 'wwwwwwwwwwwwwwwww733333333333333333333333333333333333333333333333333333333' 38 | '3swwwww7333333333333333333333333333333333333333333wwwwwwwwwwwwwwwwwwwww7sw' 39 | 'wwwwss33373733s33333w33333CT333333333333333EDTETD433333333#\x14"3333333333' 40 | '33"""233333373ED4U5UE9333C33333D33333333333333www3333333s73333333333EEDDDC' 41 | 'C3DDDDUUUDDDDD3T5333333333333333333333333333CCU333333333333333333333333333' 42 | '3334EDDD33SDD4D5U4333333333C43333333333CDDD9DDD3DCD433333333C4333333333333' 43 | '33C433333333333334443SEUCUSE4333D33333C43333333533333CU3333333333333333333' 44 | '3333333334EDDDD3CDDDDDDDDDDDDDDDDDDDDDDDDDDD33DDDDDDDDDDDDDDDDDDDDDDDDD333' 45 | '34333333C33333333333DD4DDDDDDD43333333333333333333333333333333333333333333' 46 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 47 | '33333333333333333333333333333333333333333333333333CSUUUUUUUUUUUUUUUUUUUUUU' 48 | 'UUUUU333CD43333333333333333333333333333333333333333433333U3333333333333333' 49 | '333333333UUUUUUTEDDDDD3333C3333333333333333373333333333s333333333333swwwww' 50 | '33w733wwwwwww73333s33333333337swwwwsw73333wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 51 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 52 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 53 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwDD4D33CDDDDDCDDDDDDDDDDDDDDDDD43EDDDT' 54 | 'UEUCDDD33333D33333333333333DDCDDDDCDCDD333333333DT33333333333333D533333333' 55 | '3333333333333333333CSUE4333333333333CDDDDDDDD4333333DT33333333333333333333' 56 | '333CUDDUDU3SUSU43333433333333333333333333ET533E3333SDD3U3U4333D43333C43333' 57 | '333333333s733333s33333333333CTE333333333333333333UUUUDDDDUD3333"""""(\x02"' 58 | '""""""""3333333333333333333DDDD333333333333333333333333CDDDD3333C3333T3333' 59 | '33333333333333333334343C33333333333SET334333333333DDDDDDDDDDDDDDDDDDDDDD4D' 60 | 'DDDDDDD4CDDDC4DD43333333333333333333333333333333333333333333333333C3333333' 61 | '3333333333333333333333333333333333333333333333333333333333333333333333333D' 62 | 'DD433333333333333333333333333333333333333333333333333333333333333333333333' 63 | '333333333333333333333333333334333333333333333333333333333333DD333333333333' 64 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 65 | '33333333333333333333333333333333333333333333333333DD4333333333333333333333' 66 | '33333333DDD433333333333333333333333333333333333333333333333333333333333333' 67 | '33333333333333333333333333333333333333DDDDDDD533333333333333333333333DDDTT' 68 | 'U5D4DD333C433333D333333333333333333333DDD733333s373ss33w7733333ww733333333' 69 | '333ss33333333333333333333333333333ww3333333333333333333333333333wwww33333w' 70 | 'ww33333333333333333333wwww333333333333333wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 71 | 'wwwwwwww333333wwwwwwwwwwwwwwwwwwwwwww7wwwwwswwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 72 | 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' 73 | 'wwwwwwwwwwwwwwwwwwwwwww733333333333333333333333333333333333333333333333333' 74 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 75 | '333C4""3333333333333333333333333333333333333333333333333333333333333333333' 76 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 77 | '333333333333333333333333333333333DD333333333333333333333333333333333333333' 78 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 79 | '33333333333333333333333DDD433333333333333333333333333333333333333333333333' 80 | '3333333DDD4333333333333333333333333333333333333333333333333333333333333333' 81 | '333333333333333333333333333UEDDDTEE433333333333333333333333333333333333333' 82 | '33333333333333CEUDDDE33333333333333333333333333333333333333333333333333CD3' 83 | 'DDEDD333333333333333333333333333333333333333333333333333333333333333333333' 84 | '3333333333333333333333333333333333333EDDDCDDT43333333333333333333333333333' 85 | '333333333333CDDDDDDDDDD4EDDDETD3333333333333333333333333333333333333333333' 86 | '333333333333333333DDD3CC4DDD\x94433333333333333333333333333333333SUUC4UT43' 87 | '33333333333333333333333333333333333333333333333333#"""""""B333DDDDDDD43333' 88 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 89 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 90 | '333333333333333333333333333333333333333333333333333333333333CED3SDD\x24"""' 91 | 'BDDD4CDDD333333333333333DD33333333333333333333333333333333333333333DEDDDUE' 92 | '333333333333333333333333333CCD3D33CD533333333333333333333333333CESEU333333' 93 | '3333333333333DDDD433333CU33333333333333333333333333334DC443333333333333333' 94 | '33333333333CD4DDDDD33333333333333333333DDD\x95DD333343333DDDUD433333333333' 95 | '33333333\x93\x99\x99IDDDDDDE4333333333333333333333333333333333333333333333' 96 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 97 | '33333333333333333333333333333333333333333333333333333333333CDDDDDDDDDDDDDD' 98 | 'DDDDDDDD4CDDDDDDDDDDD33333333333333333333333333333333333333333333333333333' 99 | '333333333333333333333333333333333333333CD333333333333333333333333333333333' 100 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 101 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 102 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 103 | '34333333333333333333333333333333333333333333333333333333333333333333333333' 104 | '33DD4333333333333333333333333333333333333333333333333333333333333333333"""' 105 | '"""33D4D33CD43333333333333333333CD3343333333333333333333333333333333333333' 106 | '333333333333333333333333333333333333333333333D3333333333333333333333333333' 107 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 108 | '33333333333333333333333333333333333333CT53333DY333333333333333333333333UDD' 109 | '43UT4333333333333333333333333333333333333333333333333333333333333333333333' 110 | '3333333333333333333333333D3333333333333333333333333333333333333333D4333333' 111 | '3333333333333333333333333333CDDDDD333333333333333333333333CD43333333333333' 112 | '33333333333333333333333333333333333333333333333SUDDDDUDT433333333333433333' 113 | '33333333333333333333333333333333333TEDDTTEETD33333333333333333333333333333' 114 | '33333333333333333333333333333333333333333333333333333333333333333333333333' 115 | '33333333333333CUDD3UUDE43333333333333D3333333333333333343333333333SE43CD33' 116 | '333333DD33333C33TEDCSUUU433333333S533333CDDDDDU333333ªªªªªªªªªªªªªª:3\x99' 117 | '\x99\x9933333DDDDD4233333333333333333UTEUS433333333CDCDDDDDDEDDD33433C3E43' 118 | '3#""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""' 119 | '""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""' 120 | '""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""' 121 | '"""""""""""""""""""""""""""""""""""BDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD' 122 | 'DDDDDDDDD\x24"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""' 123 | '""BDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD' 124 | 'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\x24"""""""""""""""2333373' 125 | 'r33333333\x93933CDDD4333333333333333CDUUDU53SEUUUD43£ªªªªªªªªªªªªªªªªªªªªª' 126 | 'ªªªªªªªªªªº»»»»»»»»»»»»»»»»»»»ËÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ\f'; 127 | const String _start = '\u1ac4\u2bb8\u411f\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 128 | '\u32b6\u32b6\u32b6\u3f4f\u0814\u32b6\u32b6\u32b6\u32b6\u1f81\u32b6\u32b6' 129 | '\u32b6\u1bbb\u2f6f\u3cc2\u051e\u32b6\u11d3\u079b\u2c12\u3967\u1b18\u18aa' 130 | '\u392b\u414f\u07f1\u2eb5\u1880\u1123\u047a\u1909\u08c6\u1909\u11af\u2f32' 131 | '\u1a19\u04d1\u19c3\u2e6b\u209a\u1298\u1259\u0667\u108e\u1160\u3c49\u116f' 132 | '\u1b03\u12a3\u1f7c\u121b\u2023\u1840\u34b0\u088a\u3c13\u04b6\u32b6\u41af' 133 | '\u41cf\u41ef\u4217\u32b6\u32b6\u32b6\u32b6\u32b6\u3927\u32b6\u32b6\u32b6' 134 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u18d8' 135 | '\u1201\u2e2e\u15be\u0553\u32b6\u3be9\u32b6\u416f\u32b6\u32b6\u32b6\u1a68' 136 | '\u10e5\u2a59\u2c0e\u205e\u2ef3\u1019\u04e9\u1a84\u32b6\u32b6\u3d0f\u32b6' 137 | '\u32b6\u32b6\u3f4f\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u104e' 138 | '\u076a\u32b6\u07bb\u15dc\u32b6\u10ba\u32b6\u32b6\u32b6\u32b6\u32b6\u1a3f' 139 | '\u32b6\u0cf2\u1606\u32b6\u32b6\u32b6\u0877\u32b6\u32b6\u073d\u2139\u0dcb' 140 | '\u0bcb\u09b3\u0bcb\u0fd9\u20f7\u03e3\u32b6\u32b6\u32b6\u32b6\u32b6\u0733' 141 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u041d\u0864\u32b6\u32b6\u32b6' 142 | '\u32b6\u32b6\u3915\u32b6\u3477\u32b6\u3193\u32b6\u32b6\u32b6\u32b6\u32b6' 143 | '\u32b6\u32b6\u32b6\u20be\u32b6\u36b1\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 144 | '\u32b6\u2120\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 145 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 146 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 147 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 148 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 149 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 150 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 151 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 152 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 153 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 154 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 155 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 156 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 157 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 158 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 159 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 160 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 161 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 162 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 163 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 164 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 165 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 166 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 167 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 168 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 169 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 170 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 171 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 172 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 173 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 174 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 175 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 176 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 177 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 178 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 179 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 180 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 181 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 182 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u2f80\u36ac\u369a\u32b6' 183 | '\u32b6\u32b6\u32b6\u1b8c\u32b6\u1584\u1947\u1ae4\u3c82\u1986\u03b8\u043a' 184 | '\u1b52\u2e77\u19d9\u32b6\u32b6\u32b6\u3cdf\u090a\u0912\u091a\u0906\u090e' 185 | '\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a' 186 | '\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a' 187 | '\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916' 188 | '\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906' 189 | '\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912' 190 | '\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e' 191 | '\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e' 192 | '\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a' 193 | '\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a' 194 | '\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916' 195 | '\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906' 196 | '\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912' 197 | '\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e\u0916\u091e' 198 | '\u090a\u0912\u091a\u0906\u090e\u0916\u091e\u090a\u0912\u091a\u0906\u090e' 199 | '\u0916\u093a\u0973\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f' 200 | '\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f' 201 | '\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u32b6' 202 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 203 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 204 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 205 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 206 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 207 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 208 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 209 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 210 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u3498' 211 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u0834' 212 | '\u32b6\u32b6\u2bb8\u32b6\u32b6\u36ac\u35a6\u32b9\u33d6\u32b6\u32b6\u32b6' 213 | '\u35e5\u24ee\u3847\x00\u0567\u3a12\u2826\u01d4\u2fb3\u29f7\u36f2\u32b6' 214 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u2bc7\u32b6\u32b6' 215 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 216 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 217 | '\u1e54\u32b6\u1394\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 218 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 219 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 220 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u2412\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 221 | '\u32b6\u32b6\u30b3\u2c62\u3271\u32b6\u32b6\u32b6\u12e3\u32b6\u32b6\u1bf2' 222 | '\u1d44\u2526\u32b6\u2656\u32b6\u32b6\u32b6\u0bcb\u1645\u0a85\u0ddf\u2168' 223 | '\u22af\u09c3\u09c5\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 224 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 225 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 226 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 227 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 228 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 229 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 230 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 231 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 232 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 233 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 234 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 235 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 236 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 237 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 238 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 239 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 240 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 241 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 242 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 243 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 244 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 245 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 246 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 247 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 248 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 249 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 250 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 251 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 252 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 253 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 254 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 255 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 256 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 257 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 258 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 259 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 260 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 261 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 262 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 263 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 264 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 265 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 266 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 267 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 268 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 269 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 270 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 271 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 272 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 273 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 274 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 275 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 276 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 277 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 278 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 279 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 280 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 281 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 282 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 283 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 284 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 285 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 286 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 287 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 288 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 289 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 290 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 291 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 292 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 293 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 294 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 295 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 296 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 297 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 298 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 299 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 300 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 301 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 302 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 303 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 304 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 305 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 306 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 307 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 308 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 309 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 310 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 311 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 312 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 313 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 314 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 315 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 316 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 317 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 318 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 319 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 320 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 321 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 322 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 323 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 324 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 325 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 326 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 327 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 328 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 329 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 330 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 331 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 332 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 333 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 334 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 335 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 336 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 337 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 338 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 339 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 340 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 341 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 342 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 343 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 344 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 345 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 346 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 347 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 348 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 349 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 350 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 351 | '\u32b6\u32b6\u32b6\u3f2f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u3d4f\u32b6' 352 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 353 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 354 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 355 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 356 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 357 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 358 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 359 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 360 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 361 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 362 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 363 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 364 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 365 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 366 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 367 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 368 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 369 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 370 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 371 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 372 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 373 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 374 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 375 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 376 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 377 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 378 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 379 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 380 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 381 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 382 | '\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6\u32b6' 383 | '\u32b6\u32b6\u32b6'; 384 | int low(int codeUnit) { 385 | var chunkStart = _start.codeUnitAt(codeUnit >> 6); 386 | var index = chunkStart + (codeUnit & 63); 387 | var bit = index & 1; 388 | var pair = _data.codeUnitAt(index >> 1); 389 | return (pair >> 4) & -bit | (pair & 0xF) & (bit - 1); 390 | } 391 | 392 | int high(int lead, int tail) { 393 | var offset = ((0x3ff & lead) << 10) | (0x3ff & tail); 394 | var chunkStart = _start.codeUnitAt(1024 + (offset >> 9)); 395 | var index = chunkStart + (offset & 511); 396 | var bit = index & 1; 397 | var pair = _data.codeUnitAt(index >> 1); 398 | return (pair >> 4) & -bit | (pair & 0xF) & (bit - 1); 399 | } 400 | 401 | const _stateMachine = ' 0\x10000\xa0\x80\x10@P`p`p± 0\x10000\xa0\x80\x10@P`p`p°' 402 | ' 0\x10000\xa0\x80\x11@P`p`p° 1\x10011\xa0\x80\x10@P`p`p° 1\x10111¡\x81\x10' 403 | 'AQaqaq° 1\x10011\xa0\x80\x10@Qapaq° 1\x10011\xa0\x80\x10@Paq`p° 1\x10011' 404 | '\xa0\x80\x10@P`q`p° \x91\x100\x811\xa0\x80\x10@P`p`p° 1\x10011\xa0\x81\x10' 405 | '@P`p`p° 1\x100111\x80\x10@P`p`p°!1\x11111¡\x81\x11AQaqaq±'; 406 | int move(int state, int inputCategory) => 407 | _stateMachine.codeUnitAt((state & 0xF0) | inputCategory); 408 | 409 | const _backStateMachine = '\x10@\x100@@\xa0\x80 0P`pPP±\x10@\x100@@\xa0\x80 0P`' 410 | 'pPP°\x11@\x100@@\xa0\x80 0P`pPP°\x10@\x100@@\xa0\x80 1P`pPP°\x10A\x101AA¡' 411 | '\x81 1QaqQQ°\x10@\x100@@\xa0\x80 1Q`pPP°\x10@\x100@@\xa0\x80 1QapQP°\x10@' 412 | '\x100@@\xa0\x80 1PaqQQ°\x10à\x100@@\xa0\x80 1P`pPP°±±±±\x91±Á\x81±±±±±±±±' 413 | '\x10@\x100@@Ð\x80 1P`pPP°\x11A\x111AA¡\x81!1QaqQQ±\x10@\x100@@\x90\x80 1P`' 414 | 'pPP°'; 415 | int moveBack(int state, int inputCategory) => 416 | _backStateMachine.codeUnitAt((state & 0xF0) | inputCategory); 417 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: characters 2 | version: 1.3.1-wip 3 | description: >- 4 | String replacement with operations that are Unicode/grapheme cluster aware. 5 | repository: https://github.com/dart-lang/characters 6 | 7 | topics: 8 | - strings 9 | - unicode 10 | 11 | environment: 12 | sdk: ^3.4.0 13 | 14 | dev_dependencies: 15 | dart_flutter_team_lints: ^3.0.0 16 | test: ^1.16.6 17 | -------------------------------------------------------------------------------- /test/characters_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Not all categories are currently used in tests. 6 | // They're retained in case we add more tests. 7 | // ignore_for_file: unreachable_from_main 8 | 9 | import "dart:math"; 10 | 11 | import "package:characters/characters.dart"; 12 | import "package:test/test.dart"; 13 | 14 | import "src/unicode_tests.dart"; 15 | import "src/various_tests.dart"; 16 | 17 | late Random random; 18 | 19 | void main([List? args]) { 20 | // Ensure random seed is part of every test failure message, 21 | // and that it can be reapplied for testing. 22 | var seed = (args != null && args.isNotEmpty) 23 | ? int.parse(args[0]) 24 | : Random().nextInt(0x3FFFFFFF); 25 | random = Random(seed); 26 | group("[Random Seed: $seed]", tests); 27 | group("characters", () { 28 | test("operations", () { 29 | var flag = "\u{1F1E9}\u{1F1F0}"; // Regional Indicators "DK". 30 | var string = "Hi $flag!"; 31 | expect(string.length, 8); 32 | var cs = gc(string); 33 | expect(cs.length, 5); 34 | expect(cs.toList(), ["H", "i", " ", flag, "!"]); 35 | expect(cs.skip(2).toString(), " $flag!"); 36 | expect(cs.skipLast(2).toString(), "Hi "); 37 | expect(cs.take(2).toString(), "Hi"); 38 | expect(cs.takeLast(2).toString(), "$flag!"); 39 | expect(cs.getRange(1, 4).toString(), "i $flag"); 40 | expect(cs.characterAt(1).toString(), "i"); 41 | expect(cs.characterAt(3).toString(), flag); 42 | 43 | expect(cs.contains("\u{1F1E9}"), false); 44 | expect(cs.contains(flag), true); 45 | expect(cs.contains("$flag!"), false); 46 | expect(cs.containsAll(gc("$flag!")), true); 47 | 48 | expect(cs.takeWhile((x) => x != " ").toString(), "Hi"); 49 | expect(cs.takeLastWhile((x) => x != " ").toString(), "$flag!"); 50 | expect(cs.skipWhile((x) => x != " ").toString(), " $flag!"); 51 | expect(cs.skipLastWhile((x) => x != " ").toString(), "Hi "); 52 | 53 | expect(cs.findFirst(gc(""))!.moveBack(), false); 54 | expect(cs.findFirst(gc(flag))!.current, flag); 55 | expect(cs.findLast(gc(flag))!.current, flag); 56 | expect(cs.iterator.moveNext(), true); 57 | expect(cs.iterator.moveBack(), false); 58 | expect((cs.iterator..moveNext()).current, "H"); 59 | expect(cs.iteratorAtEnd.moveNext(), false); 60 | expect(cs.iteratorAtEnd.moveBack(), true); 61 | expect((cs.iteratorAtEnd..moveBack()).current, "!"); 62 | }); 63 | 64 | testParts(gc("a"), gc("b"), gc("c"), gc("d"), gc("e")); 65 | 66 | // Composite pictogram example, from https://en.wikipedia.org/wiki/Zero-width_joiner. 67 | var flag = "\u{1f3f3}"; // U+1F3F3, Flag, waving. Category Pictogram. 68 | var white = "\ufe0f"; // U+FE0F, Variant selector 16. Category Extend. 69 | var zwj = "\u200d"; // U+200D, ZWJ 70 | var rainbow = "\u{1f308}"; // U+1F308, Rainbow. Category Pictogram 71 | 72 | testParts(gc("$flag$white$zwj$rainbow"), gc("$flag$white"), gc(rainbow), 73 | gc("$flag$zwj$rainbow"), gc("!")); 74 | }); 75 | 76 | group("CharacterRange", () { 77 | test("new", () { 78 | var range = CharacterRange("abc"); 79 | expect(range.isEmpty, true); 80 | expect(range.moveNext(), true); 81 | expect(range.current, "a"); 82 | }); 83 | group("new.at", () { 84 | test("simple", () { 85 | var range = CharacterRange.at("abc", 0); 86 | expect(range.isEmpty, true); 87 | expect(range.moveNext(), true); 88 | expect(range.current, "a"); 89 | 90 | range = CharacterRange.at("abc", 1); 91 | expect(range.isEmpty, true); 92 | expect(range.moveNext(), true); 93 | expect(range.current, "b"); 94 | 95 | range = CharacterRange.at("abc", 1, 2); 96 | expect(range.isEmpty, false); 97 | expect(range.current, "b"); 98 | expect(range.moveNext(), true); 99 | 100 | range = CharacterRange.at("abc", 0, 3); 101 | expect(range.isEmpty, false); 102 | expect(range.current, "abc"); 103 | expect(range.moveNext(), false); 104 | }); 105 | test("complicated", () { 106 | // Composite pictogram example, from https://en.wikipedia.org/wiki/Zero-width_joiner. 107 | var flag = "\u{1f3f3}"; // U+1F3F3, Flag, waving. Category Pictogram. 108 | var white = "\ufe0f"; // U+FE0F, Variant selector 16. Category Extend. 109 | var zwj = "\u200d"; // U+200D, ZWJ 110 | var rainbow = "\u{1f308}"; // U+1F308, Rainbow. Category Pictogram 111 | 112 | var rbflag = "$flag$white$zwj$rainbow"; 113 | var string = "-$rbflag-"; 114 | var range = CharacterRange.at(string, 1); 115 | expect(range.isEmpty, true); 116 | expect(range.moveNext(), true); 117 | expect(range.current, rbflag); 118 | 119 | range = range = CharacterRange.at(string, 2); 120 | expect(range.isEmpty, false); 121 | expect(range.current, rbflag); 122 | 123 | range = range = CharacterRange.at(string, 0, 2); 124 | expect(range.isEmpty, false); 125 | expect(range.current, "-$rbflag"); 126 | 127 | range = range = CharacterRange.at(string, 0, 2); 128 | expect(range.isEmpty, false); 129 | expect(range.current, "-$rbflag"); 130 | 131 | range = range = CharacterRange.at(string, 2, "-$rbflag".length - 1); 132 | expect(range.isEmpty, false); 133 | expect(range.current, rbflag); 134 | expect(range.stringBeforeLength, 1); 135 | 136 | range = range = CharacterRange.at(string, 0, string.length); 137 | expect(range.isEmpty, false); 138 | expect(range.current, string); 139 | }); 140 | }); 141 | }); 142 | } 143 | 144 | void tests() { 145 | test("empty", () { 146 | expectGC(gc(""), []); 147 | }); 148 | group("gc-ASCII", () { 149 | for (var text in [ 150 | "", 151 | "A", 152 | "123456abcdefab", 153 | ]) { 154 | test('"$text"', () { 155 | expectGC(gc(text), charsOf(text)); 156 | }); 157 | } 158 | test("CR+NL", () { 159 | expectGC(gc("a\r\nb"), ["a", "\r\n", "b"]); 160 | expectGC(gc("a\n\rb"), ["a", "\n", "\r", "b"]); 161 | }); 162 | }); 163 | group("Non-ASCII single-code point", () { 164 | for (var text in [ 165 | "à la mode", 166 | "rødgrød-æble-ål", 167 | ]) { 168 | test('"$text"', () { 169 | expectGC(gc(text), charsOf(text)); 170 | }); 171 | } 172 | }); 173 | group("Combining marks", () { 174 | var text = "a\u0300 la mode"; 175 | test('"$text"', () { 176 | expectGC(gc(text), ["a\u0300", " ", "l", "a", " ", "m", "o", "d", "e"]); 177 | }); 178 | var text2 = "æble-a\u030Al"; 179 | test('"$text2"', () { 180 | expectGC(gc(text2), ["æ", "b", "l", "e", "-", "a\u030A", "l"]); 181 | }); 182 | }); 183 | 184 | group("Regional Indicators", () { 185 | test('"🇦🇩🇰🇾🇪🇸"', () { 186 | // Andorra, Cayman Islands, Spain. 187 | expectGC(gc("🇦🇩🇰🇾🇪🇸"), ["🇦🇩", "🇰🇾", "🇪🇸"]); 188 | }); 189 | test('"X🇦🇩🇰🇾🇪🇸"', () { 190 | // Other, Andorra, Cayman Islands, Spain. 191 | expectGC(gc("X🇦🇩🇰🇾🇪🇸"), ["X", "🇦🇩", "🇰🇾", "🇪🇸"]); 192 | }); 193 | test('"🇩🇰🇾🇪🇸"', () { 194 | // Denmark, Yemen, unmatched S. 195 | expectGC(gc("🇩🇰🇾🇪🇸"), ["🇩🇰", "🇾🇪", "🇸"]); 196 | }); 197 | test('"X🇩🇰🇾🇪🇸"', () { 198 | // Other, Denmark, Yemen, unmatched S. 199 | expectGC(gc("X🇩🇰🇾🇪🇸"), ["X", "🇩🇰", "🇾🇪", "🇸"]); 200 | }); 201 | }); 202 | 203 | group("Hangul", () { 204 | // Individual characters found on Wikipedia. Not expected to make sense. 205 | test('"읍쌍된밟"', () { 206 | expectGC(gc("읍쌍된밟"), ["읍", "쌍", "된", "밟"]); 207 | }); 208 | }); 209 | 210 | group("Unicode test", () { 211 | for (var gcs in splitTests) { 212 | test("[${testDescription(gcs)}]", () { 213 | expectGC(gc(gcs.join()), gcs); 214 | }); 215 | } 216 | }); 217 | 218 | group("Emoji test", () { 219 | for (var gcs in emojis) { 220 | test("[${testDescription(gcs)}]", () { 221 | expectGC(gc(gcs.join()), gcs); 222 | }); 223 | } 224 | }); 225 | 226 | group("Zalgo test", () { 227 | for (var gcs in zalgo) { 228 | test("[${testDescription(gcs)}]", () { 229 | expectGC(gc(gcs.join()), gcs); 230 | }); 231 | } 232 | }); 233 | } 234 | 235 | // Converts text with no multi-code-point grapheme clusters into 236 | // list of grapheme clusters. 237 | List charsOf(String text) => 238 | text.runes.map(String.fromCharCode).toList(); 239 | 240 | void expectGC(Characters actual, List expected) { 241 | var text = expected.join(); 242 | 243 | // Iterable operations. 244 | expect(actual.string, text); 245 | expect(actual.toString(), text); 246 | expect(actual.toList(), expected); 247 | expect(actual.length, expected.length); 248 | if (expected.isNotEmpty) { 249 | expect(actual.first, expected.first); 250 | expect(actual.last, expected.last); 251 | } else { 252 | expect(() => actual.first, throwsStateError); 253 | expect(() => actual.last, throwsStateError); 254 | } 255 | if (expected.length == 1) { 256 | expect(actual.single, expected.single); 257 | } else { 258 | expect(() => actual.single, throwsStateError); 259 | } 260 | expect(actual.isEmpty, expected.isEmpty); 261 | expect(actual.isNotEmpty, expected.isNotEmpty); 262 | expect(actual.contains(""), false); 263 | for (var char in expected) { 264 | expect(actual.contains(char), true); 265 | } 266 | for (var i = 1; i < expected.length; i++) { 267 | expect(actual.contains(expected[i - 1] + expected[i]), false); 268 | } 269 | expect(actual.skip(1).toList(), expected.skip(1).toList()); 270 | expect(actual.take(1).toList(), expected.take(1).toList()); 271 | expect(actual.skip(1).toString(), expected.skip(1).join()); 272 | expect(actual.take(1).toString(), expected.take(1).join()); 273 | expect(actual.getRange(1, 2).toString(), expected.take(2).skip(1).join()); 274 | 275 | if (expected.isNotEmpty) { 276 | expect(actual.skipLast(1).toList(), 277 | expected.take(expected.length - 1).toList()); 278 | expect(actual.takeLast(1).toList(), 279 | expected.skip(expected.length - 1).toList()); 280 | expect(actual.skipLast(1).toString(), 281 | expected.take(expected.length - 1).join()); 282 | expect(actual.takeLast(1).toString(), 283 | expected.skip(expected.length - 1).join()); 284 | } 285 | bool isEven(String s) => s.length.isEven; 286 | 287 | expect( 288 | actual.skipWhile(isEven).toList(), expected.skipWhile(isEven).toList()); 289 | expect( 290 | actual.takeWhile(isEven).toList(), expected.takeWhile(isEven).toList()); 291 | expect( 292 | actual.skipWhile(isEven).toString(), expected.skipWhile(isEven).join()); 293 | expect( 294 | actual.takeWhile(isEven).toString(), expected.takeWhile(isEven).join()); 295 | 296 | expect(actual.skipLastWhile(isEven).toString(), 297 | expected.toList().reversed.skipWhile(isEven).toList().reversed.join()); 298 | expect(actual.takeLastWhile(isEven).toString(), 299 | expected.toList().reversed.takeWhile(isEven).toList().reversed.join()); 300 | 301 | expect(actual.where(isEven).toString(), expected.where(isEven).join()); 302 | 303 | expect((actual + actual).toString(), actual.string + actual.string); 304 | 305 | // Iteration. 306 | var it = actual.iterator; 307 | expect(it.isEmpty, true); 308 | for (var i = 0; i < expected.length; i++) { 309 | expect(it.moveNext(), true); 310 | expect(it.current, expected[i]); 311 | 312 | expect(actual.elementAt(i), expected[i]); 313 | expect(actual.skip(i).first, expected[i]); 314 | expect(actual.characterAt(i).toString(), expected[i]); 315 | expect(actual.getRange(i, i + 1).toString(), expected[i]); 316 | } 317 | expect(it.moveNext(), false); 318 | for (var i = expected.length - 1; i >= 0; i--) { 319 | expect(it.moveBack(), true); 320 | expect(it.current, expected[i]); 321 | } 322 | expect(it.moveBack(), false); 323 | expect(it.isEmpty, true); 324 | 325 | // GraphemeClusters operations. 326 | expect(actual.toUpperCase().string, text.toUpperCase()); 327 | expect(actual.toLowerCase().string, text.toLowerCase()); 328 | 329 | expect(actual.string, text); 330 | 331 | expect(actual.containsAll(gc("")), true); 332 | expect(actual.containsAll(actual), true); 333 | if (expected.isNotEmpty) { 334 | var steps = min(5, expected.length); 335 | for (var s = 0; s <= steps; s++) { 336 | var i = expected.length * s ~/ steps; 337 | expect(actual.startsWith(gc(expected.sublist(0, i).join())), true); 338 | expect(actual.endsWith(gc(expected.sublist(i).join())), true); 339 | for (var t = s + 1; t <= steps; t++) { 340 | var j = expected.length * t ~/ steps; 341 | var slice = expected.sublist(i, j).join(); 342 | var gcs = gc(slice); 343 | expect(actual.containsAll(gcs), true); 344 | } 345 | } 346 | } 347 | 348 | { 349 | // Random walk back and forth. 350 | var it = actual.iterator; 351 | var pos = -1; 352 | if (random.nextBool()) { 353 | pos = expected.length; 354 | it = actual.iteratorAtEnd; 355 | } 356 | var steps = 5 + random.nextInt(expected.length * 2 + 1); 357 | var lastMove = false; 358 | while (true) { 359 | var back = false; 360 | if (pos < 0) { 361 | expect(lastMove, false); 362 | expect(it.isEmpty, true); 363 | } else if (pos >= expected.length) { 364 | expect(lastMove, false); 365 | expect(it.isEmpty, true); 366 | back = true; 367 | } else { 368 | expect(lastMove, true); 369 | expect(it.current, expected[pos]); 370 | back = random.nextBool(); 371 | } 372 | if (--steps < 0) break; 373 | if (back) { 374 | lastMove = it.moveBack(); 375 | pos -= 1; 376 | } else { 377 | lastMove = it.moveNext(); 378 | pos += 1; 379 | } 380 | } 381 | } 382 | } 383 | 384 | Characters gc(String string) => Characters(string); 385 | 386 | void testParts( 387 | Characters a, Characters b, Characters c, Characters d, Characters e) { 388 | var cs = gc("$a$b$c$d$e"); 389 | test("$cs", () { 390 | var it = cs.iterator; 391 | expect(it.isEmpty, true); 392 | expect(it.isNotEmpty, false); 393 | expect(it.current, ""); 394 | 395 | // moveNext(). 396 | expect(it.moveNext(), true); 397 | expect(it.isEmpty, false); 398 | expect(it.current, "$a"); 399 | expect(it.moveNext(), true); 400 | expect(it.isEmpty, false); 401 | expect(it.current, "$b"); 402 | expect(it.moveNext(), true); 403 | expect(it.isEmpty, false); 404 | expect(it.current, "$c"); 405 | expect(it.moveNext(), true); 406 | expect(it.isEmpty, false); 407 | expect(it.current, "$d"); 408 | expect(it.moveNext(), true); 409 | expect(it.isEmpty, false); 410 | expect(it.current, "$e"); 411 | expect(it.moveNext(), false); 412 | expect(it.isEmpty, true); 413 | expect(it.current, ""); 414 | 415 | // moveBack(). 416 | expect(it.moveBack(), true); 417 | expect(it.isEmpty, false); 418 | expect(it.current, "$e"); 419 | expect(it.moveBack(), true); 420 | expect(it.isEmpty, false); 421 | expect(it.current, "$d"); 422 | expect(it.moveBack(), true); 423 | expect(it.isEmpty, false); 424 | expect(it.current, "$c"); 425 | expect(it.moveBack(), true); 426 | expect(it.isEmpty, false); 427 | expect(it.current, "$b"); 428 | expect(it.moveBack(), true); 429 | expect(it.isEmpty, false); 430 | expect(it.current, "$a"); 431 | expect(it.moveBack(), false); 432 | expect(it.isEmpty, true); 433 | expect(it.current, ""); 434 | 435 | // moveNext(int). 436 | expect(it.moveTo(c), true); 437 | expect(it.current, "$c"); 438 | expect(it.moveTo(b), false); 439 | expect(it.moveTo(c), false); 440 | expect(it.current, "$c"); 441 | expect(it.moveTo(d), true); 442 | expect(it.current, "$d"); 443 | 444 | // moveBack(c). 445 | expect(it.moveBackTo(c), true); 446 | expect(it.current, "$c"); 447 | expect(it.moveBackTo(d), false); 448 | expect(it.moveBackTo(c), false); 449 | expect(it.moveBackTo(a), true); 450 | expect(it.current, "$a"); 451 | 452 | // moveNext(n) 453 | expect(it.moveBack(), false); 454 | 455 | expect(it.moveNext(2), true); 456 | expect(it.current, "$a$b"); 457 | expect(it.moveNext(4), false); 458 | expect(it.current, "$c$d$e"); 459 | expect(it.moveNext(0), true); 460 | expect(it.current, ""); 461 | expect(it.moveNext(1), false); 462 | expect(it.current, ""); 463 | 464 | // moveBack(n). 465 | expect(it.moveBack(2), true); 466 | expect(it.current, "$d$e"); 467 | expect(it.moveBack(1), true); 468 | expect(it.current, "$c"); 469 | expect(it.moveBack(3), false); 470 | expect(it.current, "$a$b"); 471 | expect(it.moveBack(), false); 472 | 473 | // moveFirst. 474 | it.expandAll(); 475 | expect(it.current, "$a$b$c$d$e"); 476 | expect(it.collapseToFirst(b), true); 477 | expect(it.current, "$b"); 478 | it.expandAll(); 479 | expect(it.current, "$b$c$d$e"); 480 | expect(it.collapseToFirst(a), false); 481 | expect(it.current, "$b$c$d$e"); 482 | 483 | // moveBackTo 484 | it.expandBackAll(); 485 | expect(it.current, "$a$b$c$d$e"); 486 | expect(it.collapseToLast(c), true); 487 | expect(it.current, "$c"); 488 | 489 | // includeNext/includePrevious 490 | expect(it.expandTo(e), true); 491 | expect(it.current, "$c$d$e"); 492 | expect(it.expandTo(e), false); 493 | expect(it.expandBackTo(b), true); 494 | expect(it.current, "$b$c$d$e"); 495 | expect(it.expandBackTo(b), false); 496 | expect(it.current, "$b$c$d$e"); 497 | expect(it.collapseToFirst(c), true); 498 | expect(it.current, "$c"); 499 | 500 | // includeUntilNext/expandBackUntil 501 | expect(it.expandBackUntil(a), true); 502 | expect(it.current, "$b$c"); 503 | expect(it.expandBackUntil(a), true); 504 | expect(it.current, "$b$c"); 505 | expect(it.expandUntil(e), true); 506 | expect(it.current, "$b$c$d"); 507 | expect(it.expandUntil(e), true); 508 | expect(it.current, "$b$c$d"); 509 | 510 | // dropFirst/dropLast 511 | expect(it.dropFirst(), true); 512 | expect(it.current, "$c$d"); 513 | expect(it.dropLast(), true); 514 | expect(it.current, "$c"); 515 | it.expandBackAll(); 516 | it.expandAll(); 517 | expect(it.current, "$a$b$c$d$e"); 518 | expect(it.dropTo(b), true); 519 | expect(it.current, "$c$d$e"); 520 | expect(it.dropBackTo(d), true); 521 | expect(it.current, "$c"); 522 | 523 | it.expandBackAll(); 524 | it.expandAll(); 525 | expect(it.current, "$a$b$c$d$e"); 526 | 527 | expect(it.dropUntil(b), true); 528 | expect(it.current, "$b$c$d$e"); 529 | expect(it.dropBackUntil(d), true); 530 | expect(it.current, "$b$c$d"); 531 | 532 | it.dropWhile((x) => x == b.string); 533 | expect(it.current, "$c$d"); 534 | it.expandBackAll(); 535 | expect(it.current, "$a$b$c$d"); 536 | it.dropBackWhile((x) => x != b.string); 537 | expect(it.current, "$a$b"); 538 | it.dropBackWhile((x) => false); 539 | expect(it.current, "$a$b"); 540 | 541 | // include..While 542 | it.expandWhile((x) => false); 543 | expect(it.current, "$a$b"); 544 | it.expandWhile((x) => x != e.string); 545 | expect(it.current, "$a$b$c$d"); 546 | expect(it.collapseToFirst(c), true); 547 | expect(it.current, "$c"); 548 | it.expandBackWhile((x) => false); 549 | expect(it.current, "$c"); 550 | it.expandBackWhile((x) => x != a.string); 551 | expect(it.current, "$b$c"); 552 | 553 | var cs2 = cs.replaceAll(c, gc("")); 554 | var cs3 = cs.replaceFirst(c, gc("")); 555 | var cs4 = cs.findFirst(c)!.replaceRange(gc("")).source; 556 | var cse = gc("$a$b$d$e"); 557 | expect(cs2, cse); 558 | expect(cs3, cse); 559 | expect(cs4, cse); 560 | var cs5 = cs4.replaceAll(a, c); 561 | expect(cs5, gc("$c$b$d$e")); 562 | var cs6 = cs5.replaceAll(gc(""), a); 563 | expect(cs6, gc("$a$c$a$b$a$d$a$e$a")); 564 | var cs7 = cs6.replaceFirst(b, a); 565 | expect(cs7, gc("$a$c$a$a$a$d$a$e$a")); 566 | var cs8 = cs7.replaceFirst(e, a); 567 | expect(cs8, gc("$a$c$a$a$a$d$a$a$a")); 568 | var cs9 = cs8.replaceAll(a + a, b); 569 | expect(cs9, gc("$a$c$b$a$d$b$a")); 570 | it = cs9.iterator; 571 | it.moveTo(b + a); 572 | expect("$b$a", it.current); 573 | it.expandTo(b + a); 574 | expect("$b$a$d$b$a", it.current); 575 | var cs10 = it.replaceAll(b + a, e + e)!; 576 | expect(cs10.currentCharacters, e + e + d + e + e); 577 | expect(cs10.source, gc("$a$c$e$e$d$e$e")); 578 | var cs11 = it.replaceRange(e); 579 | expect(cs11.currentCharacters, e); 580 | expect(cs11.source, gc("$a$c$e")); 581 | 582 | var cs12 = gc("$a$b$a"); 583 | expect(cs12.split(b), [a, a]); 584 | expect(cs12.split(a), [gc(""), b, gc("")]); 585 | expect(cs12.split(a, 2), [gc(""), gc("$b$a")]); 586 | 587 | expect(cs12.split(gc("")), [a, b, a]); 588 | expect(cs12.split(gc(""), 2), [a, gc("$b$a")]); 589 | 590 | expect(gc("").split(gc("")), [gc("")]); 591 | 592 | var cs13 = gc("$b$a$b$a$b$a"); 593 | expect(cs13.split(b), [gc(""), a, a, a]); 594 | expect(cs13.split(b, 1), [cs13]); 595 | expect(cs13.split(b, 2), [gc(""), gc("$a$b$a$b$a")]); 596 | expect(cs13.split(b, 3), [gc(""), a, gc("$a$b$a")]); 597 | expect(cs13.split(b, 4), [gc(""), a, a, a]); 598 | expect(cs13.split(b, 5), [gc(""), a, a, a]); 599 | expect(cs13.split(b, 9999), [gc(""), a, a, a]); 600 | expect(cs13.split(b, 0), [gc(""), a, a, a]); 601 | expect(cs13.split(b, -1), [gc(""), a, a, a]); 602 | expect(cs13.split(b, -9999), [gc(""), a, a, a]); 603 | 604 | it = cs13.iterator..expandAll(); 605 | expect(it.current, "$b$a$b$a$b$a"); 606 | it.dropFirst(); 607 | it.dropLast(); 608 | expect(it.current, "$a$b$a$b"); 609 | expect(it.split(a).map((range) => range.current), ["", "$b", "$b"]); 610 | expect(it.split(a, 2).map((range) => range.current), ["", "$b$a$b"]); 611 | // Each split is after an *a*. 612 | var first = true; 613 | for (var range in it.split(a)) { 614 | if (range.isEmpty) { 615 | // First range is empty. 616 | expect(first, true); 617 | first = false; 618 | continue; 619 | } 620 | // Later ranges are "b" that come after "a". 621 | expect(range.current, "$b"); 622 | range.moveBack(); 623 | expect(range.current, "$a"); 624 | } 625 | 626 | expect(it.split(gc("")).map((range) => range.current), 627 | ["$a", "$b", "$a", "$b"]); 628 | 629 | expect(gc("").iterator.split(gc("")).map((range) => range.current), [""]); 630 | 631 | expect(cs.startsWith(gc("")), true); 632 | expect(cs.startsWith(a), true); 633 | expect(cs.startsWith(a + b), true); 634 | expect(cs.startsWith(gc("$a$b$c")), true); 635 | expect(cs.startsWith(gc("$a$b$c$d")), true); 636 | expect(cs.startsWith(gc("$a$b$c$d$e")), true); 637 | expect(cs.startsWith(b), false); 638 | expect(cs.startsWith(c), false); 639 | expect(cs.startsWith(d), false); 640 | expect(cs.startsWith(e), false); 641 | 642 | expect(cs.endsWith(gc("")), true); 643 | expect(cs.endsWith(e), true); 644 | expect(cs.endsWith(d + e), true); 645 | expect(cs.endsWith(gc("$c$d$e")), true); 646 | expect(cs.endsWith(gc("$b$c$d$e")), true); 647 | expect(cs.endsWith(gc("$a$b$c$d$e")), true); 648 | expect(cs.endsWith(d), false); 649 | expect(cs.endsWith(c), false); 650 | expect(cs.endsWith(b), false); 651 | expect(cs.endsWith(a), false); 652 | 653 | it = cs.findFirst(b + c)!; 654 | expect(it.startsWith(gc("")), true); 655 | expect(it.startsWith(b), true); 656 | expect(it.startsWith(b + c), true); 657 | expect(it.startsWith(a + b + c), false); 658 | expect(it.startsWith(b + c + d), false); 659 | expect(it.startsWith(a), false); 660 | 661 | expect(it.endsWith(gc("")), true); 662 | expect(it.endsWith(c), true); 663 | expect(it.endsWith(b + c), true); 664 | expect(it.endsWith(a + b + c), false); 665 | expect(it.endsWith(b + c + d), false); 666 | expect(it.endsWith(d), false); 667 | 668 | it.collapseToFirst(c); 669 | expect(it.isPrecededBy(gc("")), true); 670 | expect(it.isPrecededBy(b), true); 671 | expect(it.isPrecededBy(a + b), true); 672 | expect(it.isPrecededBy(a + b + c), false); 673 | expect(it.isPrecededBy(a), false); 674 | 675 | expect(it.isFollowedBy(gc("")), true); 676 | expect(it.isFollowedBy(d), true); 677 | expect(it.isFollowedBy(d + e), true); 678 | expect(it.isFollowedBy(c + d + e), false); 679 | expect(it.isFollowedBy(e), false); 680 | }); 681 | test("replace methods", () { 682 | // Unicode grapheme breaking character classes, 683 | // represented by their first value. 684 | 685 | var pattern = gc("\t"); // A non-combining entry to be replaced. 686 | var non = gc(""); 687 | 688 | var c = otr + cr + pattern + lf + pic + pattern + zwj + pic + otr; 689 | var r = c.replaceAll(pattern, non); 690 | expect(r, otr + cr + lf + pic + zwj + pic + otr); 691 | var ci = c.iterator..moveNextAll(); 692 | var ri = ci.replaceAll(pattern, non)!; 693 | expect(ri.currentCharacters, otr + cr + lf + pic + zwj + pic + otr); 694 | ci.dropFirst(); 695 | ci.dropLast(); 696 | expect(ci.currentCharacters, cr + pattern + lf + pic + pattern + zwj + pic); 697 | expect(ci.currentCharacters.length, 7); 698 | ri = ci.replaceAll(pattern, non)!; 699 | expect(ri.currentCharacters, cr + lf + pic + zwj + pic); 700 | expect(ri.currentCharacters.length, 2); 701 | ci.dropFirst(); 702 | ci.dropLast(5); 703 | expect(ci.currentCharacters, pattern); 704 | ri = ci.replaceAll(pattern, non)!; 705 | expect(ri.currentCharacters, cr + lf); 706 | ci.moveNext(2); 707 | ci.moveNext(1); 708 | expect(ci.currentCharacters, pattern); 709 | ri = ci.replaceAll(pattern, non)!; 710 | expect(ri.currentCharacters, pic + zwj + pic); 711 | 712 | c = otr + pic + ext + pattern + pic + ext + otr; 713 | expect(c.length, 5); 714 | ci = c.iterator..moveTo(pattern); 715 | expect(ci.currentCharacters, pattern); 716 | ri = ci.replaceAll(pattern, zwj)!; 717 | expect(ri.currentCharacters, pic + ext + zwj + pic + ext); 718 | 719 | c = reg + pattern + reg + reg; 720 | ci = c.iterator..moveTo(pattern); 721 | ri = ci.replaceRange(non); 722 | expect(ri.currentCharacters, reg + reg); 723 | expect(ri.moveNext(), true); 724 | expect(ri.currentCharacters, reg); 725 | }); 726 | } 727 | 728 | /// Sample characters from each breaking algorithm category. 729 | final Characters ctl = gc("\x00"); // Control, NUL. 730 | final Characters cr = gc("\r"); // Carriage Return, CR. 731 | final Characters lf = gc("\n"); // Newline, NL. 732 | final Characters otr = gc(" "); // Other, Space. 733 | final Characters ext = gc("\u0300"); // Extend, Combining Grave Accent. 734 | final Characters spc = gc("\u0903"); // Spacing Mark, Devanagari Sign Visarga. 735 | final Characters pre = gc("\u0600"); // Prepend, Arabic Number Sign. 736 | final Characters zwj = gc("\u200d"); // Zero-Width Joiner. 737 | final Characters pic = gc("\u00a9"); // Extended Pictographic, Copyright. 738 | final Characters reg = gc("\u{1f1e6}"); // Regional Identifier "a". 739 | final Characters hanl = gc("\u1100"); // Hangul L, Choseong Kiyeok. 740 | final Characters hanv = gc("\u1160"); // Hangul V, Jungseong Filler. 741 | final Characters hant = gc("\u11a8"); // Hangul T, Jongseong Kiyeok. 742 | final Characters hanlv = gc("\uac00"); // Hangul LV, Syllable Ga. 743 | final Characters hanlvt = gc("\uac01"); // Hangul LVT, Syllable Gag. 744 | -------------------------------------------------------------------------------- /test/src/unicode_tests.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "package:characters/src/grapheme_clusters/constants.dart"; 6 | 7 | export "unicode_grapheme_tests.dart"; 8 | 9 | /// Readable description of the [expected] grapheme clusters. 10 | /// 11 | /// The list of strings is the expected grapheme cluster separation 12 | /// of the concatenation of those strings. 13 | /// 14 | /// The description converts each code unit to a 4-digit hex number, 15 | /// puts ` × ` between the code units of the same grapheme cluster 16 | /// and ` ÷ ` before, after and between the grapheme clusters. 17 | /// (This is the format of the original Unicode test data, so it 18 | /// can be compared to the original tests.) 19 | String testDescription(List expected) { 20 | var expectedString = expected 21 | .map((s) => 22 | s.runes.map((x) => x.toRadixString(16).padLeft(4, "0")).join(" × ")) 23 | .join(" ÷ "); 24 | return "÷ $expectedString ÷"; 25 | } 26 | 27 | final List categoryName = List.filled(16, "") 28 | ..[categoryOther] = "Other" 29 | ..[categoryCR] = "CR" 30 | ..[categoryLF] = "LF" 31 | ..[categoryControl] = "Control" 32 | ..[categoryExtend] = "Extend" 33 | ..[categoryZWJ] = "ZWJ" 34 | ..[categoryRegionalIndicator] = "RI" 35 | ..[categoryPrepend] = "Prepend" 36 | ..[categorySpacingMark] = "SpacingMark" 37 | ..[categoryL] = "L" 38 | ..[categoryV] = "V" 39 | ..[categoryT] = "T" 40 | ..[categoryLV] = "LV" 41 | ..[categoryLVT] = "LVT" 42 | ..[categoryPictographic] = "Pictographic" 43 | ..[categoryEoT] = "EoT"; 44 | -------------------------------------------------------------------------------- /test/src/various_tests.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | const zalgo = [ 6 | [ 7 | 'Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍', 8 | 'A̴̵̜̰͔ͫ͗͢', 9 | 'L̠ͨͧͩ͘', 10 | 'G̴̻͈͍͔̹̑͗̎̅͛́', 11 | 'Ǫ̵̹̻̝̳͂̌̌͘', 12 | '!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞' 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /third_party/Unicode_Consortium/UNICODE_LICENSE.txt: -------------------------------------------------------------------------------- 1 | UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE 2 | 3 | See Terms of Use 4 | for definitions of Unicode Inc.’s Data Files and Software. 5 | 6 | NOTICE TO USER: Carefully read the following legal agreement. 7 | BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S 8 | DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), 9 | YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE 10 | TERMS AND CONDITIONS OF THIS AGREEMENT. 11 | IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE 12 | THE DATA FILES OR SOFTWARE. 13 | 14 | COPYRIGHT AND PERMISSION NOTICE 15 | 16 | Copyright © 1991-2023 Unicode, Inc. All rights reserved. 17 | Distributed under the Terms of Use in https://www.unicode.org/copyright.html. 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining 20 | a copy of the Unicode data files and any associated documentation 21 | (the "Data Files") or Unicode software and any associated documentation 22 | (the "Software") to deal in the Data Files or Software 23 | without restriction, including without limitation the rights to use, 24 | copy, modify, merge, publish, distribute, and/or sell copies of 25 | the Data Files or Software, and to permit persons to whom the Data Files 26 | or Software are furnished to do so, provided that either 27 | (a) this copyright and permission notice appear with all copies 28 | of the Data Files or Software, or 29 | (b) this copyright and permission notice appear in associated 30 | Documentation. 31 | 32 | THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF 33 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 34 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | NONINFRINGEMENT OF THIRD PARTY RIGHTS. 36 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS 37 | NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL 38 | DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 39 | DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 40 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 41 | PERFORMANCE OF THE DATA FILES OR SOFTWARE. 42 | 43 | Except as contained in this notice, the name of a copyright holder 44 | shall not be used in advertising or otherwise to promote the sale, 45 | use or other dealings in these Data Files or Software without prior 46 | written authorization of the copyright holder. 47 | -------------------------------------------------------------------------------- /third_party/Wikipedia/License CC BY-SA 3.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Creative Commons Attribution-ShareAlike 3.0 Unported License 3 | https://creativecommons.org/licenses/by-sa/3.0/ 4 | 5 | License 6 | ------- 7 | 8 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. 9 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 10 | 11 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 12 | 13 | 1. Definitions 14 | "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 15 | "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. 16 | "Creative Commons Compatible License" means a license that is listed at https://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. 17 | "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 18 | "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. 19 | "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 20 | "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 21 | "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 22 | "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 23 | "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 24 | "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 25 | 2. Fair Dealing Rights 26 | Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 27 | 28 | 3. License Grant 29 | Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 30 | 31 | to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 32 | to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 33 | to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 34 | to Distribute and Publicly Perform Adaptations. 35 | For the avoidance of doubt: 36 | Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 37 | Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, 38 | Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. 39 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 40 | 41 | 4. Restrictions 42 | The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 43 | 44 | You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. 45 | You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. 46 | If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 47 | Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 48 | 5. Representations, Warranties and Disclaimer 49 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 50 | 51 | 6. Limitation on Liability 52 | EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 53 | 54 | 7. Termination 55 | This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 56 | Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 57 | 8. Miscellaneous 58 | Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 59 | Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 60 | If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 61 | No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 62 | This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 63 | The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 64 | Creative Commons Notice 65 | Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 66 | 67 | Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. 68 | 69 | Creative Commons may be contacted at https://creativecommons.org/. -------------------------------------------------------------------------------- /third_party/Wikipedia/hangul.txt: -------------------------------------------------------------------------------- 1 | 한글 2 | 위키백과, 우리 모두의 백과사전. 3 | 둘러보기로 가기검색하러 가기 4 | 이 문서에는 옛 한글이 포함되어 있습니다. 5 | 관련 글꼴이 설치되지 않은 경우, 일부 문자가 깨진 글자로 표시될 수 있습니다. 옛 한글 도움말을 참고하여 볼 수 있습니다. 6 | 다른 뜻에 대해서는 한글 (동음이의) 문서를 참조하십시오. 7 | 서적에 대해서는 훈민정음 문서를, 언어에 대해서는 한국어 문서를 참조하십시오. 8 | 한글 · 조선글 9 | 10 | 11 | 한글의 구조 12 | 한글의 구조 13 | 원래 이름 훈민정음(訓民正音) 14 | 유형 음소문자 15 | 표기 언어 한국어, 제주어, 찌아찌아어 16 | 사용 시기 1443년 ~ 현재 17 | 창제자 세종 18 | ISO 15924 Hang 19 | 한국어의 표기법 20 | 문자 21 | 한글 22 | 한자 23 | 한글 점자 24 | 한글전용 25 | 국한문혼용 26 | 이두 27 | 향찰 28 | 구결 29 | 로마자 표기법 30 | 국어의 로마자 표기법 31 | 매큔-라이샤워 표기법 32 | 예일 로마자 표기법 33 | ISO/TR 11941 34 | 김복문 로마자 표기법 35 | 양병선 로마자 표기법 36 | 21세기 로마자 표기법 37 | 음소문자의 역사 38 | 원시 시나이 문자 (기원전 18 ~ 15세기) 39 | 40 | 우가리트 문자 (기원전 15세기) 41 | 원시 가나안 문자 (기원전 14세기) 42 | 페니키아 문자 (기원전 11세기) 43 | 고대 히브리 문자 (기원전 10세기) 44 | 사마리아 문자 (기원전 6세기) 45 | 아람 문자 (기원전 8세기) 46 | 브라흐미 문자와 인도 문자 (기원전 6세기) 47 | 티베트 문자 (7세기) 48 | 크메르 문자/자와 문자 (9세기) 49 | 히브리 문자 (기원전 3세기) 50 | 시리아 문자 (기원전 2세기) 51 | 나바테아 문자 (기원전 2세기) 52 | 아랍 문자 (4세기) 53 | 타나 문자 (18세기) 54 | 소그드 문자 (기원전 4세기) 55 | 돌궐 문자 (5세기) 56 | 로바쉬 문자 (12세기) 57 | 위구르 문자 (8세기) 58 | 몽골 문자 (13세기) 59 | 만주 문자 (16세기) 60 | 팔라비 문자 (기원전 3세기) 61 | 아베스타 문자 (4세기) 62 | 그리스 문자 (기원전 9세기) 63 | 에트루리아 문자 (기원전 8세기) 64 | 로마자 (기원전 7세기) 65 | 룬 문자 (2세기) 66 | 고트 문자 (3세기) 67 | 콥트 문자 (300년) 68 | 아르메니아 문자 (405년) 69 | 조지아 문자 (5세기) 70 | 글라골 문자 (862년) 71 | 키릴 문자 (10세기) 72 | 아부르 문자 (1372년) 73 | 고대 히스파니아 문자 (기원전 7세기) 74 | 남아랍 문자 금석문 (기원전 9세기) 75 | 그으즈 문자 (기원전 5 ~ 6세기) 76 | 메로이트 문자 (기원전 3세기) 77 | 오검 문자 (4세기) 78 | 한글 1443년 79 | 캐나다 문자 1840년 80 | 주음부호 1913년 81 | 전체 분류 82 | v • d • e • h 83 | 한글은 발음기관과 하늘, 땅, 사람을 본따 고안된 음소문자로, 닿소리 14자에 홀소리 10자 총 24자로 구성되어 있다. "나랏말이 중국과 달라" 문제를 느낀 조선 세종이 한국어를 표기하기 위하여 1443년 창제, 1446년 반포하였다. 낱자가 음가만 표기하기 때문에 갈래로는 음소문자에 속하나, 네모 칸에 초성, 중성, 종성을 이루는 자모음을 한데 모아 쓰는 방식 때문에 음절문자의 특성도 일부 지닌다. 원래 글자 수는 닿소리 17자에 홀소리 11자 총 28자였으나 이후 4자가 소실, 24자만 쓰이게 되었다. 대한민국과 조선민주주의인민공화국과 옌볜 조선족 자치주에서는 공용 문자로, 인도네시아 부톤 섬에서는 찌아찌아어의 보조 문자로 채택되었다. 북한에서는 조선글(朝鮮-)이라 한다. 84 | 85 | 세계에서 유일하게 만든 사람,만든 이유,만든 날짜를 아는 글자이다. 86 | 87 | 88 | 목차 89 | 1 명칭 90 | 2 역사 91 | 2.1 창제 92 | 2.2 창제 논란 93 | 2.3 조선 94 | 2.4 근대 이후 95 | 2.5 현대 이후 96 | 3 창제원리 97 | 4 구조 98 | 4.1 낱자 99 | 4.2 모아쓰기 100 | 4.3 표기 가능한 글자 수와 소리나는 음절 개수 101 | 5 한글의 유래 102 | 6 한글에 관한 여러 이설 103 | 6.1 파스파 문자 기원설 104 | 6.2 기타 한글과 유사하다고 주장하는 문자 105 | 6.2.1 가림토와 신대 문자 106 | 6.2.2 구자라트 문자 107 | 6.3 다른 언어에서의 한글 사용 108 | 7 오해와 사실 109 | 8 한글 자모일람 110 | 8.1 방언 한글 자모 111 | 8.2 고문 한글 자모 112 | 8.3 복합원음와 보음 113 | 9 관련 항목 114 | 10 각주 115 | 11 참고 문헌 116 | 12 읽을거리 117 | 13 외부 링크 118 | 명칭 119 | 창제 때는 백성(民)을 가르치는(訓) 바른(正) 소리(音), 훈민정음(訓民正音)이라 하였고, 줄여서 정음(正音)이라고도 했다. 120 | 121 | '한글'이라는 이름은 주시경이 ‘큰’, ‘바른’, ‘하나’를 뜻하는 고유어 ‘한’을 차용하여 지었다. 하지만 주시경의 의도한 뜻이 무엇이었는지는 명확히 밝혀진 바가 없다. 122 | 123 | 한글 창제 당시에는 백성을 가르치는 바른소리라는 뜻으로 훈민정음이라 하였고, 줄여서 정음(正音)이라고도 하였다. 조선시대에는 지식층으로부터 경시되며, 본래의 이름으로 쓰지 않고 막연히 언문(諺文)[1], 언서(諺書)[2], 반절(反切)[3] 로 불리거나, 혹은 암클(여성들이 배우는 글), 아햇글(어린이들이 배우는 글)이라고 불렀다고 알려져 있다. (단, 암클, 아햇글이라는 표현은 그 출처가 불분명하다.) 1894년 갑오개혁 이후 국서(國書), 국문(國文)이라고 불렀고 혹은 조선글로 부르기도 하였는데 이것은 한국의 글이라는 보통 이름일 뿐이며, 고유명사로 한글이라는 이름이 널리 쓰이기 전에는 가갸, 정음 등으로 불렀다. 124 | 125 | 처음 한글이라는 이름이 사용된 것에대한 명확한 기록은 없다. 다만 1913년 3월 23일 주시경이 ‘배달말글몯음(조선어문회, 朝鮮言文會)[4]’를 ‘한글모’로 바꾼 바 있고[5], 같은 해 9월 최남선의 출판사 ‘신문관(新文館)’에서 창간한 어린이 잡지 《아이들 보이》의 끝에 가로글씨로 '한글풀이’라 한 것이 있고[6], 1914년 4월에 ‘조선어강습원(朝鮮語講習院)’이 ‘한글배곧’으로 이름을 바꾼 것 등으로 볼 때 1913년 무렵 주시경이 처음으로 사용한 것으로 보이며, 1927년에는 조선어학회 회원들이 《한글》이라는 잡지를 매달 발간하였다. 한글이라는 명칭이 일반화된 것은 1928년 11월 11일 조선어연구회에서 가갸날을 한글날로 고쳐 부른 때부터라고 한다. 126 | 127 | 현재 한글의 명칭을 대한민국에서는 한글로, 조선민주주의인민공화국에서는 조선어자모로 부르는데[7], 2001년 2월 중국 옌지에서 열린 ‘제5차 코리안 컴퓨터 처리 국제 학술 대회(ICCKL 2001)’에서는 남과 북, 해외 동포 학자들이 국제 표준화 기구(ISO)에 등록하기 위한 명칭으로 ‘정음(Jeong'eum)’을 쓰기로 합의하였다. 128 | 129 | 다른 나라에서는 한글(Hangul/Hangeul)이라는 이름을 많이 쓰지만, 중국에서는 조선 자모(중국어: 朝鮮字母, 병음: Cháoxiǎn zìmǔ 차오셴 쯔무[*])와 같은 이름을 쓴다. 일본에서는 한글은 물론 한국어를 ‘한구루(한글)(ハングル)’로 부르기도 하는데, 이는 NHK 방송에서 한국어 강좌를 설립시에 대한민국의 ‘한국어’와 조선민주주의인민공화국의 ‘조선어’ 사이에서 중립적인 위치를 지키기 위해 한국어 강좌 명칭으로 '한글강좌'를 사용하여 많은 일본인들이 이를 보고 한글의 뜻을 한국어로 오해한 것이다. 130 | 131 | 한글이라는 이름은 본디 문자의 이름이지만, 관용적으로는 한국어를 한글로 적은 것이라는 의미로 책이나 소프트웨어, 게임 등의 한국어 번역 작업을 한글화라 하고 번역본을 한글판이라 부르기도 한다. 그리고 한글 이름, 한글 지명처럼 고유어라는 의미로 쓰이기도 한다. 하지만 표준국어대사전에서는 두 의미 모두 등재되지 않았으며, 한국어화, 한국어판이 맞는 표현이다. 132 | 133 | 역사 134 | 135 | 《훈민정음 언해》의 서두 136 | 창제 137 | 한국은 삼국시대부터 이두(吏讀)와 구결(口訣)을 써 왔는데, 구결은 본래 한문에 구두(句讀)를 떼는 데 쓰기 위한 일종의 보조적 편법에 지나지 않았고, 이두는 비록 한국어를 표시함에 틀림이 없었지만 한국어를 자유자재로 적을 수 없었으며, 그 표기법의 일원성(一元性)이 없어서 설사 이두로써 족하다 해도 한자교육이 선행되어야 했다. 이러한 문자생활의 불편은 한자를 쓰지 않고도, 배우기 쉽고 쓰기 쉬운 새로운 글자의 출현이 절실히 요구되었다. 138 | 139 | 이러한 사조가 세종때에 특히 두드러져 드디어 1443년 음력 12월에 문자혁명의 결실을 보게 되었다. 훈민정음 창제의 취지에 관하여는 세종이 손수 저술한 《훈민정음》 예의편(例義篇) 첫머리에 잘 나타나 있는데, 첫째 한국어는 중국말과 다르므로 한자를 가지고는 잘 표기할 수 없으며, 둘째 우리의 고유한 글자가 없어서 문자생활의 불편이 매우 심하고, 셋째 이런 뜻에서 새로 글자를 만들었으니 일상생활에 편하게 쓰라는 것이다. 140 | 141 | ‘훈민정음’은 “백성을 가르치는 바른소리”라는 뜻으로[8], 세종의 어제 서문과 정인지 서(序)에서 분명히 밝히고 있는바, 당시까지 한문 의존에 따른 어려움을 근본적으로 극복하기 위해 한국어의 고유문자로서 창제되었다. 142 | 143 | 한편, 훈민정음 창제 후 5년 뒤에 《동국정운(東國正韻)》이 간행되는데, 당시 조선에서 통용되던 한자음을 중국어 원음으로 교정하기 위한 책으로서 이것의 발음 표기에 훈민정음이 사용되고 있다. 따라서, 세종의 훈민정음 창제가 한자 및 한문의 폐지를 목적한 것은 아니라고 보이며, 훈민정음의 활용 범위가 상당히 넓었음을 짐작할 수 있다. 훈민정음에 대하여 반대하는 신하들이 있었는데 대표적으로 최만리는 상소를 올려 반대하였다. 그러나 세종은 "경이 운서를 아는가? 사성칠음에 자모가 몇이나 있는가? 만일 짐이 운서를 바로잡지 아니하면 누가 이를 바로잡을 것인가?" 라고 말하였다. 144 | 145 | 처음 만들었을 때는 낱자 28글자와 성조를 나타내는 기호(방점)가 따로 있었으나, 지금은 ㅿ, ㆁ, ㆆ, ㆍ 네 글자와 성조 기호(방점)가 사라져서 24글자가 되었다. (제주도를 비롯한 몇 곳에서는 아직도 ㆍ의 발음이 남아 있다.) 146 | 147 | 그 뒤로 몇 백 년에 걸쳐, 식자층은 주로 한글보다는 한문 위주의 문자 생활을 했지만 한자를 배울 수 없었던 백성과 여자들은 서로 주고 받는 편지나 계약서 등에 한글을 썼고, 궁궐에서 여자끼리 주고 받는 문서에 한글을 쓰기도 하였다. 148 | 149 | 창제 논란 150 | 오늘날 한글이라 불리는 글이 창제되었다는 사실이 세상에 알려진 것은 세종대왕 25년인 1443년이다. 창제 당시에 한글은 '훈민정음'이라 불렸으며 1446년 음력 9월 초에는 《훈민정음》(통칭 '해례본')이 책으로 엮어졌다. 이 사실은 정인지(鄭麟趾)가 쓴 〈서(序)〉로 확인된다.[9] 151 | 152 | 지금까지 논란이 되고 있는 부분은 세종대왕이 홀로 글을 창제했는지, 집현전 학자들의 도움을 받았는지, 아니면 세종대왕의 명을 받아 집현전 학자들이 글을 창제했는지가 문제이다. 세종실록(世宗實錄)은 훈민정음을 세종대왕이 친히 만들었다고 기록하고 있으며[10], 누구의 도움을 받았다는 기록은 없다.[11] 153 | 154 | 다시 말하면 시월 상친제언문이십팔자(是月 上親制諺文二十八字, 세종 25년, 12월 30일)에서 ‘상친제(上親制)’란 세종이 직접 한글을 만들었다는 뜻인데 '세종실록' 안에는 다른 업적에 관해서는 "친제"라는 말이 없었지만, 훈민정음(한글)에 관해서는 이렇게 확실하게 적어 놓았다는 것이다. 또한, 집현전 학자였던 정인지가 집필한 훈민정음 해례본의 서문 중에도 세종대왕이 직접 한글을 창제했다는 내용이 있다.[12] 155 | 156 | 그러나 성현(成俔, 1439년~1504년)의 《용재총화(慵齋叢話)》 제7권에서 세종이 언문청을 세워 신숙주, 성삼문 등에게 글을 짓도록 명을 내렸다는 주장이 나왔다. 주시경은 《대한국어문법》(1906년)에서 세종이 집현전 학자들의 도움을 받아 한글을 창제했다고 썼다. 그리하여 한글 창제에 집현전 학자들이 관여했다는 설이 우세하게 되었으나, 이기문을 비롯한 학자들은 기록에 나타난 당시 정황을 볼 때 세종이 한글을 홀로 창제한 것이 아니라고 볼 이유가 없다고 주장하고 있다. 한글 창제 후 세종은 표음주의 표기가 일반적인 당대의 표기법과는 달리 형태주의 표기를 주로 활용하고 동국정운 같은 책을 편찬한 예에서 보듯이 국어와 중국어의 전반에 걸쳐 음운학 및 언어학에 깊은 조예와 지식을 보여 주었다. 집현전 학자들은 한글 창제 후 정음청에서 한글을 사용한 편찬 사업에만 관여했다는 것이다.[13] 157 | 158 | 조선 159 | 처음에 ‘훈민정음’으로 반포된 한글은 조선시대에는 '언문'이라고 불렸다. 이것은 《세종실록》에서 '상친제언문이십팔자(上親製諺文二十八字)'라고 한 것에 연유하는데 한자를 제외한 문자는 ‘언문’이라고 불렀기 때문이다. 여성들이 많이 한글을 썼기 때문에 ‘암클’ 등으로 낮추어 불리기도 하였으나, 궁중과 일부양반층, 백성들 사이에서도 사용되었다. 160 | 161 | 1445년(세종 27) 4월에 훈민정음을 처음으로 사용하여 악장(樂章)인 《용비어천가》를 편찬하고, 1447년(세종 29) 5월에 간행하였다. 목판본 10권 5책 모두 125장에 달하는 서사시로서, 한글로 엮어진 책으로는 한국 최초의 것이 된다. 세종은 “어리석은 남녀노소 모두가 쉽게 깨달을 수 있도록” 《(세종실록》, 세종 26년) 《삼강행실도》를 훈민정음으로 번역하도록 했으며, 훈민정음이 반포된 뒤에는 일부 관리를 뽑을 때 훈민정음을 시험하도록 했다. 이후로 민간과 조정의 일부 문서에서 훈민정음을 써 왔다. 162 | 163 | 이러한 한글 보급 정책에 따라 한글은 빠르게 퍼져 반 세기 만인 1500년대 지방의 노비 수준의 신분인 도공에게까지 쓰이게 되었다.[14] 164 | 165 | 연산군은 1504년(연산군 10년) 훈민정음을 쓰거나 가르치는 것을 금했지만, 조정안에서 훈민정음을 쓰는 것을 금하지는 않았으며, 훈민정음을 아는 사람을 일부러 궁궐에 등용하기도 했다고 전한다. 166 | 167 | 율곡 이이가 《대학》에 구결을 달고 언해한 《대학율곡언해》는 1749년에 간행되었다.[15] 168 | 169 | 조선 중기 이후로 가사 문학, 한글 소설 등 한글로 창작된 문학이 유행하였고, 서간에서도 한글/정음이 종종 사용되었다. 170 | 171 | 근대 이후 172 | 1894년(조선 고종 31년) 갑오개혁에서 마침내 한글을 ‘국문’(國文 나랏글)이라고 하여, 1894년 11월 21일 칙령 제1호 공문식(公文式) 제14조[16] 및 1895년 5월 8일 칙령 제86호 공문식 제9조[17] 에서 법령을 모두 국문을 바탕으로 삼고 한문 번역을 붙이거나 국한문을 섞어 쓰도록 하였다. 1905년 지석영이 상소한 6개 항목의 〈신정국문(新訂國文)〉이 광무황제의 재가를 얻어 한글 맞춤법으로서 공포되었으나, 그 내용의 결점이 지적되면서 1906년 5월에 이능화(李能和)가 〈국문일정의견(國文一定意見)〉을 제출하는 등 논란이 되자, 당시 학부대신 이재곤(李載崑)의 건의로 1907년 7월 8일 대한제국 학부에 통일된 문자 체계를 확립하기 위한 국어 연구 기관으로 '국문연구소(國文硏究所)'가 설치되었는데, 국문연구소의 연구 성과는 1909년 12월 28일 학부에 제출한 보고서로서 〈국문연구의정안(國文硏究議定案)〉 및 어윤적, 이종일(李鍾一), 이억(李億), 윤돈구(尹敦求), 송기용(宋綺用), 유필근(柳苾根), 지석영, 이민응(李敏應)의 8위원 연구안으로 완결되었다. 173 | 174 | 175 | 한글과 한문이 혼용되어 쓰인 매일신보 1944년 기사 176 | 한편, 민간에서는 1906년 주시경이 《대한국어문법(大韓國語文法)》을 저술하여 1908년에 《국어문전음학(國語文典音學)》으로 출판하였으며, 1908년 최광옥(崔光玉)의 《대한문전(大韓文典)》, 1909년 유길준(兪吉濬)의 《대한문전(大韓文典)》, 김희상(金熙祥)의 《초등국어어전(初等國語語典)》, 1910년 주시경의 《국어문법(國語文法)》등이 출간되고, 이후에도 1911년 김희상의 《조선어전(朝鮮語典)》, 1913년 남궁억(南宮檍)의 〈조선문법(朝鮮文法)〉, 이규영(李奎榮)의 〈말듬〉, 1925년 이상춘(李常春)의 《조선어문법(朝鮮語文法)》 등으로 이어지면서, 1937년 최현배의 《우리말본》으로 집대성된다. 177 | 178 | 이와 함께, 조선어학회와 같은 모임에서 꾸준히 애쓴 덕에 조금씩 한국어의 표준 문자로 힘을 얻게 되어 누구나 쓸 수 있게끔, 널리 퍼지게 되었다. '한글'이라는 이름은 주시경이 지은 것이며 조선어학회가 이 이름을 널리 알리기 시작한 것도 이 즈음이다. 일제강점기를 거쳐 광복을 맞이한 다음에는 남북한 모두 공문서와 법전에 한글을 쓰게 되었고, 끝내 조선어를 받아적는 글자로 자리잡게 되었다. 179 | 180 | 현대 이후 181 | 한국에서는 한글전용법이 시행되어 한자의 사용이 줄어들면서 1990년대 그 사용이 절정을 이루었다.[18] 이후 정부차원에서의 영어우대정책으로 인해 한글의 사용이 점차 줄고 있다는 지적이 있다.[19] 182 | 183 | 2009년에는 문자가 없어 의사 소통에 곤란을 겪었던 인도네시아의 소수 민족인 찌아찌아족이 자신들의 언어 찌아찌아어의 표기 문자로 시범적으로 한글을 채택, 도입하였다. 그러나 주 정부의 반대와 소수만 배우는 문제 등으로 인해서 이 방법은 사용되지 않고 있다. 그리고 2012년에 솔로몬 제도에 있는 일부 주가 모어 표기문자로 한글을 도입하였다.[20] 184 | 185 | 창제원리 186 | 『훈민정음 해례본(訓民正音 解例本)』을 바탕으로 한글과 음양오행의 관계를 기록하였다. 187 | 188 | 가. 모음은 음양의 원리를 기본으로 만들어졌다. 189 | 190 | 기본 모음'ㆍ, ㅡ, ㅣ'를 보면 'ㆍ'(아래아)는 양(陽)인 하늘(天)을 본 떠 만들고, 'ㅡ'는 음(陰)인 땅(地)을 본 떠 만들었으며 'ㅣ'는 음과 양의 중간자인 인간(人)의 형상을 본 떠 만들었다. 천지인(天地人)은 단군사상에서 유래한 것으로 우주를 구성하는 주요한 요소인 하늘(·)과 땅(ㅡ), 사람(ㅣ)을 나타낸다.[21] 191 | 『훈민정음 해례본』에 따르면 'ㅏ,ㅑ, ㅗ, ㅛ'는 'ㆍ'(아래아) 계열의 글자이다. 192 | 'ㆍ'(아래아)의 속성은 양이다. 양의 특성은 위로의 상승, 바깥으로의 확장이다. 따라서 점을 위, 바깥 쪽에다 찍은 것. 193 | 194 | 'ㅓ, ㅕ, ㅜ, ㅠ'는 그 반대로 'ㅡ' 계열의 글자이기 때문에 음의 속성을 따라, 하강, 수축의 뜻으로 점을 안쪽, 아래로 찍은 것. 195 | 나. 자음은 오행을 바탕으로 만들어졌다. 196 | 197 | 『훈민정음 해례본』에선 각 방위와 발음기관을 연결시키고, 해당 발음기관에서 나는 소리 또한 방위와 연관시키고 있다. 방위는 또 계절과 연결이 되므로, 결국 소리는 계절과 연결된다. 198 | (소리=방위=계절, 소리=계절) 계절은 봄, 여름, 늦여름, 가을, 겨울 순이므로, 소리 역시 어금닛소리(ㄱ, 봄), 혓소리(ㄴ, 여름), 입술소리(ㅁ, 늦여름), 잇소리(ㅅ, 가을), 목소리(ㅇ,겨울) 순으로 배열한다. 199 | 200 | 『훈민정음 해례본』에서 기본 자음을 ㄱ,ㄴ,ㅁ,ㅅ,ㅇ,ㄹ 순으로 배열한 것은 오행 원리와 연관이 있다. 201 | 자음과 오행의 관계 정리표 202 | 속성 계절 방위 음성 음계 203 | 목(木, 나무) 춘(春, 봄) 동(東, 동녘) 어금닛소리(ㄱ,ㅋ,ㄲ) 각(角) 204 | 화(火, 불) 하(夏, 여름) 남, (南, 남녘) 혓소리(ㄴ,ㄷ,ㅌ,ㄸ) 치(徵) 205 | 토(土, 흙) 계하 (季夏, 늦여름) 중앙(中, 無定) 입술소리(ㅁ,ㅂ,ㅍ,ㅃ,) 궁(宮) 206 | 금(金, 쇠) 추(秋, 가을) 서(西, 서녘) 잇소리(ㅅ,ㅆ,ㅈ,ㅊ,ㅉ) 상(商) 207 | 수(水, 물) 동(冬, 겨울) 북(北, 북녘) 목소리(ㅇ, ㅎ) 우(羽) 208 | 구조 209 | 한글은 낱소리 문자에 속하며, 낱자하나는 낱소리하나를 나타낸다. 낱소리는 닿소리(자음)와 홀소리(모음)로 이루어진다. 210 | 211 | 한 소리마디는 첫소리(초성), 가운뎃소리(중성), 끝소리(종성)의 낱소리 세 벌로 이루어지는데, 첫소리와 끝소리에는 닿소리를 쓰고 가운뎃소리에는 홀소리를 쓴다. 한글은 낱자를 하나씩 풀어쓰지 않고 하나의 글자 마디로 모아쓰는 특징을 가지고 있다. 212 | 213 | 낱자 214 | 이 부분의 본문은 한글 낱자입니다. 215 | 처음 한글 낱자는 닿소리 17자와 홀소리 11자로 총 28가지였다. 오늘날 한글 낱자에 쓰이지 않는 없어진 글자를 소실자(消失字)라 하는데, 닿소리 ㅿ(반시옷), ㆁ(옛이응), ㆆ(여린히읗)과 홀소리 ㆍ(아래아)의 네 글자이다. 이로써 현대 한글은 모두 24자로서 닿소리 14자와 홀소리 10자로 되었다. 낱자의 이름과 순서는 다음과 같다. 216 | 217 | 훈민정음 창제 당시에는 낱자 자체의 칭호법(稱號法)은 표시되어 있지 않았고, 중종 때 최세진의 《훈몽자회》에 이르러 각 낱자의 명칭이 붙게 되었다. 하지만 기역, 디귿, 시옷은 이두식 한자어 명칭을 그대로 사용하여 일제시대의 언문 철자법을 거쳐 지금까지 그대로 사용하게 되었다.[22] 218 | 219 | 각 자모에 대한 소릿값을 살펴보면, 첫소리 아·설·순·치·후(牙舌脣齒喉)와 반설·반치(反舌半齒)의 7음으로 구별하였고, 모음은 따로 구별하지 않았다. 이러한 7음과 각 자모의 독특한 배열 순서는 중국 운서(韻書)를 그대로 모방한 것이라고 여겨진다. 그리고 실제로 쓸 적에는 각 낱자를 독립시켜 소리 나는 차례대로 적지 않고, 반드시 닿소리와 홀소리를 어울려 쓰기로 하였으니, 곧 <· ㅡ ㅗ ㅜ ㅛ ㅠ >는 자음 아래에 쓰고, <ㅏ ㅓ ㅑ ㅕ>는 자음 오른쪽에 붙여 쓰기로 하였다. 즉 음절문자(音節文字)로 하되, 그 모양이 네모꼴이 되도록 하였으니, 이는 한자의 꼴에 영향을 받았기 때문이라 여겨진다. 220 | 221 | 닿소리 222 | ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅅ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ 223 | 기윽/기역 니은 디읃/디귿 리을 미음 비읍 시읏/시옷 이응 지읒 치읓 키읔 티읕 피읖 히읗 224 | 홀소리 225 | ㅏ ㅑ ㅓ ㅕ ㅗ ㅛ ㅜ ㅠ ㅡ ㅣ 226 | 아 야 어 여 오 요 우 유 으 이 227 | 이 스물네 가지를 바탕으로 하는데 모두 홑소리(단음)이고, 홑소리로 나타낼 수 없는 겹소리(복음)는 두세 홑소리를 어울러서 적되, 그 이름과 순서는 다음과 같다. 228 | 229 | 겹닿소리 230 | ㄲ ㄸ ㅃ ㅆ ㅉ 231 | 된기윽/쌍기역 된디읃/쌍디귿 된비읍/쌍비읍 된시읏/쌍시옷 된지읒/쌍지읒 232 | 겹홀소리 233 | ㅐ ㅒ ㅔ ㅖ ㅘ ㅙ ㅚ ㅝ ㅞ ㅟ ㅢ 234 | 애 얘 에 예 와 왜 외 워 웨 위 의 235 | 소실자 닿소리 236 | ㅿ ㆁ ㆆ 237 | 반시옷 옛이응 여린히읗 238 | 유성 치경 마찰음 연구개 비음 성문 파열음 239 | 반시옷은 알파벳의 z에 해당하는 음가를 가진 것으로 추정되며 여린히읗은 1을 강하게 발음 시 혀로 목구멍을 막으며 발음된다. 240 | 241 | 현대 한글에서는 끝소리가 없으면 받침을 쓰지 않고 끝소리가 있을 때에만 홑받침 또는 겹받침을 쓰는데, 홑받침에는 모든 닿소리가 쓰이며, 겹받침에는 홑홀소리 아래에만 놓이는 겹닿소리 ㄲ(쌍기역)과 ㅆ(쌍시옷)과 따로 이름이 없지만 모든 홀소리 아래에 놓일 수 있는 겹받침으로만 쓰이는 겹닿소리가 있다. 모든 받침의 소릿값은 끝소리 규칙에 따라 8갈래로 모인다.[23] 242 | 243 | 겹받침 244 | ㄲ ㅆ ㄳ ㄵ ㄶ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅄ 245 | 받침의 소릿값 246 | ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅇ 247 | 사전에 올릴 때에는 첫소리 > 가운뎃소리 > 끝소리의 순으로 정렬하되, 그 정렬 순서는 다음과 같다. 248 | 249 | 정렬 순서 250 | 첫소리 ㄱ ㄲ ㄴ ㄷ ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ 251 | 가운뎃소리 ㅏ ㅐ ㅑ ㅒ ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ ㅡ ㅢ ㅣ 252 | 끝소리 ( ) ㄱ ㄲ ㄳ ㄴ ㄵ ㄶ ㄷ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅄ ㅅ ㅆ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ 253 | 모아쓰기 254 | 한글의 모든 낱자는 한데 모아쓰도록 하고 있으며, 닿소리를 가장 먼저 쓰고 그 오른쪽이나 아래에 홀소리를 적으며, 모든 받침은 닿소리와 홀소리 밑에 놓인다. 따라서, 글자 마디로 모아쓸 때는 다음과 같은 틀에 맞추어 쓴다. 255 | 256 | 중성이 ㅏ, ㅐ, ㅑ, ㅒ, ㅓ, ㅔ, ㅕ, ㅖ, ㅣ일 때는 중성을 초성의 오른쪽에 붙여 쓴다. 257 | 초성 중성 258 | 초성 중성 259 | 종성 260 | 중성이 ㅗ, ㅛ, ㅜ, ㅠ, ㅡ일 때는 중성을 아래쪽에 붙여 쓴다. 종성이 있으면 그 아래 붙여 쓴다. 261 | 초성 262 | 중성 263 | 초성 264 | 중성 265 | 종성 266 | 중성이 ㅘ, ㅙ, ㅚ, ㅝ, ㅞ, ㅟ, ㅢ와 같이 아래쪽에 붙이는 모음과 오른쪽에 붙이는 모음의 복합일 때는 다음과 같이 아래쪽에 먼저, 그 다음 오른쪽에 붙여 쓴다. 종성은 마찬가지로 아래쪽에 붙여 쓴다. 267 | 초성 중성 268 | 중성 269 | 초성 중성 270 | 중성 271 | 종성 272 | 표기 가능한 글자 수와 소리나는 음절 개수 273 | 현대 한글은 낱자를 엮어 11,172(첫소리 19 × 가운뎃소리 21 × (끝소리 27 + 끝소리 없음 1))글자 마디를 쓸 수 있다. 11,172자 중 399자는 무받침 글자이며 10,773자는 받침 글자이다. 사용 빈도는 KS X 1001 완성형 한글 코드에 선별된 2,350글자가 상위 99.9%로 알려져 있다.[출처 필요] 274 | 275 | 어문 규정에 의하여, 현대 한국어 표준어에서 실제 사용하는 음절은 이보다 적다. 한국어의 소리는 첫소리+가운뎃소리(+끝소리)로 이루어지는데, 표준어에서 첫소리에는 19가지 닿소리가 모두 쓰이되 첫소리에 놓인 ㅇ은 소리 나지 않는다. 끝소리는 7종성법에 따라 7갈래로 모이며 끝소리가 없는 것까지 더하여 모두 8갈래이므로 현대 한국어의 발음은 첫소리 19 × 가운뎃소리 21 × 끝소리 8 = 3,192가지 소리가 된다. 276 | 277 | 그런데, 표준 발음법을 따르면 구개음 ㅈ, ㅉ, ㅊ 뒤의 이중 모음 ㅑ, ㅒ, ㅕ, ㅖ, ㅛ, ㅠ는 단모음 ㅏ, ㅐ, ㅓ, ㅔ, ㅗ, ㅜ로 소리나므로 첫소리 3 × 가운뎃소리 6 × 끝소리 8 = 144소리가 빠지고, 아울러 소리나는 첫소리 (ㅇ이 아닌 첫소리 뒤에 오는)를 얹은 가운뎃소리 [ㅢ]는 ㄴ을 제외하면(ㄴ의 경우는 구개음화에 따른 다른 음소로 인정하고 있다.) [ㅣ]로 소리나므로(한글 맞춤법 제9항 및 표준 발음법 제5항 단서 3) 첫소리 17 × 가운뎃소리 1 × 끝소리 8 = 136 소리가 다시 빠진다. 따라서, 현재 한국어 표준어에서 실제 사용하는 소리마디는 3192 − 144 − 136 = 2,912가지가 된다. 278 | 279 | 옛 한글의 경우, 2009년 10월 1일 발표된 유니코드 5.2에 포함되어 있는 옛 한글 자모의 총 개수는 초성 124개, 중성 95개, 종성 137개와 채움 문자 2개(초성, 중성)이다. 방점 2개는 현재 유니코드에 등록돼 있다. 방점을 제외하고, 총 조합 가능한 글자 마디 개수를 구한다면 다음과 같다. 280 | 281 | 조합 가능한 한글 코드(125×96×138): 1,656,000개 282 | 완성된 한글(124×95×138): 1,625,640개 283 | 조합 가능한 비표준 한글: 총 16,989개 284 | 채움 문자로만 구성된 한글: 1개 285 | 초성, 종성만 있는 비표준 한글(124×137): 16,988개 286 | ∴ 표준 한글 총 개수(조합 가능한 한글 코드 − 비표준 한글): 1,639,011개 287 | 한글의 유래 288 | 《세종실록》에 최만리가 훈민정음이 “고전(古篆)을 본땄다(倣)”라고 말한 기록이 있는데,[24][25] 이 말이 모호하기 때문에 여러 가지 해석이 있다. ‘고전’의 해석에는 한자의 전자체(篆字體)라는 설과 당시에 ‘몽고전자’(蒙古篆字)로도 불렸던 파스파 문자를 말하는 것이라는 설이 있다. 《환단고기》를 인정하는 사람은 이것이 가림토를 일컫는 말이라고 주장한다. 또한 ‘본땄다’(倣)에 대해서도 그 생김새만이 닮았을 뿐이라는 풀이와 만드는 데에 참고를 했다, 또는 모두 본땄다 등의 여러 가지 해석이 있다. 289 | 290 | 1940년 《훈민정음》(해례본)이 발견되기 이전에는 훈민정음의 창제 원리를 설명한 문헌이 존재하지 않아 그 유래에 대한 여러 이론이 제기되었다. 그 이전에 제기되었던 주요 학설은 다음과 같다. 291 | 292 | 발음 기관 상형설: 발음 기관을 상형했다는 설. 신경준(申景濬), 홍양호(洪良浩), 최현배 293 | 전자 기원설: 한문 비석 등에 쓰이는 전자체에서 유래되었다는 설. 황윤석(黃胤錫), 이능화 294 | 몽골 문자 기원설: 몽골문자(파스파)에서 유래했다는 설. 이익(李翼), 유희(柳僖), 게리 레드야드(Gari Ledyard) 295 | 범자(梵字) 기원설: 불경과 함께 고대 인도 문자가 전해져, 그것에서 유래했다는 설. 성현, 이수광(李晬光) 296 | 고대 문자 전래설: 훈민정음 이전 민간에서 전해지던 고대문자로부터 유래했다는 설. 297 | 창문 상형설: 한옥의 창살 모양에서 유래했다는 설. 에카르트(P. A. Eckardt) 298 | 서장(西藏)글자·오행(五行)이론.[26] 299 | 《훈민정음》(해례본)에는 자음과 모음 각각에 대한 창제 원리가 상세히 설명되어 기본 자음 5자는 발음 기관의 모양을 추상화하고, 기본 모음 3자는 천지인 3재를 상징하여 창제되었고 다른 글자들이 획을 덧붙이는 방식으로 만들어졌다고 분명히 밝힘으로써, 여러 이설들을 잠재우고 정설이 되었다. 300 | 301 | 한글에 관한 여러 이설 302 | 파스파 문자 기원설 303 | 304 | (위) ’파스파 문자 ꡂ [k], ꡊ [t], ꡎ [p], ꡛ [s], ꡙ [l]와 그에 대응하는 것으로 여겨지는 한글 문자 ㄱ [k], ㄷ [t], ㅂ [p], ㅈ [ts], ㄹ [l]. 305 | (아래) 중국어를 표현하기 위한 파스파 문자 ꡯ w, ꡤ v, ꡰ f의 파생과 그의 변형 문자 ꡜ [h] 306 | (왼쪽) 밑에 기호를 덧붙인 ꡧ [w][27] 와 유사한 중국어 표기용 한글 ㅱ w/m, ㅸ v, ㆄ f. 이 한글들은 기본자 ㅁ과 ㅇ에서 유래했다. 307 | 1966년 컬럼비아 대학의 게리 레드야드 교수는 그의 논문에서 훈민정음에서 언급한 고전(古篆)을 몽고전자(蒙古篆字)로 해석하며 한글이 파스파 문자에서 그 기하학적 모양을 차용했다고 주장했다.[28] 레드야드는 그 근거로 당시 조선의 궁에는 파스파 문자가 쓰이고 있었고, 집현전 학자 일부는 파스파 문자를 잘 알고 있었다는 점을 들며, 한글의 기본 자음은 ㄱ, ㄷ, ㅂ, ㅈ, ㄹ라고 제시했다. 308 | 309 | 레드야드에 따르면 이 다섯개의 글자는 그 모양이 단순화되어 파열음을 위한 가획을 할 수 있는 여지(ㅋ, ㅌ, ㅍ, ㅊ)를 만들어 냈다고 한다. 그는 전통적인 설명과는 다르게 비파열음 ㄴ, ㅁ, ㅅ은 기본자 ㄷ, ㅂ, ㅈ의 윗부분이 지워진 형태라 주장했다. 그는 ㅁ이 ㅂ의 윗부분을 지워서 파생되기는 쉽지만, ㅁ에서 ㅂ의 모양을 이끌어 내는 것은 불분명하다고 주장했다. 즉 다른 파열음과 같은 방법으로 파생되었다면 ㅂ의 모양은 ㅁ위에 한 획이 더해진 형태(ㄱ-ㅋ, ㄷ-ㅌ, ㅈ-ㅊ의 관계처럼)여야 한다는 것이다. 310 | 311 | ㆁ자의 유래에 대한 설명도 기존과 다르다. 많은 중국 단어는 ng으로 시작하는데 세종대왕 집권 시기 즈음의 중국에서는 앞에 나오는 ng는 [ŋ]으로 발음하거나 발음하지 않았으며, 이런 단어가 한국어로 차용되었을 경우에도 이는 묵음이었다. 또한 논리적으로 추론 가능한 ng음의 모양은 ㄱ에서 가로 획을 제한 모양인데, 이는 모음 ㅣ과 구분하기 힘들었을 것이다. 따라서 세종대왕은 가로 획을 제한 ㄱ에 묵음이라는 뜻의 ㅇ을 더해 ㆁ을 만들었을 것이라 주장한다. 이는 단어 중간 혹은 끝에서의 [ŋ]의 발음과 단어 처음 부분에서의 묵음을 상징적으로 나타낼 수 있는 것이었다. 312 | 313 | 중국어를 표기하기 위한 다른 글자는 ㅱ이었는데 훈민정음은 이를 微(미)의 초성이라 설명했다. 이는 중국 방언에 따라 m 혹은 w로 발음되는데 한글에서는 ㅁ([m])과 ㅇ의 조합(이에 대응되는 파스파 문자에서는 [w]로 발음한다)으로 만들어졌다. 파스파 문자에서 글자 밑에 환형의 모양을 그리는 것은 모음 뒤의 w를 의미했다. 레드야드는 ㅱ자의 'ㅇ'모양이 이 과정을 통해 만들어 졌다고 주장했다. 314 | 315 | 마지막 증거로 레드야드는 ㄷ의 좌측 상단에 작게 삐져나온 형상(입술 모양으로)은 파스파 문자의 d와 유사하다는 점을 들었다. 이러한 입술 모양은 티베트 문자의 d인 ད에서도 찾아볼 수 있다. 316 | 317 | 만약 레드야드의 이러한 기원설이 사실이라면 한글은 파스파 문자→티베트 문자→브라미 문자→아람 문자를 거쳐 결국 중동 페니키아 문자의 일족에 속하게 된다. 하지만 파스파문자는 세계의 다른 고대문자들처럼 상형문자일 뿐만 아니라 각 글자가 한가지의 음을 나타내지 않고, 그 문자를 사용하던 언어권에 따라 각기 다른 음을 가졌을 것이기 때문에 한글과 같이 소리를 표기하는 문자와의 상관관계는 레드야드 혼자만이 인정하고 있다. 318 | 319 | 이에 대해 2009년 국어학자 정광(鄭光)은 훈민정음이 36개 중국어 초성을 기본으로 하는 등 파스파 문자로부터 일부 영향을 받았지만 글자를 만든 원리가 서로 다르며, 자음과 모음을 분리하여 독창적으로 만든 문자라고 반론하였다.[29] 320 | 321 | 기타 한글과 유사하다고 주장하는 문자 322 | 생김새가 한글과 유사한 문자가 있어서 한글 이전의 고대문자에 영향을 받았다는 주장이 있는데, 우연히 닮은 경우이거나 신뢰할 수 없는 출처를 근거로 하고 있다고 설명된다. 323 | 324 | 가림토와 신대 문자 325 | 송호수는 1984년 《광장(廣場)》 1월호 기고문에서 〈천부경〉과 《환단고기》〈태백일사〉를 참조하여 한글이 단군 시대부터 있었고, 단군조선의 가림다문(加臨多文)에서 한글과 일본의 아히루 문자가 기원했다고 주장하였다.[30] 이에 대하여 국어학자 이근수는 《광장(廣場)》 2월호의 기고문을 통하여 과학적 논증이 없는 이상 추론일 뿐이며, 참조한 고서의 대부분이 야사임을 지적하였다.[31] 또한 가림토 문자는 《환단고기》의 저자로 의심되고 있는 이유립이 한글의 모(母)문자로 창작한 가공의 문자일 가능성이 높아[32] 이러한 주장은 역사학계 및 언어학계에서 인정받지 못하고 있다. 326 | 327 | 일본의 신대 문자 중에서도 모습이 한글과 비슷한 것이 있어 이를 가림토와 연관이 있다고 주장하기도 하나, 신대 문자가 새겨져 있는 비석마다 문자의 모습이 달라 일관성이 없고 언어학자들이 추정하는 고대 일본어의 음운 구조와도 맞지 않으며,[33] 신대 문자가 기록되었다고 하는 유물 거의 전부가 18~19세기의 것이고 에도 시대 전의 것을 찾을 수 없는바, 신대 문자라는 것은 고대 일본에 문자가 있었다고 주장하기 위한 에도 시대의 위작이며, 특히 그 중에 한글과 유사한 것들은 오히려 한글을 모방한 것임이 밝혀졌다.[34] 328 | 329 | 구자라트 문자 330 | 1983년 9월 KBS가 방영한 8부작 다큐멘터리 《신왕오천축국전》은[35] 구자라트 문자를 소개하면서 '자음은 ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅅ, ㅇ 등이고, 모음은 ㅏ, ㅑ, ㅓ, ㅕ, ㅗ, ㅛ, ㅜ, ㅠ, ㅡ, ㅣ의 열 자가 꼭 같았으며, 받침까지도 비슷하게 쓰고 있었다'고 주장하였다. 331 | 332 | 또한, 개천학회 회장 송호수[36]는 1984년 이를 인용하면서 '자음에서는 상당수가 같고, 모음은 10자가 꼭 같다는 것이다'라고 썼다. 그는 구자라트 문자가 가림토에서 비롯되었다고 주장했다.[37][38] 333 | 334 | 그러나 구자라트 문자는 문자 구성상 자모로 완전히 분리되는 한글과는 달리 모든 자음이 딸림 모음을 수반하는 아부기다이며, 데바나가리 문자에서 수직선을 제거한 데바나가리 파생문자로서 다른 인도계 여러 문자와 친족 관계가 명확하게 밝혀져 있기 때문에 이는 구자라트 문자의 특정 글자체와 한글 사이의 표면적 유사성에 대한 착오일 뿐이다.[39] 335 | 336 | 다른 언어에서의 한글 사용 337 | 한글은 2009년에 처음으로 인도네시아의 소수 민족인 찌아찌아족의 언어인 찌아찌아어를 표기하는데 사용되었다. 338 | 339 | 이밖에도 한국에서는 한글을 표기 문자로 보급하기 위하여 노력하고 있으며 2012년 솔로몬제도의 토착어를 한글로 표기하여 교육하는 활동이 시작됐다. 2012년 10월부터 시행된 것은 2개 언어이며 결과에 따라 솔로몬제도 전역으로 확대할 예정이다.[40] 340 | 341 | 간혹, 영어 발음을 정확하게 표기하기 위해 옛 한글 등을 부활시킨 표기법을 연구하는 경우도 있으나, 이 역시 개인 연구자에 의한 것이다. 그리고 한국인이 아닌 사람이 만든 인공어618-Vuro나 인공 문자 井卜文(Jingbu Script) 등에서 일부 한글 또는 한글을 모티브로 한 문자를 개인 수준에서 사용한 예를 볼 수 있다. 342 | 343 | 오해와 사실 344 | 이 부분의 본문은 한글에 대한 오해입니다. 345 | 유네스코의 세계기록유산에 등재된 것은 한글이 아니라, 책《훈민정음》(해례본)이다. 346 | 유네스코 세계기록유산은 기록물이 담고 있는 내용이 아니라 기록물 자체만을 등록 대상으로 한다. 347 | 실제의 한글은 모든 언어의 발음을 표기할 수 있는 것이 아니다. 또한, 현재의 한글은 창제 당시의 훈민정음보다 표현할 수 있는 발음 수가 적다. 348 | '모든 소리를 표현할 수 있다는 것'은 원래 언어학적 명제가 아니고, 창제 당시에 '모든 소리는 기본 5음의 조화로 이루어진다'는 사상을 배경으로 한 철학적 표현이다. 349 | 한글 낱자는 모두 소릿값이 확정되어 있고 실제 한글 쓰임에서는 모아쓰기의 규칙도 정해져 있으므로, 한글로 표현되는 소리의 숫자는 본래 유한하며, 한글은 기본적으로 한국어에 맞추어져 있다. 350 | 현재 한글은 한국어 발음에만 사용하고 있으나, 원래의 훈민정음에서는 모아쓰기가 좀 더 다양하며, 아울러 《동국정운》에 따르면 실제의 한국어 발음뿐만 아니라, 이론적인 한자음도 훈민정음으로써 표현하고 있다. 351 | 한글은 언어의 이름이 아니라 글자의 이름이다. 352 | 창제 당시의 이름인 '훈민정음'과 그 약칭인 '정음'도 본래 글자의 이름이었다. 353 | 찌아찌아족의 찌아찌아어의 표기에는 사용되나 공식은 아니다. 354 | 한글 자모일람 355 | 방언 한글 자모 356 | ㄰ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿ 357 | 358 | ㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏ 359 | 360 | ㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟ 361 | 362 | ㅠㅡㅢㅣㅤㅥㅦㅧㅨㅩㅪㅫㅬㅭㅮㅯ 363 | 364 | ㅰㅱㅲㅳㅴㅵㅶㅷㅸㅹㅺㅻㅼㅽㅾㅿ 365 | 366 | ㆀㆁㆂㆃㆄㆅㆆㆇㆈㆉㆊㆋㆌㆍㆎ㆏ 367 | 368 | 고문 한글 자모 369 | ᄀᄁᄂᄃᄄᄅᄆᄇᄈᄉᄊᄋᄌᄍᄎᄏ 370 | 371 | ᄐᄑᄒᄓᄔᄕᄖᄗᄘᄙᄚᄛᄜᄝᄞᄟ 372 | 373 | ᄠᄡᄢᄣᄤᄥᄦᄧᄨᄩᄪᄫᄬᄭᄮᄯ 374 | 375 | ᄰᄱᄲᄳᄴᄵᄶᄷᄸᄹᄺᄻᄼᄽᄾᄿ 376 | 377 | ᅐᅑᅒᅓᅔᅕᅖᅗᅘᅙᅚᅛᅜᅝᅞᅟ 378 | 379 | 복합원음와 보음 380 | ᅠᅡᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯ 381 | 382 | ᅰᅱᅲᅳᅴᅵᅶᅷᅸᅹᅺᅻᅼᅽᅾᅿ 383 | 384 | ᆀᆁᆂᆃᆄᆅᆆᆇᆈᆉᆊᆋᆌᆍᆎᆏ 385 | 386 | ᆐᆑᆒᆓᆔᆕᆖᆗᆘᆙᆚᆛᆜᆝᆞᆟ 387 | 388 | ᆠᆡᆢᆨᆩᆪᆫᆬᆭᆮᆯ 389 | 390 | ᆰᆱᆲᆳᆴᆵᆶᆷᆸᆹᆺᆻᆼᆽᆾᆿ 391 | 392 | ᇀᇁᇂᇃᇄᇅᇆᇇᇈᇉᇊᇋᇌᇍᇎᇏ 393 | 394 | ᇐᇑᇒᇓᇔᇕᇖᇗᇘᇙᇚᇛᇜᇝᇞᇟ 395 | 396 | ᇠᇡᇢᇣᇤᇥᇦᇧᇨᇩᇪᇫᇬᇭᇮᇯ 397 | 398 | ᇰᇱᇲᇳᇴᇵᇶᇷᇸᇹᇺᇻᇼᇽᇾᇿ 399 | 400 | 관련 항목 401 | 국어 402 | 세종대왕 403 | 주시경 404 | 한글의 우수성에 관한 논란 405 | 문자 406 | 한글 맞춤법 407 | 한글 낱자 408 | 한글 낱자 목록 409 | 한국어 410 | 한국어 로마자 표기법 411 | 한글전용과 국한문혼용 412 | 조선어 신철자법 413 | 옛 한글 414 | 훈민정음 415 | 한글날 416 | 한글 위키백과 417 | 한글의 모든 글자 418 | 한글을 표기하는 언어 목록 419 | 한국어 420 | 카리어 421 | 꽈라아에어[41] 422 | 찌아찌아어 423 | 각주 424 | 이것은 훈민정음 창제 당시부터 보인다. 예컨대, 《세종실록》은 훈민정음 창제를 上親制諺文二十八字…是謂訓民正音(주상께서 친히 언문 28자를 만들어 … 이것을 훈민정음이라 이른다)이라고 기록하는데, 이것은 한글의 이름이거나 또는 굳이 한글만 지칭한 것은 아니고 한자 이외의 문자를 통칭하는 표현이다. 예컨대 《순조실록(純祖實錄)》 9년 12월 2일 기사에 역관 현의순(玄義洵)이 대마도의 사정을 보고한 글 가운데 敎之以諺文名之曰假名(언문을 가르치는데, 그 이름을 일러 가나라고 한다)과 같은 문장이 있어, 일본 문자에 대해서도 언문이라는 표현이 사용됨을 볼 수 있다.) 또한 《세종실록》 28년 11월 8일자에 언문청이라는 한글을 보급하는 구실을 하는 기관 이름이 나온다. 425 | 한문을 지칭하는 ‘진서(眞書)’와 대비되는 표현이다. 426 | 諺文字母俗所謂反切二十七字(세간에서 이른바 반절 27자라고 하는 언문 자모). 최세진(崔世珍), 〈범례(凡例)〉, 《훈몽자회(訓蒙字會)》. 1527. 반절은 본래 2개의 한자로 다른 한자음을 표기하는 방법을 말하며, 이렇게 소리의 표기에 사용된 글자를 반절자(反切字)라고 한다. 당시 훈민정음이 이와 비슷한 용법으로 한자음 표기에도 사용되었기 때문에 반절이라고 불렸던 것으로 보인다. 427 | 1908년 설립한 ‘국어연구학회(國語硏究學會)’가 1911년 9월에 명칭을 바꾼 것으로, 공식적으로 한글과 한문 표기를 나란히 사용했다. 428 | ‘本會의 名稱을 한글모라 改稱하고 이 몯음을 세움몯음으로…’, 〈한글모세움몯음적발〉, 《한글모 죽보기》. 이규영. 1917. 429 | 한글풀이의 수록이 확인되는 것은 1914년 3월의 제7호부터 1914년 7월의 제11호까지 430 | 〈맞춤법〉, 《조선말규범집》. 북한(조선민주주의인민공화국) 내각직속 국어사정위원회. 1987. 431 | “훈민정음은 백성(百姓) 가르치시는 정(正)한 소리라”(현대어 표기로 옮김), 〈세종어제훈민정음〉, 《월인석보》. 1459년. 432 | 癸亥冬我殿下創制正音二十八字略揭例義以示之名曰訓民正音 (계해년 겨울, 우리 전하께서 정음 28자를 창제하시어, 간략하게 예를 들어 보이시고 이름을 훈민정음이라 하셨다). 정인지,〈서(序)〉, 《훈민정음》. 1446년. 433 | “上親制諺文二十八字…是謂訓民正音”, 《세종실록》 25년 12월. 434 | 정인지는 훈민정음을 지은 세종이 집현전 학자들에게 ‘해설서’의 편찬을 명했다고 적고 있다. 遂命詳加解釋以喩諸人…謹作諸解及例以敍其梗槪 (마침내, 해석을 상세히 더하여 사람들을 깨우치라고 명하시어… 여러 풀이와 예를 지어 그 개요를 펴내니), 정인지, 〈서〉, 《훈민정음》. 1446년. 435 | "錢下槍制', 《훈민정음 해례본》 436 | 〈훈민정음 친제론〉, 이기문, 《한국문화》제13집. 서울대학교 규장각 한국학연구원, 1992년. 437 | '라랴러려' 분청사기..."16세기 지방 하층민도 한글 사용".YTN.2011-09-08. 438 | [1] 439 | 第十四條 法律勅令總以國文爲本漢文附譯或用國漢文 440 | 第九條 法律命令은 다 國文으로써 本을 삼꼬 漢譯을 附하며 或國漢文을 混用홈 441 | 세계는 지금 '언어전쟁' 중 442 | “한글 홀대하는 사회”. 2012년 11월 30일에 원본 문서에서 보존된 문서. 2010년 2월 2일에 확인함. 443 | 솔로몬제도 일부 주(州)서 표기문자로 한글 채택 444 | 최현철 기자 (2010년 10월 20일). “‘천지인’ 개발자 특허권 기부 … 표준 제정 임박”. 중앙일보. 2012년 11월 15일에 확인함. 445 | [북녘말] 기윽 디읃 시읏 / 김태훈 : 칼럼 : 사설.칼럼 : 뉴스 : 한겨레 446 | 然ㄱㆁㄷㄴㅂㅁㅅㄹ八字可足用也如ㅂ·ㅣㅅ곶爲梨花여ㅿ의갗爲狐皮而ㅅ字可以通用故只用ㅅ字 ,훈민정음 해례 종성해 447 | 其字倣古篆分爲初中終聲合之然後乃成字 : (그 글자는 옛 전자(篆字)를 모방하고, 초·중·종성으로 나뉘어 그것을 합한 연후에 글자를 이룬다.) 《세종실록》 25년 12월 30일. 448 | 象形而字倣古篆因聲而音叶七調 (물건의 형상을 본떠서 글자는 고전을 모방하고, 소리(聲)로 인(因)하여 음(音)은 칠조(七調)에 맞아). 《세종실록》28년 9월 29일. 이 기사는 《훈민정음》의 정인지 〈서(序)〉를 옮겨 놓은 것이다. 449 | 글로벌세계대백과, 〈양반관료의 문화〉, 한글 창제. 450 | 이 문자들은 확실히 증명되진 않았다. 어떤 필사본에는 ꡜ h에서 파생된 저 세 글자 모두 아래쪽이 환형으로 되어있기도 하다. 451 | "The Korean Language Reform of 1446", Gari Ledyard. (1966) 452 | [Why] 세종대왕 한글 창제가 '표절' 누명 쓰고 있다고?, 《조선닷컴》, 2009.10.10. 453 | "한글은 檀君시대부터 있었다" 송호수 교수 주장에 學界관심, 《경향신문》, 1984.1.12. 454 | 한글 世宗전 創製 "터무니없다" 李覲洙교수, 宋鎬洙교수 主張 반박, 《경향신문》, 1984.2.6. 455 | 문명, 《만들어진 한국사》, 파란, 2010 456 | "日 神代文字는 한글의 僞作이다", 《경향신문》, 1985.7.17. 457 | MBC 다큐멘터리 “미스터리 한글, 해례6211의 비밀”, 2007. 10. 7. 방송 458 | 《新往五天竺國傳(신왕오천축국전)》. 문순태, KBS 특별취재반. 한국방송사업단, 1983. 참조 459 | 당시 보도에는 S베일러대 교수로 소개되었다. 460 | 〈한글은 세종 이전에도 있었다〉, 송호수,《廣場(광장)》1984년 1월호. 세계평화교수협의회. 461 | 일본 神代文字 논란[깨진 링크(과거 내용 찾기)], 충북대학 국어국문학과 국어학강의실 462 | 〈한글과 비슷한(?) 구자라트 문자〉, 김광해, 《새국어소식》1999년 10월호. 한국어문진흥회 463 | [2] 464 | 섬나라 솔로몬제도 2개주도 한글 쓴다 465 | 참고 문헌 466 | 〈한글〉, 《한국민족문화대백과》. 한국학중앙연구원. 467 | 읽을거리 468 | Portal icon 한국 포털 469 | Portal icon 문화 포털 470 | 한글날에 생각해보는 훈민정음 미스터리. 김현미.《신동아》 2006년 10월호. 471 | 히브리문자 기원설을 계기로 본 훈민정음. 이기혁. 《신동아》 1997년 5월호. 472 | 어느 기하학자의 한글 창제. 《동아일보》, 2007-10-19. (영어의 v, f, θ, ð, l 등의 발음을 한글로 표기하기 - 고등과학원 최재경 교수의 제안) 473 | '한글 연구가' 최성철씨 "이젠 한글표기법 독립운동할 때". 《동아일보》, 2006-10-09. 474 | 한글과 코드: 한글과 컴퓨터 코드에 관하여 475 | 외부 링크 476 | 위키미디어 공용에 관련된 미디어 자료와 분류가 있습니다. 477 | 한글 (분류) 478 | 위키낱말사전에 479 | 관련된 항목이 있습니다. 480 | 한글 481 | 디지털한글박물관 482 | 한글학회 483 | 한글재단 484 | 국립국어원 485 | 한글 듣기 테스트 486 | "한글 자음 쓰기 영상" - 유튜브 487 | "한글 모음 쓰기 영상" - 유튜브 488 | Heckert GNU white.svgCc.logo.circle.svg 이 문서에는 다음커뮤니케이션(현 카카오)에서 GFDL 또는 CC-SA 라이선스로 배포한 글로벌 세계대백과사전의 "양반관료의 문화" 항목을 기초로 작성된 글이 포함되어 있습니다. -------------------------------------------------------------------------------- /tool/README.txt: -------------------------------------------------------------------------------- 1 | To re-generate generated files run: 2 | ``` 3 | dart tool/generate.dart 4 | ``` 5 | 6 | To update to new data files, update the paths in `tool/src/data_files` 7 | if necessary, and run `dart tool.generate.dart -u -o`. 8 | The `-u` means read new files from `https://unicode.org/Public`, 9 | and `-o` means try to optimize table chunk sizes, 10 | which is most important when using new data files. 11 | 12 | -------------------------------------------------------------------------------- /tool/benchmark.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "package:characters/src/grapheme_clusters/breaks.dart"; 6 | import "package:characters/src/grapheme_clusters/constants.dart"; 7 | 8 | import "../test/src/text_samples.dart"; 9 | import "../test/src/unicode_grapheme_tests.dart"; 10 | import "../test/src/various_tests.dart"; 11 | 12 | // Low-level benchmark of the grapheme cluster step functions. 13 | 14 | void main(List args) { 15 | var count = 5; 16 | if (args.isNotEmpty) { 17 | count = int.parse(args[0]); 18 | } 19 | var gcsf = 0; 20 | var gcsb = 0; 21 | 22 | var text = genesis + 23 | hangul + 24 | genesis + 25 | diacretics + 26 | recJoin(splitTests + emojis + zalgo); 27 | var codeUnits = text.length; 28 | var codePoints = text.runes.length; 29 | for (var i = 0; i < count; i++) { 30 | gcsf = benchForward(text, i, codePoints, codeUnits); 31 | gcsb = benchBackward(text, i, codePoints, codeUnits); 32 | } 33 | print("gc: Grapheme Clusters, cp: Code Points, cu: Code Units."); 34 | if (gcsf != gcsb) { 35 | print("ERROR: Did not count the same number of grapheme clusters: " 36 | "$gcsf forward vs. $gcsb backward."); 37 | } else { 38 | print("Total: $gcsf gc, $codePoints cp, $codeUnits cu"); 39 | print("Avg ${(codePoints / gcsf).toStringAsFixed(3)} cp/gc"); 40 | print("Avg ${(codeUnits / gcsf).toStringAsFixed(3)} cu/gc"); 41 | } 42 | } 43 | 44 | String recJoin(List> texts) => 45 | texts.map((x) => x.join("")).join("\n"); 46 | 47 | int benchForward(String text, int i, int cp, int cu) { 48 | var n = 0; 49 | var gc = 0; 50 | var e = 0; 51 | var sw = Stopwatch()..start(); 52 | do { 53 | var breaks = Breaks(text, 0, text.length, stateSoTNoBreak); 54 | while (breaks.nextBreak() >= 0) { 55 | gc++; 56 | } 57 | e = sw.elapsedMilliseconds; 58 | n++; 59 | } while (e < 2000); 60 | print("Forward #$i: ${(gc / e).round()} gc/ms, " 61 | "${(n * cp / e).round()} cp/ms, " 62 | "${(n * cu / e).round()} cu/ms, " 63 | "$n rounds"); 64 | return gc ~/ n; 65 | } 66 | 67 | int benchBackward(String text, int i, int cp, int cu) { 68 | var n = 0; 69 | var gc = 0; 70 | var e = 0; 71 | var sw = Stopwatch()..start(); 72 | do { 73 | var breaks = BackBreaks(text, text.length, 0, stateEoTNoBreak); 74 | while (breaks.nextBreak() >= 0) { 75 | gc++; 76 | } 77 | e = sw.elapsedMilliseconds; 78 | n++; 79 | } while (e < 2000); 80 | print("Backward #$i: ${(gc / e).round()} gc/ms, " 81 | "${(n * cp / e).round()} cp/ms, " 82 | "${(n * cu / e).round()} cu/ms, " 83 | "$n rounds"); 84 | return gc ~/ n; 85 | } 86 | -------------------------------------------------------------------------------- /tool/bin/gentable.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:io"; 6 | import "dart:typed_data"; 7 | 8 | import "../src/args.dart"; 9 | import "../src/automaton_builder.dart"; 10 | import "../src/data_files.dart"; 11 | import "../src/grapheme_category_loader.dart"; 12 | import "../src/indirect_table.dart"; 13 | import "../src/shared.dart"; 14 | import "../src/string_literal_writer.dart"; 15 | import "../src/table_builder.dart"; 16 | 17 | // Generates tables used by the grapheme cluster breaking algorithm 18 | // and a state machine used to implement the algorithm. 19 | 20 | // The task of this tool is to take the complete table of 21 | // grapheme cluster categories for every code point (U+0000 ... U+10FFFF) 22 | // and build a smaller table with the same information. 23 | // 24 | // The approach taken is to split the large table into chunks, 25 | // smaller chunks for data in the BMP (U+0000 ... U+FFFF) 26 | // which have higher variation than later data, 27 | // and larger chunks for non-BMP ("astral" planes) code points. 28 | // This also corresponds to the split between one-UTF-16 code unit 29 | // character and surrogate pairs, which gives us a natural 30 | // branch in the string parsing code. 31 | // 32 | // Then a single table is built containing all these chunks, but where 33 | // the chunks are allowed to overlap. 34 | // An indirection table points to the start of each chunk 35 | // in the larger table. 36 | // 37 | // Having many small chunks increases the size of the indirection table, 38 | // and large chunks reduces the chance of chunks being completely 39 | // equivalent. 40 | // 41 | // The state machines are based on the extended Grapheme Cluster breaking 42 | // algorithm. The forward-scanning state machine is entirely regular. 43 | // The backwards-scanning state machine needs to call out to do look-ahead 44 | // in some cases (for example, how to combine a regional identifier 45 | // depends on whether there is an odd or even number of 46 | // previous regional identifiers.) 47 | 48 | /// Print more information to stderr while generating. 49 | /// 50 | /// Set while developing if you don't want to pass `-v` on the command line 51 | /// every time. 52 | /// Only affects this file when it's run directly. 53 | const defaultVerbose = false; 54 | 55 | /// Default location for table file. 56 | const tableFile = "lib/src/grapheme_clusters/table.dart"; 57 | 58 | // Best values found for current tables. 59 | // Update if better value found when updating data files. 60 | // (May consider benchmark performance as well as size.) 61 | 62 | // TODO: Write out best sizes to a file after an update, and read them back 63 | // next time, instead of hardcoding in the source file. 64 | 65 | // Chunk sizes must be powers of 2. 66 | const int defaultLowChunkSize = 64; 67 | 68 | // 512 gives best size by 431b and no discernible performance difference 69 | // from 1024 in benchmark. 70 | const int defaultHighChunkSize = 512; 71 | 72 | void main(List args) { 73 | var flags = parseArgs(args, "gentable", allowOptimize: true); 74 | var output = flags.dryrun 75 | ? null 76 | : flags.targetFile ?? File(path(packageRoot, tableFile)); 77 | 78 | if (output != null && !output.existsSync()) { 79 | try { 80 | output.createSync(recursive: true); 81 | } catch (e) { 82 | stderr.writeln("Cannot find or create file: ${output.path}"); 83 | stderr.writeln("Writing to stdout"); 84 | output = null; 85 | } 86 | } 87 | generateTables(output, 88 | update: flags.update, 89 | dryrun: flags.dryrun, 90 | verbose: flags.verbose, 91 | optimize: flags.optimize); 92 | } 93 | 94 | Future generateTables(File? output, 95 | {bool update = false, 96 | bool dryrun = false, 97 | bool optimize = false, 98 | bool verbose = defaultVerbose}) async { 99 | // Generate the category mapping for all Unicode code points. 100 | // This is the table we want to create an compressed version of. 101 | var table = await loadGraphemeCategories(update: update, verbose: verbose); 102 | if (update) { 103 | // Force license file update. 104 | await licenseFile.load(checkForUpdate: true); 105 | } 106 | 107 | var lowChunkSize = defaultLowChunkSize; 108 | var highChunkSize = defaultHighChunkSize; 109 | 110 | int optimizeTable( 111 | IndirectTable chunkTable, int lowChunkSize, int highChunkSize) { 112 | var index = 0; 113 | do { 114 | chunkTable.entries.add(TableEntry(0, index, lowChunkSize)); 115 | index += lowChunkSize; 116 | } while (index < 0x10000); 117 | var lowChunkCount = chunkTable.entries.length; 118 | do { 119 | chunkTable.entries.add(TableEntry(0, index, highChunkSize)); 120 | index += highChunkSize; 121 | } while (index < 0x110000); 122 | var highChunkCount = chunkTable.entries.length - lowChunkCount; 123 | assert(lowChunkCount * lowChunkSize + highChunkCount * highChunkSize == 124 | 0x110000); 125 | assert(chunkTable.chunks.length == 1); 126 | assert(_validate(table, chunkTable, lowChunkSize, highChunkSize, 127 | verbose: false)); 128 | 129 | chunkifyTable(chunkTable); 130 | assert(chunkTable.entries.length == lowChunkCount + highChunkCount); 131 | assert(_validate(table, chunkTable, lowChunkSize, highChunkSize, 132 | verbose: false)); 133 | 134 | combineChunkedTable(chunkTable); 135 | assert(chunkTable.entries.length == lowChunkCount + highChunkCount); 136 | assert(chunkTable.chunks.length == 1); 137 | assert(_validate(table, chunkTable, lowChunkSize, highChunkSize, 138 | verbose: false)); 139 | 140 | var size = chunkTable.chunks[0].length ~/ 2 + chunkTable.entries.length * 2; 141 | return size; 142 | } 143 | 144 | var chunkTable = IndirectTable([table.sublist(0, table.length)], []); 145 | var size = optimizeTable(chunkTable, lowChunkSize, highChunkSize); 146 | if (verbose) { 147 | stderr.writeln("Default chunk size: $lowChunkSize/$highChunkSize: $size"); 148 | } 149 | if (optimize) { 150 | // Chunk sizes must be powers of 2. 151 | // Smaller chunk sizes gives more smaller chunks, 152 | // with more chance of overlap, 153 | // but each chunks adds an entry to the index table. 154 | for (var low in [64, 128, 32, 256]) { 155 | for (var high in [512, 1024, 256, 2048]) { 156 | if (low == lowChunkSize && high == highChunkSize) continue; 157 | var newChunk = IndirectTable([table.sublist(0, table.length)], []); 158 | var newSize = optimizeTable(newChunk, low, high); 159 | if (verbose) { 160 | var delta = newSize - size; 161 | stderr.writeln("${size < newSize ? "Worse" : "Better"}" 162 | " chunk size: $low/$high: $newSize " 163 | "(${delta > 0 ? "+$delta" : delta})"); 164 | } 165 | if (newSize < size) { 166 | lowChunkSize = low; 167 | highChunkSize = high; 168 | chunkTable = newChunk; 169 | size = newSize; 170 | } 171 | } 172 | } 173 | if (verbose) { 174 | stderr.writeln("Best low chunk size: $lowChunkSize"); 175 | stderr.writeln("Best high chunk size: $highChunkSize"); 176 | stderr.writeln("Best table size: $size"); 177 | } 178 | } 179 | 180 | // Write the table and automaton to source. 181 | var buffer = StringBuffer(copyright) 182 | ..writeln("// Generated code. Do not edit.") 183 | ..writeln("// Generated from [${graphemeBreakPropertyData.sourceLocation}]" 184 | "(../../${graphemeBreakPropertyData.targetLocation})") 185 | ..writeln("// and [${emojiData.sourceLocation}]" 186 | "(../../${emojiData.targetLocation}).") 187 | ..writeln("// Licensed under the Unicode Inc. License Agreement") 188 | ..writeln("// (${licenseFile.sourceLocation}, " 189 | "../../third_party/${licenseFile.targetLocation})") 190 | ..writeln(); 191 | 192 | writeTables(buffer, chunkTable, lowChunkSize, highChunkSize, 193 | verbose: verbose); 194 | 195 | writeForwardAutomaton(buffer, verbose: verbose); 196 | buffer.writeln(); 197 | writeBackwardAutomaton(buffer, verbose: verbose); 198 | 199 | if (output == null) { 200 | stdout.write(buffer); 201 | } else { 202 | output.writeAsStringSync(buffer.toString()); 203 | } 204 | } 205 | 206 | // ----------------------------------------------------------------------------- 207 | // Combined table writing. 208 | void writeTables( 209 | StringSink out, IndirectTable table, int lowChunkSize, int highChunkSize, 210 | {required bool verbose}) { 211 | _writeNybbles(out, "_data", table.chunks[0], verbose: verbose); 212 | _writeStringLiteral(out, "_start", table.entries.map((e) => e.start).toList(), 213 | verbose: verbose); 214 | _writeLookupFunction(out, "_data", "_start", lowChunkSize); 215 | out.writeln(); 216 | _writeSurrogateLookupFunction( 217 | out, "_data", "_start", 65536 ~/ lowChunkSize, highChunkSize); 218 | out.writeln(); 219 | } 220 | 221 | void _writeStringLiteral(StringSink out, String name, List data, 222 | {required bool verbose}) { 223 | if (verbose) { 224 | stderr.writeln("Writing ${data.length} chars"); 225 | } 226 | var prefix = "const String $name = "; 227 | out.write(prefix); 228 | var writer = StringLiteralWriter(out, padding: 4, escape: _needsEscape); 229 | writer.start(prefix.length); 230 | for (var i = 0; i < data.length; i++) { 231 | writer.add(data[i]); 232 | } 233 | writer.end(); 234 | out.write(";\n"); 235 | } 236 | 237 | void _writeNybbles(StringSink out, String name, List data, 238 | {required bool verbose}) { 239 | if (verbose) { 240 | stderr.writeln("Writing ${data.length} nybbles"); 241 | } 242 | var prefix = "const String $name = "; 243 | out.write(prefix); 244 | var writer = StringLiteralWriter(out, padding: 4, escape: _needsEscape); 245 | writer.start(prefix.length); 246 | for (var i = 0; i < data.length - 1; i += 2) { 247 | var n1 = data[i]; 248 | var n2 = data[i + 1]; 249 | assert(0 <= n1 && n1 <= 15); 250 | assert(0 <= n2 && n2 <= 15); 251 | writer.add(n1 + n2 * 16); 252 | } 253 | if (data.length.isOdd) writer.add(data.last); 254 | writer.end(); 255 | out.write(";\n"); 256 | } 257 | 258 | bool _needsEscape(int codeUnit) => 259 | codeUnit > 0xff || codeUnit == 0x7f || codeUnit & 0x60 == 0; 260 | 261 | void _writeLookupFunction( 262 | StringSink out, String dataName, String startName, int chunkSize) { 263 | out.write(_lookupMethod("low", dataName, startName, chunkSize)); 264 | } 265 | 266 | void _writeSurrogateLookupFunction(StringSink out, String dataName, 267 | String startName, int startOffset, int chunkSize) { 268 | out.write(_lookupSurrogatesMethod( 269 | "high", dataName, startName, startOffset, chunkSize)); 270 | } 271 | 272 | String _lookupMethod( 273 | String name, String dataName, String startName, int chunkSize) => 274 | """ 275 | int $name(int codeUnit) { 276 | var chunkStart = $startName.codeUnitAt(codeUnit >> ${chunkSize.bitLength - 1}); 277 | var index = chunkStart + (codeUnit & ${chunkSize - 1}); 278 | var bit = index & 1; 279 | var pair = $dataName.codeUnitAt(index >> 1); 280 | return (pair >> 4) & -bit | (pair & 0xF) & (bit - 1); 281 | } 282 | """; 283 | 284 | String _lookupSurrogatesMethod(String name, String dataName, String startName, 285 | int startOffset, int chunkSize) => 286 | chunkSize == 1024 287 | ? """ 288 | int $name(int lead, int tail) { 289 | var chunkStart = $startName.codeUnitAt($startOffset + (0x3ff & lead)); 290 | var index = chunkStart + (0x3ff & tail); 291 | var bit = index & 1; 292 | var pair = $dataName.codeUnitAt(index >> 1); 293 | return (pair >> 4) & -bit | (pair & 0xF) & (bit - 1); 294 | } 295 | """ 296 | : """ 297 | int $name(int lead, int tail) { 298 | var offset = ((0x3ff & lead) << 10) | (0x3ff & tail); 299 | var chunkStart = $startName.codeUnitAt($startOffset + (offset >> ${chunkSize.bitLength - 1})); 300 | var index = chunkStart + (offset & ${chunkSize - 1}); 301 | var bit = index & 1; 302 | var pair = $dataName.codeUnitAt(index >> 1); 303 | return (pair >> 4) & -bit | (pair & 0xF) & (bit - 1); 304 | } 305 | """; 306 | 307 | // ----------------------------------------------------------------------------- 308 | bool _validate(Uint8List table, IndirectTable indirectTable, int lowChunkSize, 309 | int highChunkSize, 310 | {required bool verbose}) { 311 | var lowChunkCount = 65536 ~/ lowChunkSize; 312 | var lowChunkShift = lowChunkSize.bitLength - 1; 313 | var lowChunkMask = lowChunkSize - 1; 314 | for (var i = 0; i < 65536; i++) { 315 | var value = table[i]; 316 | var entryIndex = i >> lowChunkShift; 317 | var entry = indirectTable.entries[entryIndex]; 318 | var indirectValue = indirectTable.chunks[entry.chunkNumber] 319 | [entry.start + (i & lowChunkMask)]; 320 | if (value != indirectValue) { 321 | stderr.writeln("$entryIndex: $entry"); 322 | stderr.writeln('Error: ${i.toRadixString(16)} -> Expected $value,' 323 | ' was $indirectValue'); 324 | printIndirectTable(indirectTable); 325 | return false; 326 | } 327 | } 328 | var highChunkShift = highChunkSize.bitLength - 1; 329 | var highChunkMask = highChunkSize - 1; 330 | for (var i = 0x10000; i < 0x110000; i++) { 331 | var j = i - 0x10000; 332 | var value = table[i]; 333 | var entryIndex = lowChunkCount + (j >> highChunkShift); 334 | var entry = indirectTable.entries[entryIndex]; 335 | var indirectValue = indirectTable.chunks[entry.chunkNumber] 336 | [entry.start + (j & highChunkMask)]; 337 | if (value != indirectValue) { 338 | stderr.writeln("$entryIndex: $entry"); 339 | stderr.writeln('Error: ${i.toRadixString(16)} -> Expected $value,' 340 | ' was $indirectValue'); 341 | printIndirectTable(indirectTable); 342 | return false; 343 | } 344 | } 345 | if (verbose) { 346 | stderr.writeln("Table validation success"); 347 | } 348 | return true; 349 | } 350 | 351 | void printIndirectTable(IndirectTable table) { 352 | stderr.writeln("IT(chunks: ${table.chunks.map((x) => "#${x.length}")}," 353 | " entries: ${table.entries}"); 354 | } 355 | -------------------------------------------------------------------------------- /tool/bin/gentest.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:io"; 6 | 7 | import "../src/args.dart"; 8 | import "../src/data_files.dart"; 9 | import "../src/shared.dart"; 10 | import "../src/string_literal_writer.dart"; 11 | 12 | // Generates tests for grapheme cluster splitting from the Unicode 13 | // GraphemeBreakTest.txt file. 14 | // 15 | // Fetches the data files from the Unicode web site. 16 | 17 | const defaultVerbose = false; 18 | 19 | const testFile = "test/src/unicode_grapheme_tests.dart"; 20 | 21 | void main(List args) async { 22 | var flags = parseArgs(args, "gentest"); 23 | 24 | var output = flags.dryrun 25 | ? null 26 | : flags.targetFile ?? File(path(packageRoot, testFile)); 27 | 28 | if (output != null && !output.existsSync()) { 29 | try { 30 | output.createSync(recursive: true); 31 | } catch (e) { 32 | stderr.writeln("Cannot find or create file: ${output.path}"); 33 | stderr.writeln("Writing to stdout"); 34 | output = null; 35 | } 36 | } 37 | 38 | await generateTests(output, 39 | update: flags.update, verbose: flags.verbose, dryrun: flags.dryrun); 40 | } 41 | 42 | Future generateTests(File? output, 43 | {bool update = false, 44 | bool dryrun = false, 45 | bool verbose = defaultVerbose}) async { 46 | var buffer = StringBuffer(copyright) 47 | ..writeln("// Generated code. Do not edit.") 48 | ..writeln("// Generated from [${graphemeTestData.sourceLocation}]" 49 | "(../../${graphemeTestData.targetLocation})") 50 | ..writeln("// and [${emojiTestData.sourceLocation}]" 51 | "(../../${emojiTestData.targetLocation}).") 52 | ..writeln("// Licensed under the Unicode Inc. License Agreement") 53 | ..writeln("// (${licenseFile.sourceLocation}, " 54 | "../../third_party/${licenseFile.targetLocation})") 55 | ..writeln("// ignore_for_file: lines_longer_than_80_chars") 56 | ..writeln(); 57 | 58 | var texts = await Future.wait([ 59 | graphemeTestData.load(checkForUpdate: update), 60 | emojiTestData.load(checkForUpdate: update) 61 | ]); 62 | if (update) { 63 | // Force license file update. 64 | await licenseFile.load(checkForUpdate: true); 65 | } 66 | { 67 | buffer 68 | ..writeln("// Grapheme cluster tests.") 69 | ..writeln("const List> splitTests = ["); 70 | var test = texts[0]; 71 | var lineRE = RegExp(r"^(÷.*?)#", multiLine: true); 72 | var tokensRE = RegExp(r"[÷×]|[\dA-F]+"); 73 | var writer = StringLiteralWriter(buffer, lineLength: 9999, escape: _escape); 74 | for (var line in lineRE.allMatches(test)) { 75 | var tokens = tokensRE.allMatches(line[0]!).map((x) => x[0]!).toList(); 76 | assert(tokens.first == "÷"); 77 | assert(tokens.last == "÷"); 78 | 79 | var parts = >[]; 80 | var chars = []; 81 | for (var i = 1; i < tokens.length; i += 2) { 82 | var cp = int.parse(tokens[i], radix: 16); 83 | chars.add(cp); 84 | if (tokens[i + 1] == "÷") { 85 | parts.add(chars); 86 | chars = []; 87 | } 88 | } 89 | buffer.write(" ["); 90 | for (var i = 0; i < parts.length; i++) { 91 | if (i > 0) buffer.write(", "); 92 | writer.start(0); 93 | parts[i].forEach(writer.add); 94 | writer.end(); 95 | } 96 | buffer.writeln("],"); 97 | } 98 | buffer.writeln("];"); 99 | } 100 | { 101 | buffer 102 | ..writeln("// Emoji tests.") 103 | ..writeln("const List> emojis = ["); 104 | // Emojis 105 | var emojis = texts[1]; 106 | var lineRE = RegExp(r"^([ \dA-F]*?);", multiLine: true); 107 | var tokensRE = RegExp(r"[\dA-F]+"); 108 | var writer = StringLiteralWriter(buffer, lineLength: 9999, escape: _escape); 109 | for (var line in lineRE.allMatches(emojis)) { 110 | buffer.write(" ["); 111 | writer.start(); 112 | for (var token in tokensRE.allMatches(line[1]!)) { 113 | var value = int.parse(token[0]!, radix: 16); 114 | writer.add(value); 115 | } 116 | writer.end(); 117 | buffer.writeln("],"); 118 | } 119 | buffer.writeln("];"); 120 | } 121 | if (dryrun || output == null) { 122 | stdout.write(buffer); 123 | } else { 124 | if (verbose) { 125 | stderr.writeln("Writing ${output.path}"); 126 | } 127 | output.writeAsStringSync(buffer.toString()); 128 | } 129 | } 130 | 131 | bool _escape(int cp) => cp > 0xff || cp & 0x60 == 0 || cp == 0x7f; 132 | -------------------------------------------------------------------------------- /tool/generate.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:io" show File; 6 | 7 | import "bin/gentable.dart" show generateTables, tableFile; 8 | import "bin/gentest.dart" show generateTests, testFile; 9 | import "src/args.dart"; 10 | import "src/shared.dart"; 11 | 12 | /// Generates both tests and tables. 13 | /// 14 | /// Use this tool for updates, and `bin/gentable.dart` and `bin/gentest.dart` 15 | /// mainly during development. 16 | void main(List args) async { 17 | var flags = 18 | parseArgs(args, "generate", allowOptimize: true, allowFile: false); 19 | await generateTables(File(path(packageRoot, tableFile)), 20 | optimize: flags.optimize, 21 | update: flags.update, 22 | dryrun: flags.dryrun, 23 | verbose: flags.verbose); 24 | await generateTests(File(path(packageRoot, testFile)), 25 | update: flags.update, dryrun: flags.dryrun, verbose: flags.verbose); 26 | } 27 | -------------------------------------------------------------------------------- /tool/src/args.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Very primitive arguments parser for the generator commands. 6 | 7 | import "dart:io"; 8 | 9 | class Flags { 10 | final bool verbose; 11 | final bool update; 12 | final bool dryrun; 13 | final bool optimize; 14 | final File? targetFile; 15 | Flags(this.targetFile, 16 | {required this.update, 17 | required this.dryrun, 18 | required this.verbose, 19 | required this.optimize}); 20 | } 21 | 22 | Flags parseArgs(List args, String toolName, 23 | {bool allowOptimize = false, bool allowFile = true}) { 24 | var update = false; 25 | var dryrun = false; 26 | var verbose = false; 27 | var optimize = false; 28 | File? output; 29 | for (var arg in args) { 30 | if (arg == "-h" || arg == "--help") { 31 | stderr 32 | ..writeln( 33 | "Usage: $toolName.dart [-u] ${allowOptimize ? "[-i|-o] " : ""}[-n]" 34 | "${allowFile ? " " : ""}") 35 | ..writeln("-h | --help : Print this help and exit") 36 | ..writeln("-u | --update : Fetch new data files") 37 | ..writeln( 38 | "-n | --dryrun : Write to stdout instead of target file"); 39 | if (allowOptimize) { 40 | stderr.writeln( 41 | "-o | -i | --optimize : Optimize size parameters for tables"); 42 | } 43 | stderr.writeln("-v | --verbose : Print more information"); 44 | if (allowFile) { 45 | stderr.writeln("If no target file is given, writes to stdout."); 46 | } 47 | exit(0); 48 | } else if (arg == "-u" || arg == "--update") { 49 | update = true; 50 | } else if (arg == "-n" || arg == "--dryrun") { 51 | dryrun = true; 52 | } else if (arg == "-v" || arg == "--verbose") { 53 | verbose = true; 54 | } else if (allowOptimize && arg == "-o" || 55 | arg == "-i" || 56 | arg.startsWith("--opt")) { 57 | // Try to find a better size for the table. 58 | // No need to do this unless the representation changes or 59 | // the input tables are updated. 60 | // The current value is optimal for the data and representation used. 61 | optimize = true; 62 | } else if (arg.startsWith("-") || !allowFile) { 63 | stderr.writeln("Unrecognized flag: $arg"); 64 | } else { 65 | output = File(arg); 66 | } 67 | } 68 | return Flags(output, 69 | update: update, dryrun: dryrun, verbose: verbose, optimize: optimize); 70 | } 71 | -------------------------------------------------------------------------------- /tool/src/atsp.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // See: Asymmetric Traveling Salesman Problem. 6 | 7 | // Strategy for finding optimal overlapping of chunks of a larger table, 8 | // to save space. 9 | // Does so by solving traveling salesman/hamiltonian cycle in a graph 10 | // where the distance between chunks is how little they overlap 11 | // (chunk length minus overlap size). 12 | 13 | /// An asymmetric weighted complete graph. 14 | /// 15 | /// The vertices are identified by numbers 0 through [vertexCount] - 1. 16 | /// Edges are pairs of vertices. 17 | class Graph { 18 | /// Number of vertices. 19 | final int vertexCount; 20 | 21 | /// Table of weights, a list of length `vertexCount`*`vertexCount`. 22 | final List _table; 23 | 24 | /// Creates a new complete graph with [vertexCount] vertices. 25 | /// 26 | /// The initial weights on all edges are [initialWeight]. 27 | Graph(this.vertexCount, [int initialWeight = 0]) 28 | : _table = List.filled(vertexCount * vertexCount, initialWeight); 29 | 30 | /// Update the weight on the edges from [fromVertex] to [toVertex]. 31 | void setWeight(int fromVertex, int toVertex, int newWeight) { 32 | _table[fromVertex * vertexCount + toVertex] = newWeight; 33 | } 34 | 35 | /// The weight of the edge from [fromVertex] to [toVertex]. 36 | int weight(int fromVertex, int toVertex) => 37 | _table[fromVertex * vertexCount + toVertex]; 38 | 39 | /// The cumulative weight of the (sub-)path from `path[from]` to `path[to]`. 40 | /// 41 | /// If [to] is less than [from], the sub-path is traversed in reverse. 42 | /// The values in `path` should be vertices in this graph. 43 | int pathWeight(List path, int from, int to) { 44 | var weight = 0; 45 | var cursor = path[from]; 46 | var step = from <= to ? 1 : -1; 47 | for (var i = from; i != to;) { 48 | i += step; 49 | var next = path[i]; 50 | weight += this.weight(cursor, next); 51 | cursor = next; 52 | } 53 | return weight; 54 | } 55 | 56 | int get maxWeight => _table.reduce((a, b) => a >= b ? a : b); 57 | } 58 | 59 | /// Optimize a cycle of a graph to minimize the edge weight. 60 | /// 61 | /// The [cycle] must have the same node as first and last element. 62 | /// 63 | /// This is an implementation of one step of 3-opt, a simple algorithm to 64 | /// approximate an asymmetric traveling salesman problem (ATSP). 65 | /// It splits the cycle into three parts and then find the best recombination 66 | /// of the parts, each potentially reversed. 67 | bool opt3(Graph graph, List cycle) { 68 | // Perhaps optimize the weight computations by creating 69 | // a single array of cumulative weights, so any range can be computed 70 | // as a difference between two points in that array. 71 | for (var i = 1; i < cycle.length; i++) { 72 | // Find three cut points in the cycle, A|B, C|D, and E|F, 73 | // then find the cumulative weights of each section 74 | // B-C, C-D, and E-A, in both directions, as well as the 75 | // weight between the end-points. 76 | // 77 | // with Z being used to represent the start/end of the list 78 | // representation (so the A-F/F-A ranges cross over the cycle 79 | // representation edges) 80 | // Find the weights 81 | var nodeA = cycle[i - 1]; 82 | var nodeB = cycle[i]; 83 | // Weight of one-step transition from A to B. 84 | var wAB = graph.weight(nodeA, nodeB); 85 | var wBA = graph.weight(nodeB, nodeA); 86 | // Weight of entire path for start to A. 87 | var pZA = graph.pathWeight(cycle, 0, i - 1); 88 | var pAZ = graph.pathWeight(cycle, i - 1, 0); 89 | for (var j = i + 1; j < cycle.length; j++) { 90 | var nodeC = cycle[j - 1]; 91 | var nodeD = cycle[j]; 92 | var wAC = graph.weight(nodeA, nodeC); 93 | var wCA = graph.weight(nodeC, nodeA); 94 | var wAD = graph.weight(nodeA, nodeD); 95 | var wDA = graph.weight(nodeD, nodeA); 96 | var wBD = graph.weight(nodeB, nodeD); 97 | var wDB = graph.weight(nodeD, nodeB); 98 | var wCD = graph.weight(nodeC, nodeD); 99 | var wDC = graph.weight(nodeD, nodeC); 100 | var pBC = graph.pathWeight(cycle, i, j - 1); 101 | var pCB = graph.pathWeight(cycle, j - 1, i); 102 | for (var k = j + 1; k < cycle.length; k++) { 103 | var nodeE = cycle[k - 1]; 104 | var nodeF = cycle[k]; 105 | var wAE = graph.weight(nodeA, nodeE); 106 | var wEA = graph.weight(nodeE, nodeA); 107 | var wBE = graph.weight(nodeB, nodeE); 108 | var wEB = graph.weight(nodeE, nodeB); 109 | var wCE = graph.weight(nodeC, nodeE); 110 | var wEC = graph.weight(nodeE, nodeC); 111 | var wBF = graph.weight(nodeB, nodeF); 112 | var wFB = graph.weight(nodeF, nodeB); 113 | var wCF = graph.weight(nodeC, nodeF); 114 | var wFC = graph.weight(nodeF, nodeC); 115 | var wEF = graph.weight(nodeE, nodeF); 116 | var wFE = graph.weight(nodeF, nodeE); 117 | var wDF = graph.weight(nodeD, nodeF); 118 | var wFD = graph.weight(nodeF, nodeD); 119 | var pDE = graph.pathWeight(cycle, j, k - 1); 120 | var pED = graph.pathWeight(cycle, k - 1, j); 121 | var pFA = graph.pathWeight(cycle, k, cycle.length - 1) + pZA; 122 | var pAF = graph.pathWeight(cycle, cycle.length - 1, k) + pAZ; 123 | 124 | // Find best recombination of the three sections B-C, D-E, F-A, 125 | // with each possibly reversed. 126 | // Since there are only two ways to order three-element cycles, 127 | // and three parts that can be reversed, this gives 16 combinations. 128 | var wABCDEF = pFA + wAB + pBC + wCD + pDE + wEF; 129 | var wACBDEF = pFA + wAC + pCB + wBD + pDE + wEF; 130 | var wABCEDF = pFA + wAB + pBC + wCE + pED + wDF; 131 | var wACBEDF = pFA + wAC + pCB + wBE + pED + wDF; 132 | var wFBCDEA = pAF + wFB + pBC + wCD + pDE + wEA; 133 | var wFCBDEA = pAF + wFC + pCB + wBD + pDE + wEA; 134 | var wFBCEDA = pAF + wFB + pBC + wCE + pED + wDA; 135 | var wFCBEDA = pAF + wFC + pCB + wBE + pED + wDA; 136 | var wADEBCF = pFA + wAD + pDE + wEB + pBC + wCF; 137 | var wADECBF = pFA + wAD + pDE + wEC + pCB + wBF; 138 | var wAEDBCF = pFA + wAE + pED + wDB + pBC + wCF; 139 | var wAEDCBF = pFA + wAE + pED + wDC + pCB + wBF; 140 | var wFDEBCA = pAF + wFD + pDE + wEB + pBC + wCA; 141 | var wFDECBA = pAF + wFD + pDE + wEC + pCB + wBA; 142 | var wFEDBCA = pAF + wFE + pED + wDB + pBC + wCA; 143 | var wFEDCBA = pAF + wFE + pED + wDC + pCB + wBA; 144 | var best = min([ 145 | wABCDEF, 146 | wACBDEF, 147 | wABCEDF, 148 | wACBEDF, 149 | wFBCDEA, 150 | wFCBDEA, 151 | wFBCEDA, 152 | wFCBEDA, 153 | wADEBCF, 154 | wADECBF, 155 | wAEDBCF, 156 | wAEDCBF, 157 | wFDEBCA, 158 | wFDECBA, 159 | wFEDBCA, 160 | wFEDCBA 161 | ]); 162 | if (best < wABCDEF) { 163 | // Reorder and reverse to match the (or a) best solution. 164 | if (best == wACBDEF) { 165 | _reverse(cycle, i, j - 1); 166 | } else if (best == wABCEDF) { 167 | _reverse(cycle, j, k - 1); 168 | } else if (best == wACBEDF) { 169 | _reverse(cycle, i, j - 1); 170 | _reverse(cycle, j, k - 1); 171 | } else if (best == wFBCDEA) { 172 | _reverse(cycle, i, k - 1); 173 | _reverse(cycle, 0, cycle.length - 1); 174 | } else if (best == wFCBDEA) { 175 | _reverse(cycle, i, j - 1); 176 | _reverse(cycle, i, k - 1); 177 | _reverse(cycle, 0, cycle.length - 1); 178 | } else if (best == wFBCEDA) { 179 | _reverse(cycle, j, k - 1); 180 | _reverse(cycle, i, k - 1); 181 | _reverse(cycle, 0, cycle.length - 1); 182 | } else if (best == wFCBEDA) { 183 | _reverse(cycle, i, j - 1); 184 | _reverse(cycle, j, k - 1); 185 | _reverse(cycle, i, k - 1); 186 | _reverse(cycle, 0, cycle.length - 1); 187 | } else if (best == wADEBCF) { 188 | _reverse(cycle, i, j - 1); 189 | _reverse(cycle, j, k - 1); 190 | _reverse(cycle, i, k - 1); 191 | } else if (best == wADECBF) { 192 | _reverse(cycle, j, k - 1); 193 | _reverse(cycle, i, k - 1); 194 | } else if (best == wAEDBCF) { 195 | _reverse(cycle, i, j - 1); 196 | _reverse(cycle, i, k - 1); 197 | } else if (best == wAEDCBF) { 198 | _reverse(cycle, i, k - 1); 199 | } else if (best == wFDEBCA) { 200 | _reverse(cycle, i, j - 1); 201 | _reverse(cycle, j, k - 1); 202 | _reverse(cycle, 0, cycle.length - 1); 203 | } else if (best == wFDECBA) { 204 | _reverse(cycle, j, k - 1); 205 | _reverse(cycle, 0, cycle.length - 1); 206 | } else if (best == wFEDBCA) { 207 | _reverse(cycle, i, j - 1); 208 | _reverse(cycle, 0, cycle.length - 1); 209 | } else if (best == wFEDCBA) { 210 | _reverse(cycle, 0, cycle.length - 1); 211 | } else { 212 | throw AssertionError("Unreachable"); 213 | } 214 | return true; 215 | } 216 | } 217 | } 218 | } 219 | return false; 220 | } 221 | 222 | /// Reverses a slice of a list. 223 | void _reverse(List list, int from, int to) { 224 | while (from < to) { 225 | var tmp = list[from]; 226 | list[from] = list[to]; 227 | list[to] = tmp; 228 | from++; 229 | to--; 230 | } 231 | } 232 | 233 | int min(List values) { 234 | var result = values[0]; 235 | for (var i = 1; i < values.length; i++) { 236 | var value = values[i]; 237 | if (value < result) result = value; 238 | } 239 | return result; 240 | } 241 | -------------------------------------------------------------------------------- /tool/src/automaton_builder.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:io"; 6 | import "dart:typed_data"; 7 | 8 | import "package:characters/src/grapheme_clusters/constants.dart"; 9 | 10 | import "string_literal_writer.dart"; 11 | 12 | // Builder for state automata used to find 13 | // next/previous grapheme cluster break. 14 | 15 | // The automaton states are described below, and the code builds tables 16 | // for those automatons, then writes the table bytes as a string literal. 17 | 18 | ////////////////////////////////////////////////////////////////////////////// 19 | // Transition table for grapheme cluster break automaton. 20 | // For each previous state and each input character category, 21 | // emit a new state and whether to break before that input character. 22 | // The table uses `!` to mark a break before the input character, 23 | // and then the output state. 24 | // 25 | // We do not care that there is no break between a start-of-text and 26 | // and end-of-text (and empty text). We could handle that with one extra 27 | // state, but it will never matter for the code using this table. 28 | // 29 | // Cat : State 30 | // : SoT Brk CR Otr Pre L V T Pic PicZ Reg SoTN : 31 | // --------------------------------------------------------------------- 32 | // Other: !Otr !Otr !Otr !Otr Otr !Otr !Otr !Otr !Otr !Otr !Otr Otr : 33 | // CR : !CR !CR !CR !CR !CR !CR !CR !CR !CR !CR !CR CR : 34 | // LF : !Brk !Brk Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk Brk : 35 | // Ctrl : !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk Brk : 36 | // Ext : !Otr !Otr !Otr Otr Otr Otr Otr Otr Pic Otr Otr Otr : 37 | // ZWJ : !Otr !Otr !Otr Otr Otr Otr Otr Otr PicZ Otr Otr Otr : 38 | // Spac : !Otr !Otr !Otr Otr Otr Otr Otr Otr Otr Otr Otr Otr : 39 | // Prep : !Pre !Pre !Pre !Pre Pre !Pre !Pre !Pre !Pre !Pre !Pre Pre : 40 | // Reg : !Reg !Reg !Reg !Reg Reg !Reg !Reg !Reg !Reg !Reg Otr Reg : 41 | // L : !L !L !L !L L L !L !L !L !L !L L : 42 | // V : !V !V !V !V V V V !V !V !V !V V : 43 | // T : !T !T !T !T T !T T T !T !T !T T : 44 | // LV : !V !V !V !V V V !V !V !V !V !V V : 45 | // LVT : !T !T !T !T T T !T !T !T !T !T T : 46 | // Pic : !Pic !Pic !Pic !Pic Pic !Pic !Pic !Pic !Pic Pic !Pic Pic : 47 | // EoT : - ! - ! - ! - ! - ! - ! - ! - ! - ! - ! - - : 48 | 49 | void writeForwardAutomaton(StringSink buffer, {required bool verbose}) { 50 | assert(categories.length == 16); 51 | var table = Uint8List(states.length * categories.length); 52 | void transition(int state, int category, int newState, bool breakBefore) { 53 | table[state + category] = newState | (breakBefore ? 0 : stateNoBreak); 54 | } 55 | 56 | for (var state in states) { 57 | transition(state, categoryOther, stateOther, 58 | state != statePrepend && state != stateSoTNoBreak); 59 | transition(state, categoryCR, stateCR, state != stateSoTNoBreak); 60 | transition(state, categoryLF, stateBreak, 61 | state != stateCR && state != stateSoTNoBreak); 62 | transition(state, categoryControl, stateBreak, state != stateSoTNoBreak); 63 | if (state != statePictographic) { 64 | transition(state, categoryExtend, stateOther, state <= stateCR); 65 | transition(state, categoryZWJ, stateOther, state <= stateCR); 66 | } else { 67 | transition(state, categoryExtend, statePictographic, false); 68 | transition(state, categoryZWJ, statePictographicZWJ, false); 69 | } 70 | if (state != stateRegionalSingle) { 71 | transition(state, categoryRegionalIndicator, stateRegionalSingle, 72 | state != statePrepend && state != stateSoTNoBreak); 73 | } else { 74 | transition(state, categoryRegionalIndicator, stateOther, false); 75 | } 76 | transition(state, categoryPrepend, statePrepend, 77 | state != statePrepend && state != stateSoTNoBreak); 78 | transition(state, categorySpacingMark, stateOther, state <= stateCR); 79 | transition(state, categoryL, stateL, 80 | state != statePrepend && state != stateL && state != stateSoTNoBreak); 81 | transition(state, categoryLV, stateV, 82 | state != statePrepend && state != stateL && state != stateSoTNoBreak); 83 | transition(state, categoryLVT, stateT, 84 | state != statePrepend && state != stateL && state != stateSoTNoBreak); 85 | transition( 86 | state, 87 | categoryV, 88 | stateV, 89 | state != statePrepend && 90 | state != stateL && 91 | state != stateV && 92 | state != stateSoTNoBreak); 93 | transition( 94 | state, 95 | categoryT, 96 | stateT, 97 | state != statePrepend && 98 | state != stateV && 99 | state != stateT && 100 | state != stateSoTNoBreak); 101 | transition( 102 | state, 103 | categoryPictographic, 104 | statePictographic, 105 | state != statePrepend && 106 | state != statePictographicZWJ && 107 | state != stateSoTNoBreak); 108 | transition(state, categoryEoT, stateSoTNoBreak, 109 | state != stateSoT && state != stateSoTNoBreak); 110 | } 111 | var stringWriter = StringLiteralWriter(buffer, padding: 4); 112 | buffer.write("const _stateMachine = "); 113 | stringWriter.start("const _stateMachine = ".length); 114 | for (var i = 0; i < table.length; i++) { 115 | stringWriter.add(table[i]); 116 | } 117 | stringWriter.end(); 118 | buffer.write(";\n"); 119 | buffer.write(_moveMethod); 120 | 121 | if (verbose) _writeForwardTable(table); 122 | } 123 | 124 | const String _moveMethod = """ 125 | int move(int state, int inputCategory) => 126 | _stateMachine.codeUnitAt((state & 0xF0) | inputCategory); 127 | """; 128 | 129 | const String _moveBackMethod = """ 130 | int moveBack(int state, int inputCategory) => 131 | _backStateMachine.codeUnitAt((state & 0xF0) | inputCategory); 132 | """; 133 | 134 | const states = [ 135 | stateSoT, 136 | stateBreak, 137 | stateCR, 138 | stateOther, 139 | statePrepend, 140 | stateL, 141 | stateV, 142 | stateT, 143 | statePictographic, 144 | statePictographicZWJ, 145 | stateRegionalSingle, 146 | stateSoTNoBreak, 147 | ]; 148 | 149 | const categories = [ 150 | categoryOther, 151 | categoryCR, 152 | categoryLF, 153 | categoryControl, 154 | categoryExtend, 155 | categoryZWJ, 156 | categoryRegionalIndicator, 157 | categoryPrepend, 158 | categorySpacingMark, 159 | categoryL, 160 | categoryV, 161 | categoryT, 162 | categoryLV, 163 | categoryLVT, 164 | categoryPictographic, 165 | categoryEoT, 166 | ]; 167 | 168 | ////////////////////////////////////////////////////////////////////////////// 169 | // Transition table for *reverse* grapheme cluster break automaton. 170 | // For each previous state and each previous input character category, 171 | // emit a new state and whether to break after that input character. 172 | // The table uses `!` to mark a break before the input character, 173 | // and then the output state. 174 | // Some breaks cannot be determined without look-ahead. Those return 175 | // specially marked states, with `$` in the name. 176 | // Those states will trigger a special code path which will then update 177 | // the state and/or index as necessary. 178 | // Cat : State: 179 | // : EoT Brk LF Otr Ext L V T Pic LA Reg EoTN RegE : 180 | // -------------------------------------------------------------------------- 181 | // Other: !Otr !Otr !Otr !Otr Otr !Otr !Otr !Otr !Otr - !Otr Otr !Otr : 182 | // LF : !LF !LF !LF !LF !LF !LF !LF !LF !LF - !LF LF !LF : 183 | // CR : !Brk !Brk Brk !Brk !Brk !Brk !Brk !Brk !Brk - !Brk Brk !Brk : 184 | // Ctrl : !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk !Brk - !Brk Brk !Brk : 185 | // Ext : !Ext !Ext !Ext !Ext Ext !Ext !Ext !Ext !Ext LA !Ext Ext !Ext : 186 | // ZWJ : !Ext !Ext !Ext !Ext Ext !Ext !Ext !Ext $LAZP - !Ext Ext !Ext : 187 | // Spac : !Ext !Ext !Ext !Ext Ext !Ext !Ext !Ext !Ext - !Ext Ext !Ext : 188 | // Prep : !Otr !Otr !Otr Otr Otr Otr Otr Otr Otr - Otr Otr Otr : 189 | // Reg : !Reg !Reg !Reg !Reg Reg !Reg !Reg !Reg !Reg RegE$LARe Reg !LA : 190 | // L : !L !L !L !L L L L !L !L - !L L !L : 191 | // V : !V !V !V !V V !V V V !V - !V V !V : 192 | // T : !T !T !T !T T !T !T T !T - !T T !T : 193 | // LV : !L !L !L !L L !L L L !L - !L L !L : 194 | // LVT : !L !L !L !L L !L !L L !L - !L L !L : 195 | // Pic : !Pic !Pic !Pic !Pic Pic !Pic !Pic !Pic !Pic Pic !Pic Pic !Pic : 196 | // SoT : - ! - ! - ! - ! - ! - ! - ! - ! - - ! - - ! - : 197 | 198 | const backStates = [ 199 | stateEoT, 200 | stateBreak, 201 | stateLF, 202 | stateOther, 203 | stateExtend, 204 | stateL, 205 | stateV, 206 | stateT, 207 | statePictographic, 208 | stateZWJPictographic | stateRegionalOdd, // Known disjoint look-ahead. 209 | stateRegionalSingle, 210 | stateEoTNoBreak, 211 | stateRegionalEven, 212 | ]; 213 | 214 | void writeBackwardAutomaton(StringSink buffer, {required bool verbose}) { 215 | assert(categories.length == 16); 216 | var table = Uint8List(backStates.length * categories.length); 217 | void transition(int state, int category, int newState, bool breakBefore) { 218 | table[state + category] = newState | (breakBefore ? 0 : stateNoBreak); 219 | } 220 | 221 | for (var state in backStates) { 222 | if (state == stateZWJPictographic | stateRegionalOdd) { 223 | // Special state where we know the previous character 224 | // to some degree. 225 | // Most inputs are unreachable. Use EoT-nobreak as unreachable marker. 226 | for (var i = 0; i <= categoryEoT; i++) { 227 | transition(state, i, stateEoTNoBreak, false); 228 | } 229 | transition(state, categoryExtend, stateZWJPictographic, false); 230 | transition(state, categoryPictographic, statePictographic, false); 231 | transition(state, categoryRegionalIndicator, stateRegionalEven, false); 232 | // Remaining inputs are unreachable. 233 | continue; 234 | } 235 | transition(state, categoryOther, stateOther, 236 | state != stateExtend && state != stateEoTNoBreak); 237 | transition(state, categoryLF, stateLF, state != stateEoTNoBreak); 238 | transition(state, categoryCR, stateBreak, 239 | state != stateLF && state != stateEoTNoBreak); 240 | transition(state, categoryControl, stateBreak, state != stateEoTNoBreak); 241 | if (state != stateZWJPictographic) { 242 | transition( 243 | state, 244 | categoryExtend, 245 | stateExtend, 246 | state != stateExtend && 247 | state != stateZWJPictographic && 248 | state != stateEoTNoBreak); 249 | } 250 | transition(state, categorySpacingMark, stateExtend, 251 | state != stateExtend && state != stateEoTNoBreak); 252 | if (state != statePictographic) { 253 | transition(state, categoryZWJ, stateExtend, 254 | state != stateExtend && state != stateEoTNoBreak); 255 | } else { 256 | transition(state, categoryZWJ, stateZWJPictographicLookahead, true); 257 | } 258 | if (state == stateRegionalEven) { 259 | transition(state, categoryRegionalIndicator, stateRegionalOdd, true); 260 | } else if (state == stateRegionalSingle) { 261 | transition( 262 | state, categoryRegionalIndicator, stateRegionalLookahead, true); 263 | } else { 264 | transition(state, categoryRegionalIndicator, stateRegionalSingle, 265 | state != stateExtend && state != stateEoTNoBreak); 266 | } 267 | transition(state, categoryPrepend, stateOther, state <= stateLF); 268 | transition( 269 | state, 270 | categoryL, 271 | stateL, 272 | state != stateExtend && 273 | state != stateL && 274 | state != stateV && 275 | state != stateEoTNoBreak); 276 | transition( 277 | state, 278 | categoryLV, 279 | stateL, 280 | state != stateExtend && 281 | state != stateV && 282 | state != stateT && 283 | state != stateEoTNoBreak); 284 | transition(state, categoryLVT, stateL, 285 | state != stateExtend && state != stateT && state != stateEoTNoBreak); 286 | transition( 287 | state, 288 | categoryV, 289 | stateV, 290 | state != stateExtend && 291 | state != stateT && 292 | state != stateV && 293 | state != stateEoTNoBreak); 294 | transition(state, categoryT, stateT, 295 | state != stateExtend && state != stateT && state != stateEoTNoBreak); 296 | transition( 297 | state, 298 | categoryPictographic, 299 | statePictographic, 300 | state != stateExtend && 301 | state != stateZWJPictographic && 302 | state != stateEoTNoBreak); 303 | // Use EoT-NoBreak as maker for unreachable. 304 | transition(state, categorySoT, stateEoTNoBreak, 305 | state != stateEoT && state != stateEoTNoBreak); 306 | } 307 | var stringWriter = StringLiteralWriter(buffer, padding: 4); 308 | buffer.write("const _backStateMachine = "); 309 | stringWriter.start("const _backStateMachine = ".length); 310 | for (var i = 0; i < table.length; i++) { 311 | stringWriter.add(table[i]); 312 | } 313 | stringWriter.end(); 314 | buffer.write(";\n"); 315 | buffer.write(_moveBackMethod); 316 | if (verbose) _writeBackTable(table); 317 | } 318 | 319 | void _writeForwardTable(Uint8List table) { 320 | _writeTable( 321 | table, 322 | const [ 323 | "SoT", 324 | "Brk", 325 | "CR", 326 | "Otr", 327 | "Pre", 328 | "L", 329 | "V", 330 | "T", 331 | "Pic", 332 | "PicZ", 333 | "Reg", 334 | "SoTN", 335 | ], 336 | const [ 337 | "Other", 338 | "CR", 339 | "LF", 340 | "Ctrl", 341 | "Ext", 342 | "ZWJ", 343 | "Spac", 344 | "Prep", 345 | "Reg", 346 | "L", 347 | "V", 348 | "T", 349 | "LV", 350 | "LVT", 351 | "Pic", 352 | "EoT", 353 | ], 354 | stateSoTNoBreak, 355 | stateSoTNoBreak); 356 | } 357 | 358 | void _writeBackTable(Uint8List table) { 359 | _writeTable( 360 | table, 361 | const [ 362 | "EoT", 363 | "Brk", 364 | "LF", 365 | "Otr", 366 | "Ext", 367 | "L", 368 | "V", 369 | "T", 370 | "Pic", 371 | "LA", 372 | "Reg", 373 | "EoTN", 374 | "RegE", 375 | "LARe", 376 | "LAZP", 377 | ], 378 | const [ 379 | "Other", 380 | "LF", 381 | "CR", 382 | "Ctrl", 383 | "Ext", 384 | "ZWJ", 385 | "Spac", 386 | "Prep", 387 | "Reg", 388 | "L", 389 | "V", 390 | "T", 391 | "LV", 392 | "LVT", 393 | "Pic", 394 | "SoT", 395 | ], 396 | stateEoTNoBreak, 397 | stateRegionalEven); 398 | } 399 | 400 | void _writeTable(Uint8List table, List stateNames, 401 | List catNames, int ignoreState, int maxState) { 402 | const catIndex = { 403 | "Other": categoryOther, 404 | "LF": categoryLF, 405 | "CR": categoryCR, 406 | "Ctrl": categoryControl, 407 | "Ext": categoryExtend, 408 | "ZWJ": categoryZWJ, 409 | "Spac": categorySpacingMark, 410 | "Prep": categoryPrepend, 411 | "Reg": categoryRegionalIndicator, 412 | "L": categoryL, 413 | "V": categoryV, 414 | "T": categoryT, 415 | "LV": categoryLV, 416 | "LVT": categoryLVT, 417 | "Pic": categoryPictographic, 418 | "SoT": categorySoT, 419 | "EoT": categoryEoT, 420 | }; 421 | 422 | var buf = StringBuffer(); 423 | buf.write(" : "); 424 | for (var i = 0; i <= maxState; i += 0x10) { 425 | buf.write(stateNames[i >> 4].padRight(5, " ")); 426 | } 427 | buf.writeln(":"); 428 | buf.writeln("-" * (buf.length - 1)); 429 | for (var ci = 0; ci < catNames.length; ci++) { 430 | var catName = catNames[ci]; 431 | buf 432 | ..write(catName.padRight(5)) 433 | ..write(": "); 434 | var cat = catIndex[catName]!; 435 | for (var si = 0; si <= maxState; si += 0x10) { 436 | var value = table[si + cat]; 437 | var prefix = " "; 438 | if (value & 0xF0 > maxState) { 439 | prefix = r"$"; 440 | } else if (value & stateNoBreak == 0) { 441 | prefix = "!"; 442 | } 443 | var stateName = stateNames[value >> 4]; 444 | // EoT is marker for unreachable states. 445 | if ((value & 0xF0) == ignoreState) stateName = " - "; 446 | buf 447 | ..write(prefix) 448 | ..write(stateName.padRight(4, " ")); 449 | } 450 | buf.writeln(" :"); 451 | } 452 | stderr.writeln(buf); 453 | } 454 | -------------------------------------------------------------------------------- /tool/src/data_files.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:io"; 6 | import "shared.dart"; 7 | 8 | // Abstraction over files fetched from the `unicode.org/Public` UCD repository. 9 | 10 | // TODO: Find way to detect newest Unicode version, 11 | // and compute URIs from that. 12 | 13 | final graphemeBreakPropertyData = DataFile( 14 | // "https://unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt", 15 | "https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt", 16 | "third_party/Unicode_Consortium/GraphemeBreakProperty.txt"); 17 | 18 | final emojiData = DataFile( 19 | // "https://unicode.org/Public/15.0.0/ucd/emoji/emoji-data.txt", 20 | "https://unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt", 21 | "third_party/Unicode_Consortium/emoji_data.txt"); 22 | 23 | final graphemeTestData = DataFile( 24 | // "https://unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakTest.txt", 25 | "https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt", 26 | "third_party/Unicode_Consortium/GraphemeBreakTest.txt"); 27 | 28 | final emojiTestData = DataFile( 29 | // "https://www.unicode.org/Public/emoji/15.0/emoji-test.txt", 30 | "https://unicode.org/Public/emoji/latest/emoji-test.txt", 31 | "third_party/Unicode_Consortium/emoji_test.txt"); 32 | 33 | final licenseFile = DataFile("https://www.unicode.org/license.txt", 34 | "third_party/Unicode_Consortium/UNICODE_LICENSE.txt"); 35 | 36 | class DataFile { 37 | final String sourceLocation; 38 | // Target location relative to package root. 39 | final String targetLocation; 40 | String? _contents; 41 | DataFile(this.sourceLocation, this.targetLocation); 42 | 43 | Future get contents async => _contents ??= await load(); 44 | 45 | Future load({bool checkForUpdate = false}) async => 46 | (checkForUpdate ? null : _contents) ?? 47 | (_contents = await fetch(sourceLocation, 48 | targetFile: File(path(packageRoot, targetLocation)), 49 | forceLoad: checkForUpdate)); 50 | } 51 | -------------------------------------------------------------------------------- /tool/src/grapheme_category_loader.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:async"; 6 | import "dart:io" show stderr; 7 | import "dart:typed_data"; 8 | 9 | import "package:characters/src/grapheme_clusters/constants.dart"; 10 | 11 | import "data_files.dart"; 12 | 13 | // Loads the grapheme breaking categories from Unicode data files. 14 | 15 | Future loadGraphemeCategories( 16 | {bool update = false, bool verbose = false}) async { 17 | var dataFiles = await Future.wait([ 18 | graphemeBreakPropertyData.load(checkForUpdate: update), 19 | emojiData.load(checkForUpdate: update), 20 | // This data used to be in: 21 | // https://www.unicode.org/Public/12.0.0/ucd/auxiliary/GraphemeBreakProperty-12.0.0d16.txt 22 | // Make sure it's included. 23 | Future.value( 24 | "D800..DFFF ; Control # Cc ..\n"), 25 | ]); 26 | var table = _parseCategories(dataFiles, verbose: verbose); 27 | return table; 28 | } 29 | 30 | // ----------------------------------------------------------------------------- 31 | // Unicode table parser. 32 | final _tableRE = RegExp(r"^([\dA-F]{4,5})(?:..([\dA-F]{4,5}))?\s*;\s*(\w+)\s*#", 33 | multiLine: true); 34 | 35 | // The relevant names that occur in the Unicode tables. 36 | final categoryByName = { 37 | "CR": categoryCR, 38 | "LF": categoryLF, 39 | "Control": categoryControl, 40 | "Extend": categoryExtend, 41 | "ZWJ": categoryZWJ, 42 | "Regional_Indicator": categoryRegionalIndicator, 43 | "Prepend": categoryPrepend, 44 | "SpacingMark": categorySpacingMark, 45 | "L": categoryL, 46 | "V": categoryV, 47 | "T": categoryT, 48 | "LV": categoryLV, 49 | "LVT": categoryLVT, 50 | "Extended_Pictographic": categoryPictographic, 51 | }; 52 | 53 | Uint8List _parseCategories(List files, {required bool verbose}) { 54 | var result = Uint8List(0x110000); 55 | result.fillRange(0, result.length, categoryOther); 56 | var count = 0; 57 | var categoryCount = {}; 58 | var categoryMin = { 59 | for (var category in categoryByName.keys) category: 0x10FFFF 60 | }; 61 | int min(int a, int b) => a < b ? a : b; 62 | for (var file in files) { 63 | for (var match in _tableRE.allMatches(file)) { 64 | var from = int.parse(match[1]!, radix: 16); 65 | var to = match[2] == null ? from : int.parse(match[2]!, radix: 16); 66 | var category = match[3]!; 67 | assert(from <= to); 68 | var categoryCode = categoryByName[category]; 69 | if (categoryCode != null) { 70 | assert(result.getRange(from, to + 1).every((x) => x == categoryOther)); 71 | result.fillRange(from, to + 1, categoryCode); 72 | count += to + 1 - from; 73 | categoryMin[category] = min(categoryMin[category]!, from); 74 | categoryCount[category] = 75 | (categoryCount[category] ?? 0) + (to + 1 - from); 76 | } 77 | } 78 | } 79 | if (verbose) { 80 | stderr.writeln("Loaded $count entries"); 81 | categoryCount.forEach((category, count) { 82 | stderr.writeln(" $category: $count, min: U+" 83 | "${categoryMin[category]!.toRadixString(16).padLeft(4, "0")}"); 84 | }); 85 | } 86 | if (result[0xD800] != categoryControl) { 87 | stderr.writeln("WARNING: Surrogates are not controls. Check inputs."); 88 | } 89 | if (categoryMin["Regional_Indicator"]! < 0x10000) { 90 | stderr.writeln("WARNING: Regional Indicator in BMP. " 91 | "Code assuming all RIs are non-BMP will fail"); 92 | } 93 | return result; 94 | } 95 | -------------------------------------------------------------------------------- /tool/src/indirect_table.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:typed_data"; 6 | 7 | /// A table with chunks and indirections. 8 | /// 9 | /// Contains a number, one or more, of chunks, 10 | /// and a list of entries which point to entire chunks or parts of chunks. 11 | /// 12 | /// The entries represent sequnences of values. 13 | /// Each such sequence is stored in one of the chunks. 14 | /// 15 | /// The main goal of these tools are to go from an initial complete 16 | /// table with one chunk and non-overlapping entries, 17 | /// to a smaller table with one chunk where the entry sequences may overlap. 18 | /// 19 | /// Having multiple chunks is an intermediate step which allows the code 20 | /// to keep the entries consistent during the transformations. 21 | class IndirectTable { 22 | /// Individual chunks. 23 | List chunks; 24 | 25 | /// Position and length of each entry in one of the [chunks]. 26 | List entries; 27 | IndirectTable(this.chunks, this.entries); 28 | } 29 | 30 | class TableEntry { 31 | int chunkNumber; 32 | int start; 33 | int length; 34 | TableEntry(this.chunkNumber, this.start, this.length); 35 | int get end => start + length; 36 | 37 | void update(int chunkNumber, int start, int length) { 38 | this.chunkNumber = chunkNumber; 39 | this.start = start; 40 | this.length = length; 41 | } 42 | 43 | TableEntry copy() => TableEntry(chunkNumber, start, length); 44 | 45 | void copyFrom(TableEntry other) { 46 | chunkNumber = other.chunkNumber; 47 | start = other.start; 48 | length = other.length; 49 | } 50 | 51 | @override 52 | String toString() => 53 | "$chunkNumber[${start.toRadixString(16)}:${end.toRadixString(16)}]"; 54 | } 55 | -------------------------------------------------------------------------------- /tool/src/list_overlap.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Given a list of lists of integers, figure out a good way to overlap 6 | // these into a single list, with a list of indices telling where each 7 | // original list started. 8 | 9 | import "dart:typed_data"; 10 | 11 | import "atsp.dart"; 12 | import "indirect_table.dart"; 13 | 14 | /// Takes a set of distinct chunks, and finds a semi-optimal overlapping. 15 | /// 16 | /// The overlapping is a single chunk, of minimal length, containing all 17 | /// the original chunk's contents, and an indirection entry pointing 18 | /// to the position in the new table. 19 | IndirectTable combineLists(List input) { 20 | // See how much chunks are overlapping. 21 | var chunkCount = input.length; 22 | var graph = Graph(chunkCount + 1); 23 | for (var i = 0; i < input.length; i++) { 24 | var firstChunk = input[i]; 25 | for (var j = 0; j < input.length; j++) { 26 | if (i == j) continue; 27 | var secondChunk = input[j]; 28 | var overlap = _overlap(firstChunk, secondChunk); 29 | graph.setWeight(i, j, secondChunk.length - overlap); 30 | } 31 | } 32 | 33 | // Find an optimal(ish) path. 34 | 35 | // First create a cycle through the one extra node (index `chunkCount`). 36 | var path = List.filled(chunkCount + 2, chunkCount); 37 | for (var i = 0; i <= chunkCount; i++) { 38 | path[i + 1] = i; 39 | } 40 | 41 | while (opt3(graph, path)) {} 42 | // Then break the cycle at the extra node. 43 | // The way we optimize, it's still first/last. 44 | assert(path.last == chunkCount); 45 | assert(path.first == chunkCount); 46 | 47 | var chunkLength = 48 | input[path[1]].length + graph.pathWeight(path, 1, path.length - 2); 49 | 50 | var chunkData = Uint8List(chunkLength); 51 | var entries = List.filled(input.length, TableEntry(0, 0, 0)); 52 | { 53 | // Handle path chunks. 54 | var prevChunkNum = path[1]; 55 | var firstChunk = input[prevChunkNum]; 56 | chunkData.setRange(0, firstChunk.length, firstChunk); 57 | entries[prevChunkNum] = TableEntry(0, 0, firstChunk.length); 58 | var index = firstChunk.length; 59 | for (var i = 2; i < path.length - 1; i++) { 60 | var nextChunkNum = path[i]; 61 | var chunk = input[nextChunkNum]; 62 | var nonOverlap = graph.weight(prevChunkNum, nextChunkNum); 63 | var overlap = chunk.length - nonOverlap; 64 | entries[nextChunkNum] = TableEntry(0, index - overlap, chunk.length); 65 | chunkData.setRange(index, index + nonOverlap, chunk, overlap); 66 | index += nonOverlap; 67 | prevChunkNum = nextChunkNum; 68 | } 69 | } 70 | return IndirectTable([chunkData], entries); 71 | } 72 | 73 | /// Finds how much overlap there is between [first] and [second] in that order. 74 | int _overlap(Uint8List first, Uint8List second) { 75 | var maxOverlap = 76 | (first.length < second.length ? first.length : second.length) - 1; 77 | outer: 78 | for (var overlap = maxOverlap; overlap > 0; overlap--) { 79 | var firstStart = first.length - overlap; 80 | for (var j = 0; j < overlap; j++) { 81 | if (first[firstStart + j] != second[j]) continue outer; 82 | } 83 | return overlap; 84 | } 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /tool/src/shared.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:async"; 6 | import "dart:convert"; 7 | import "dart:io"; 8 | 9 | // Shared tools used by other libraries. 10 | 11 | /// Quick and dirty URI loader. 12 | /// 13 | /// Stashes copy in specified file, or in file in tmp directory. 14 | Future fetch(String location, 15 | {File? targetFile, bool forceLoad = false}) async { 16 | if (targetFile == null) { 17 | var safeLocation = location.replaceAll(RegExp(r'[^\w]+'), '-'); 18 | targetFile = File(path(Directory.systemTemp.path, safeLocation)); 19 | } 20 | if (!forceLoad && targetFile.existsSync()) { 21 | return targetFile.readAsString(); 22 | } 23 | var uri = Uri.parse(location); 24 | String contents; 25 | if (uri.isScheme("file")) { 26 | contents = File.fromUri(uri).readAsStringSync(); 27 | } else { 28 | var client = HttpClient(); 29 | var request = await client.getUrl(uri); 30 | var response = await request.close(); 31 | if (response.statusCode != HttpStatus.ok) { 32 | throw HttpException(response.reasonPhrase, uri: uri); 33 | } 34 | contents = await utf8.decoder.bind(response).join(""); 35 | client.close(); 36 | } 37 | targetFile.writeAsStringSync(contents); 38 | return contents; 39 | } 40 | 41 | const copyright = """ 42 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 43 | // for details. All rights reserved. Use of this source code is governed by a 44 | // BSD-style license that can be found in the LICENSE file. 45 | 46 | """; 47 | 48 | /// Combines file paths into one path. 49 | /// 50 | /// No fancy stuff, just adds path separator between parts, 51 | /// if previous part doesn't end with one. 52 | /// (Don't let later parts start with a path separator!) 53 | /// Converts forward slashes to backwards slashes in Windows. 54 | /// 55 | /// Empty parts are ignored. 56 | String path(String path, [String path2 = "", String path3 = ""]) { 57 | var separator = Platform.pathSeparator; 58 | path = _windowize(path); 59 | if (path2.isEmpty && path3.isEmpty) return path; 60 | var buffer = StringBuffer(path); 61 | var prev = path; 62 | for (var part in [path2, path3]) { 63 | if (part.isEmpty) continue; 64 | part = _windowize(part); 65 | if (!prev.endsWith(separator)) { 66 | buffer.write(separator); 67 | } 68 | buffer.write(part); 69 | prev = part; 70 | } 71 | return buffer.toString(); 72 | } 73 | 74 | String _windowize(String path) => 75 | Platform.isWindows ? path.replaceAll("/", r"\") : path; 76 | 77 | /// Package root directory. 78 | String packageRoot = _findRootDir().path; 79 | 80 | /// Finds package root in the parent chain of the current directory. 81 | /// 82 | /// Recognizes package root by `pubspec.yaml` file. 83 | Directory _findRootDir() { 84 | var dir = Directory.current; 85 | while (true) { 86 | var pubspec = File("${dir.path}${Platform.pathSeparator}pubspec.yaml"); 87 | if (pubspec.existsSync()) return dir; 88 | var parent = dir.parent; 89 | if (dir.path == parent.path) { 90 | throw UnsupportedError( 91 | "Cannot find package root directory. Run tools from inside package!"); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tool/src/string_literal_writer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Class to write string literals for bytes or words. 6 | /// 7 | /// The string will be `'` delimited. 8 | /// Escapes as necessary, and performs line breaks to stay within 80 9 | /// characters. 10 | class StringLiteralWriter { 11 | final StringSink buffer; 12 | final String _padding; 13 | final int _lineLength; 14 | final bool Function(int) _escape; 15 | int _currentLineLength = 0; 16 | 17 | static final Map _escapeCache = {}; 18 | 19 | StringLiteralWriter(this.buffer, 20 | {int padding = 0, int lineLength = 80, bool Function(int)? escape}) 21 | : _padding = " " * padding, 22 | _lineLength = lineLength, 23 | _escape = escape ?? _defaultEscape; 24 | 25 | static bool _defaultEscape(int codeUnit) { 26 | return codeUnit < 0x20 || codeUnit >= 0x7f && codeUnit <= 0xa0; 27 | } 28 | 29 | void start([int initialOffset = 0]) { 30 | if (initialOffset >= _lineLength - 2) { 31 | buffer 32 | ..write('\n') 33 | ..write(_padding); 34 | initialOffset = _padding.length; 35 | } 36 | buffer.write("'"); 37 | _currentLineLength = initialOffset + 1; 38 | } 39 | 40 | /// Adds a single UTF-16 code unit. 41 | void add(int codeUnit) { 42 | // Always escape: `\n`, `\r`, `'`, `$` and `\`, plus anything the user wants. 43 | if (_escape(codeUnit) || 44 | codeUnit == 0x24 || 45 | codeUnit == 0x27 || 46 | codeUnit == 0x5c || 47 | codeUnit == 0x0a || 48 | codeUnit == 0x0d) { 49 | _writeEscape(codeUnit); 50 | return; 51 | } 52 | if (_currentLineLength >= _lineLength - 1) { 53 | _wrap(); 54 | } 55 | _currentLineLength++; 56 | buffer.writeCharCode(codeUnit); 57 | } 58 | 59 | void _writeEscape(int codeUnit) { 60 | var replacement = _escapeCache[codeUnit]; 61 | if (replacement == null) { 62 | if (codeUnit < 0x10) { 63 | if (codeUnit == "\b".codeUnitAt(0)) { 64 | replacement = r"\b"; 65 | } else if (codeUnit == "\t".codeUnitAt(0)) { 66 | replacement = r"\t"; 67 | } else if (codeUnit == "\n".codeUnitAt(0)) { 68 | replacement = r"\n"; 69 | } else if (codeUnit == "\v".codeUnitAt(0)) { 70 | replacement = r"\v"; 71 | } else if (codeUnit == "\f".codeUnitAt(0)) { 72 | replacement = r"\f"; 73 | } else if (codeUnit == "\r".codeUnitAt(0)) { 74 | replacement = r"\r"; 75 | } else { 76 | replacement = r"\x0" + codeUnit.toRadixString(16); 77 | } 78 | } else if (codeUnit < 0x100) { 79 | if (codeUnit == r"$".codeUnitAt(0)) { 80 | replacement = r"\$"; 81 | } else if (codeUnit == "'".codeUnitAt(0)) { 82 | replacement = r"\'"; 83 | } 84 | if (codeUnit == r"\".codeUnitAt(0)) { 85 | replacement = r"\\"; 86 | } else { 87 | replacement = r"\x" + codeUnit.toRadixString(16); 88 | } 89 | } else if (codeUnit < 0x1000) { 90 | replacement = r"\u0" + codeUnit.toRadixString(16); 91 | } else if (codeUnit < 0x10000) { 92 | replacement = r"\u" + codeUnit.toRadixString(16); 93 | } else { 94 | replacement = "\\u{${codeUnit.toRadixString(16)}}"; 95 | } 96 | _escapeCache[codeUnit] = replacement; 97 | } 98 | if (_currentLineLength + replacement.length + 1 > _lineLength) { 99 | _wrap(); 100 | } 101 | buffer.write(replacement); 102 | _currentLineLength += replacement.length; 103 | } 104 | 105 | void _wrap() { 106 | buffer 107 | ..write("'\n") 108 | ..write(_padding) 109 | ..write("'"); 110 | _currentLineLength = _padding.length + 1; 111 | } 112 | 113 | void end() { 114 | buffer.write("'"); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tool/src/table_builder.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import "dart:collection"; 6 | import "dart:typed_data"; 7 | 8 | import "indirect_table.dart"; 9 | import "list_overlap.dart"; 10 | 11 | /// Splits an indirect table with one large chunk into separate smaller chunks. 12 | /// 13 | /// No new chunk is larger than the largest entry. 14 | /// 15 | /// Preserves the entries, but they now point into the new chunks. 16 | /// All chunks are distinct, and no chunk is a sub-list of another chunk. 17 | void chunkifyTable(IndirectTable table) { 18 | if (table.chunks.length != 1) { 19 | throw ArgumentError("Single chunk table required"); 20 | } 21 | var data = table.chunks[0]; 22 | var entries = table.entries.toList(); 23 | entries.sort((a, b) => b.length - a.length); 24 | var uniqueChunks = []; 25 | var duplicateDetector = 26 | HashMap(equals: _equals, hashCode: _hash); 27 | for (var entry in entries) { 28 | var chunk = data.sublist(entry.start, entry.end); 29 | var existingEntry = duplicateDetector[chunk]; 30 | if (existingEntry != null) { 31 | entry.copyFrom(existingEntry); 32 | } else { 33 | // Check if chunk is a sublist of any existing chunk. 34 | var chunkNum = 0; 35 | var indexOf = 0; 36 | for (; chunkNum < uniqueChunks.length; chunkNum++) { 37 | var existingChunk = uniqueChunks[chunkNum]; 38 | if (existingChunk.length > chunk.length) { 39 | var position = _indexOf(chunk, existingChunk); 40 | if (position >= 0) { 41 | indexOf = position; 42 | break; 43 | } 44 | } 45 | } 46 | if (chunkNum == uniqueChunks.length) { 47 | uniqueChunks.add(chunk); 48 | } 49 | entry.update(chunkNum, indexOf, entry.length); 50 | duplicateDetector[chunk] = entry; 51 | } 52 | } 53 | table.chunks = uniqueChunks; 54 | } 55 | 56 | int _indexOf(Uint8List short, Uint8List long) { 57 | var length = short.length; 58 | var range = long.length - length; 59 | outer: 60 | for (var i = 0; i < range; i++) { 61 | for (var j = 0; j < short.length; j++) { 62 | if (short[j] != long[i + j]) continue outer; 63 | } 64 | return i; 65 | } 66 | return -1; 67 | } 68 | 69 | /// Combines an indirect table with multiple chunks into only one chunk. 70 | void combineChunkedTable(IndirectTable table) { 71 | var overlapped = combineLists(table.chunks); 72 | for (var entry in table.entries) { 73 | var chunkEntry = overlapped.entries[entry.chunkNumber]; 74 | entry.update(0, entry.start + chunkEntry.start, entry.length); 75 | } 76 | table.chunks = [overlapped.chunks[0]]; 77 | } 78 | 79 | /// Hash on a list. 80 | int _hash(Uint8List list) { 81 | var view = list.buffer.asUint32List(); 82 | var hash = 0; 83 | for (var i = 0; i < view.length; i++) { 84 | hash = (hash * 37 ^ view[i]) & 0xFFFFFFFF; 85 | } 86 | return hash; 87 | } 88 | 89 | /// Equality of lists of equal length. 90 | bool _equals(Uint8List a, Uint8List b) { 91 | assert(a.length == b.length); 92 | assert(a.length % 8 == 0); 93 | // Compare 32 bits at a time. 94 | var aView = a.buffer.asInt64List(); 95 | var bView = b.buffer.asInt64List(); 96 | for (var i = 0; i < aView.length; i++) { 97 | if (aView[i] != bView[i]) return false; 98 | } 99 | return true; 100 | } 101 | --------------------------------------------------------------------------------