ecma335
Version, currently 1.0.02 versions
- 1.2.0latestSep 28, 2026
- 1.0.0Jun 10, 2026
github.com/mjblack/ecma335
A Crystal ECMA335 parser and tooling
Installation
# Add this to your shard.yml
dependencies:
ecma335:
github: mjblack/ecma335
version: ~> 1.0.0Then run:
shards installshard.yml
- Crystal
>= 1.19.1- License
- MIT
- Author
- Matthew J. Black
- Target
ecma335-toolfrom src/ecma335_tool.cr
Dependencies
This version declares no dependencies.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
ecma335
ECMA 335 Parser
Installation
-
Add the dependency to your
shard.yml:dependencies: ecma335: github: mjblack/ecma335 -
Run
shards install
Usage
require "ecma335"
parsed = Ecma335.parse("winmd/Windows.Win32.winmd")
puts "Metadata version: #{parsed.metadata_root.version_string}"
parsed.metadata_root.streams.each do |stream|
puts "#{stream.name}: offset=0x#{stream.offset.to_s(16)} size=#{stream.size}"
end
if tables = parsed.metadata_root.tables_stream
puts "TypeDef rows: #{tables.row_count("TypeDef")}"
puts "MethodDef rows: #{tables.row_count("MethodDef")}"
puts "coverage: #{(tables.parse_coverage_ratio * 100).round(2)}%"
p tables.skipped_tables
tables.type_defs.first(5).each do |row|
puts "TypeDef: #{row.type_namespace}.#{row.type_name}"
end
tables.method_defs.first(5).each do |row|
puts "MethodDef: #{row.name} (rva=0x#{row.rva.to_s(16)})"
if sig = row.decoded_signature
params = sig.parameter_types.join(", ")
puts " signature: (#{params}) -> #{sig.return_type}"
puts " canonical: #{sig.to_signature_string(canonical: true)}"
end
end
end
Consumer API
This library is parser-focused. It does not generate Crystal bindings directly.
Ecma335.parse also builds a normalized API model suitable for a separate generator app:
parsed = Ecma335.parse("winmd/Windows.Win32.winmd")
api = parsed.api_model
if api
type = api.type?("Windows.Win32.Foundation.HRESULT")
p type.try(&.methods.size)
p type.try(&.token)
p type.try(&.generic_params)
namespace_types = api.types_in_namespace("Windows.Win32.Foundation")
puts "types in namespace: #{namespace_types.size}"
method = api.method?("Windows.Win32.Foundation.HRESULT", "SomeMethod")
p method.try(&.native_import)
p method.try(&.token)
p method.try(&.generic_params)
# Classification comes from TypeDef flags and the base type, not from names.
handle = api.type?("Windows.Win32.Foundation.HANDLE")
if handle
p handle.value_type? # true
p handle.union? # value type with explicit layout
p handle.enum?, handle.delegate?, handle.interface?, handle.static_class?
p handle.base_type # "System.ValueType"
p handle.packing_size, handle.class_size # from ClassLayout, or nil
# Custom attributes are available on types, fields, methods and params
# with their constructor and named arguments decoded.
p handle.has_attribute?("NativeTypedef")
p handle.attribute?("InvalidHandleValue").try(&.fixed_arg?(0)) # "-1"
p handle.fields.first.signature # "nativeint"
end
apis = api.type?("Windows.Win32.System.Threading.Apis")
if apis
create_thread = apis.methods.find { |m| m.name == "CreateThread" }
p create_thread.try(&.set_last_error?) # from ImplMap flags
p create_thread.try(&.params.last.out?) # Param flags
p create_thread.try(&.params.first.attribute?("Const"))
p create_thread.try(&.return_attributes.map(&.name))
infinite = apis.fields.find { |f| f.name == "INFINITE" }
p infinite.try(&.constant_type), infinite.try(&.constant_value) # "uint32", "4294967295"
end
# Nested types are unique only by token; enclosing/nested links carry both.
slist = api.type?("Windows.Win32.System.Kernel.SLIST_HEADER")
slist.try &.nested_type_tokens.each { |token| p api.type_by_token?(token).try(&.name) }
end
# Token-based metadata row helpers (TypeDef/MethodDef/Field)
p parsed.type_def_by_token?(0x02000001_u32).try(&.type_name)
p parsed.method_def_by_token?(0x06000001_u32).try(&.name)
p parsed.field_by_token?(0x04000001_u32).try(&.name)
# Optional strict mode: raise when parser has to skip undecoded tables.
strict_parsed = Ecma335.parse("winmd/Windows.Win32.winmd", strict: true)
Development
Run tests:
crystal spec
Fetch Windows.Win32.winmd (uses the version pinned in winmd.version):
pwsh ./scripts/fetch-winmd.ps1
Override the version or output path:
pwsh ./scripts/fetch-winmd.ps1 -Version 70.0.11-preview
pwsh ./scripts/fetch-winmd.ps1 -OutputPath winmd/Windows.Win32.winmd
WinMD Utility
This repo includes a small CLI utility for inspecting WinMD files:
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd stats
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list types --limit 20
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list types --namespace Windows.Win32.UI.Controls
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list methods --type Windows.Win32.Foundation.HRESULT --limit 20
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd list methods --all
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd show type Windows.Win32.Foundation.HRESULT
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd show method Windows.Win32.Foundation.HRESULT#SomeMethod
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd show type Windows.Win32.Foundation.HRESULT --json
crystal run src/ecma335_tool.cr -- --json winmd/Windows.Win32.winmd stats
crystal run src/ecma335_tool.cr -- --json winmd/Windows.Win32.winmd list methods --limit 20
crystal run src/ecma335_tool.cr -- --json winmd/Windows.Win32.winmd search CreateFile --methods
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd search CreateFile --methods --limit 20
crystal run src/ecma335_tool.cr -- winmd/Windows.Win32.winmd search CreateFile --methods --all
Notes:
list typeshides special metadata entries like<Module>by default.list typesin text mode shows nested types as dotted paths (for exampleParentType._Anonymous_e__Union).- Use
--include-specialto include those entries. --namespaceexpects a namespace (for exampleWindows.Win32.UI.Controls), but if you pass an exact full type name it will still return that direct match.listandsearchdefault to--limit 50; use--allfor uncapped output.
Or build and run the shard target:
shards build ecma335-tool
./bin/ecma335-tool winmd/Windows.Win32.winmd stats
Integration test behavior:
spec/ecma335_spec.crincludes an integration example forwinmd/Windows.Win32.winmd.- If that file exists, the integration example runs by default.
- If the file is missing, that single integration example is marked pending with a helpful message.
Contributing
- Fork it (https://github.com/mjblack/ecma335/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Matthew J. Black - creator and maintainer
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
1.0.0- Tagged
- Jun 10, 2026
- Commit
924907cc0d76- Crystal
>= 1.19.1- Indexed
- yes
Dependents
Repository
github.com/mjblack/ecma335
Metadata
- Created
- Aug 12, 2026
- Updated
- Sep 30, 2026
- Synced
- Sep 30, 2026
- Versions
- 2