go get github.com/pt-main/lcLc is a production-oriented toolkit for building things like language tools, compiler, interpreters or bytecode-driven processors in Go.
It is intentionally straightforward to adopt, while preserving industrial runtime properties:
- explicit execution lifecycle,
- deterministic output assembly,
- context-aware cancellation,
- thread-safe core primitives,
- clear extension contracts for parsers and command handlers, plugins.
Lc does not enforce one grammar style or one VM model.
Instead, it gives you one runtime surface with two engine backends:
- String Engine for text-first processing.
- Byte Engine for binary instruction execution.
Input string (code) and process that - edit, execute, generate code, etc.
Default lifecycle:
- store input in scope;
- parse input to
[]ParsedNode; - dispatch handlers by
ParsedNode.Switch; - emit output through
UEP.Generator(if need).
Input bytecode and process that.
Default lifecycle:
- store input in scope;
- parse input to
[]ParsedBytes; - decode opcode from
ParsedBytes.Switchbytes using configured endianness; - dispatch opcode handler;
- advance instruction pointer automatically or manually.
StringEngine Example
package main
import (
"fmt"
"strings"
"github.com/pt-main/lc"
enginepkg "github.com/pt-main/lc/engine"
"github.com/pt-main/lc/parsing/stringParsing"
"github.com/pt-main/lc/public"
)
func main() {
parser := &stringParsing.Parser2{}
engine, err := lc.NewEngineBuilder(public.StringEngineType, public.StringResType).
WithPipeline([]string{"main"}).
WithStringParser(parser).
WithDefaultEvents(true).
Build()
if err != nil {
panic(err)
}
err = engine.NewCommandString("print", func(se *enginepkg.StringEngine, node stringParsing.ParsedNode) error {
args, _ := node.Metadata["args"].(string)
return se.UEP.Generator.AddString(args, "main")
}, "append text to output")
if err != nil {
panic(err)
}
err = engine.ProcessString(strings.Join([]string{
"print service_start",
"print service_ready",
}, "\n"))
if err != nil {
panic(err)
}
out, err := engine.GetUEP().Generator.GetStringRes("\n")
if err != nil {
panic(err)
}
fmt.Println(out)
}ByteEngine Example
package main
import (
"fmt"
"github.com/pt-main/lc"
"github.com/pt-main/lc/parsing/byteParsing"
enginepkg "github.com/pt-main/lc/engine"
"github.com/pt-main/lc/tooling/bytecode"
"github.com/pt-main/lc/public"
)
func main() {
parser := &byteParsing.Parser1{
Config: byteParsing.Parser1Config{
GConfig: bytecode.GenerationConfig{
CommandBytelen: 1,
ArgscountBytelen: 1,
ArglenBytelen: 1,
Endianess: public.LittleEndian,
},
Shifter: bytecode.Shift{},
},
}
engine, err := lc.NewEngineBuilder(public.ByteEngineType, public.ByteResType).
WithPipeline([]string{"main"}).
WithByteParser(parser).
WithDefaultEvents(true).
Build()
if err != nil {
panic(err)
}
err = engine.NewCommandByte(1, func(be *enginepkg.ByteEngine, node byteParsing.ParsedBytes) error {
return be.UEP.Generator.AddBytes(node.Raw, "main")
}, "mirror instruction bytes", true)
if err != nil {
panic(err)
}
code := []byte{
0x01, 0x01, 0x03, 0x61, 0x62, 0x63, // opcode=1, args=1, argLen=3, arg="abc"
}
err = engine.ProcessBytes(code)
if err != nil {
panic(err)
}
out, err := engine.GetUEP().Generator.GetBytesRes()
if err != nil {
panic(err)
}
fmt.Printf("%x\n", out)
}Engines core contains all necessary tools for runtime work. UEP contains then.
You can use it like:
engine, _ := lc.NewStringEngine(...)
engine.UEP.Generator.AddString(...)
engine.UEP...Or:
engine, _ := lc.NewEngineBuilder(...).
[...].
Build()
engine.GetUEP().Generator.AddString(...)
engine.GetUEP()...Engine arch is event-driven. Events can communicate with Events.Scope, work with context (Events.Context), call by pipeline.
Event handlers input *Events, *EventInput.
You can override Events by implementing core.EventsInterface.
events := core.NewEvents(context.Background()) // new manager
events.NewEvent("event1", handler1) // create main handler in "event1" event
events.NewEvent("event1", handler2) // append handler to end of "event1"
events.NewEventBefore("event1", handler3) // append handler to start of "event1"
// "event1" - [handler3, handler1, handler2]Powerful tool for codegen.
Work with points pipeline for storing code in independent points. Can generate bytes or string.
pipeline := []string{"pre", "main"}
generator := core.NewGenerator([result-type], pipeline)
generator.AddStrings([]string{ // add strings to main
"string1 ",
"string2.",
}, "main")
generator.AddStrings([]string{ // add strings to pre
"string3 ",
"string4. ",
}, "pre")
res := core.GetStringRes(generator, "") // get code
// res = string3 string4. string1 string2.The Scope is a thread-safe map[string]interface{} shared across all event handlers, parsers, and commands. It serves as a runtime context for passing data between pipeline stages.
Important: Do not overwrite keys from public/ package in your custom handlers unless you know exactly what you're doing — they are used by default events.
engine, _ := lc.NewEngineBuilder(...).
WithScope(core.ScopeType{
"tenant_id": "prod-001",
"env": "production",
}).
Build()
// later, in your command handler:
func myHandler(se *engine.StringEngine, node stringParsing.ParsedNode) error {
tenant, _ := core.ScopeGet[string](se.UEP.Scope, "tenant_id")
fmt.Println("Running for tenant:", tenant)
return nil
}Structured logger built into UEP. Supports status-based formatting and log level filtering.
logger := core.NewLogger("") // uses default format: "[?BE]%s[?RT] [?CN][%v][?RT] [?GN][%s][?RT]\n"
logger.Logging["debug"] = true // enable debug output
// other logging will be disabled
// in your engine builder:
engine, _ := lc.NewEngineBuilder(...).
WithLogger(logger).
Build()
// in your handlers:
func myHandler(se *engine.StringEngine, node stringParsing.ParsedNode) error {
se.UEP.Logger.PrintLog("debug", "Processing node: "+node.Switch)
se.UEP.Logger.PrintLog("error", "Error: "+...) // disabled
...
}logger := core.NewLogger("")
logger.Statuses["warn"] = "[?YW]WARN[?RT] [%v] [?RD]%s[?RT]\n" // pt-main/tap color format
logger.PrintLog("warn", "This is a warning")Lc has a built‑in plugin manager that allows dynamic registration and execution of external logic. Plugins are isolated via their own events and scope.
import "github.com/pt-main/lc/tooling/plugin"
myPlugin := plugin.NewPlugin(
"my_plugin", // name
"init_event", // event called on init
"main_event", // event called on Run()
"close_event", // event called on Close()
)
// Add handlers to plugin events
myPlugin.Events.NewEvent("init_event", func(ev *core.Events, _ *EventInput) error {
ev.Scope["plugin_ready"] = true
return nil
})
myPlugin.Events.NewEvent("main_event", func(ev *core.Events, _ *EventInput) error {
// input is whatever was passed to plugin.Run()
return nil
})engne, _ := lc.NewEngineBuilder(...).
WithPlugins(myPlugin).
Build()
// Later, call plugin methods:
result, err := engine.Plugins.CallPlugin("my_plugin", "some input")Lc ships with several parsers for different use cases:
| Parser | Description | Best for |
|---|---|---|
| Lexer | Token-based lexer with regexp2 rules, supports bracket balancing and prev/next links | Tokenization |
| Parser1 | Regex-based grammar with line continuation and bracket balancing | DSLs with line-oriented syntax |
| Parser2 | Simple command args line parser |
Quick prototyping, shell-like languages |
| Parser3 | PEG-inspired parser with combinators (Sequence, Choice, Repeat, Optional, Named) | Complex grammars, AST generation |
| Adapter | Parser3 adapter for string engine. |
parser := &stringParsing.Parser2{}
// Input: "print hello world"
// Output: ParsedNode{Switch: "print", Metadata: {args: "hello world"}}| Parser | Description |
|---|---|
| Parser1 | Binary instruction decoder with configurable field lengths and endianness |
parser := &byteParsing.Parser1{
Config: byteParsing.Parser1Config{
GConfig: bytecode.GenerationConfig{
CommandBytelen: 1,
ArgscountBytelen: 1,
ArglenBytelen: 1,
Endianess: public.LittleEndian,
},
Shifter: bytecode.Shift{},
},
}All Process* methods have WithCtx variants that accept context.Context. This allows:
- Timeout-based cancellation
- Graceful shutdown
- Request-scoped values
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := engine.ProcessStringWithCtx(input, ctx)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Execution timed out")
}- Event handlers run in registration order.
- Generator result follows declared pipeline order.
Process[*]WithCtxrespects cancellation/deadline.- Default String dispatch skips unknown commands.
- Default Byte dispatch expects valid opcode/autoshift registration for processed commands.
Lc provides core mechanisms for operational visibility:
- thread-safe
core.Logger, - event lifecycle hooks (call start/call end),
- centralized runtime scope for contextual metadata,
- structured error wrapping in default event flows.
Apache 2.0 - see LICENSE.
By Pt.
