├── .clang-format ├── .clangd ├── .editorconfig ├── .gitignore ├── README.md ├── example ├── before.gx.go ├── foo │ └── foo.gx.go ├── gxsl │ └── main.gx.go ├── main.gx.go ├── person │ ├── person.gx.go │ └── person.hh ├── rect.hh ├── sum_fields.hh └── util.gx.go ├── go.mod ├── go.sum ├── gx.go ├── gx.hh └── run.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | IndentWidth: 2 4 | SortIncludes: false 5 | BreakBeforeBraces: Attach 6 | SpaceInEmptyBlock: false 7 | MaxEmptyLinesToKeep: 2 8 | PointerAlignment: Right 9 | AllowShortFunctionsOnASingleLine: None 10 | AlwaysBreakTemplateDeclarations: Yes 11 | SpaceAfterTemplateKeyword: false 12 | AllowShortLambdasOnASingleLine: false 13 | AllowShortBlocksOnASingleLine: false 14 | IncludeBlocks: Preserve 15 | AccessModifierOffset: -2 16 | ColumnLimit: 100 17 | FixNamespaceComments: false 18 | ... 19 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | # Help clangd with the 'build/example.gx.cc' output file 2 | CompileFlags: 3 | Add: [-std=c++20, -Wall, -O3, -I../example] 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.go] 2 | indent_style = tab 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gx 2 | gx.exe 3 | build/* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Currently being used mostly for game / interactive application experiments-- 2 | - [Development workflow in a game](https://www.youtube.com/watch?v=8He97Sl9iy0) (state-preserving code reload incl. crash recovery, reflection-based serialization and scene editor UI) 3 | - [Game jam game it was used on](https://github.com/nikki93/raylib-5k) (source included, link to game playable in web (Wasm), same engine as in above video) 4 | 5 | # gx, a Go -> C++ compiler 6 | 7 | Historically I've used C and C++ for gameplay programming so far, but there are various usability issues with it, especially regarding various dark corners and tangents in the language. With intention I'm able to be aware of and stick to a subset that works well, but generally it's tough to have new programmers pick it up or to establish consensus and best practices across a team (I lead engineering on a team that uses C++ for a game engine, and the language definitely complicates things). This was the reasoning behind making Go itself -- eg. in this talk from Rob Pike https://www.infoq.com/presentations/Go-Google/ (esp. around 11 minutes in) 8 | 9 | I went on a bit of a journey diving into Nim, Zig, Rust, Go and a few other things for the same purpose, and each thing was almost there but with its own differences that made it not be exactly what I wanted. Semantically Go was my favorite -- just a great combination of simplification and focus. But the runtime didn't fit the game development scenario so well, especially for WebAssembly which is definitely a target I need. When testing a game on WebAssembly with Go I was hitting GC pauses frequently and the perf was pretty not great. I had to stick with C++ in practice, both for my main project at work and my side projects. I know I can "do things to maybe cause the GC to run less" or such, but then that immediately starts to detract from the goal of having a language where I can focus on just the gameplay code. 10 | 11 | Over time I collected some ideas about what I'd like to see in my own language for this purpose, but actually building my own compiler (parser, semantic analysis, codegen etc.) from scratch, and also all of the tools alongside (editor integration, github highlighting its syntax etc.) is just a huge task. So I was also kind of thinking about taking an existing language toolchain and modifying it. Modifying C++ was something I looked into briefly but LLVM's codebase is just not fun. I looked at TypeScript briefly. Some of the other languages are still kind of evolving pretty fast / are early, and that adds design+impl overhead about tracking their evolution. But then I found that Go's parser and typechecker that are already in the standard library had a really nice API. And the 'tools/packages' library goes further and automatically resolves packages with usual Go semantics (including modules) which was perfect. Design-wise it was also perfect I think, by providing a focused core that I can add semantics around as I need. Methods being definable outside structs was just the icing on the cake in terms of alignment with the data-oriented style I was after (func (t Thing) Foo(...) codegens foo(t Thing, ...), which interops really well both with calling to C libraries but also overload-resolution in surrounding C++ code that calls your code). 12 | 13 | So I figured I could probably write a compiler to C++ pretty easily since C++ is about at the same level, I don't really have to 'lower' any of the Go constructs, it's just translation. I started on that and it turned out to work pretty soon. All the tools just work since it appears as regular go code: my editor integration autocompletes and shows errors, highlights, GitHub highlighting and cross references work, gofmt works, etc. 14 | 15 | That's the background / journey, but to summarize just the reasons: 16 | 17 | * **Portability**: C++ runs in a lot of contexts that I care about: WebAssembly, natively on major desktops, and on iOS and Android. This is pretty important for me for game and UI application use cases. 18 | * **Interop**: With this compiler I have direct interop to existing C++ libraries and codebases. There's no added overhead or anything since it actually just directly codegens calls to those libraries in the generated code. All you need to do is put //gx:extern TheCppFunc above a function declaration in Go and you're saying "don't generate this function, and when calling just call TheCppFunc instead." For example I use EnTT for the entity-component data storage in the game demo here, and I'm able to call to the C++ including Go's generic syntax translating to template calls. That's not as easy with Cgo (and also Cgo adds huge overhead). This is often pretty important in game / UI application development. 19 | * **Performance**: This compiler targets a specific subset of Go that I think of as being perfect for data-oriented gameplay code. There's no GC or concurrency. Slices and strings are just value types and their memory is released automatically at the end of a scope or if their containing struct was dropped (this is just C++ behavior). So the perf loss of the GC is just not there. But there's not much of a loss of ergonomics, since game code is usually not a pointer soup, and the ECS is the main way that I store all of the dynamic data. You don't see any 'manual memory management' in any of the game code at all, but there's also no GC. That's just the GC point -- in general there's more control over what code is generated since I have a general sense of what C++ compiles to. eg. the language only supports non-escaping lambdas, and those get usually inlined by clang. This gains all of the optimization of clang+llvm. 20 | * **Control**: The entire compiler is ~1500 lines of Go, which makes it easy for me to make any change as it comes up in practice as I work on the game or other applications. For example, it's useful in my game to track all of the structs that make up components for entities, so that I can deserialize them from JSON. This was a pretty easy feature to add by just adding it to the compiler. Generally speaking all of the 'metaprogramming' things I want to do (of which there are specific things that help in games, mostly related to game content stored as data made by level designers or showing them in editor tools) is pretty straightforward to do with this level of compiler control. In metaprogramming-capable languages like Nim and Zig that I dug into, you still have to orient what you want to do in terms of their metaprogramming model and it doesn't often fit or often isn't even possible. Another example -- this was the entire change needed to add default values for struct fields (granted, it leverages the C++ feature, but that's also kind of the point -- C++ is a great kitchen sink of language features that I can now design in terms of). 21 | 22 | The current scope of this is just to use in this game, which is a side project I'm working on with a few friends. The stretch goal later is to make it more of a framework and tool (including the scene editor and entity system) for new programmers to use to dive into gameplay programming in a data oriented style, while incrementally being able to go deeper into engine things and not feel like there's a big dichotomy between some scripting language they use and the underlying engine (this is often how it is with existing engines). You can always mess with the bytes, call to C/C++ things, implement things yourself, etc. But at the same time I want there to be usability things like a scene and property editor, even if you own the data yourself in structs and slices. That's what you see in the [demo video](https://www.youtube.com/watch?v=8He97Sl9iy0). 23 | -------------------------------------------------------------------------------- /example/before.gx.go: -------------------------------------------------------------------------------- 1 | // This file appears before 'main.gx.go' in AST order. Used to test that 2 | // references to symbols from later files leads to proper hoisting in output. 3 | 4 | package main 5 | 6 | type Before struct { 7 | p Point // Reference to type spec from main 8 | } 9 | 10 | var globalW = globalX + 42 // Reference to value spec from main 11 | -------------------------------------------------------------------------------- /example/foo/foo.gx.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Foo struct { 4 | val int 5 | } 6 | 7 | type Bar struct { 8 | X, Y int 9 | } 10 | 11 | func (f *Foo) Val() int { 12 | return f.val 13 | } 14 | 15 | func NewFoo(val int) Foo { 16 | return Foo{val} 17 | } 18 | -------------------------------------------------------------------------------- /example/gxsl/main.gx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Built-ins 5 | // 6 | 7 | //gxsl:extern vec2 8 | type Vec2 struct { 9 | X, Y float64 10 | } 11 | 12 | //gxsl:extern vec4 13 | type Vec4 struct { 14 | X, Y, Z, W float64 15 | } 16 | 17 | //gxsl:extern + 18 | func (v Vec4) Add(u Vec4) Vec4 19 | 20 | //gxsl:extern - 21 | func (v Vec4) Negate() Vec4 22 | 23 | //gxsl:extern * 24 | func (v Vec4) Multiply(u Vec4) Vec4 25 | 26 | //gxsl:extern * 27 | func (v Vec4) Scale(f float64) Vec4 28 | 29 | //gxsl:extern dot 30 | func (v Vec4) DotProduct(u Vec4) float64 31 | 32 | //gxsl:extern sampler2D 33 | type Sampler2D struct{} 34 | 35 | //gxsl:extern texture2D 36 | func Texture2D(sampler Sampler2D, coord Vec2) Vec4 37 | 38 | //gxsl:extern gl_FragColor 39 | var gl_FragColor Vec4 40 | 41 | // 42 | // Shader 43 | // 44 | 45 | //gx:extern INVALID 46 | type Varyings struct { 47 | FragTexCoord Vec2 48 | FragColor Vec4 49 | } 50 | 51 | //gx:extern INVALID 52 | type RedTextureParams struct { 53 | ColDiffuse Vec4 54 | Texture0 Sampler2D 55 | Triple FloatTriple 56 | Tricky Varyings 57 | } 58 | 59 | //gx:extern INVALID 60 | func scaleByFive(vec Vec4) Vec4 { 61 | return scaleByNum(vec, 3).Add(scaleByTwo(vec)) 62 | } 63 | 64 | //gx:extern INVALID 65 | func scaleByTwo(vec Vec4) Vec4 { 66 | return scaleByNum(vec, 2) 67 | } 68 | 69 | //gx:extern INVALID 70 | func scaleByNum(vec Vec4, num float64) Vec4 { 71 | return vec.Scale(num) 72 | } 73 | 74 | //gx:extern INVALID 75 | type FloatPair struct { 76 | A, B float64 77 | } 78 | 79 | //gx:extern INVALID 80 | func (pair FloatPair) Sum() float64 { 81 | return pair.A + pair.B 82 | } 83 | 84 | //gx:extern INVALID 85 | type FloatTriple struct { 86 | A, B, C float64 87 | } 88 | 89 | //gx:extern INVALID 90 | var red = Vec4{-1, -0.2, -0.2, -1}.Negate() 91 | 92 | //gxsl:shader 93 | func redTextureShader(uniforms RedTextureParams, varyings Varyings) { 94 | result := red 95 | 96 | texelColor := Texture2D(uniforms.Texture0, varyings.FragTexCoord) 97 | result = result.Multiply(texelColor) 98 | 99 | result = result.Multiply(uniforms.ColDiffuse) 100 | 101 | result = result.Multiply(varyings.FragColor) 102 | 103 | result = scaleByFive(result.Scale(result.DotProduct(Vec4{1, 0, 0, 1}))) 104 | 105 | floatPair := FloatPair{2, 3} 106 | result = result.Scale(floatPair.Sum()) 107 | 108 | gl_FragColor = result 109 | } 110 | 111 | // 112 | // Main 113 | // 114 | 115 | func main() { 116 | } 117 | -------------------------------------------------------------------------------- /example/main.gx.go: -------------------------------------------------------------------------------- 1 | //gx:include 2 | //gx:include "rect.hh" 3 | //gx:include "sum_fields.hh" 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/nikki93/gx/example/foo" 9 | "github.com/nikki93/gx/example/person" 10 | ) 11 | 12 | // 13 | // Basics 14 | // 15 | 16 | func fib(n int) int { 17 | if n <= 1 { 18 | return n 19 | } else { 20 | return fib(n-1) + fib(n-2) 21 | } 22 | } 23 | 24 | func testFib() { 25 | check(fib(6) == 8) 26 | } 27 | 28 | func testUnary() { 29 | check(-(3) == -3) 30 | check(+(3) == 3) 31 | } 32 | 33 | func testVariables() { 34 | x := 3 35 | y := 4 36 | check(x == 3) 37 | check(y == 4) 38 | y = y + 2 39 | x = x + 1 40 | check(y == 6) 41 | check(x == 4) 42 | y += 2 43 | x += 1 44 | check(y == 8) 45 | check(x == 5) 46 | } 47 | 48 | func testIncDec() { 49 | x := 0 50 | x++ 51 | check(x == 1) 52 | x-- 53 | check(x == 0) 54 | } 55 | 56 | func testIf() { 57 | x := 0 58 | if cond := false; cond { 59 | x = 2 60 | } 61 | check(x == 0) 62 | } 63 | 64 | func testFor() { 65 | { 66 | sum := 0 67 | for i := 0; i < 5; i++ { 68 | sum += i 69 | } 70 | check(sum == 10) 71 | } 72 | { 73 | sum := 0 74 | i := 0 75 | for i < 5 { 76 | sum += i 77 | i++ 78 | } 79 | check(sum == 10) 80 | } 81 | { 82 | sum := 0 83 | i := 0 84 | for { 85 | if i >= 5 { 86 | break 87 | } 88 | sum += i 89 | i++ 90 | } 91 | check(sum == 10) 92 | } 93 | } 94 | 95 | // 96 | // Pointers 97 | // 98 | 99 | func setToFortyTwo(ptr *int) { 100 | *ptr = 42 101 | } 102 | 103 | func testPointer() { 104 | val := 42 105 | check(val == 42) 106 | ptr := &val 107 | *ptr = 14 108 | check(val == 14) 109 | setToFortyTwo(ptr) 110 | check(val == 42) 111 | } 112 | 113 | // 114 | // Structs 115 | // 116 | 117 | type Outer struct { 118 | x int 119 | y int 120 | inner Inner 121 | } 122 | 123 | type Inner struct { 124 | z int 125 | } 126 | 127 | func outerSum(o Outer) int { 128 | return o.x + o.y + o.inner.z 129 | } 130 | 131 | func setXToFortyTwo(o *Outer) { 132 | o.x = 42 133 | } 134 | 135 | type PtrPtr struct { 136 | pp **int // Should be formatted as `int **pp;` in C++ 137 | } 138 | 139 | func testStruct() { 140 | { 141 | s := Outer{} 142 | check(s.x == 0) 143 | check(s.y == 0) 144 | check(s.inner.z == 0) 145 | { 146 | p := &s 147 | p.x = 2 148 | check(p.x == 2) 149 | check(s.x == 2) 150 | s.y = 4 151 | check(p.y == 4) 152 | } 153 | check(outerSum(s) == 6) 154 | setXToFortyTwo(&s) 155 | check(s.x == 42) 156 | } 157 | { 158 | s := Outer{2, 3, Inner{4}} 159 | check(s.x == 2) 160 | check(s.y == 3) 161 | check(s.inner.z == 4) 162 | s.x += 1 163 | s.y += 1 164 | s.inner.z += 1 165 | check(s.x == 3) 166 | check(s.y == 4) 167 | check(s.inner.z == 5) 168 | } 169 | { 170 | s := Outer{x: 2, y: 3, inner: Inner{z: 4}} 171 | check(s.x == 2) 172 | check(s.y == 3) 173 | check(s.inner.z == 4) 174 | } 175 | { 176 | s := Outer{ 177 | x: 2, 178 | y: 3, 179 | inner: Inner{ 180 | z: 4, 181 | }, 182 | } 183 | check(s.x == 2) 184 | check(s.y == 3) 185 | check(s.inner.z == 4) 186 | } 187 | { 188 | // Out-of-order elements in struct literal no longer allowed 189 | //s := Outer{ 190 | // inner: Inner{ 191 | // z: 4, 192 | // }, 193 | // y: 3, 194 | // x: 2, 195 | //} 196 | } 197 | { 198 | i := 42 199 | p := &i 200 | pp := &p 201 | d := PtrPtr{pp} 202 | **d.pp = 14 203 | check(d.pp != nil) 204 | check(i == 14) 205 | } 206 | { 207 | p := PtrPtr{} 208 | check(p.pp == nil) 209 | } 210 | } 211 | 212 | func testLocalStruct() { 213 | type Inner struct { 214 | z string 215 | } 216 | inner := Inner{"hello"} 217 | check(inner.z == "hello") 218 | } 219 | 220 | // 221 | // Methods 222 | // 223 | 224 | type Point struct { 225 | x, y float32 226 | } 227 | 228 | func (p Point) sum() float32 { 229 | return p.x + p.y 230 | } 231 | 232 | func (p *Point) setZero() { 233 | p.x = 0 234 | p.y = 0 235 | } 236 | 237 | func testMethod() { 238 | p := Point{2, 3} 239 | check(p.sum() == 5) 240 | ptr := &p 241 | check(ptr.sum() == 5) // Pointer as value receiver 242 | p.setZero() // Addressable value as pointer receiver 243 | check(p.x == 0) 244 | check(p.y == 0) 245 | } 246 | 247 | // 248 | // Generics 249 | // 250 | 251 | type Numeric interface { 252 | int | float64 253 | } 254 | 255 | func add[T Numeric](a, b T) T { 256 | return a + b 257 | } 258 | 259 | type Holder[T any] struct { 260 | Item T 261 | } 262 | 263 | func incrHolder[T Numeric](h *Holder[T]) { 264 | h.Item += 1 265 | } 266 | 267 | func (h Holder[T]) get() T { 268 | return h.Item 269 | } 270 | 271 | func (h *Holder[T]) set(Item T) { 272 | h.Item = Item 273 | } 274 | 275 | func testGenerics() { 276 | { 277 | check(add(1, 2) == 3) 278 | check(add(1.2, 2.0) == 3.2) 279 | check(add[float64](1.2, 2.0) == 3.2) 280 | } 281 | { 282 | i := Holder[int]{42} 283 | check(i.Item == 42) 284 | incrHolder(&i) 285 | check(i.Item == 43) 286 | 287 | f := Holder[float64]{42} 288 | check(f.Item == 42) 289 | check(add(f.Item, 20) == 62) 290 | incrHolder(&f) 291 | check(f.Item == 43) 292 | 293 | p := Holder[Point]{Point{1, 2}} 294 | check(p.Item.x == 1) 295 | check(p.Item.y == 2) 296 | p.Item.setZero() 297 | check(p.Item.x == 0) 298 | check(p.Item.y == 0) 299 | 300 | p.set(Point{3, 2}) 301 | check(p.Item.x == 3) 302 | check(p.Item.y == 2) 303 | check(p.get().x == 3) 304 | check(p.get().y == 2) 305 | } 306 | } 307 | 308 | // 309 | // Lambdas 310 | // 311 | 312 | func iterateOneToTen(f func(int)) { 313 | for i := 1; i <= 10; i++ { 314 | f(i) 315 | } 316 | } 317 | 318 | func testLambdas() { 319 | { 320 | val := 42 321 | check(val == 42) 322 | foo := func(newVal int) { 323 | val = newVal 324 | } 325 | foo(14) 326 | check(val == 14) 327 | 328 | val2 := func() int { 329 | return val 330 | }() 331 | check(val2 == val) 332 | } 333 | { 334 | sum := 0 335 | iterateOneToTen(func(i int) { 336 | sum += i 337 | }) 338 | check(sum == 55) 339 | } 340 | } 341 | 342 | // 343 | // Arrays 344 | // 345 | 346 | func setSecondElementToThree(arr *[4]int) { 347 | arr[1] = 3 348 | } 349 | 350 | type HasArray struct { 351 | arr [4]int 352 | } 353 | 354 | func testArrays() { 355 | { 356 | arr := [4]int{1, 2, 3, 4} 357 | check(arr[2] == 3) 358 | sum := 0 359 | for i := 0; i < len(arr); i++ { 360 | sum += arr[i] 361 | } 362 | check(sum == 10) 363 | check(arr[1] == 2) 364 | setSecondElementToThree(&arr) 365 | check(arr[1] == 3) 366 | } 367 | { 368 | stuff := [...]int{1, 2, 3} 369 | check(len(stuff) == 3) 370 | sum := 0 371 | for i, elem := range stuff { 372 | check(i+1 == elem) 373 | sum += elem 374 | } 375 | check(sum == 6) 376 | // Other cases of for-range are checked in `testSlices` 377 | } 378 | { 379 | arr := [...][2]int{{1, 2}, {3, 4}} 380 | check(len(arr) == 2) 381 | check(arr[0][0] == 1) 382 | check(arr[0][1] == 2) 383 | check(arr[1][0] == 3) 384 | check(arr[1][1] == 4) 385 | } 386 | { 387 | h := HasArray{} 388 | check(len(h.arr) == 4) 389 | check(h.arr[0] == 0) 390 | check(h.arr[1] == 0) 391 | check(h.arr[2] == 0) 392 | check(h.arr[3] == 0) 393 | } 394 | { 395 | h := HasArray{[4]int{1, 2, 3, 4}} 396 | check(len(h.arr) == 4) 397 | check(h.arr[2] == 3) 398 | } 399 | } 400 | 401 | // 402 | // Slices 403 | // 404 | 405 | func appendFortyTwo(s *[]int) { 406 | *s = append(*s, 42) 407 | } 408 | 409 | func testSlices() { 410 | { 411 | s := []int{} 412 | check(len(s) == 0) 413 | s = append(s, 1) 414 | s = append(s, 2) 415 | check(len(s) == 2) 416 | check(s[0] == 1) 417 | check(s[1] == 2) 418 | appendFortyTwo(&s) 419 | check(len(s) == 3) 420 | check(s[2] == 42) 421 | } 422 | { 423 | s := [][]int{{1}, {}, {3, 4}} 424 | check(len(s) == 3) 425 | check(len(s[0]) == 1) 426 | check(s[0][0] == 1) 427 | check(len(s[1]) == 0) 428 | check(len(s[2]) == 2) 429 | check(s[2][0] == 3) 430 | check(s[2][1] == 4) 431 | } 432 | { 433 | stuff := []int{1, 2} 434 | stuff = append(stuff, 3) 435 | check(len(stuff) == 3) 436 | { 437 | sum := 0 438 | for i, elem := range stuff { 439 | check(i+1 == elem) 440 | sum += elem 441 | } 442 | check(sum == 6) 443 | } 444 | { 445 | sum := 0 446 | for i := range stuff { 447 | sum += i 448 | } 449 | check(sum == 3) 450 | } 451 | { 452 | sum := 0 453 | for _, elem := range stuff { 454 | sum += elem 455 | } 456 | check(sum == 6) 457 | } 458 | { 459 | count := 0 460 | for range stuff { 461 | count += 1 462 | } 463 | check(count == 3) 464 | } 465 | { 466 | stuff = []int{} 467 | count := 0 468 | for range stuff { 469 | count += 1 470 | } 471 | check(count == 0) 472 | } 473 | } 474 | } 475 | 476 | // 477 | // Seq (generic slice with own methods) 478 | // 479 | 480 | type Seq[T any] []T 481 | 482 | func (s *Seq[T]) len() int { 483 | return len(*s) 484 | } 485 | 486 | func (s *Seq[T]) add(val T) { 487 | *s = append(*s, val) 488 | } 489 | 490 | type Increr[T any] interface { 491 | *T 492 | incr() 493 | } 494 | 495 | func incrSeq[T any, PT Increr[T]](s *Seq[T]) { 496 | for i := range *s { 497 | PT(&(*s)[i]).incr() 498 | } 499 | } 500 | 501 | type SingleIncr struct { 502 | val int 503 | } 504 | 505 | func (s *SingleIncr) incr() { 506 | s.val += 1 507 | } 508 | 509 | type DoubleIncr struct { 510 | val int 511 | } 512 | 513 | func (s *DoubleIncr) incr() { 514 | s.val += 2 515 | } 516 | 517 | func testSeqs() { 518 | { 519 | s := Seq[int]{} 520 | check(s.len() == 0) 521 | s.add(1) 522 | s.add(2) 523 | check(s.len() == 2) 524 | check(s[0] == 1) 525 | check(s[1] == 2) 526 | } 527 | { 528 | s := Seq[int]{1, 2, 3} 529 | check(s.len() == 3) 530 | sum := 0 531 | for i, elem := range s { 532 | check(i+1 == elem) 533 | sum += elem 534 | } 535 | check(sum == 6) 536 | } 537 | { 538 | s := Seq[Point]{{1, 2}, {3, 4}} 539 | check(s.len() == 2) 540 | check(s[0].x == 1) 541 | check(s[0].y == 2) 542 | check(s[1].x == 3) 543 | check(s[1].y == 4) 544 | s.add(Point{5, 6}) 545 | check(s[2].x == 5) 546 | check(s[2].y == 6) 547 | } 548 | { 549 | s := Seq[Point]{{x: 1, y: 2}, {x: 3, y: 4}} 550 | check(s.len() == 2) 551 | } 552 | { 553 | s := Seq[Seq[int]]{{1}, {}, {3, 4}} 554 | check(s.len() == 3) 555 | check(len(s[0]) == 1) 556 | check(s[0][0] == 1) 557 | check(s[1].len() == 0) 558 | check(s[2].len() == 2) 559 | check(s[2][0] == 3) 560 | check(s[2][1] == 4) 561 | } 562 | { 563 | s := Seq[SingleIncr]{{1}, {2}, {3}} 564 | incrSeq(&s) 565 | check(s[0].val == 2) 566 | check(s[1].val == 3) 567 | check(s[2].val == 4) 568 | } 569 | { 570 | s := Seq[DoubleIncr]{{1}, {2}, {3}} 571 | incrSeq(&s) 572 | check(s[0].val == 3) 573 | check(s[1].val == 4) 574 | check(s[2].val == 5) 575 | } 576 | } 577 | 578 | // 579 | // Global variables 580 | // 581 | 582 | var globalY = globalX - three() 583 | var globalX, globalZ = initialGlobalX, 14 584 | var globalSlice []int 585 | 586 | const initialGlobalX = 23 587 | 588 | func setGlobalXToFortyTwo() { 589 | globalX = 42 590 | } 591 | 592 | func checkGlobalXIsFortyTwo() { 593 | check(globalX == 42) 594 | } 595 | 596 | func three() int { 597 | return 3 598 | } 599 | 600 | func isGlobalSliceEmpty() bool { 601 | return len(globalSlice) == 0 602 | } 603 | 604 | func apply(val int, fn func(int) int) int { 605 | return fn(val) 606 | } 607 | 608 | var globalApplied = apply(3, func(i int) int { return 2 * i }) 609 | 610 | type Enum int 611 | 612 | const ( 613 | ZeroEnum Enum = 0 614 | OneEnum = 1 615 | TwoEnum = 2 616 | ) 617 | 618 | func testGlobalVariables() { 619 | { 620 | check(globalX == 23) 621 | check(globalY == 20) 622 | check(globalZ == 14) 623 | setGlobalXToFortyTwo() 624 | checkGlobalXIsFortyTwo() 625 | check(initialGlobalX == 23) 626 | } 627 | { 628 | check(isGlobalSliceEmpty()) 629 | globalSlice = append(globalSlice, 1) 630 | globalSlice = append(globalSlice, 2) 631 | check(len(globalSlice) == 2) 632 | check(globalSlice[0] == 1) 633 | check(globalSlice[1] == 2) 634 | check(!isGlobalSliceEmpty()) 635 | } 636 | { 637 | check(globalApplied == 6) 638 | } 639 | { 640 | check(ZeroEnum == 0) 641 | check(OneEnum == 1) 642 | check(TwoEnum == 2) 643 | } 644 | } 645 | 646 | // 647 | // Imports 648 | // 649 | 650 | func testImports() { 651 | { 652 | f := foo.Foo{} 653 | check(f.Val() == 0) 654 | } 655 | { 656 | f := foo.NewFoo(42) 657 | check(f.Val() == 42) 658 | } 659 | { 660 | b := foo.Bar{X: 2, Y: 3} 661 | check(b.X == 2) 662 | check(b.Y == 3) 663 | } 664 | } 665 | 666 | // 667 | // Externs 668 | // 669 | 670 | //gx:extern rect::NUM_VERTICES 671 | const RectNumVertices = 0 // Ensure use of actual C++ constant value 672 | 673 | //gx:extern rect::Rect 674 | type Rect struct { 675 | X, Y float32 676 | Width, Height float32 677 | } 678 | 679 | //gx:extern rect::area 680 | func area(r Rect) float32 681 | 682 | //gx:extern rect::area 683 | func (r Rect) area() float32 684 | 685 | func testExterns() { 686 | { 687 | check(RectNumVertices == 4) 688 | r := Rect{X: 100, Y: 100, Width: 20, Height: 30} 689 | check(r.X == 100) 690 | check(r.Y == 100) 691 | check(r.Width == 20) 692 | check(r.Height == 30) 693 | check(area(r) == 600) 694 | check(r.area() == 600) 695 | } 696 | { 697 | check(person.Population == 0) 698 | p := person.NewPerson(20, 100) 699 | check(person.Population == 1) 700 | check(p.Age() == 20) 701 | check(p.Health() == 100) 702 | p.Grow() 703 | check(p.Age() == 21) 704 | check(p.GXValue == 42) 705 | check(p.GetAgeAdder()(1) == 22) 706 | } 707 | } 708 | 709 | // 710 | // Conversions 711 | // 712 | 713 | func testConversions() { 714 | { 715 | f := float32(2.2) 716 | i := int(f) 717 | check(i == 2) 718 | d := 2.2 719 | check(f-float32(d) == 0) 720 | } 721 | { 722 | slice := []int{1, 2} 723 | seq := Seq[int](slice) 724 | seq.add(3) 725 | check(seq.len() == 3) 726 | check(seq[0] == 1) 727 | check(seq[1] == 2) 728 | check(seq[2] == 3) 729 | } 730 | } 731 | 732 | // 733 | // Meta 734 | // 735 | 736 | type Nums struct { 737 | A, B, C int 738 | D int `attribs:"twice"` 739 | } 740 | 741 | //gx:extern sumFields 742 | func sumFields(val interface{}) int 743 | 744 | func testMeta() { 745 | n := Nums{1, 2, 3, 4} 746 | check(sumFields(n) == 14) 747 | } 748 | 749 | // 750 | // Defaults 751 | // 752 | 753 | type HasDefaults struct { 754 | foo int `default:"42"` 755 | bar float32 `default:"6.4"` 756 | point Point `default:"{ 1, 2 }"` 757 | } 758 | 759 | func testDefaults() { 760 | h := HasDefaults{} 761 | check(h.foo == 42) 762 | check(h.bar == 6.4) 763 | check(h.point.x == 1) 764 | check(h.point.y == 2) 765 | } 766 | 767 | // 768 | // Strings 769 | // 770 | 771 | //gx:extern std::strcmp 772 | func strcmp(a, b string) int 773 | 774 | type HasString struct { 775 | s string 776 | } 777 | 778 | func testStrings() { 779 | { 780 | s0 := "" 781 | check(len(s0) == 0) 782 | check(strcmp(s0, "") == 0) 783 | 784 | s1 := "foo" 785 | check(len(s1) == 3) 786 | check(s1[0] == 'f') 787 | check(s1[1] == 'o') 788 | check(s1[2] == 'o') 789 | check(strcmp(s1, "foo") == 0) 790 | 791 | s2 := "foo" 792 | check(strcmp(s1, s2) == 0) 793 | check(strcmp(s1, "nope") != 0) 794 | check(strcmp(s1, "foo") == 0) 795 | check(s1 == s2) 796 | check(s1 != "nope") 797 | check(s1 == string("foo")) 798 | check(s1 != string("fao")) 799 | 800 | s3 := s2 801 | check(strcmp(s1, s3) == 0) 802 | 803 | sum := 0 804 | for i, c := range s3 { 805 | sum += i 806 | if i == 0 { 807 | check(c == 'f') 808 | } 809 | if i == 1 { 810 | check(c == 'o') 811 | } 812 | if i == 2 { 813 | check(c == 'o') 814 | } 815 | } 816 | check(sum == 3) 817 | } 818 | { 819 | h0 := HasString{} 820 | check(len(h0.s) == 0) 821 | check(strcmp(h0.s, "") == 0) 822 | 823 | h1 := HasString{"foo"} 824 | check(len(h1.s) == 3) 825 | check(h1.s[0] == 'f') 826 | check(h1.s[1] == 'o') 827 | check(h1.s[2] == 'o') 828 | check(strcmp(h1.s, "foo") == 0) 829 | 830 | h2 := HasString{"foo"} 831 | check(strcmp(h1.s, h2.s) == 0) 832 | check(strcmp(h1.s, HasString{"nope"}.s) != 0) 833 | check(strcmp(h1.s, HasString{"foo"}.s) == 0) 834 | 835 | h3 := h2 836 | check(strcmp(h1.s, h3.s) == 0) 837 | } 838 | { 839 | secondCharIsO := func(s string) bool { 840 | return len(s) >= 2 && s[1] == 'o' 841 | } 842 | check(secondCharIsO("foo")) 843 | check(!secondCharIsO("fxo")) 844 | check(!secondCharIsO("x")) 845 | } 846 | } 847 | 848 | // 849 | // Defer 850 | // 851 | 852 | func testDefer() { 853 | x := 0 854 | { 855 | defer func() { x = 1 }() 856 | check(x == 0) 857 | } 858 | check(x == 1) 859 | 860 | setXTo2 := func() { 861 | x = 2 862 | } 863 | { 864 | defer setXTo2() 865 | check(x == 1) 866 | } 867 | check(x == 2) 868 | 869 | y := 0 870 | setYTo1AndReturn5 := func() int { 871 | y = 1 872 | return 5 873 | } 874 | setXToValue := func(val int) { 875 | x = val 876 | } 877 | { 878 | defer setXToValue(setYTo1AndReturn5()) 879 | check(y == 0) 880 | } 881 | check(y == 1) 882 | check(x == 5) 883 | } 884 | 885 | // 886 | // Main 887 | // 888 | 889 | func main() { 890 | testFib() 891 | testUnary() 892 | testVariables() 893 | testIncDec() 894 | testIf() 895 | testFor() 896 | testPointer() 897 | testStruct() 898 | testMethod() 899 | testGenerics() 900 | testLambdas() 901 | testArrays() 902 | testSlices() 903 | testSeqs() 904 | testGlobalVariables() 905 | testImports() 906 | testExterns() 907 | testConversions() 908 | testMeta() 909 | testDefaults() 910 | testStrings() 911 | testDefer() 912 | } 913 | -------------------------------------------------------------------------------- /example/person/person.gx.go: -------------------------------------------------------------------------------- 1 | //gx:include "person/person.hh" 2 | //gx:externs person:: 3 | 4 | package person 5 | 6 | type Person struct { 7 | age int 8 | health float32 9 | GXValue int //gx:extern cppValue 10 | } 11 | 12 | var Population int 13 | 14 | func NewPerson(age int, health float32) Person 15 | 16 | //gx:extern person::GetAge 17 | func (p Person) Age() float32 18 | 19 | //gx:extern person::GetHealth 20 | func (p Person) Health() int 21 | 22 | func (p *Person) Grow() 23 | 24 | //gx:extern INVALID 25 | type AgeAdder func(i int) int 26 | 27 | func (p *Person) GetAgeAdder() AgeAdder 28 | -------------------------------------------------------------------------------- /example/person/person.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace person { 5 | 6 | struct Person { 7 | int age; 8 | float health; 9 | int cppValue = 42; 10 | }; 11 | 12 | inline int Population = 0; 13 | 14 | inline Person NewPerson(int age, float health) { 15 | ++Population; 16 | return Person { age, health }; 17 | } 18 | 19 | inline int GetAge(Person p) { 20 | return p.age; 21 | } 22 | 23 | inline float GetHealth(Person p) { 24 | return p.health; 25 | } 26 | 27 | inline void Grow(Person *p) { 28 | p->age++; 29 | } 30 | 31 | inline auto GetAgeAdder(Person *p) { 32 | return [age=p->age](int i) { 33 | return age + i; 34 | }; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /example/rect.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace rect { 5 | 6 | inline constexpr auto NUM_VERTICES = 4; 7 | 8 | struct Rect { 9 | float x, y; 10 | float width, height; 11 | }; 12 | 13 | inline float area(Rect r) { 14 | return r.width * r.height; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /example/sum_fields.hh: -------------------------------------------------------------------------------- 1 | struct SumFieldsAttribs { 2 | const char *name; 3 | bool twice = false; 4 | }; 5 | 6 | #define GX_FIELD_ATTRIBS SumFieldsAttribs 7 | 8 | int sumFields(auto &val) { 9 | auto sum = 0; 10 | forEachField(val, [&](auto fieldTag, auto &fieldVal) { 11 | if constexpr (fieldTag.attribs.twice) { 12 | sum += 2 * fieldVal; 13 | } else { 14 | sum += fieldVal; 15 | } 16 | }); 17 | return sum; 18 | } 19 | -------------------------------------------------------------------------------- /example/util.gx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func check(val bool) { 4 | if val { 5 | println("ok") 6 | } else { 7 | println("not ok") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nikki93/gx 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.0 6 | 7 | require golang.org/x/tools v0.25.0 8 | 9 | require ( 10 | golang.org/x/mod v0.21.0 // indirect 11 | golang.org/x/sync v0.8.0 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 2 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 3 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 4 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 5 | golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= 6 | golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 7 | -------------------------------------------------------------------------------- /gx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "go/ast" 8 | "go/token" 9 | "go/types" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "reflect" 14 | "regexp" 15 | "sort" 16 | "strconv" 17 | "strings" 18 | "unicode" 19 | 20 | "golang.org/x/tools/go/packages" 21 | ) 22 | 23 | type Target int 24 | 25 | const ( 26 | CPP Target = iota 27 | GLSL 28 | ) 29 | 30 | type Compiler struct { 31 | mainPkgPath string 32 | 33 | fileSet *token.FileSet 34 | types *types.Info 35 | 36 | target Target 37 | 38 | externs map[Target]map[types.Object]string 39 | fieldIndices map[*types.Var]int 40 | methodRenames map[types.Object]string 41 | methodFieldTags map[types.Object]string 42 | genTypeExprs map[Target]map[types.Type]string 43 | genTypeDecls map[*ast.TypeSpec]string 44 | genTypeDefns map[Target]map[*ast.TypeSpec]string 45 | genTypeMetas map[*ast.TypeSpec]string 46 | genFuncDecls map[Target]map[*ast.FuncDecl]string 47 | 48 | genIdentifierCount int 49 | 50 | indent int 51 | atBlockEnd bool 52 | 53 | errors *strings.Builder 54 | output *strings.Builder 55 | outputCC *strings.Builder 56 | outputHH *strings.Builder 57 | outputGLSLs map[string]*strings.Builder 58 | } 59 | 60 | // 61 | // Error and writing utilities 62 | // 63 | 64 | func (c *Compiler) errorf(pos token.Pos, format string, args ...interface{}) { 65 | fmt.Fprintf(c.errors, "%s: ", c.fileSet.PositionFor(pos, true)) 66 | fmt.Fprintf(c.errors, format, args...) 67 | fmt.Fprintln(c.errors) 68 | } 69 | 70 | func (c *Compiler) errored() bool { 71 | return c.errors.Len() != 0 72 | } 73 | 74 | func (c *Compiler) write(s string) { 75 | c.atBlockEnd = false 76 | if peek := c.output.String(); len(peek) > 0 && peek[len(peek)-1] == '\n' { 77 | for i := 0; i < 2*c.indent; i++ { 78 | c.output.WriteByte(' ') 79 | } 80 | } 81 | c.output.WriteString(s) 82 | } 83 | 84 | func trimFinalSpace(s string) string { 85 | if l := len(s); l > 0 && s[l-1] == ' ' { 86 | return s[0 : l-1] 87 | } else { 88 | return s 89 | } 90 | } 91 | 92 | func lowerFirst(s string) string { 93 | result := []rune(s) 94 | for i := 0; i < len(result) && unicode.IsUpper(result[i]); i++ { 95 | if !(i > 0 && i+1 < len(result) && unicode.IsLower(result[i+1])) { 96 | result[i] = unicode.ToLower(result[i]) 97 | } 98 | } 99 | return string(result) 100 | } 101 | 102 | func (c *Compiler) generateIdentifier(prefix string) string { 103 | c.genIdentifierCount++ 104 | builder := &strings.Builder{} 105 | builder.WriteString("gx__") 106 | builder.WriteString(prefix) 107 | builder.WriteString(strconv.Itoa(c.genIdentifierCount)) 108 | return builder.String() 109 | } 110 | 111 | // 112 | // Types 113 | // 114 | 115 | func (c *Compiler) genTypeExpr(typ types.Type, pos token.Pos) string { 116 | if result, ok := c.genTypeExprs[c.target][typ]; ok { 117 | return result 118 | } 119 | 120 | builder := &strings.Builder{} 121 | switch typ := typ.(type) { 122 | case *types.Basic: 123 | switch typ.Kind() { 124 | case types.Bool, types.UntypedBool: 125 | builder.WriteString("bool") 126 | case types.Int, types.UntypedInt: 127 | switch c.target { 128 | case CPP: 129 | builder.WriteString("int") 130 | case GLSL: 131 | builder.WriteString("float") 132 | } 133 | case types.Float32, types.Float64, types.UntypedFloat: 134 | builder.WriteString("float") 135 | case types.Uint: 136 | builder.WriteString("gx::uint") 137 | case types.Uint8: 138 | builder.WriteString("std::uint8_t") 139 | case types.Uint16: 140 | builder.WriteString("std::uint16_t") 141 | case types.Uint32: 142 | builder.WriteString("std::uint32_t") 143 | case types.Uint64: 144 | builder.WriteString("std::uint64_t") 145 | case types.String: 146 | builder.WriteString("gx::String") 147 | default: 148 | c.errorf(pos, "%s not supported", typ.String()) 149 | } 150 | builder.WriteByte(' ') 151 | case *types.Pointer: 152 | if typ, ok := typ.Elem().(*types.Basic); ok && typ.Kind() == types.String { 153 | builder.WriteString("const ") 154 | } 155 | builder.WriteString(c.genTypeExpr(typ.Elem(), pos)) 156 | builder.WriteByte('*') 157 | case *types.Named: 158 | name := typ.Obj() 159 | if ext, ok := c.externs[c.target][name]; ok { 160 | builder.WriteString(ext) 161 | } else { 162 | builder.WriteString(name.Name()) 163 | } 164 | if typeArgs := typ.TypeArgs(); typeArgs != nil { 165 | builder.WriteString("<") 166 | for i, nTypeArgs := 0, typeArgs.Len(); i < nTypeArgs; i++ { 167 | if i > 0 { 168 | builder.WriteString(", ") 169 | } 170 | builder.WriteString(trimFinalSpace(c.genTypeExpr(typeArgs.At(i), pos))) 171 | } 172 | builder.WriteString(">") 173 | } 174 | builder.WriteByte(' ') 175 | case *types.TypeParam: 176 | builder.WriteString(typ.Obj().Name()) 177 | builder.WriteByte(' ') 178 | case *types.Array: 179 | builder.WriteString("gx::Array<") 180 | builder.WriteString(trimFinalSpace(c.genTypeExpr(typ.Elem(), pos))) 181 | builder.WriteString(", ") 182 | builder.WriteString(strconv.FormatInt(typ.Len(), 10)) 183 | builder.WriteString(">") 184 | builder.WriteByte(' ') 185 | case *types.Slice: 186 | builder.WriteString("gx::Slice<") 187 | builder.WriteString(trimFinalSpace(c.genTypeExpr(typ.Elem(), pos))) 188 | builder.WriteString(">") 189 | builder.WriteByte(' ') 190 | default: 191 | c.errorf(pos, "%s not supported", typ.String()) 192 | } 193 | 194 | result := builder.String() 195 | c.genTypeExprs[c.target][typ] = result 196 | return result 197 | } 198 | 199 | func (c *Compiler) genTypeDecl(typeSpec *ast.TypeSpec) string { 200 | if result, ok := c.genTypeDecls[typeSpec]; ok { 201 | return result 202 | } 203 | 204 | builder := &strings.Builder{} 205 | if typeSpec.TypeParams != nil { 206 | builder.WriteString("template<") 207 | for i, typeParam := range typeSpec.TypeParams.List { 208 | for j, name := range typeParam.Names { 209 | if i > 0 || j > 0 { 210 | builder.WriteString(", ") 211 | } 212 | builder.WriteString("typename ") 213 | builder.WriteString(name.String()) 214 | } 215 | } 216 | builder.WriteString(">\n") 217 | } 218 | switch typeSpec.Type.(type) { 219 | case *ast.StructType: 220 | builder.WriteString("struct ") 221 | builder.WriteString(typeSpec.Name.String()) 222 | case *ast.InterfaceType: 223 | // Empty -- only used as generic constraint during typecheck 224 | builder = &strings.Builder{} 225 | default: 226 | builder.WriteString("using ") 227 | builder.WriteString(typeSpec.Name.String()) 228 | builder.WriteString(" = ") 229 | typ := c.types.TypeOf(typeSpec.Type) 230 | builder.WriteString(trimFinalSpace(c.genTypeExpr(typ, typeSpec.Type.Pos()))) 231 | } 232 | 233 | result := builder.String() 234 | c.genTypeDecls[typeSpec] = result 235 | return result 236 | } 237 | 238 | func (c *Compiler) genTypeDefn(typeSpec *ast.TypeSpec) string { 239 | if result, ok := c.genTypeDefns[c.target][typeSpec]; ok { 240 | return result 241 | } 242 | 243 | builder := &strings.Builder{} 244 | switch typ := typeSpec.Type.(type) { 245 | case *ast.StructType: 246 | builder.WriteString(c.genTypeDecl(typeSpec)) 247 | builder.WriteString(" {\n") 248 | for _, field := range typ.Fields.List { 249 | if fieldType := c.types.TypeOf(field.Type); fieldType != nil { 250 | var defaultVal string 251 | if tag := field.Tag; tag != nil && tag.Kind == token.STRING { 252 | unquoted, _ := strconv.Unquote(tag.Value) 253 | defaultVal = reflect.StructTag(unquoted).Get("default") 254 | } 255 | typeExpr := c.genTypeExpr(fieldType, field.Type.Pos()) 256 | for _, fieldName := range field.Names { 257 | builder.WriteString(" ") 258 | builder.WriteString(typeExpr) 259 | builder.WriteString(fieldName.String()) 260 | if defaultVal != "" { 261 | builder.WriteString(" = ") 262 | builder.WriteString(defaultVal) 263 | } 264 | builder.WriteString(";\n") 265 | } 266 | } 267 | } 268 | builder.WriteByte('}') 269 | case *ast.InterfaceType: 270 | // Empty -- only used as generic constraint during typecheck 271 | default: 272 | // Empty -- alias declaration is definition 273 | } 274 | 275 | result := builder.String() 276 | c.genTypeDefns[c.target][typeSpec] = result 277 | return result 278 | } 279 | 280 | func (c *Compiler) genTypeMeta(typeSpec *ast.TypeSpec) string { 281 | if result, ok := c.genTypeMetas[typeSpec]; ok { 282 | return result 283 | } 284 | 285 | builder := &strings.Builder{} 286 | switch typ := typeSpec.Type.(type) { 287 | case *ast.StructType: 288 | typeParamsBuilder := &strings.Builder{} 289 | if typeSpec.TypeParams != nil { 290 | for i, typeParam := range typeSpec.TypeParams.List { 291 | for j, name := range typeParam.Names { 292 | if i > 0 || j > 0 { 293 | typeParamsBuilder.WriteString(", ") 294 | } 295 | typeParamsBuilder.WriteString("typename ") 296 | typeParamsBuilder.WriteString(name.String()) 297 | } 298 | } 299 | } 300 | typeParams := typeParamsBuilder.String() 301 | typeExprBuilder := &strings.Builder{} 302 | typeExprBuilder.WriteString(typeSpec.Name.String()) 303 | if typeSpec.TypeParams != nil { 304 | typeExprBuilder.WriteString("<") 305 | for i, typeParam := range typeSpec.TypeParams.List { 306 | for j, name := range typeParam.Names { 307 | if i > 0 || j > 0 { 308 | typeExprBuilder.WriteString(", ") 309 | } 310 | typeExprBuilder.WriteString(name.String()) 311 | } 312 | } 313 | typeExprBuilder.WriteString(">") 314 | } 315 | typeExpr := typeExprBuilder.String() 316 | 317 | // `gx::FieldTag` specializations 318 | tagIndex := 0 319 | for _, field := range typ.Fields.List { 320 | if field.Type != nil { 321 | for _, fieldName := range field.Names { 322 | if fieldName.IsExported() { 323 | uppercase := false 324 | var attribs []string 325 | if tag := field.Tag; tag != nil && tag.Kind == token.STRING { 326 | unquoted, _ := strconv.Unquote(tag.Value) 327 | if attribsTag := reflect.StructTag(unquoted).Get("attribs"); attribsTag != "" { 328 | for _, attrib := range strings.Split(attribsTag, ",") { 329 | if attrib == "uppercase" { 330 | uppercase = true 331 | } else { 332 | attribs = append(attribs, strings.TrimSpace(attrib)) 333 | } 334 | } 335 | } 336 | } 337 | builder.WriteString("template<") 338 | builder.WriteString(typeParams) 339 | builder.WriteString(">\nstruct gx::FieldTag<") 340 | builder.WriteString(typeExpr) 341 | builder.WriteString(", ") 342 | builder.WriteString(strconv.Itoa(tagIndex)) 343 | builder.WriteString("> {\n") 344 | builder.WriteString(" inline static constexpr gx::FieldAttribs attribs { .name = \"") 345 | if uppercase { 346 | builder.WriteString(fieldName.String()) 347 | } else { 348 | builder.WriteString(lowerFirst(fieldName.String())) 349 | } 350 | builder.WriteByte('"') 351 | for _, attrib := range attribs { 352 | builder.WriteString(", .") 353 | builder.WriteString(attrib) 354 | builder.WriteString(" = true") 355 | } 356 | builder.WriteString(" };\n};\n") 357 | tagIndex++ 358 | } 359 | } 360 | } 361 | } 362 | 363 | // `forEachField` 364 | if typeParams != "" { 365 | builder.WriteString("template<") 366 | builder.WriteString(typeParams) 367 | builder.WriteString(">\n") 368 | } 369 | builder.WriteString("inline void forEachField(") 370 | builder.WriteString(typeExpr) 371 | builder.WriteString(" &val, auto &&func) {\n") 372 | tagIndex = 0 373 | for _, field := range typ.Fields.List { 374 | if field.Type != nil { 375 | for _, fieldName := range field.Names { 376 | if fieldName.IsExported() { 377 | builder.WriteString(" func(gx::FieldTag<") 378 | builder.WriteString(typeExpr) 379 | builder.WriteString(", ") 380 | builder.WriteString(strconv.Itoa(tagIndex)) 381 | builder.WriteString(">(), val.") 382 | builder.WriteString(fieldName.String()) 383 | builder.WriteString(");\n") 384 | tagIndex++ 385 | } 386 | } 387 | } 388 | } 389 | builder.WriteString("}") 390 | case *ast.InterfaceType: 391 | // Empty -- only used as generic constraint during typecheck 392 | default: 393 | // Empty -- alias declaration is definition 394 | } 395 | 396 | result := builder.String() 397 | c.genTypeMetas[typeSpec] = result 398 | return result 399 | } 400 | 401 | // 402 | // Functions 403 | // 404 | 405 | var methodFieldTagRe = regexp.MustCompile(`^(.*)_([^_]*)$`) 406 | 407 | func (c *Compiler) genFuncDecl(decl *ast.FuncDecl) string { 408 | if result, ok := c.genFuncDecls[c.target][decl]; ok { 409 | return result 410 | } 411 | 412 | obj := c.types.Defs[decl.Name] 413 | sig := obj.Type().(*types.Signature) 414 | recv := sig.Recv() 415 | 416 | builder := &strings.Builder{} 417 | 418 | // Type parameters 419 | addTypeParams := func(typeParams *types.TypeParamList) { 420 | if typeParams != nil { 421 | builder.WriteString("template<") 422 | for i, nTypeParams := 0, typeParams.Len(); i < nTypeParams; i++ { 423 | if i > 0 { 424 | builder.WriteString(", ") 425 | } 426 | builder.WriteString("typename ") 427 | builder.WriteString(typeParams.At(i).Obj().Name()) 428 | } 429 | builder.WriteString(">\n") 430 | } 431 | } 432 | var recvNamedType *types.Named 433 | if recv != nil { 434 | switch recvType := recv.Type().(type) { 435 | case *types.Named: 436 | recvNamedType = recvType 437 | addTypeParams(recvType.TypeParams()) 438 | case *types.Pointer: 439 | switch elemType := recvType.Elem().(type) { 440 | case *types.Named: 441 | recvNamedType = elemType 442 | addTypeParams(elemType.TypeParams()) 443 | } 444 | } 445 | } 446 | addTypeParams(sig.TypeParams()) 447 | 448 | // Return type 449 | if rets := sig.Results(); rets.Len() > 1 { 450 | c.errorf(decl.Type.Results.Pos(), "multiple return values not supported") 451 | } else if rets.Len() == 1 { 452 | ret := rets.At(0) 453 | builder.WriteString(c.genTypeExpr(ret.Type(), ret.Pos())) 454 | } else { 455 | if obj.Pkg().Name() == "main" && decl.Name.String() == "main" && recv == nil { 456 | builder.WriteString("int ") 457 | } else { 458 | builder.WriteString("void ") 459 | } 460 | } 461 | 462 | // Field tag 463 | name := decl.Name.String() 464 | fieldTag := "" 465 | if recvNamedType != nil { 466 | if structType, ok := recvNamedType.Underlying().(*types.Struct); ok { 467 | if matches := methodFieldTagRe.FindStringSubmatch(name); len(matches) == 3 { 468 | name = matches[1] 469 | fieldName := matches[2] 470 | matchingTagIndex := -1 471 | tagIndex := 0 472 | numFields := structType.NumFields() 473 | for fieldIndex := 0; fieldIndex < numFields; fieldIndex++ { 474 | if field := structType.Field(fieldIndex); field.Exported() && !field.Embedded() { 475 | if field.Name() == fieldName { 476 | matchingTagIndex = tagIndex 477 | } 478 | tagIndex++ 479 | } 480 | } 481 | typeExpr := trimFinalSpace(c.genTypeExpr(recvNamedType, recv.Pos())) 482 | if matchingTagIndex == -1 { 483 | c.errorf(decl.Name.Pos(), "struct %s has no field named %s", typeExpr, fieldName) 484 | } else { 485 | fieldTagBuilder := &strings.Builder{} 486 | fieldTagBuilder.WriteString("gx::FieldTag<") 487 | fieldTagBuilder.WriteString(typeExpr) 488 | fieldTagBuilder.WriteString(", ") 489 | fieldTagBuilder.WriteString(strconv.Itoa(matchingTagIndex)) 490 | fieldTagBuilder.WriteString(">") 491 | fieldTag = fieldTagBuilder.String() 492 | c.methodRenames[obj] = name 493 | c.methodFieldTags[obj] = fieldTag 494 | } 495 | } 496 | } 497 | } 498 | 499 | // Name 500 | builder.WriteString(name) 501 | 502 | // Parameters 503 | builder.WriteByte('(') 504 | addParam := func(param *types.Var) { 505 | typ := param.Type() 506 | if _, ok := typ.(*types.Signature); ok { 507 | builder.WriteString("auto &&") 508 | } else if basicType, ok := typ.(*types.Basic); ok && basicType.Kind() == types.String { 509 | builder.WriteString("const gx::String &") 510 | } else { 511 | builder.WriteString(c.genTypeExpr(typ, param.Pos())) 512 | } 513 | builder.WriteString(param.Name()) 514 | } 515 | if recv != nil { 516 | if fieldTag != "" { 517 | builder.WriteString(fieldTag) 518 | builder.WriteString(", ") 519 | } 520 | addParam(recv) 521 | } 522 | for i, nParams := 0, sig.Params().Len(); i < nParams; i++ { 523 | if i > 0 || recv != nil { 524 | builder.WriteString(", ") 525 | } 526 | addParam(sig.Params().At(i)) 527 | } 528 | builder.WriteByte(')') 529 | 530 | result := builder.String() 531 | c.genFuncDecls[c.target][decl] = result 532 | return result 533 | } 534 | 535 | // 536 | // Expressions 537 | // 538 | 539 | func (c *Compiler) writeIdent(ident *ast.Ident) { 540 | typ := c.types.Types[ident] 541 | if typ.IsNil() { 542 | c.write("nullptr") 543 | return 544 | } 545 | if typ.IsBuiltin() { 546 | c.write("gx::") 547 | } 548 | if ext, ok := c.externs[c.target][c.types.Uses[ident]]; ok { 549 | c.write(ext) 550 | } else { 551 | c.write(ident.Name) // TODO: Package namespace 552 | } 553 | } 554 | 555 | func (c *Compiler) writeBasicLit(lit *ast.BasicLit) { 556 | switch lit.Kind { 557 | case token.INT: 558 | c.write(lit.Value) 559 | switch c.target { 560 | case GLSL: 561 | c.write(".0") 562 | } 563 | case token.FLOAT: 564 | c.write(lit.Value) 565 | switch c.target { 566 | case CPP: 567 | c.write("f") 568 | } 569 | case token.STRING: 570 | if lit.Value[0] == '`' { 571 | c.write("R\"(") 572 | c.write(lit.Value[1 : len(lit.Value)-1]) 573 | c.write(")\"") 574 | } else { 575 | c.write(lit.Value) 576 | } 577 | case token.CHAR: 578 | c.write(lit.Value) 579 | default: 580 | c.errorf(lit.Pos(), "unsupported literal kind") 581 | } 582 | } 583 | 584 | func (c *Compiler) writeFuncLit(lit *ast.FuncLit) { 585 | sig := c.types.TypeOf(lit).(*types.Signature) 586 | if c.indent == 0 { 587 | c.write("[](") 588 | } else { 589 | c.write("[&](") 590 | } 591 | for i, nParams := 0, sig.Params().Len(); i < nParams; i++ { 592 | if i > 0 { 593 | c.write(", ") 594 | } 595 | param := sig.Params().At(i) 596 | if _, ok := param.Type().(*types.Signature); ok { 597 | c.write("auto &&") 598 | } else if basicType, ok := param.Type().(*types.Basic); ok && basicType.Kind() == types.String { 599 | c.write("const gx::String &") 600 | } else { 601 | c.write(c.genTypeExpr(param.Type(), param.Pos())) 602 | } 603 | c.write(param.Name()) 604 | } 605 | c.write(") ") 606 | c.writeBlockStmt(lit.Body) 607 | c.atBlockEnd = false 608 | } 609 | 610 | func (c *Compiler) writeCompositeLit(lit *ast.CompositeLit) { 611 | useParens := c.target == GLSL 612 | typeExpr := (c.genTypeExpr(c.types.TypeOf(lit), lit.Pos())) 613 | if useParens { 614 | c.write(trimFinalSpace(typeExpr)) 615 | c.write("(") 616 | } else { 617 | c.write(typeExpr) 618 | c.write("{") 619 | } 620 | if len(lit.Elts) > 0 { 621 | if _, ok := lit.Elts[0].(*ast.KeyValueExpr); ok { 622 | if typ, ok := c.types.TypeOf(lit).Underlying().(*types.Struct); ok { 623 | if nFields := typ.NumFields(); nFields != 0 { 624 | if _, ok := c.fieldIndices[typ.Field(0)]; !ok { 625 | for i := 0; i < nFields; i++ { 626 | c.fieldIndices[typ.Field(i)] = i 627 | } 628 | } 629 | } 630 | lastIndex := 0 631 | for _, elt := range lit.Elts { 632 | field := c.types.ObjectOf(elt.(*ast.KeyValueExpr).Key.(*ast.Ident)).(*types.Var) 633 | if index := c.fieldIndices[field]; index < lastIndex { 634 | c.errorf(lit.Pos(), "struct literal fields must appear in definition order") 635 | break 636 | } else { 637 | lastIndex = index 638 | } 639 | } 640 | } 641 | } 642 | if c.fileSet.Position(lit.Pos()).Line == c.fileSet.Position(lit.Elts[0].Pos()).Line { 643 | if !useParens { 644 | c.write(" ") 645 | } 646 | for i, elt := range lit.Elts { 647 | if i > 0 { 648 | c.write(", ") 649 | } 650 | c.writeExpr(elt) 651 | } 652 | if !useParens { 653 | c.write(" ") 654 | } 655 | } else { 656 | c.write("\n") 657 | c.indent++ 658 | nElts := len(lit.Elts) 659 | for i, elt := range lit.Elts { 660 | c.writeExpr(elt) 661 | if !(useParens && i == nElts-1) { 662 | c.write(",") 663 | } 664 | c.write("\n") 665 | } 666 | c.indent-- 667 | } 668 | } 669 | if useParens { 670 | c.write(")") 671 | } else { 672 | c.write("}") 673 | } 674 | } 675 | 676 | func (c *Compiler) writeParenExpr(bin *ast.ParenExpr) { 677 | c.write("(") 678 | c.writeExpr(bin.X) 679 | c.write(")") 680 | } 681 | 682 | func (c *Compiler) writeSelectorExpr(sel *ast.SelectorExpr) { 683 | switch c.target { 684 | case GLSL: 685 | if ident, ok := sel.X.(*ast.Ident); ok { 686 | if glslStorageClass(ident.Name) != "" { 687 | if ext, ok := c.externs[GLSL][c.types.Uses[sel.Sel]]; ok { 688 | c.write(ext) 689 | } else { 690 | c.write(c.types.TypeOf(sel.X).(*types.Named).Obj().Name()) 691 | c.write("_") 692 | c.write(sel.Sel.Name) 693 | } 694 | return 695 | } 696 | } 697 | } 698 | if basic, ok := c.types.TypeOf(sel.X).(*types.Basic); !(ok && basic.Kind() == types.Invalid) { 699 | if _, ok := c.types.TypeOf(sel.X).(*types.Pointer); ok { 700 | c.write("gx::deref(") 701 | c.writeExpr(sel.X) 702 | c.write(")") 703 | } else { 704 | c.writeExpr(sel.X) 705 | } 706 | c.write(".") 707 | } 708 | c.writeIdent(sel.Sel) 709 | } 710 | 711 | func (c *Compiler) writeIndexExpr(ind *ast.IndexExpr) { 712 | if _, ok := c.types.TypeOf(ind.X).(*types.Pointer); ok { 713 | c.write("gx::deref(") 714 | c.writeExpr(ind.X) 715 | c.write(")") 716 | } else { 717 | c.writeExpr(ind.X) 718 | } 719 | c.write("[") 720 | c.writeExpr(ind.Index) 721 | c.write("]") 722 | } 723 | 724 | func (c *Compiler) writeCallExpr(call *ast.CallExpr) { 725 | method := false 726 | funType := c.types.Types[call.Fun] 727 | if _, ok := funType.Type.Underlying().(*types.Signature); ok || funType.IsBuiltin() { 728 | // Function or method 729 | if sel, ok := call.Fun.(*ast.SelectorExpr); ok { 730 | obj := c.types.Uses[sel.Sel] 731 | if sig, ok := obj.Type().(*types.Signature); ok && sig.Recv() != nil { 732 | switch c.target { 733 | case GLSL: 734 | if ext, ok := c.externs[GLSL][c.types.Uses[sel.Sel]]; ok && !unicode.IsLetter(rune(ext[0])) { 735 | switch len(call.Args) { 736 | case 0: 737 | c.write("(") 738 | c.write(ext) 739 | c.write("(") 740 | c.writeExpr(sel.X) 741 | c.write("))") 742 | return 743 | case 1: 744 | c.write("((") 745 | c.writeExpr(sel.X) 746 | c.write(") ") 747 | c.write(ext) 748 | c.write(" (") 749 | c.writeExpr(call.Args[0]) 750 | c.write("))") 751 | return 752 | } 753 | c.errorf(call.Fun.Pos(), "GXSL operators must be unary or binary") 754 | } 755 | } 756 | method = true 757 | if rename, ok := c.methodRenames[obj]; ok { 758 | c.write(rename) 759 | } else { 760 | c.writeIdent(sel.Sel) 761 | } 762 | c.write("(") 763 | if fieldTag, ok := c.methodFieldTags[obj]; ok { 764 | c.write(fieldTag) 765 | c.write("{}, ") 766 | } 767 | _, xPtr := c.types.TypeOf(sel.X).(*types.Pointer) 768 | _, recvPtr := sig.Recv().Type().(*types.Pointer) 769 | if xPtr && !recvPtr { 770 | c.write("gx::deref(") 771 | c.writeExpr(sel.X) 772 | c.write(")") 773 | } else if !xPtr && recvPtr { 774 | c.write("&(") 775 | c.writeExpr(sel.X) 776 | c.write(")") 777 | } else { 778 | c.writeExpr(sel.X) 779 | } 780 | } 781 | } 782 | if !method { 783 | var typeArgs *types.TypeList 784 | switch fun := call.Fun.(type) { 785 | case *ast.Ident: // f(...) 786 | c.writeIdent(fun) 787 | typeArgs = c.types.Instances[fun].TypeArgs 788 | case *ast.SelectorExpr: // pkg.f(...) 789 | c.writeIdent(fun.Sel) 790 | typeArgs = c.types.Instances[fun.Sel].TypeArgs 791 | case *ast.IndexExpr: 792 | switch fun := fun.X.(type) { 793 | case *ast.Ident: // f[T](...) 794 | c.writeIdent(fun) 795 | typeArgs = c.types.Instances[fun].TypeArgs 796 | case *ast.SelectorExpr: // pkg.f[T](...) 797 | c.writeIdent(fun.Sel) 798 | typeArgs = c.types.Instances[fun.Sel].TypeArgs 799 | } 800 | default: 801 | c.writeExpr(fun) 802 | } 803 | switch c.target { 804 | case CPP: 805 | if typeArgs != nil { 806 | c.write("<") 807 | for i, nTypeArgs := 0, typeArgs.Len(); i < nTypeArgs; i++ { 808 | if i > 0 { 809 | c.write(", ") 810 | } 811 | c.write(trimFinalSpace(c.genTypeExpr(typeArgs.At(i), call.Fun.Pos()))) 812 | } 813 | c.write(">") 814 | } 815 | } 816 | c.write("(") 817 | } 818 | } else { 819 | // Conversion 820 | typeExpr := trimFinalSpace(c.genTypeExpr(funType.Type, call.Fun.Pos())) 821 | if _, ok := call.Fun.(*ast.ParenExpr); ok { 822 | c.write("(") 823 | c.write(typeExpr) 824 | c.write(")") 825 | } else { 826 | c.write(typeExpr) 827 | } 828 | c.write("(") 829 | } 830 | for i, arg := range call.Args { 831 | if i > 0 || method { 832 | c.write(", ") 833 | } 834 | c.writeExpr(arg) 835 | } 836 | c.write(")") 837 | } 838 | 839 | func (c *Compiler) writeStarExpr(star *ast.StarExpr) { 840 | c.write("gx::deref(") 841 | c.writeExpr(star.X) 842 | c.write(")") 843 | } 844 | 845 | func (c *Compiler) writeUnaryExpr(un *ast.UnaryExpr) { 846 | switch op := un.Op; op { 847 | case token.ADD, token.SUB, token.NOT: 848 | c.write(op.String()) 849 | case token.AND: 850 | if !c.types.Types[un.X].Addressable() { 851 | c.errorf(un.OpPos, "cannot take address of a temporary object") 852 | } 853 | c.write(op.String()) 854 | default: 855 | c.errorf(un.OpPos, "unsupported unary operator") 856 | } 857 | c.writeExpr(un.X) 858 | } 859 | 860 | func (c *Compiler) writeBinaryExpr(bin *ast.BinaryExpr) { 861 | needParens := false 862 | switch bin.Op { 863 | case token.AND, token.OR, token.XOR: 864 | needParens = true 865 | } 866 | if needParens { 867 | c.write("(") 868 | } 869 | c.writeExpr(bin.X) 870 | c.write(" ") 871 | switch op := bin.Op; op { 872 | case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ, 873 | token.ADD, token.SUB, token.MUL, token.QUO, token.REM, 874 | token.AND, token.OR, token.XOR, token.SHL, token.SHR, 875 | token.LAND, token.LOR: 876 | c.write(op.String()) 877 | default: 878 | c.errorf(bin.OpPos, "unsupported binary operator") 879 | } 880 | c.write(" ") 881 | c.writeExpr(bin.Y) 882 | if needParens { 883 | c.write(")") 884 | } 885 | } 886 | 887 | func (c *Compiler) writeKeyValueExpr(kv *ast.KeyValueExpr) { 888 | if name, ok := kv.Key.(*ast.Ident); !ok { 889 | c.errorf(kv.Pos(), "unsupported literal key") 890 | } else { 891 | c.write(".") 892 | c.writeIdent(name) 893 | c.write(" = ") 894 | c.writeExpr(kv.Value) 895 | } 896 | } 897 | 898 | func (c *Compiler) writeExpr(expr ast.Expr) { 899 | switch expr := expr.(type) { 900 | case *ast.Ident: 901 | c.writeIdent(expr) 902 | case *ast.BasicLit: 903 | c.writeBasicLit(expr) 904 | case *ast.FuncLit: 905 | c.writeFuncLit(expr) 906 | case *ast.CompositeLit: 907 | c.writeCompositeLit(expr) 908 | case *ast.ParenExpr: 909 | c.writeParenExpr(expr) 910 | case *ast.SelectorExpr: 911 | c.writeSelectorExpr(expr) 912 | case *ast.IndexExpr: 913 | c.writeIndexExpr(expr) 914 | case *ast.CallExpr: 915 | c.writeCallExpr(expr) 916 | case *ast.StarExpr: 917 | c.writeStarExpr(expr) 918 | case *ast.UnaryExpr: 919 | c.writeUnaryExpr(expr) 920 | case *ast.BinaryExpr: 921 | c.writeBinaryExpr(expr) 922 | case *ast.KeyValueExpr: 923 | c.writeKeyValueExpr(expr) 924 | default: 925 | c.errorf(expr.Pos(), "unsupported expression type") 926 | } 927 | } 928 | 929 | // 930 | // Statements 931 | // 932 | 933 | func (c *Compiler) writeExprStmt(exprStmt *ast.ExprStmt) { 934 | c.writeExpr(exprStmt.X) 935 | } 936 | 937 | func (c *Compiler) writeIncDecStmt(incDecStmt *ast.IncDecStmt) { 938 | c.write("(") 939 | c.writeExpr(incDecStmt.X) 940 | c.write(")") 941 | c.write(incDecStmt.Tok.String()) 942 | } 943 | 944 | func (c *Compiler) writeAssignStmt(assignStmt *ast.AssignStmt) { 945 | if len(assignStmt.Lhs) != 1 { 946 | c.errorf(assignStmt.Pos(), "multi-value assignment unsupported") 947 | return 948 | } 949 | if assignStmt.Tok == token.DEFINE { 950 | typ := c.types.TypeOf(assignStmt.Rhs[0]) 951 | switch c.target { 952 | case CPP: 953 | if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.String { 954 | c.write("gx::String ") 955 | } else { 956 | c.write("auto ") 957 | } 958 | case GLSL: 959 | c.write(c.genTypeExpr(typ, assignStmt.Pos())) 960 | } 961 | } 962 | c.writeExpr(assignStmt.Lhs[0]) 963 | c.write(" ") 964 | switch op := assignStmt.Tok; op { 965 | case token.DEFINE: 966 | c.write("=") 967 | case token.ASSIGN, 968 | token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN, 969 | token.AND_ASSIGN, token.OR_ASSIGN, token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN: 970 | c.write(op.String()) 971 | default: 972 | c.errorf(assignStmt.TokPos, "unsupported assignment operator") 973 | } 974 | c.write(" ") 975 | c.writeExpr(assignStmt.Rhs[0]) 976 | } 977 | 978 | func (c *Compiler) writeDeferStmt(deferStmt *ast.DeferStmt) { 979 | c.write("gx::Defer ") 980 | c.write(c.generateIdentifier("Defer")) 981 | c.write("([&](){\n") 982 | c.indent++ 983 | c.writeCallExpr(deferStmt.Call) 984 | c.write(";\n") 985 | c.indent-- 986 | c.write("});") 987 | } 988 | 989 | func (c *Compiler) writeReturnStmt(retStmt *ast.ReturnStmt) { 990 | if len(retStmt.Results) > 1 { 991 | c.errorf(retStmt.Results[0].Pos(), "multiple return values not supported") 992 | } else if len(retStmt.Results) == 1 { 993 | c.write("return ") 994 | c.writeExpr(retStmt.Results[0]) 995 | } else { 996 | c.write("return") 997 | } 998 | } 999 | 1000 | func (c *Compiler) writeBranchStmt(branchStmt *ast.BranchStmt) { 1001 | switch tok := branchStmt.Tok; tok { 1002 | case token.BREAK, token.CONTINUE: 1003 | c.write(tok.String()) 1004 | default: 1005 | c.errorf(branchStmt.TokPos, "unsupported branch statement") 1006 | } 1007 | } 1008 | 1009 | func (c *Compiler) writeBlockStmt(block *ast.BlockStmt) { 1010 | c.write("{\n") 1011 | c.indent++ 1012 | c.writeStmtList(block.List) 1013 | c.indent-- 1014 | c.write("}") 1015 | c.atBlockEnd = true 1016 | } 1017 | 1018 | func (c *Compiler) writeIfStmt(ifStmt *ast.IfStmt) { 1019 | c.write("if (") 1020 | if ifStmt.Init != nil { 1021 | c.writeStmt(ifStmt.Init) 1022 | c.write("; ") 1023 | } 1024 | c.writeExpr(ifStmt.Cond) 1025 | c.write(") ") 1026 | c.writeStmt(ifStmt.Body) 1027 | if ifStmt.Else != nil { 1028 | c.write(" else ") 1029 | c.writeStmt(ifStmt.Else) 1030 | } 1031 | } 1032 | 1033 | func (c *Compiler) writeForStmt(forStmt *ast.ForStmt) { 1034 | c.write("for (") 1035 | if forStmt.Init != nil { 1036 | c.writeStmt(forStmt.Init) 1037 | } 1038 | c.write("; ") 1039 | if forStmt.Cond != nil { 1040 | c.writeExpr(forStmt.Cond) 1041 | } 1042 | c.write("; ") 1043 | if forStmt.Post != nil { 1044 | c.writeStmt(forStmt.Post) 1045 | } 1046 | c.write(") ") 1047 | c.writeStmt(forStmt.Body) 1048 | } 1049 | 1050 | func (c *Compiler) writeRangeStmt(rangeStmt *ast.RangeStmt) { 1051 | if rangeStmt.Tok == token.ASSIGN { 1052 | c.errorf(rangeStmt.TokPos, "must use := in range statement") 1053 | } 1054 | var key *ast.Ident 1055 | if rangeStmt.Key != nil { 1056 | if ident, ok := rangeStmt.Key.(*ast.Ident); ok && ident.Name != "_" { 1057 | key = ident 1058 | } 1059 | } 1060 | c.write("for (") 1061 | if key != nil { 1062 | c.write("auto ") 1063 | c.writeIdent(key) 1064 | c.write(" = -1; ") 1065 | } 1066 | c.write("auto &") 1067 | if rangeStmt.Value != nil { 1068 | c.writeExpr(rangeStmt.Value) 1069 | } else { 1070 | if ident, ok := rangeStmt.Value.(*ast.Ident); ok && ident.Name != "_" { 1071 | c.writeIdent(ident) 1072 | } else { 1073 | c.write("_ [[maybe_unused]]") 1074 | } 1075 | } 1076 | c.write(" : ") 1077 | c.writeExpr(rangeStmt.X) 1078 | c.write(") {\n") 1079 | c.indent++ 1080 | if key != nil { 1081 | c.write("++") 1082 | c.writeIdent(key) 1083 | c.write(";\n") 1084 | } 1085 | c.writeStmtList(rangeStmt.Body.List) 1086 | c.indent-- 1087 | c.write("}") 1088 | c.atBlockEnd = true 1089 | } 1090 | 1091 | func (c *Compiler) writeDeclStmt(declStmt *ast.DeclStmt) { 1092 | switch decl := declStmt.Decl.(type) { 1093 | case *ast.GenDecl: 1094 | for _, spec := range decl.Specs { 1095 | switch spec := spec.(type) { 1096 | case *ast.TypeSpec: 1097 | typeDefn := c.genTypeDefn(spec) 1098 | typeDefnIndented := &strings.Builder{} 1099 | for _, r := range typeDefn { 1100 | typeDefnIndented.WriteRune(r) 1101 | if r == '\n' { 1102 | for i := 0; i < 2*c.indent; i++ { 1103 | typeDefnIndented.WriteByte(' ') 1104 | } 1105 | } 1106 | } 1107 | c.write(typeDefnIndented.String()) 1108 | default: 1109 | c.errorf(declStmt.Pos(), "unsupported declaration statement") 1110 | } 1111 | } 1112 | } 1113 | } 1114 | 1115 | func (c *Compiler) writeStmt(stmt ast.Stmt) { 1116 | switch stmt := stmt.(type) { 1117 | case *ast.ExprStmt: 1118 | c.writeExprStmt(stmt) 1119 | case *ast.IncDecStmt: 1120 | c.writeIncDecStmt(stmt) 1121 | case *ast.AssignStmt: 1122 | c.writeAssignStmt(stmt) 1123 | case *ast.DeferStmt: 1124 | c.writeDeferStmt(stmt) 1125 | case *ast.ReturnStmt: 1126 | c.writeReturnStmt(stmt) 1127 | case *ast.BranchStmt: 1128 | c.writeBranchStmt(stmt) 1129 | case *ast.BlockStmt: 1130 | c.writeBlockStmt(stmt) 1131 | case *ast.IfStmt: 1132 | c.writeIfStmt(stmt) 1133 | case *ast.ForStmt: 1134 | c.writeForStmt(stmt) 1135 | case *ast.RangeStmt: 1136 | c.writeRangeStmt(stmt) 1137 | case *ast.DeclStmt: 1138 | c.writeDeclStmt(stmt) 1139 | default: 1140 | c.errorf(stmt.Pos(), "unsupported statement type") 1141 | } 1142 | } 1143 | 1144 | func (c *Compiler) writeStmtList(list []ast.Stmt) { 1145 | for _, stmt := range list { 1146 | c.writeStmt(stmt) 1147 | if !c.atBlockEnd { 1148 | c.write(";") 1149 | } 1150 | c.write("\n") 1151 | } 1152 | } 1153 | 1154 | // 1155 | // GLSL 1156 | // 1157 | 1158 | func glslStorageClass(name string) string { 1159 | switch name { 1160 | case "attributes", "uniforms", "varyings": 1161 | return name[0 : len(name)-1] 1162 | } 1163 | return "" 1164 | } 1165 | 1166 | // 1167 | // Top-level 1168 | // 1169 | 1170 | func (c *Compiler) compile() { 1171 | // Initialize maps 1172 | c.externs = map[Target]map[types.Object]string{CPP: {}, GLSL: {}} 1173 | c.fieldIndices = map[*types.Var]int{} 1174 | c.methodRenames = map[types.Object]string{} 1175 | c.methodFieldTags = map[types.Object]string{} 1176 | c.genTypeExprs = map[Target]map[types.Type]string{CPP: {}, GLSL: {}} 1177 | c.genTypeDecls = map[*ast.TypeSpec]string{} 1178 | c.genTypeDefns = map[Target]map[*ast.TypeSpec]string{CPP: {}, GLSL: {}} 1179 | c.genTypeMetas = map[*ast.TypeSpec]string{} 1180 | c.genFuncDecls = map[Target]map[*ast.FuncDecl]string{CPP: {}, GLSL: {}} 1181 | 1182 | // Initialize builders 1183 | c.errors = &strings.Builder{} 1184 | c.outputCC = &strings.Builder{} 1185 | c.outputHH = &strings.Builder{} 1186 | c.outputGLSLs = map[string]*strings.Builder{} 1187 | 1188 | // Load main package 1189 | packagesConfig := &packages.Config{ 1190 | Mode: packages.NeedImports | packages.NeedDeps | 1191 | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo, 1192 | } 1193 | loadPkgs, err := packages.Load(packagesConfig, c.mainPkgPath) 1194 | if err != nil { 1195 | fmt.Fprintln(c.errors, err) 1196 | } 1197 | if len(loadPkgs) == 0 { 1198 | return 1199 | } 1200 | for _, pkg := range loadPkgs { 1201 | for _, err := range pkg.Errors { 1202 | if err.Pos != "" { 1203 | fmt.Fprintf(c.errors, "%s: %s\n", err.Pos, err.Msg) 1204 | } else { 1205 | fmt.Fprintln(c.errors, err.Msg) 1206 | } 1207 | } 1208 | } 1209 | if c.errored() { 1210 | return 1211 | } 1212 | c.fileSet = loadPkgs[0].Fset 1213 | 1214 | // Collect packages in dependency order 1215 | var pkgs []*packages.Package 1216 | { 1217 | visited := map[*packages.Package]bool{} 1218 | var visit func(pkg *packages.Package) 1219 | visit = func(pkg *packages.Package) { 1220 | if !visited[pkg] { 1221 | visited[pkg] = true 1222 | for _, dep := range pkg.Imports { 1223 | visit(dep) 1224 | } 1225 | pkgs = append(pkgs, pkg) 1226 | if pkg.Fset != c.fileSet { 1227 | c.errorf(0, "internal error: filesets differ") 1228 | } 1229 | } 1230 | } 1231 | for _, pkg := range loadPkgs { 1232 | visit(pkg) 1233 | } 1234 | } 1235 | sort.Slice(pkgs, func(i, j int) bool { 1236 | return pkgs[i].ID < pkgs[j].ID 1237 | }) 1238 | 1239 | // Collect types info 1240 | c.types = &types.Info{ 1241 | Types: map[ast.Expr]types.TypeAndValue{}, 1242 | Instances: map[*ast.Ident]types.Instance{}, 1243 | Defs: map[*ast.Ident]types.Object{}, 1244 | Uses: map[*ast.Ident]types.Object{}, 1245 | Implicits: map[ast.Node]types.Object{}, 1246 | Selections: map[*ast.SelectorExpr]*types.Selection{}, 1247 | Scopes: map[ast.Node]*types.Scope{}, 1248 | } 1249 | for _, pkg := range pkgs { 1250 | for k, v := range pkg.TypesInfo.Types { 1251 | c.types.Types[k] = v 1252 | } 1253 | for k, v := range pkg.TypesInfo.Instances { 1254 | c.types.Instances[k] = v 1255 | } 1256 | for k, v := range pkg.TypesInfo.Defs { 1257 | c.types.Defs[k] = v 1258 | } 1259 | for k, v := range pkg.TypesInfo.Uses { 1260 | c.types.Uses[k] = v 1261 | } 1262 | for k, v := range pkg.TypesInfo.Implicits { 1263 | c.types.Implicits[k] = v 1264 | } 1265 | for k, v := range pkg.TypesInfo.Selections { 1266 | c.types.Selections[k] = v 1267 | } 1268 | for k, v := range pkg.TypesInfo.Scopes { 1269 | c.types.Scopes[k] = v 1270 | } 1271 | } 1272 | 1273 | // Collect exports, externs and GXSL shaders 1274 | exports := map[types.Object]bool{} 1275 | gxslShaders := map[types.Object]bool{} 1276 | { 1277 | exportRe := regexp.MustCompile(`//gx:export`) 1278 | externsRe := regexp.MustCompile(`//gx:externs (.*)`) 1279 | externRe := regexp.MustCompile(`//gx:extern (.*)`) 1280 | gxslShaderRe := regexp.MustCompile(`//gxsl:shader`) 1281 | gxslExternRe := regexp.MustCompile(`//gxsl:extern (.*)`) 1282 | parseDirective := func(re *regexp.Regexp, doc *ast.CommentGroup) string { 1283 | if doc != nil { 1284 | for _, comment := range doc.List { 1285 | if matches := re.FindStringSubmatch(comment.Text); len(matches) > 0 { 1286 | return matches[len(matches)-1] 1287 | } 1288 | } 1289 | } 1290 | return "" 1291 | } 1292 | for _, pkg := range pkgs { 1293 | for _, file := range pkg.Syntax { 1294 | fileExt := "" 1295 | if len(file.Comments) > 0 { 1296 | fileExt = parseDirective(externsRe, file.Comments[0]) 1297 | } 1298 | for _, decl := range file.Decls { 1299 | switch decl := decl.(type) { 1300 | case *ast.GenDecl: 1301 | declExt := parseDirective(externRe, decl.Doc) 1302 | for _, spec := range decl.Specs { 1303 | switch spec := spec.(type) { 1304 | case *ast.TypeSpec: 1305 | extern := false 1306 | if specExt := parseDirective(externRe, spec.Doc); specExt != "" { 1307 | c.externs[CPP][c.types.Defs[spec.Name]] = specExt 1308 | extern = true 1309 | } else if declExt != "" { 1310 | c.externs[CPP][c.types.Defs[spec.Name]] = declExt 1311 | extern = true 1312 | } else if fileExt != "" { 1313 | c.externs[CPP][c.types.Defs[spec.Name]] = fileExt + spec.Name.String() 1314 | extern = true 1315 | } 1316 | if extern { 1317 | if typ, ok := spec.Type.(*ast.StructType); ok { 1318 | for _, field := range typ.Fields.List { 1319 | fieldExt := parseDirective(externRe, field.Comment) 1320 | if fieldExt == "" { 1321 | fieldExt = parseDirective(externRe, field.Doc) 1322 | } 1323 | for _, fieldName := range field.Names { 1324 | if fieldExt != "" { 1325 | c.externs[CPP][c.types.Defs[fieldName]] = fieldExt 1326 | } else if unicode.IsUpper(rune(fieldName.String()[0])) { 1327 | c.externs[CPP][c.types.Defs[fieldName]] = lowerFirst(fieldName.String()) 1328 | } 1329 | } 1330 | } 1331 | } 1332 | } 1333 | if gxslExt := parseDirective(gxslExternRe, decl.Doc); gxslExt != "" { 1334 | c.externs[GLSL][c.types.Defs[spec.Name]] = gxslExt 1335 | if typ, ok := spec.Type.(*ast.StructType); ok { 1336 | for _, field := range typ.Fields.List { 1337 | for _, fieldName := range field.Names { 1338 | if unicode.IsUpper(rune(fieldName.String()[0])) { 1339 | c.externs[GLSL][c.types.Defs[fieldName]] = lowerFirst(fieldName.String()) 1340 | } 1341 | } 1342 | } 1343 | } 1344 | } 1345 | if typ, ok := spec.Type.(*ast.StructType); ok { 1346 | for _, field := range typ.Fields.List { 1347 | ext := parseDirective(externRe, field.Comment) 1348 | if ext == "" { 1349 | ext = parseDirective(externRe, field.Doc) 1350 | } 1351 | if ext != "" { 1352 | for _, fieldName := range field.Names { 1353 | c.externs[CPP][c.types.Defs[fieldName]] = ext 1354 | } 1355 | } 1356 | gxslExt := parseDirective(gxslExternRe, field.Comment) 1357 | if gxslExt == "" { 1358 | gxslExt = parseDirective(gxslExternRe, field.Doc) 1359 | } 1360 | if gxslExt != "" { 1361 | for _, fieldName := range field.Names { 1362 | c.externs[GLSL][c.types.Defs[fieldName]] = gxslExt 1363 | } 1364 | } 1365 | } 1366 | } 1367 | case *ast.ValueSpec: 1368 | specExt := parseDirective(externRe, spec.Doc) 1369 | for _, name := range spec.Names { 1370 | if specExt != "" { 1371 | c.externs[CPP][c.types.Defs[name]] = specExt 1372 | } else if declExt != "" { 1373 | c.externs[CPP][c.types.Defs[name]] = declExt 1374 | } else if fileExt != "" { 1375 | c.externs[CPP][c.types.Defs[name]] = fileExt + name.String() 1376 | } 1377 | if gxslExt := parseDirective(gxslExternRe, decl.Doc); gxslExt != "" { 1378 | c.externs[GLSL][c.types.Defs[name]] = gxslExt 1379 | } 1380 | } 1381 | } 1382 | } 1383 | case *ast.FuncDecl: 1384 | if parseDirective(exportRe, decl.Doc) != "" { 1385 | exports[c.types.Defs[decl.Name]] = true 1386 | } 1387 | if parseDirective(gxslShaderRe, decl.Doc) != "" { 1388 | gxslShaders[c.types.Defs[decl.Name]] = true 1389 | } 1390 | if declExt := parseDirective(externRe, decl.Doc); declExt != "" { 1391 | c.externs[CPP][c.types.Defs[decl.Name]] = declExt 1392 | } else if fileExt != "" { 1393 | c.externs[CPP][c.types.Defs[decl.Name]] = fileExt + decl.Name.String() 1394 | } 1395 | if gxslExt := parseDirective(gxslExternRe, decl.Doc); gxslExt != "" { 1396 | c.externs[GLSL][c.types.Defs[decl.Name]] = gxslExt 1397 | } 1398 | } 1399 | } 1400 | } 1401 | } 1402 | } 1403 | 1404 | // Collect top-level decls and exports in output order 1405 | var typeSpecs []*ast.TypeSpec 1406 | var valueSpecs []*ast.ValueSpec 1407 | var funcDecls []*ast.FuncDecl 1408 | var gxslShaderDecls []*ast.FuncDecl 1409 | behaviors := map[types.Object]bool{} 1410 | objTypeSpecs := map[types.Object]*ast.TypeSpec{} 1411 | objValueSpecs := map[types.Object]*ast.ValueSpec{} 1412 | objFuncDecls := map[types.Object]*ast.FuncDecl{} 1413 | { 1414 | for _, pkg := range pkgs { 1415 | for _, file := range pkg.Syntax { 1416 | for _, decl := range file.Decls { 1417 | switch decl := decl.(type) { 1418 | case *ast.GenDecl: 1419 | for _, spec := range decl.Specs { 1420 | switch spec := spec.(type) { 1421 | case *ast.TypeSpec: 1422 | objTypeSpecs[c.types.Defs[spec.Name]] = spec 1423 | case *ast.ValueSpec: 1424 | for _, name := range spec.Names { 1425 | objValueSpecs[c.types.Defs[name]] = spec 1426 | } 1427 | } 1428 | } 1429 | case *ast.FuncDecl: 1430 | objFuncDecls[c.types.Defs[decl.Name]] = decl 1431 | } 1432 | } 1433 | } 1434 | } 1435 | typeSpecVisited := map[*ast.TypeSpec]bool{} 1436 | valueSpecVisited := map[*ast.ValueSpec]bool{} 1437 | for _, pkg := range pkgs { 1438 | for _, file := range pkg.Syntax { 1439 | for _, decl := range file.Decls { 1440 | switch decl := decl.(type) { 1441 | case *ast.GenDecl: 1442 | for _, spec := range decl.Specs { 1443 | switch spec := spec.(type) { 1444 | case *ast.TypeSpec: 1445 | var visitTypeSpec func(typeSpec *ast.TypeSpec, export bool) 1446 | visitTypeSpec = func(typeSpec *ast.TypeSpec, export bool) { 1447 | if _, ok := c.externs[CPP][c.types.Defs[typeSpec.Name]]; ok { 1448 | return 1449 | } 1450 | obj := c.types.Defs[typeSpec.Name] 1451 | visited := typeSpecVisited[typeSpec] 1452 | if visited && !(export && !exports[obj]) { 1453 | return 1454 | } 1455 | if !visited { 1456 | typeSpecVisited[typeSpec] = true 1457 | if structType, ok := typeSpec.Type.(*ast.StructType); ok { 1458 | for _, field := range structType.Fields.List { 1459 | if field.Names == nil { 1460 | if ident, ok := field.Type.(*ast.Ident); ok && ident.Name == "Behavior" { 1461 | behaviors[obj] = true 1462 | export = true 1463 | } 1464 | } 1465 | } 1466 | } 1467 | } 1468 | if export { 1469 | exports[obj] = true 1470 | } 1471 | ast.Inspect(typeSpec.Type, func(node ast.Node) bool { 1472 | if ident, ok := node.(*ast.Ident); ok { 1473 | if typeSpec, ok := objTypeSpecs[c.types.Uses[ident]]; ok { 1474 | visitTypeSpec(typeSpec, export) 1475 | } 1476 | } 1477 | return true 1478 | }) 1479 | if !visited { 1480 | typeSpecs = append(typeSpecs, typeSpec) 1481 | } 1482 | } 1483 | visitTypeSpec(spec, false) 1484 | case *ast.ValueSpec: 1485 | var visitValueSpec func(valueSpec *ast.ValueSpec) 1486 | visitValueSpec = func(valueSpec *ast.ValueSpec) { 1487 | if valueSpecVisited[valueSpec] { 1488 | return 1489 | } 1490 | valueSpecVisited[valueSpec] = true 1491 | ast.Inspect(valueSpec, func(node ast.Node) bool { 1492 | if ident, ok := node.(*ast.Ident); ok { 1493 | if valueSpec, ok := objValueSpecs[c.types.Uses[ident]]; ok { 1494 | visitValueSpec(valueSpec) 1495 | } 1496 | } 1497 | return true 1498 | }) 1499 | extern := false 1500 | for _, name := range valueSpec.Names { 1501 | if _, ok := c.externs[CPP][c.types.Defs[name]]; ok { 1502 | extern = true 1503 | } 1504 | } 1505 | if !extern { 1506 | valueSpecs = append(valueSpecs, valueSpec) 1507 | } 1508 | } 1509 | visitValueSpec(spec) 1510 | } 1511 | } 1512 | case *ast.FuncDecl: 1513 | if _, ok := c.externs[CPP][c.types.Defs[decl.Name]]; !ok { 1514 | if _, ok := gxslShaders[c.types.Defs[decl.Name]]; !ok { 1515 | funcDecls = append(funcDecls, decl) 1516 | } else { 1517 | gxslShaderDecls = append(gxslShaderDecls, decl) 1518 | } 1519 | } 1520 | } 1521 | } 1522 | } 1523 | } 1524 | } 1525 | 1526 | // `#include`s 1527 | var includes string 1528 | { 1529 | re := regexp.MustCompile(`//gx:include (.*)`) 1530 | visited := map[string]bool{} 1531 | builder := &strings.Builder{} 1532 | for _, pkg := range pkgs { 1533 | for _, file := range pkg.Syntax { 1534 | if len(file.Comments) > 0 { 1535 | for _, comment := range file.Comments[0].List { 1536 | if matches := re.FindStringSubmatch(comment.Text); len(matches) > 1 { 1537 | include := matches[1] 1538 | if !visited[include] { 1539 | visited[include] = true 1540 | builder.WriteString("#include ") 1541 | builder.WriteString(include) 1542 | builder.WriteString("\n") 1543 | } 1544 | } 1545 | } 1546 | } 1547 | } 1548 | } 1549 | builder.WriteString("#include \"gx.hh\"\n") 1550 | includes = builder.String() 1551 | } 1552 | 1553 | // Output '.cc' 1554 | { 1555 | c.output = c.outputCC 1556 | 1557 | // Macro indicating we're in generated CC 1558 | c.write("#define GX_GENERATED_CC\n\n") 1559 | 1560 | // Includes 1561 | c.write(includes) 1562 | 1563 | // Types 1564 | c.write("\n\n") 1565 | c.write("//\n// Types\n//\n\n") 1566 | for _, typeSpec := range typeSpecs { 1567 | if typeDecl := c.genTypeDecl(typeSpec); typeDecl != "" { 1568 | c.write(typeDecl) 1569 | c.write(";\n") 1570 | } 1571 | } 1572 | for _, typeSpec := range typeSpecs { 1573 | if typeDefn := c.genTypeDefn(typeSpec); typeDefn != "" { 1574 | c.write("\n") 1575 | if behaviors[c.types.Defs[typeSpec.Name]] { 1576 | c.write("ComponentTypeListAdd(") 1577 | c.write(typeSpec.Name.String()) 1578 | c.write(");\n") 1579 | } 1580 | c.write(typeDefn) 1581 | c.write(";\n") 1582 | } 1583 | } 1584 | 1585 | // Meta 1586 | c.write("\n\n") 1587 | c.write("//\n// Meta\n//\n") 1588 | for _, typeSpec := range typeSpecs { 1589 | if typeDecl := c.genTypeDecl(typeSpec); typeDecl != "" { 1590 | if meta := c.genTypeMeta(typeSpec); meta != "" { 1591 | c.write("\n") 1592 | c.write(meta) 1593 | c.write("\n") 1594 | } 1595 | } 1596 | } 1597 | 1598 | // Function declarations 1599 | c.write("\n\n") 1600 | c.write("//\n// Function declarations\n//\n\n") 1601 | for _, funcDecl := range funcDecls { 1602 | c.write(c.genFuncDecl(funcDecl)) 1603 | c.write(";\n") 1604 | } 1605 | 1606 | // Variables 1607 | c.write("\n\n") 1608 | c.write("//\n// Variables\n//\n\n") 1609 | for _, valueSpec := range valueSpecs { 1610 | for i, name := range valueSpec.Names { 1611 | if name.Obj.Kind == ast.Con { 1612 | c.write("constexpr ") 1613 | } 1614 | c.write(c.genTypeExpr(c.types.TypeOf(valueSpec.Names[i]), valueSpec.Pos())) 1615 | c.writeIdent(name) 1616 | if len(valueSpec.Values) > 0 { 1617 | c.write(" = ") 1618 | c.writeExpr(valueSpec.Values[i]) 1619 | } 1620 | c.write(";\n") 1621 | } 1622 | } 1623 | 1624 | // Function definitions 1625 | c.write("\n\n") 1626 | c.write("//\n// Function definitions\n//\n") 1627 | for _, funcDecl := range funcDecls { 1628 | if funcDecl.Body != nil { 1629 | c.write("\n") 1630 | c.write(c.genFuncDecl(funcDecl)) 1631 | c.write(" ") 1632 | c.writeBlockStmt(funcDecl.Body) 1633 | c.write("\n") 1634 | } 1635 | } 1636 | } 1637 | 1638 | // Output '.hh' 1639 | { 1640 | // `#pragma once` 1641 | c.outputHH.WriteString("#pragma once\n\n") 1642 | 1643 | // Don't re-define in generated CC 1644 | c.outputHH.WriteString("#ifndef GX_GENERATED_CC\n\n") 1645 | 1646 | // Includes 1647 | c.outputHH.WriteString(includes) 1648 | 1649 | // Types 1650 | c.outputHH.WriteString("\n\n") 1651 | c.outputHH.WriteString("//\n// Types\n//\n\n") 1652 | for _, typeSpec := range typeSpecs { 1653 | if exports[c.types.Defs[typeSpec.Name]] { 1654 | if typeDecl := c.genTypeDecl(typeSpec); typeDecl != "" { 1655 | c.outputHH.WriteString(typeDecl) 1656 | c.outputHH.WriteString(";\n") 1657 | } 1658 | } 1659 | } 1660 | for _, typeSpec := range typeSpecs { 1661 | if exports[c.types.Defs[typeSpec.Name]] { 1662 | if typeDefn := c.genTypeDefn(typeSpec); typeDefn != "" { 1663 | c.outputHH.WriteString("\n") 1664 | if behaviors[c.types.Defs[typeSpec.Name]] { 1665 | c.outputHH.WriteString("ComponentTypeListAdd(") 1666 | c.outputHH.WriteString(typeSpec.Name.String()) 1667 | c.outputHH.WriteString(");\n") 1668 | } 1669 | c.outputHH.WriteString(typeDefn) 1670 | c.outputHH.WriteString(";\n") 1671 | } 1672 | } 1673 | } 1674 | 1675 | // Meta 1676 | c.outputHH.WriteString("\n\n") 1677 | c.outputHH.WriteString("//\n// Meta\n//\n") 1678 | for _, typeSpec := range typeSpecs { 1679 | if exports[c.types.Defs[typeSpec.Name]] { 1680 | if typeDecl := c.genTypeDecl(typeSpec); typeDecl != "" { 1681 | if meta := c.genTypeMeta(typeSpec); meta != "" { 1682 | c.outputHH.WriteString("\n") 1683 | c.outputHH.WriteString(meta) 1684 | c.outputHH.WriteString("\n") 1685 | } 1686 | } 1687 | } 1688 | } 1689 | 1690 | // Function declarations 1691 | c.outputHH.WriteString("\n\n") 1692 | c.outputHH.WriteString("//\n// Function declarations\n//\n\n") 1693 | for _, funcDecl := range funcDecls { 1694 | export := exports[c.types.Defs[funcDecl.Name]] 1695 | if !export && funcDecl.Recv != nil { 1696 | for _, recv := range funcDecl.Recv.List { 1697 | ast.Inspect(recv.Type, func(node ast.Node) bool { 1698 | if export { 1699 | return false 1700 | } 1701 | if ident, ok := node.(*ast.Ident); ok && exports[c.types.Uses[ident]] { 1702 | export = true 1703 | return false 1704 | } 1705 | return true 1706 | }) 1707 | } 1708 | } 1709 | if export { 1710 | c.outputHH.WriteString(c.genFuncDecl(funcDecl)) 1711 | c.outputHH.WriteString(";\n") 1712 | } 1713 | } 1714 | 1715 | // Closing `#ifndef GX_GENERATED_CC` 1716 | c.outputHH.WriteString("\n#endif\n") 1717 | } 1718 | 1719 | // Output '.glsl's 1720 | { 1721 | c.target = GLSL 1722 | for _, gxslShaderDecl := range gxslShaderDecls { 1723 | c.output = &strings.Builder{} 1724 | c.outputGLSLs[gxslShaderDecl.Name.Name] = c.output 1725 | 1726 | c.write("#version 100\nprecision mediump float;\n\n") 1727 | 1728 | // Collect dependencies 1729 | mainParamTypeExprs := map[ast.Node]bool{} 1730 | for _, param := range gxslShaderDecl.Type.Params.List { 1731 | if len(param.Names) > 0 && glslStorageClass(param.Names[0].Name) != "" { 1732 | ast.Inspect(param.Type, func(node ast.Node) bool { 1733 | mainParamTypeExprs[node] = true 1734 | return true 1735 | }) 1736 | } 1737 | } 1738 | visited := map[ast.Node]bool{} 1739 | var typeSpecDeps []*ast.TypeSpec 1740 | var valueSpecDeps []*ast.ValueSpec 1741 | var funcDeclDeps []*ast.FuncDecl 1742 | var visitDeps func(node ast.Node) 1743 | visitTypeSpec := func(typeSpec *ast.TypeSpec, shouldAppend bool) { 1744 | if visited[typeSpec] { 1745 | return 1746 | } 1747 | if _, ok := c.externs[GLSL][c.types.Defs[typeSpec.Name]]; ok { 1748 | return 1749 | } 1750 | visited[typeSpec] = true 1751 | visitDeps(typeSpec) 1752 | if shouldAppend { 1753 | typeSpecDeps = append(typeSpecDeps, typeSpec) 1754 | } 1755 | } 1756 | visitValueSpec := func(valueSpec *ast.ValueSpec) { 1757 | if visited[valueSpec] { 1758 | return 1759 | } 1760 | visited[valueSpec] = true 1761 | visitDeps(valueSpec) 1762 | for _, name := range valueSpec.Names { 1763 | if _, ok := c.externs[GLSL][c.types.Defs[name]]; ok { 1764 | return 1765 | } 1766 | } 1767 | valueSpecDeps = append(valueSpecDeps, valueSpec) 1768 | } 1769 | visitFuncDecl := func(funcDecl *ast.FuncDecl) { 1770 | if visited[funcDecl] { 1771 | return 1772 | } 1773 | if _, ok := c.externs[GLSL][c.types.Defs[funcDecl.Name]]; ok { 1774 | return 1775 | } 1776 | visited[funcDecl] = true 1777 | visitDeps(funcDecl) 1778 | if funcDecl != gxslShaderDecl { 1779 | funcDeclDeps = append(funcDeclDeps, funcDecl) 1780 | } 1781 | } 1782 | visitDeps = func(node ast.Node) { 1783 | ast.Inspect(node, func(node ast.Node) bool { 1784 | if ident, ok := node.(*ast.Ident); ok { 1785 | if typeSpec, ok := objTypeSpecs[c.types.Uses[ident]]; ok { 1786 | _, isMainParamTypeExpr := mainParamTypeExprs[ident] 1787 | visitTypeSpec(typeSpec, !isMainParamTypeExpr) 1788 | } else if valueSpec, ok := objValueSpecs[c.types.Uses[ident]]; ok { 1789 | visitValueSpec(valueSpec) 1790 | } else if funcDecl, ok := objFuncDecls[c.types.Uses[ident]]; ok { 1791 | visitFuncDecl(funcDecl) 1792 | } 1793 | } 1794 | return true 1795 | }) 1796 | } 1797 | visitDeps(gxslShaderDecl) 1798 | 1799 | // Types 1800 | for _, typeSpec := range typeSpecDeps { 1801 | if typeDefn := c.genTypeDefn(typeSpec); typeDefn != "" { 1802 | c.write(typeDefn) 1803 | c.write(";\n\n") 1804 | } 1805 | } 1806 | 1807 | // Main function parameters 1808 | obj := c.types.Defs[gxslShaderDecl.Name] 1809 | sig := obj.Type().(*types.Signature) 1810 | for i, nParams := 0, sig.Params().Len(); i < nParams; i++ { 1811 | param := sig.Params().At(i) 1812 | if storageClass := glslStorageClass(param.Name()); storageClass != "" { 1813 | if structType, ok := param.Type().Underlying().(*types.Struct); ok { 1814 | numFields := structType.NumFields() 1815 | for fieldIndex := 0; fieldIndex < numFields; fieldIndex++ { 1816 | field := structType.Field(fieldIndex) 1817 | c.write(storageClass) 1818 | c.write(" ") 1819 | c.write(c.genTypeExpr(field.Type(), field.Pos())) 1820 | if ext, ok := c.externs[GLSL][field]; ok { 1821 | c.write(ext) 1822 | } else { 1823 | c.write(param.Type().(*types.Named).Obj().Name()) 1824 | c.write("_") 1825 | c.write(field.Name()) 1826 | } 1827 | c.write(";\n") 1828 | } 1829 | if numFields > 0 { 1830 | c.write("\n") 1831 | } 1832 | } 1833 | } 1834 | } 1835 | 1836 | // Variables 1837 | for _, valueSpec := range valueSpecDeps { 1838 | for i, name := range valueSpec.Names { 1839 | c.write("const ") 1840 | c.write(c.genTypeExpr(c.types.TypeOf(valueSpec.Names[i]), valueSpec.Pos())) 1841 | c.writeIdent(name) 1842 | if len(valueSpec.Values) > 0 { 1843 | c.write(" = ") 1844 | c.writeExpr(valueSpec.Values[i]) 1845 | } 1846 | c.write(";\n") 1847 | } 1848 | } 1849 | if len(valueSpecDeps) > 0 { 1850 | c.write("\n") 1851 | } 1852 | 1853 | // Functions 1854 | for _, funcDecl := range funcDeclDeps { 1855 | if funcDecl.Body != nil { 1856 | c.write(c.genFuncDecl(funcDecl)) 1857 | c.write(" ") 1858 | c.writeBlockStmt(funcDecl.Body) 1859 | c.write("\n\n") 1860 | } 1861 | } 1862 | 1863 | // Main function 1864 | c.write("void main() ") 1865 | c.writeBlockStmt(gxslShaderDecl.Body) 1866 | c.write("\n") 1867 | } 1868 | } 1869 | } 1870 | 1871 | // 1872 | // Main 1873 | // 1874 | 1875 | //go:embed gx.hh 1876 | var gxHH string 1877 | 1878 | func main() { 1879 | // Arguments 1880 | nArgs := len(os.Args) 1881 | if nArgs < 3 { 1882 | fmt.Println("usage: gx [glsl_output_prefix] [glsl_output_suffix]") 1883 | return 1884 | } 1885 | mainPkgPath := os.Args[1] 1886 | outputPrefix := os.Args[2] 1887 | glslOutputPrefix := outputPrefix + "_" 1888 | if nArgs >= 4 { 1889 | glslOutputPrefix = os.Args[3] 1890 | } 1891 | glslOutputSuffix := ".glsl" 1892 | if nArgs >= 5 { 1893 | glslOutputSuffix = os.Args[4] 1894 | } 1895 | 1896 | // Compile 1897 | c := Compiler{mainPkgPath: mainPkgPath} 1898 | c.compile() 1899 | 1900 | // Print output 1901 | if c.errored() { 1902 | fmt.Println(c.errors) 1903 | os.Exit(1) 1904 | } else { 1905 | readersEqual := func(a, b io.Reader) bool { 1906 | bufA := make([]byte, 1024) 1907 | bufB := make([]byte, 1024) 1908 | for { 1909 | nA, errA := io.ReadFull(a, bufA) 1910 | nB, _ := io.ReadFull(b, bufB) 1911 | if !bytes.Equal(bufA[:nA], bufB[:nB]) { 1912 | return false 1913 | } 1914 | if errA == io.EOF { 1915 | return true 1916 | } 1917 | } 1918 | } 1919 | writeFileIfChanged := func(path string, contents string) { 1920 | byteContents := []byte(contents) 1921 | if f, err := os.Open(path); err == nil { 1922 | defer f.Close() 1923 | if readersEqual(f, bytes.NewReader(byteContents)) { 1924 | return 1925 | } 1926 | } 1927 | os.WriteFile(path, byteContents, 0644) 1928 | } 1929 | writeFileIfChanged(filepath.Dir(outputPrefix)+"/gx.hh", gxHH) 1930 | writeFileIfChanged(outputPrefix+".gx.cc", c.outputCC.String()) 1931 | writeFileIfChanged(outputPrefix+".gx.hh", c.outputHH.String()) 1932 | for name, outputGLSL := range c.outputGLSLs { 1933 | writeFileIfChanged(glslOutputPrefix+name+".gx"+glslOutputSuffix, outputGLSL.String()) 1934 | } 1935 | } 1936 | } 1937 | -------------------------------------------------------------------------------- /gx.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace gx { 11 | 12 | 13 | using uint = unsigned int; 14 | 15 | 16 | // 17 | // Print 18 | // 19 | 20 | inline void print(bool val) { 21 | std::printf(val ? "true" : "false"); 22 | } 23 | 24 | inline void print(int val) { 25 | std::printf("%d", val); 26 | } 27 | 28 | inline void print(float val) { 29 | std::printf("%g", val); 30 | } 31 | 32 | inline void print(double val) { 33 | std::printf("%f", val); 34 | } 35 | 36 | inline void print(const char *val) { 37 | std::printf("%s", val); 38 | } 39 | 40 | template 41 | void print(A &a, B &&b, Args &&...args) { 42 | print(a); 43 | print(b); 44 | (print(std::forward(args)), ...); 45 | } 46 | 47 | template 48 | void println(Args &&...args) { 49 | print(std::forward(args)...); 50 | print("\n"); 51 | } 52 | 53 | template 54 | void fatal(Args &&...args) { 55 | println(std::forward(args)...); 56 | std::fflush(stdout); 57 | std::abort(); 58 | } 59 | 60 | 61 | // 62 | // Pointer 63 | // 64 | 65 | template 66 | T &deref(T *ptr) { 67 | #ifndef GX_NO_CHECKS 68 | if (!ptr) { 69 | fatal("gx: nil pointer dereference"); 70 | } 71 | #endif 72 | return *ptr; 73 | } 74 | 75 | template 76 | const T &deref(const T *ptr) { 77 | return deref(const_cast(ptr)); 78 | } 79 | 80 | 81 | // 82 | // Array 83 | // 84 | 85 | template 86 | struct Array { 87 | T data[N] {}; 88 | 89 | T &operator[](int i) { 90 | #ifndef GX_NO_CHECKS 91 | if (!(0 <= i && i < N)) { 92 | fatal("gx: array index out of bounds"); 93 | } 94 | #endif 95 | return data[i]; 96 | } 97 | 98 | const T &operator[](int i) const { 99 | return const_cast(*this)[i]; 100 | } 101 | 102 | T *begin() { 103 | return &data[0]; 104 | } 105 | 106 | const T *begin() const { 107 | return &data[0]; 108 | } 109 | 110 | T *end() { 111 | return &data[N]; 112 | } 113 | 114 | const T *end() const { 115 | return &data[N]; 116 | } 117 | }; 118 | 119 | template 120 | constexpr int len(const Array &a) { 121 | return N; 122 | } 123 | 124 | 125 | // 126 | // Slice 127 | // 128 | 129 | template 130 | struct Slice { 131 | T *data = nullptr; 132 | int size = 0; 133 | int capacity = 0; 134 | 135 | Slice() = default; 136 | 137 | Slice(const Slice &other) { 138 | if (this != &other) { 139 | copyFrom(other.data, other.size); 140 | } 141 | } 142 | 143 | Slice &operator=(const Slice &other) { 144 | if (this != &other) { 145 | destruct(); 146 | copyFrom(other.data, other.size); 147 | } 148 | return *this; 149 | } 150 | 151 | Slice(Slice &&other) { 152 | if (this != &other) { 153 | moveFrom(other); 154 | } 155 | } 156 | 157 | Slice &operator=(Slice &&other) { 158 | if (this != &other) { 159 | destruct(); 160 | moveFrom(other); 161 | } 162 | return *this; 163 | } 164 | 165 | Slice(std::initializer_list l) { 166 | copyFrom(l.begin(), l.size()); 167 | } 168 | 169 | ~Slice() { 170 | destruct(); 171 | } 172 | 173 | void copyFrom(const T *data_, int size_) { 174 | data = (T *)std::malloc(sizeof(T) * size_); 175 | size = size_; 176 | capacity = size_; 177 | for (auto i = 0; auto &elem : *this) { 178 | new (&elem) T(data_[i++]); 179 | } 180 | } 181 | 182 | void moveFrom(Slice &other) { 183 | data = other.data; 184 | size = other.size; 185 | capacity = other.capacity; 186 | other.data = nullptr; 187 | other.size = 0; 188 | other.capacity = 0; 189 | } 190 | 191 | void destruct() { 192 | for (auto &elem : *this) { 193 | elem.~T(); 194 | } 195 | std::free(data); 196 | } 197 | 198 | T &operator[](int i) { 199 | #ifndef GX_NO_CHECKS 200 | if (!(0 <= i && i < size)) { 201 | fatal("gx: slice index out of bounds"); 202 | } 203 | #endif 204 | return data[i]; 205 | } 206 | 207 | const T &operator[](int i) const { 208 | return const_cast(*this)[i]; 209 | } 210 | 211 | T *begin() { 212 | return &data[0]; 213 | } 214 | 215 | const T *begin() const { 216 | return &data[0]; 217 | } 218 | 219 | T *end() { 220 | return &data[size]; 221 | } 222 | 223 | const T *end() const { 224 | return &data[size]; 225 | } 226 | }; 227 | 228 | template 229 | int len(const Slice &s) { 230 | return s.size; 231 | } 232 | 233 | template 234 | void insert(Slice &s, int i, T val) { 235 | #ifndef GX_NO_CHECKS 236 | if (!(0 <= i && i <= s.size)) { 237 | fatal("gx: slice index out of bounds"); 238 | } 239 | #endif 240 | auto moveCount = s.size - i; 241 | ++s.size; 242 | if (s.size > s.capacity) { 243 | s.capacity = s.capacity == 0 ? 2 : s.capacity << 1; 244 | s.data = (T *)std::realloc(s.data, sizeof(T) * s.capacity); 245 | } 246 | std::memmove(&s.data[i + 1], &s.data[i], sizeof(T) * moveCount); 247 | new (&s.data[i]) T(std::move(val)); 248 | } 249 | 250 | template 251 | Slice &append(Slice &s, T val) { 252 | insert(s, s.size, std::move(val)); 253 | return s; 254 | } 255 | 256 | template 257 | T &append(Slice &s) { 258 | insert(s, s.size, T {}); 259 | return s[len(s) - 1]; 260 | } 261 | 262 | template 263 | void remove(Slice &s, int i) { 264 | #ifndef GX_NO_CHECKS 265 | if (!(0 <= i && i < s.size)) { 266 | fatal("gx: slice index out of bounds"); 267 | } 268 | #endif 269 | auto moveCount = s.size - (i + 1); 270 | s.data[i].~T(); 271 | std::memmove(&s.data[i], &s.data[i + 1], sizeof(T) * moveCount); 272 | --s.size; 273 | } 274 | 275 | 276 | // 277 | // String 278 | // 279 | 280 | struct String { 281 | Slice slice; 282 | 283 | String() 284 | : slice({ '\0' }) { 285 | } 286 | 287 | String(const char *s) { 288 | slice.copyFrom(s, std::strlen(s) + 1); 289 | } 290 | 291 | operator const char *() const { 292 | return (const char *)slice.data; 293 | } 294 | 295 | char &operator[](int i) { 296 | #ifndef GX_NO_CHECKS 297 | if (!(0 <= i && i < slice.size)) { 298 | fatal("gx: string index out of bounds"); 299 | } 300 | #endif 301 | return slice[i]; 302 | } 303 | 304 | auto begin() const { 305 | return slice.begin(); 306 | } 307 | 308 | auto end() const { 309 | return slice.end() - 1; 310 | } 311 | }; 312 | 313 | inline int len(const String &s) { 314 | return s.slice.size - 1; 315 | } 316 | 317 | inline bool operator==(const String &a, const String &b) { 318 | auto aSize = a.slice.size; 319 | if (aSize != b.slice.size) { 320 | return false; 321 | } 322 | return !std::memcmp(a.slice.data, b.slice.data, aSize); 323 | } 324 | 325 | inline bool operator==(const String &a, const char *b) { 326 | return !std::strcmp(a, b); 327 | } 328 | 329 | 330 | // 331 | // Defer 332 | // 333 | 334 | template 335 | struct Defer { 336 | F f; 337 | 338 | Defer(const Defer &) = delete; 339 | Defer &operator=(const Defer &) = delete; 340 | 341 | Defer(F f_) 342 | : f(std::move(f_)) { 343 | } 344 | 345 | ~Defer() { 346 | f(); 347 | } 348 | }; 349 | 350 | template 351 | Defer(T) -> Defer; 352 | 353 | 354 | // 355 | // Meta 356 | // 357 | 358 | #ifndef GX_FIELD_ATTRIBS 359 | struct FieldAttribs { 360 | const char *name; 361 | }; 362 | #else 363 | using FieldAttribs = GX_FIELD_ATTRIBS; 364 | #endif 365 | 366 | template 367 | struct FieldTag {}; 368 | 369 | 370 | } 371 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | PLATFORM="macOS" 6 | CLANG="clang++" 7 | GO="go" 8 | TIME="time" 9 | TIME_TOTAL="time" 10 | EXE="" 11 | 12 | if [[ -f /proc/version ]]; then 13 | if grep -q Linux /proc/version; then 14 | PLATFORM="lin" 15 | TIME="time --format=%es\n" 16 | TIME_TOTAL="time --format=total\t%es\n" 17 | fi 18 | if grep -q Microsoft /proc/version; then 19 | PLATFORM="win" 20 | CLANG="clang++.exe" 21 | GO="go.exe" 22 | EXE=".exe" 23 | fi 24 | fi 25 | CLANG="$TIME $CLANG" 26 | GO="$TIME $GO" 27 | 28 | case "$1" in 29 | # Desktop 30 | release) 31 | mkdir -p build 32 | rm -rf build/* 33 | 34 | $GO build gx.go 35 | 36 | $TIME ./gx$EXE ./example build/example 37 | if [[ -f build/example.gx.cc ]]; then 38 | $CLANG -std=c++20 -Wall -O3 -Iexample -o build/example build/example.*.cc 39 | fi 40 | $TIME ./gx$EXE ./example/gxsl build/example_gxsl build/example_gxsl_ .frag 41 | if [[ -f build/example_gxsl.gx.cc ]]; then 42 | $CLANG -std=c++20 -Wall -O3 -o build/example_gxsl build/example_gxsl.*.cc 43 | fi 44 | 45 | if [[ -f build/example ]]; then 46 | ./build/example 47 | fi 48 | if [[ -f build/example_gxsl ]]; then 49 | cd build/ 50 | for f in *.frag; do 51 | glslangValidator$EXE $f | sed "s/^ERROR: 0/build\/$f/g" | sed "/\.frag$/d" 52 | done 53 | cd - > /dev/null 54 | fi 55 | 56 | exit 1 57 | ;; 58 | esac 59 | --------------------------------------------------------------------------------