| 1 | <?php |
| 2 | /************************************************************* |
| 3 | # 076 License |
| 4 | |
| 5 | Copyright (c) テクニカル諏訪子 |
| 6 | |
| 7 | Permission is hereby granted to any person obtaining a copy of the software |
| 8 | Little Beast (the "Software") to use, modify, merge, copy, publish, distribute, |
| 9 | sublicense, and/or sell copies of the Software, subject to the following conditions: |
| 10 | |
| 11 | 1. **Origin Attribution**: |
| 12 | - You must not misrepresent the origin of the Software; you must not claim |
| 13 | you created the original Software. |
| 14 | - If the Software is used in a product, you must either: |
| 15 | a. Provide clear attribution in the product's documentation, user interface, |
| 16 | or other visible areas, **OR** |
| 17 | b. Pay the original developers a fee they specify in writing. |
| 18 | 2. **Usage Restriction**: |
| 19 | - The Software, or any derivative works, dependencies, or libraries |
| 20 | incorporating it, must not be used for censorship or to suppress freedom of |
| 21 | speech, expression, or creativity. Prohibited uses include, but are not |
| 22 | limited to: |
| 23 | - Censorship of so-called "hate speech", visuals, non-mainstream opinions, |
| 24 | ideas, or objective reality. |
| 25 | - Tools or systems designed to restrict access to information or |
| 26 | artistic works. |
| 27 | 3. **Notice Preservation**: |
| 28 | - This license and the above copyright notice must remain intact in all copies |
| 29 | of the source code. |
| 30 | |
| 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| 32 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
| 33 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 34 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| 35 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |
| 36 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 37 | */ |
| 38 | namespace Std\Test; |
| 39 | |
| 40 | require_once __DIR__.'/../../../autoload.php'; |
| 41 | |
| 42 | use Std\Lib\Tester; |
| 43 | use Std\Lib\Syntax; |
| 44 | |
| 45 | $test = new Tester([ |
| 46 | 'colorOutput' => true, |
| 47 | 'verboseOutput' => true |
| 48 | ]); |
| 49 | |
| 50 | // タグを外してデコードすると元のコードに戻るか |
| 51 | function syntaxRoundtrip(string $lang, string $code): bool { |
| 52 | $html = (new Syntax($lang))->colorize($code); |
| 53 | return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8') === $code; |
| 54 | } |
| 55 | |
| 56 | $samples = [ |
| 57 | 'c' => "#include <stdio.h>\n/* a */ int main(void) { printf(\"%d\\n\", 0x1F); return 0; } // b", |
| 58 | 'cpp' => "auto s = R\"(raw \"x\")\"; [[nodiscard]] std::vector<int> v{1'000};", |
| 59 | 'glsl' => "layout(location = 0) out vec4 fragColor;\nvoid main() { fragColor = texture(tex, gl_FragCoord.xy); }", |
| 60 | 'go' => "func main() { s := `raw\nstring`; fmt.Println(len(s), nil) }", |
| 61 | 'hlsl' => "[numthreads(8, 8, 1)]\nvoid CSMain(uint3 id : SV_DispatchThreadID) { float4x4 m; }", |
| 62 | 'slang' => "[shader(\"compute\")]\nvoid main<T : IFloat>(uniform Ptr<T> p) { let x = detach(1.0); }", |
| 63 | 'swift' => "@main struct A { /* /* nested */ */ let s = #\"raw\"#; #if DEBUG\n print(\"\\(s)\") }", |
| 64 | 'objc' => "#import <Foundation/Foundation.h>\n@interface Foo : NSObject\n@end\nNSLog(@\"%@\", [obj valueForKey:@\"k\"]);", |
| 65 | 'csharp' => "var p = @\"C:\\dir\"; var m = $\"{p}\"; var r = \"\"\"\nraw\n\"\"\";", |
| 66 | 'asm' => "section .text\n_start: mov rax, 1 ; nasm\n mov x8, #64\n li a7, 93 # riscv", |
| 67 | 'asm-x8664' => "%define N 1\nrep movsb\nmovl \$1, %eax", |
| 68 | 'asm-aarch64' => "b.eq done\nadd v0.4s, v1.4s, v2.4s // neon", |
| 69 | 'asm-aarch32' => "addeq r0, r0, #1 @ arm", |
| 70 | 'asm-riscv' => "fcvt.w.s a0, fa0 # x", |
| 71 | 'asm-mips' => "addiu \$sp, \$sp, -8 # x", |
| 72 | 'asm-power' => "addi r3, r3, 1 # x", |
| 73 | 'asm-z80' => "ex af,af' ; x\nld a,'A'", |
| 74 | 'asm-6502' => "lda #\$FF ; x", |
| 75 | 'asm-68000' => "* comment\nmove.l #1,d0", |
| 76 | ]; |
| 77 | |
| 78 | $test->describe('シンタックスハイライト', function($test) use ($samples): void { |
| 79 | $test->it('全ての言語で、タグを外すと元のコードに戻るはず', function($test) use ($samples): void { |
| 80 | foreach ($samples as $lang => $code) { |
| 81 | $test->assertTrue(syntaxRoundtrip($lang, $code), "{$lang}の往復に失敗"); |
| 82 | } |
| 83 | }); |
| 84 | |
| 85 | $test->it('HTMLをエスケープするはず', function($test): void { |
| 86 | $html = (new Syntax('c'))->colorize('"</code><script>" // <b>'); |
| 87 | $test->assertFalse(str_contains($html, '<script>')); |
| 88 | $test->assertStringContains('</code>', $html); |
| 89 | }); |
| 90 | |
| 91 | $test->it('トークンを正しく分類するはず', function($test): void { |
| 92 | $html = (new Syntax('c'))->colorize('int x = 42; // c'); |
| 93 | $test->assertStringContains('<span class="hl-keyword">int</span>', $html); |
| 94 | $test->assertStringContains('<span class="hl-number">42</span>', $html); |
| 95 | $test->assertStringContains('<span class="hl-comment">// c</span>', $html); |
| 96 | |
| 97 | $html = (new Syntax('hlsl'))->colorize('float4 p : SV_Position;'); |
| 98 | $test->assertStringContains('<span class="hl-variable">SV_Position</span>', $html); |
| 99 | |
| 100 | $html = (new Syntax('asm'))->colorize("loop:\n mov x8, #64"); |
| 101 | $test->assertStringContains('<span class="hl-label">loop</span>', $html); |
| 102 | $test->assertStringContains('<span class="hl-number">#64</span>', $html); |
| 103 | }); |
| 104 | |
| 105 | $test->it('文字列の中の数字やキーワードに色を付けないはず', function($test): void { |
| 106 | $html = (new Syntax('c'))->colorize('"int 42"'); |
| 107 | $test->assertEquals('<span class="hl-string">"int 42"</span>', $html); |
| 108 | }); |
| 109 | |
| 110 | $test->it('言語毎の特有の構文を分類するはず', function($test): void { |
| 111 | $cases = [ |
| 112 | ['swift', '/* a /* b */ c */ let x', '<span class="hl-comment">/* a /* b */ c */</span> <span class="hl-keyword">let</span>'], |
| 113 | ['csharp', 'var p = @"C:\\dir";', '<span class="hl-string">@"C:\\dir"</span>'], |
| 114 | ['csharp', 'async Task', '<span class="hl-keyword">async</span> <span class="hl-type">Task</span>'], |
| 115 | ['objc', '@interface Foo : NSObject', '<span class="hl-keyword">@interface</span> Foo : <span class="hl-type">NSObject</span>'], |
| 116 | ['slang', 'interface IFoo { float3 f(); }', '<span class="hl-type">float3</span> <span class="hl-function">f</span>()'], |
| 117 | ['hlsl', 'Texture2D<float4> t : register(t0);', '<span class="hl-type">Texture2D</span><<span class="hl-type">float4</span>>'], |
| 118 | ['glsl', 'uniform sampler2D tex;', '<span class="hl-keyword">uniform</span> <span class="hl-type">sampler2D</span>'], |
| 119 | ['go', 'return nil', '<span class="hl-keyword">return</span> <span class="hl-constant">nil</span>'], |
| 120 | ['cpp', 'template<typename T>', '<span class="hl-keyword">template</span><<span class="hl-keyword">typename</span> T>'], |
| 121 | ['c', '#include <stdio.h>', '<span class="hl-preprocessor">#include</span> <span class="hl-string"><stdio.h></span>'], |
| 122 | ['asm-x8664', 'mov rax, [rbp]', '<span class="hl-instruction">mov</span> <span class="hl-register">rax</span>, [<span class="hl-register">rbp</span>]'], |
| 123 | ['asm-6502', 'lda #$FF', '<span class="hl-instruction">lda</span> <span class="hl-number">#$FF</span>'], |
| 124 | ]; |
| 125 | foreach ($cases as [$lang, $code, $expect]) { |
| 126 | $test->assertStringContains($expect, (new Syntax($lang))->colorize($code), "{$lang}:{$code}"); |
| 127 | } |
| 128 | }); |
| 129 | |
| 130 | $test->it('空のコードとCRLFを扱えるはず', function($test): void { |
| 131 | $test->assertEquals('', (new Syntax('c'))->colorize('')); |
| 132 | // CRLFはLFに揃える(行コメントに「\r」が入らない様に) |
| 133 | $html = (new Syntax('c'))->colorize("int a; // x\r\nint b;\r\n"); |
| 134 | $test->assertEquals("int a; // x\nint b;\n", html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8')); |
| 135 | $test->assertStringContains('<span class="hl-comment">// x</span>', $html); |
| 136 | }); |
| 137 | |
| 138 | $test->it('長いコードでも時間が掛からないはず', function($test): void { |
| 139 | $code = str_repeat("for (int i = 0; i < 10; i++) { x += \"s\"; } /* c */ // d\n", 2000); |
| 140 | $start = microtime(true); |
| 141 | $test->assertTrue(syntaxRoundtrip('cpp', $code)); |
| 142 | $test->assertTrue(microtime(true) - $start < 5, '時間が掛かり過ぎ'); |
| 143 | }); |
| 144 | |
| 145 | $test->it('別名を解決するはず', function($test): void { |
| 146 | $test->assertEquals('cpp', Syntax::resolve('c++')); |
| 147 | $test->assertEquals('csharp', Syntax::resolve('C#')); |
| 148 | $test->assertEquals('asm-aarch64', Syntax::resolve('arm64')); |
| 149 | $test->assertFalse(Syntax::isSupported('sh')); |
| 150 | $test->assertEquals('objc', Syntax::resolve('objective-c')); |
| 151 | $test->assertEquals('asm-x8664', Syntax::resolve('nasm')); |
| 152 | $test->assertEquals('glsl', Syntax::resolve('frag')); |
| 153 | $test->assertTrue(Syntax::isSupported('slang')); |
| 154 | $test->assertTrue(Syntax::isSupported('HLSL')); |
| 155 | }); |
| 156 | }); |
| 157 | |
| 158 | $test->printSummary(); |