Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions internal/vm/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ func (vm *VM) bootstrap() {
}
return &object.Array{Elems: vm.methodNames(c, true)}
})
vm.cObject.define("singleton_methods", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value {
// Default includes singleton methods inherited from the receiver's
// superclasses (a class's class methods); a false argument restricts to the
// receiver's own. For a plain object the singleton methods are those on its
// per-object singleton class.
all := len(args) == 0 || args[0].Truthy()
return &object.Array{Elems: vm.singletonMethodNames(self, all)}
})
vm.cObject.define("frozen?", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Bool(isFrozen(self))
})
Expand Down Expand Up @@ -287,12 +295,14 @@ func (vm *VM) bootstrap() {
exc("SyntaxError", "ScriptError")
exc("LoadError", "ScriptError")

vm.registerFile() // needs the exception hierarchy (Errno::ENOENT < StandardError)
vm.registerIO() // IO/StringIO + $stdout/$stderr/$stdin (needs IOError/EOFError)
vm.registerDir() // Dir (reuses the Errno module set up by registerFile)
vm.registerZlib() // needs the exception hierarchy (Zlib::Error < StandardError)
vm.registerFiber() // needs the exception hierarchy (FiberError < StandardError)
vm.registerThread() // needs StandardError/StopIteration (ThreadError, ClosedQueueError)
vm.registerFile() // needs the exception hierarchy (Errno::ENOENT < StandardError)
vm.registerIO() // IO/StringIO + $stdout/$stderr/$stdin (needs IOError/EOFError)
vm.registerDir() // Dir (reuses the Errno module set up by registerFile)
vm.registerTmpdir() // Dir.tmpdir / Dir.mktmpdir (layers onto Dir; require "tmpdir")
vm.registerProcess() // Process module — identity + clock_gettime
vm.registerZlib() // needs the exception hierarchy (Zlib::Error < StandardError)
vm.registerFiber() // needs the exception hierarchy (FiberError < StandardError)
vm.registerThread() // needs StandardError/StopIteration (ThreadError, ClosedQueueError)

// Exception instance protocol: initialize stores @message; message/to_s
// return it (or the class name when unset).
Expand Down Expand Up @@ -3714,6 +3724,45 @@ func (vm *VM) methodNames(c *RClass, all bool) []object.Value {
return out
}

// singletonMethodNames returns the singleton-method names of self as sorted
// Symbols. For a class/module these are its class methods (def self.foo) — walked
// up the superclass chain when all is true, restricted to its own when false. For
// any other object they are the methods on its per-object singleton class, if it
// has one.
func (vm *VM) singletonMethodNames(self object.Value, all bool) []object.Value {
seen := map[string]bool{}
var names []string
add := func(n string) {
if !seen[n] {
seen[n] = true
names = append(names, n)
}
}
collect := func(tbl map[string]*Method) {
for n, m := range tbl {
if !m.undefined {
add(n)
}
}
}
if c, ok := self.(*RClass); ok {
collect(c.smethods)
if all {
for s := c.super; s != nil; s = s.super {
collect(s.smethods)
}
}
} else if sc := vm.objSingleton(self); sc != nil {
collect(sc.methods)
}
sort.Strings(names)
out := make([]object.Value, len(names))
for i, n := range names {
out[i] = object.Symbol(n)
}
return out
}

// constNameArg coerces a const_get/const_set/const_defined? name (a Symbol or
// String) to its text, rejecting a name that does not begin with an uppercase
// letter — as Ruby does.
Expand Down
137 changes: 137 additions & 0 deletions internal/vm/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) the go-embedded-ruby/ruby authors
//
// SPDX-License-Identifier: BSD-3-Clause

package vm

import (
"os"
"time"

"github.com/go-embedded-ruby/ruby/internal/object"
)

// processClockEpoch anchors CLOCK_MONOTONIC at the first reading so the value is
// a small, ever-increasing duration like MRI's monotonic clock (which is not
// tied to the wall clock). Go's time is internally monotonic but does not expose
// a raw counter, so subtracting a fixed start is the faithful equivalent.
var (
processClockEpoch = time.Now()
processMonoNow = time.Now // seam for deterministic tests
)

// registerProcess installs the Process module — the subset of MRI's Process that
// Puppet touches at boot and on the local-apply path: identity queries (pid,
// ppid, uid/euid/gid/egid, groups), the clock_gettime timer with its CLOCK_*
// constants, and the maxgroups accessor. Methods that need a real fork/exec
// model are deliberately left out; this is identity + timing, all CGO=0 via Go's
// os and time packages.
func (vm *VM) registerProcess() {
mod := newClass("Process", nil)
mod.isModule = true
vm.consts["Process"] = mod
def := func(name string, fn NativeFn) { mod.smethods[name] = &Method{name: name, owner: mod, native: fn} }

def("pid", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(os.Getpid())
})
def("ppid", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(os.Getppid())
})
def("uid", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(processUID())
})
def("euid", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(processEUID())
})
def("gid", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(processGID())
})
def("egid", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(processEGID())
})
def("groups", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
gids, err := processGroups()
if err != nil {
return &object.Array{}
}
elems := make([]object.Value, len(gids))
for i, g := range gids {
elems[i] = object.Integer(int64(g))
}
return &object.Array{Elems: elems}
})
// maxgroups is a platform tunable; reading it returns the conventional 16 cap
// and assigning it is accepted but ignored (the kernel limit is fixed), which
// matches MRI on the platforms we target. Puppet sets it inside a rescue.
def("maxgroups", func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value {
return object.Integer(processMaxGroups)
})
def("maxgroups=", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value {
return args[0] // accepted-and-ignored; returns the assigned value
})

def("clock_gettime", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value {
clk := intArg(args[0])
var d time.Duration
switch clk {
case clockMonotonic:
d = processMonoNow().Sub(processClockEpoch)
default: // CLOCK_REALTIME and any other clock -> wall clock since the epoch
now := processMonoNow()
d = time.Duration(now.UnixNano())
}
return clockGettimeUnit(d, args)
})

mod.consts["CLOCK_REALTIME"] = object.Integer(clockRealtime)
vm.consts["Process::CLOCK_REALTIME"] = object.Integer(clockRealtime)
mod.consts["CLOCK_MONOTONIC"] = object.Integer(clockMonotonic)
vm.consts["Process::CLOCK_MONOTONIC"] = object.Integer(clockMonotonic)
}

// Clock identifiers match the Linux/macOS values MRI exposes (CLOCK_REALTIME=0,
// CLOCK_MONOTONIC=6 on Darwin); only the relative ordering matters to callers,
// who pass the constant straight back.
const (
clockRealtime = 0
clockMonotonic = 6
processMaxGroups = 16
)

// clockGettimeUnit converts a duration to the requested unit symbol (default
// :float_second), matching Process.clock_gettime's unit argument.
func clockGettimeUnit(d time.Duration, args []object.Value) object.Value {
unit := "float_second"
if len(args) > 1 {
if s, ok := args[1].(object.Symbol); ok {
unit = string(s)
}
}
switch unit {
case "nanosecond":
return object.Integer(d.Nanoseconds())
case "microsecond":
return object.Integer(d.Microseconds())
case "millisecond":
return object.Integer(d.Milliseconds())
case "second":
return object.Integer(int64(d.Seconds()))
case "float_microsecond":
return object.Float(float64(d.Nanoseconds()) / 1e3)
case "float_millisecond":
return object.Float(float64(d.Nanoseconds()) / 1e6)
default: // float_second
return object.Float(d.Seconds())
}
}

// Identity seams over the os package so tests can drive every branch without
// depending on the host's actual uid/gid/groups.
var (
processUID = os.Getuid
processEUID = os.Geteuid
processGID = os.Getgid
processEGID = os.Getegid
processGroups = os.Getgroups
)
1 change: 1 addition & 0 deletions internal/vm/require.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var providedFeatures = map[string]bool{
"stringio": true, "securerandom": true,
"English": true, "ostruct": true, "benchmark": true,
"forwardable": true, "delegate": true, "pathname": true, "uri": true,
"tmpdir": true,
}

// registerRequire installs Kernel#require and #require_relative — the runtime
Expand Down
Loading
Loading