Help us shape the future of Go! Take the 2026 Go Developer Survey

lc

package module
v1.5.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 13 Imported by: 4

README

Lc — Language Creator & Devkit

banner-low-50p-8b

go get github.com/pt-main/lc

Lc is a production-oriented framework and toolkit for building language runtimes, compiler-like execution pipelines, command interpreters, and bytecode-driven processors in Go.

Lc contains -

  • Byte & String Engine with Universal Engine abstraction
  • Parser (byte & string) - simple parsers, peg parser, lexers, etc.
  • Plugin system (works with Universal Engine)
  • Tooling (bytecode, ast, profilers, etc.)

It is intentionally straightforward to adopt, while preserving industrial runtime properties:

  • explicit execution lifecycle (just use EngineUniversal.End()),
  • 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.
  • Universal Engine - abstraction for work with string/byte engine with plugins, context (with cancelation), and simple building.

Table of Contents

Quick start

- StringEngine Example
package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/pt-main/lc"
	enginepkg "github.com/pt-main/lc/engine"
	"github.com/pt-main/lc/engine/core"
	"github.com/pt-main/lc/parsing/stringParsing"
	"github.com/pt-main/lc/public"
)

func main() {
	parser := &stringParsing.Parser2{} // parsing format: 'command arg1, arg2...'

	engine, err := lc.NewEngineBuilder(public.StringEngineType, public.StringResType).
		WithPipeline([]string{"main"}).
		WithStringParser(parser).
		WithDefaultEvents(true).
		Build()
	if err != nil {
		panic(err)
	}

	err = engine.NewCommandString("log", func(se enginepkg.StringEngineInterface, node *stringParsing.ParsedNode) core.ErrorInterface {
		args, _ := node.Metadata["args"].(string)
		return se.GetUep().Generator.AddString(fmt.Sprintf("Log [%v]: %v",
			time.Now().Format(time.Stamp), args), "main")
	}, "append log with timestamp")
	if err != nil {
		panic(err)
	}

	err = engine.ProcessString(strings.Join([]string{
		"log service_start",
		"log service_ready",
	}, "\n"))
	if err != nil {
		panic(err)
	}

	uep, _ := engine.GetUEP()
	out, err := core.GetStringRes(uep.Generator, "\n")
	if err != nil {
		panic(err)
	}
	fmt.Println(out)
}
$ go run ./example/readme/byte
abc
- ByteEngine Example
package main

import (
	"fmt"

	"github.com/pt-main/lc"
	enginepkg "github.com/pt-main/lc/engine"
	"github.com/pt-main/lc/engine/core"
	"github.com/pt-main/lc/parsing/byteParsing"
	"github.com/pt-main/lc/public"
	"github.com/pt-main/lc/tooling/bytecode"
)

func main() {
	// Parsing format:
	// instruction {
	//     [bytes : cmd] [bytes : argscount] [bytes : arglen]  [bytes arglen : arg],
	//                                       [bytes : arglen2] [bytes arglen2 : arg2]...
	// }
	parser := &byteParsing.Parser1{
		Config: byteParsing.Parser1Config{
			GConfig: bytecode.GenerationConfig{
				CommandBytelen:   1,
				ArgscountBytelen: 1,
				ArglenBytelen:    2,
				Endianess:        public.LittleEndian,
			},
			Shifter: bytecode.Shift{},
		},
	}

	engine, err := lc.NewEngineBuilder(public.ByteEngineType, public.StringResType).
		WithPipeline([]string{"main"}).
		WithByteParser(parser).
		WithDefaultEvents(true).
		WithColors().
		Build()
	if err != nil {
		panic(err)
	}

	err = engine.NewCommandByte(1, func(be enginepkg.ByteEngineInterface, node *byteParsing.ParsedBytes) core.ErrorInterface {
		for _, arg := range node.Args {
			if err := be.GetUep().Generator.AddString(string(arg), "main"); err != nil {
				return err
			}
		}
		return nil
	}, "add to output instruction", true)
	if err != nil {
		panic(err)
	}

	code := []byte{
		0x01,       		// opcode=1
		0x01,       		// argsCount=1
		0x03, 0x00, 		// arglen=3 (little endian, 2 bytes)
		0x61, 0x62, 0x63, 	// args="abc" (3 bytes)
	}

	err = engine.ProcessBytes(code)
	if err != nil {
		panic(err)
	}

	uep, _ := engine.GetUEP()
	out, err := core.GetStringRes(uep.Generator, "")
	if err != nil {
		panic(err)
	}

	fmt.Printf("%v\n", out)
}
$ go run ./example/readme/string
Log [Aug  7 18:44:40]: service_start
Log [Aug  7 18:44:40]: service_ready

You can find more examples at examles/

Engine model

String Engine

Input string (code) and process that - edit, execute, generate code, etc.

Default lifecycle:

  1. store input in scope;
  2. parse input to []ParsedNode;
  3. dispatch handlers by ParsedNode.Switch;
  4. emit output through UEP.Generator (if need).

Byte Engine

Input bytecode and process that. Very fast hotloop (raw speed - ~200m ops/s on i7-4770HQ).

Default lifecycle:

  1. store input in scope;
  2. parse input to []ParsedBytes;
  3. convert ParsedBytes to ByteCallAttr - small structure for hotloop;
  4. dispatch opcode handler;
  5. advance instruction pointer automatically or manually.

Tools and features

Powerful core and UEP (Universal Engine Params)

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()
uep, _ := engine.GetUEP()
uep.Generator.AddString(...)
uep...

Events

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.

Example
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]

Generator

Powerful tool for codegen.

Work with points pipeline for storing code in independent points. Can generate bytes or string.

Example
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.

Scope

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.

Custom scope usage
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
}

Logger

Structured logger built into UEP. Supports status-based formatting and log level filtering.

Example
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
	...
}
Custom status format
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")

Plugin System

Lc has a built‑in plugin manager that allows dynamic registration and execution of external logic. Plugins has their own events and scope, and not isolated (have access to engine and plugin manager).

Creating a plugin

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()
	"scope_return",       // plugin.Run (or plugin method) event can put output here
)

// Add handlers to plugin events
myPlugin.Events.NewEvent("init_event", func(ev *core.Events, i *EventInput) error {
	ev.Scope["plugin_ready"] = true
	return nil
})

myPlugin.Events.NewEvent("main_event", func(ev *core.Events, i *EventInput) error {
	// i.Input is whatever was passed to plugin.Run()
	return nil
})

Registering and using a plugin

engne, _ := lc.NewEngineBuilder(...).
	WithPlugins(myPlugin). // call "init_event"
	Build()

// Later, call plugin methods:
result, err := engine.Plugins.RunPlugin("my_plugin", "some input") // call "main_event"

Parsers — ready‑to‑use implementations

Lc ships with several parsers for different use cases:

StringParsing parsers

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.
Example: Parser2 (simplest)
parser := &stringParsing.Parser2{}
// Input: "print hello world"
// Output: ParsedNode{Switch: "print", Metadata: {args: "hello world"}}

ByteParsing parsers

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{},
	},
}

Context support

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")
}

License

Apache 2.0 - see LICENSE.

By Pt.

Documentation

Index

Constants

View Source
const Version = "1.5.8"

Variables

This section is empty.

Functions

func NewByteEngine

func NewByteEngine(
	generator_res_type public.ResType,
	pipeline []string,
	add_default_events bool,
	parser byteParser,
	endianess public.EndianType,
	colorEnable bool,
	context context.Context,
) *engine.ByteEngine

NewByteEngine creates a byte-oriented engine for binary formats or bytecode.

The endianess parameter (e.g., bytecode.LittleEndian) is stored in scope.

It registers default events when add_default_events is true.

The parser must implement paraing.ParserInterface.

func NewStringEngine

func NewStringEngine(
	generator_res_type public.ResType,
	pipeline []string,
	add_default_events bool,
	parser stringParser,
	colorEnable bool,
	context context.Context,
) *engine.StringEngine

NewStringEngine creates a ready-to-use string-based engine. Parameters:

generator_res_type – core.StringResType (usually) for text generation.
pipeline – ordered list of generation points (e.g., []string{"pre","main"}).
add_default_events – if true, registers standard parsing and call events.
parser – an implementation parser.ParserInterface.

Returns a StringEngine with empty command map and initialized UEP.

Types

type EngineBuilder

type EngineBuilder struct {
	// contains filtered or unexported fields
}

EngineBuilder is a fluent builder for constructing universal engines. It allows to configure pipeline stages, event handling, logging, custom parsers, scope variables, and byte order before calling Build(). Use NewEngineBuilder to create a builder instance.

func NewEngineBuilder

func NewEngineBuilder(engineType public.EngineType, resType public.ResType) *EngineBuilder

NewEngineBuilder creates a new EngineBuilder for the given engine type. engineType must be either ByteEngineType or StringEngineType. Defaults: pipeline = []string{"main"}, default events enabled, endianess = bytecode.LittleEndian, empty scope. Example:

builder := lc.NewEngineBuilder(lc.StringEngineType).
            WithPipeline([]string{"pre","main"}).
            WithStringParser(myParser)

func (*EngineBuilder) Build

func (b *EngineBuilder) Build() (*EngineUniversal, error)

Build constructs and returns an EngineUniversal or an error if required components are missing (e.g., a string parser for a StringEngine). The returned engineUniversal can process strings or bytes depending on its type and provides methods to register commands.

func (*EngineBuilder) WithByteParser

func (b *EngineBuilder) WithByteParser(parser byteParser) *EngineBuilder

func (*EngineBuilder) WithColors

func (b *EngineBuilder) WithColors() *EngineBuilder

func (*EngineBuilder) WithContext

func (b *EngineBuilder) WithContext(ctx context.Context) *EngineBuilder

func (*EngineBuilder) WithDefaultEvents

func (b *EngineBuilder) WithDefaultEvents(add bool) *EngineBuilder

func (*EngineBuilder) WithEndianess

func (b *EngineBuilder) WithEndianess(endianess public.EndianType) *EngineBuilder

func (*EngineBuilder) WithLogger

func (b *EngineBuilder) WithLogger(logger *core.Logger) *EngineBuilder

func (*EngineBuilder) WithPipeline

func (b *EngineBuilder) WithPipeline(pipeline []string) *EngineBuilder

func (*EngineBuilder) WithPlugins added in v1.3.2

func (b *EngineBuilder) WithPlugins(plugins ...plugin.PluginInterface) *EngineBuilder

func (*EngineBuilder) WithScope

func (b *EngineBuilder) WithScope(scope core.ScopeType) *EngineBuilder

func (*EngineBuilder) WithStringParser

func (b *EngineBuilder) WithStringParser(parser stringParser) *EngineBuilder

type EngineUniversal

type EngineUniversal struct {
	Plugins      *lcplugin.PluginManager
	Type         public.EngineType
	StringEngine engine.EngineInterface[string, string, stringParsing.ParsedNode]
	ByteEngine   engine.EngineInterface[int, []byte, byteParsing.ParsedBytes]

	Context        context.Context
	CtxCancelCause context.CancelCauseFunc
	// contains filtered or unexported fields
}

func (*EngineUniversal) CheckEnded added in v1.4.2

func (e *EngineUniversal) CheckEnded() (err core.ErrorInterface)

func (*EngineUniversal) End added in v1.4.1

func (e *EngineUniversal) End() (err error)

End - function for stop engines lifecycle.

func (*EngineUniversal) GetUEP

func (*EngineUniversal) NewCommandByte

func (e *EngineUniversal) NewCommandByte(
	opcode int, handler core.CommandType[engine.ByteEngineInterface, byteParsing.ParsedBytes], name string,
	autoByecodeIdxShift bool,
) error

NewCommandByte registers a bytecode command identified by an opcode. If opcode == -1, the engine automatically assigns the next available opcode. handler receives (*ByteEngine, ParsedBytes).

func (*EngineUniversal) NewCommandString

func (e *EngineUniversal) NewCommandString(
	cmdSwitch string, handler core.CommandType[engine.StringEngineInterface, stringParsing.ParsedNode], doc string,
) error

NewCommandString registers a text-based command in a StringEngine. cmdSwitch is the command name (e.g., "print"). handler must have signature func([]interface{}) error where arguments are (*StringEngine, ParsedNode). doc is an optional documentation string.

func (*EngineUniversal) ProcessBytes

func (e *EngineUniversal) ProcessBytes(input []byte) core.ErrorInterface

ProcessBytes feeds a byte slice into the engine (ByteEngineType only). The input is passed via scope under key "input_[]byte", then parsed and processed.

func (*EngineUniversal) ProcessBytesWithCtx

func (e *EngineUniversal) ProcessBytesWithCtx(input []byte, ctx context.Context) core.ErrorInterface

func (*EngineUniversal) ProcessString

func (e *EngineUniversal) ProcessString(input string) core.ErrorInterface

ProcessString feeds a string input into the engine. It works only for engines of type StringEngineType; otherwise returns an core.ErrorInterface. Internally triggers the parse and call events, executing registered handlers.

func (*EngineUniversal) ProcessStringWithCtx

func (e *EngineUniversal) ProcessStringWithCtx(input string, ctx context.Context) core.ErrorInterface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL