vprism
vprism
Pre-release Status
vprism
The public entry point parses Ruby source through Prism and returns a canonical, strongly typed AST:
import vprism
result := vprism.parse('puts "hello"')!
println(result.root)
Parser options use a V configuration struct:
result := vprism.parse_with_options(source, vprism.ParseOptions{
filepath: 'lib/example.rb'
line: 10
version: .ruby_3_4
})!
file_result := vprism.parse_file('lib/example.rb')!
Files can also be parsed through Prism's stream parser:
stream_result := vprism.parse_stream_file('lib/example.rb')!
stream_analyzer := vprism.new_analyzer(stream_result)
Fast syntax checks use Prism's
pm_parse_success_p
if vprism.is_valid(source)! {
println('valid Ruby')
}
Lexer APIs use Prism's
pm_serialize_lex
pm_serialize_parse_lex
lexed := vprism.lex(source)!
for token in lexed.tokens {
println('${token.kind}: ${lexed.text(token)!}')
}
parsed := vprism.parse_lex(source)!
println(parsed.parse.root)
Token kinds expose Prism's official token type names:
for token in lexed.tokens {
println('${token.kind.prism_name()}: ${lexed.text(token)!}')
}
Comment-only parsing uses Prism's
pm_serialize_parse_comments
comments := vprism.parse_comments(source)!
for comment in comments.comments {
println(comments.text(comment)!)
}
Locations can be converted to one-based lines and byte columns:
position := result.position_at(node.base().location.start_offset)!
source_range := result.node_range(node)!
line := result.line_text(position.line)!
Prism diagnostics and node flags are exposed as typed V values:
for diagnostic in result.metadata.errors {
if diagnostic.kind == .err_def_name {
println(diagnostic.error_level()!)
}
}
call := result.find_first(.call)!.as_call()!
if call.has_flag(ast.call_node_safe_navigation) {
println('safe navigation')
}
Query helpers are available on both the parse result and AST nodes:
calls := result.find_all(.call)
first_def := result.find_first(.def)
Generated accessors expose each concrete node type:
call_node := result.find_first(.call)!
call := call_node.as_call()!
println(result.constant_value(call.name)!)
if receiver := call.receiver {
println(result.node_text(receiver)!)
}
Concrete node fields directly contain
ast.Node
?ast.Node
[]ast.Node
AnalysisIndex
Higher-level Ruby analysis is provided by
analysis.Analyzer
parsed := vprism.parse(source)!
analyzer := vprism.new_analyzer(parsed)
for method in analyzer.methods() {
println('${method.visibility} ${method.name}')
for parameter in method.parameters {
println('${parameter.kind}: ${parameter.name}')
}
for call in method.calls {
println('calls: ${call.name}')
}
}
for call in analyzer.calls() {
println('${call.name}: ${call.arguments.len} arguments')
if receiver := call.receiver {
println('receiver: ${receiver.text}')
}
if block := call.block {
println('block: ${block.kind}')
}
}
for class_info in analyzer.classes() {
println('${class_info.constant_path}: ${class_info.methods.len} methods')
if superclass := class_info.superclass {
println('superclass: ${superclass.text}')
}
}
for module_info in analyzer.modules() {
println('${module_info.constant_path}: ${module_info.nested_definitions.len} definitions')
}
for call in analyzer.calls() {
for scope in analyzer.scope_path(call.node)! {
println('${scope.kind}: ${scope.name}')
}
if method := analyzer.enclosing_method(call.node) {
println('owned by method: ${method.name}')
}
}
for constant in analyzer.constants() {
println('${constant.usage}: ${constant.path}')
}
for variable in analyzer.variables() {
println('${variable.kind} ${variable.usage}: ${variable.name}')
}
for dependency in analyzer.dependencies() {
println('${dependency.kind}: ${dependency.path}')
}
for flow in analyzer.control_flows() {
println('${flow.kind}: ${flow.text}')
}
for alias_info in analyzer.aliases() {
println('${alias_info.new_name.name} -> ${alias_info.old_name.name}')
}
for region in analyzer.exception_regions() {
println('${region.rescues.len} rescue clauses')
}
index := analyzer.analysis_index()
for call in analyzer.calls() {
if parent := index.parent(call.node) {
println('parent: ${parent.base().kind}')
}
}
Prism debug output is available for JSON and pretty-printed ASTs:
println(vprism.dump_json(source)!)
println(vprism.prettyprint(source)!)
Ruby name queries wrap Prism's string query APIs:
println(vprism.is_local_name('value')!)
println(vprism.is_constant_name('Value')!)
println(vprism.is_method_name('[]=')!)
Scope
vprism
vprism
vprism
Design
-
ffi/wraps the Prism C API. -
serialize/contains Prism serialized byte-stream result models and decoders. -
ast/contains the public AST model. -
analysis/contains optional high-level single-source structural analysis. -
generated/contains code generated from Prism's config.yml. -
serialize/node_decode.vcontains the generated direct strong AST decoder. -
serialize/lex.vcontains lexer and parse+lex result models and decoders. -
tools/contains maintainer tools, including the AST generator.
Generated files are committed so VPM users do not need to run the generator.
Examples
examples/parse_file.v
examples/parse_stream_file.v
examples/print_ast.v
examples/find_calls.v
examples/dump_json.v
examples/prettyprint.v
examples/query_names.v
Maintainer Checks
Run the package checks before publishing:
v run tools/check.v
For a faster development check that skips
-prod test
v run tools/check.v --quick
Prism Library
vprism
thirdparty/prism/include
thirdparty/prism/src
ffi/prism_shim.c
thirdparty/prism/include
thirdparty/prism/src
The Prism sources in
thirdparty/prism/src
node.c
serialize.c
diagnostic.c
token_type.c
prettyprint.c
Native failures include the shim reason, such as buffer initialization failure or allocation failure.