Shell-Skripte objektorientiert und größenwahnsinnig

Kamikaze

Warrior of Sunlight
Teammitglied
Erst einmal hier der aktuelle Stand meines Frameworks für objektorientierte Shell-Skripte:
Code:
#!/bin/sh -f
#
# Copyright (c) 2009
# Dominic Fandrey <kamikaze@bsdforen.de>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# version 1.3

# Include once.
test -n "$bsda_obj" && return 0
bsda_obj=1

#
# This file contains helper functions for creating object oriented
# shell scripts.
#
# The most significant function is bsda_obj:createClass, which basically
# creates a class, including getters, setters, constructor, destructor, a
# reset and a copy function.

#
# DEFINING CLASSES
#
# A detailed description on how to define classes can be found with the
# bsda_obj:createClass() function.
#

#
# IMPLEMENTING METHODS
#
# All that requires to be done to get a functional class after defining it,
# is to implement the required methods.
# Methods are really just functions that the constructor
# creates a wrapper for that forwards the object reference to them.
#
# The following special variables are available:
# 	this	A reference to the current object
#	class	The name of the class this object is an instance of
#	caller	Provides access to functions to manipulate the caller context,
#		which is the recommended way of returning data to the caller
#
# The following variable names may not be used in a method:
#	_return
#	_var
#
# A method must always be named "<class>.<method>". So a valid implementation
# for a method named "bar" and a class named "foo" would look like this:
#
#	foo.bar() {
#	}
#
# The object reference is always available in the variable "this", which
# performs the same function as "self" in python or "this" in Java.
#
# Attributes are resolved as "<objectId><attribute>", the following example
# shows how to read an attribue, manipulate it and write the new value.
# Directly operating on attributes is not possible.
#
#	foo.bar() {
#		local count
#		# Get counter value.
#		bsda_obj:getVar count ${this}count
#		# Increase counter value copy.
#		count=$(($count + 1))
#		# Store the counter value.
#		setvar ${this}count $count
#	}
#
# The following example does the same with getters and setters. Getters and
# setters are documented later.
#
#	foo.bar() {
#		local count
#		# Get counter value.
#		$this.getCount count
#		# Increase counter value copy.
#		count=$(($count + 1))
#		# Store the counter value.
#		$this.setCount $count
#	}
#
# To return data into the calling context $caller.setvar is used. It
# provides the possibility to overwrite variables in the caller context
# even when there is a local variable using the same name.
# Note that it has to be assumed that the names of variables used within
# a method are unknown to the caller, so this can always be the case.
#
# The name of the variable to store something in the caller context is
# normally given by the caller itself as a parameter to the method call.
#
# The following method illustrates this, the attribute count is fetched
# and returned to the caller through the variable named in $1.
# Afterwards the attribute is incremented:
#
#	foo.countInc() {
#		local count
#		# Get counter value.
#		$this.getCount count
#		$caller.setvar $1 $count
#		# Increase counter value copy.
#		count=$(($count + 1))
#		# Store the counter value.
#		$this.setCount $count
#	}
#
# This is how a call could look like:
#
#	local count
#	$obj.countInc count
#	echo "The current count is $count."
#
# Note that both the method and the caller use the local variable count, yet by
# using $caller.setvar the method is still able to overwrite count in the
# caller context.
#
# If a method uses no local variables (which is only sensible in very rare
# cases), the regular shell builtin setvar can be used to overwrite variables
# in the caller context to reduce overhead.
#

#
# CONSTRUCTOR
#
# This block documents the use of a constructor created by the
# bsda_obj:createClass function below.
#
# The name of the class acts as the name of the constructor. The first
# parameter is the name of a variable to store the object reference in.
# An object reference is a unique id that allows the accessing of all methods
# belonging to an object.
#
# The object id is well suited for "grep -F", which is nice to have when
# implementing lists.
#
# The following example shows how to create an object of the type "foo:bar",
# by calling the "foo:bar" constructor:
#
#	foo:bar foobar
#
# The following example shows how to use a method belonging to the object:
#
#	$foobar.copy foobarCopy
#
# @param 1
#	The name of the variable to store the reference to the new object in.
# @param @
#	The remaining parameters are forwarded to an init function,
#	if one was specified.
#

#
# RESET
#
# This block documents the use of a resetter created by the
# bsda_obj:createClass function below.
#
# The resetter first calls the cleanup method with all parameters, if one
# has been defined. Afterwards it simply removes all attributes from memory.
#
# The resetter does not call the init method afterwards, because it would
# not be possible to provide different parameters to the init and cleanup
# methods in that case.
#
# The following example shows how to reset an object referenced by "foobar".
#
#	$foobar.reset
#
# @param @
#	The parameters are forwareded to a cleanup function if one was
#	specified.
#

#
# DESTRUCTOR
#
# This block documents the use of a destructor created by the
# bsda_obj:createClass function below.
#
# The destructor calls a cleanup method with all parameters, if
# one was specified. Afterwards it simply removes all method wrappers and
# attributes from memory.
#
# The following example illustrates the use of the destructor on an object
# that is referenced by the variable "foobar".
#
#	$foobar.delete
#
# @param @
#	The parameters are forwareded to a cleanup function if one was
#	specified.
#

#
# COPY
#
# This block documents the use of a copy method created by the
# bsda_obj:createClass function below.
#
# The copy method creates a new object of the same type and copies all
# attributes over to the new object.
#
# The following exampe depicts the copying of an object referenced by the
# variable "foobar". The new object will be referenced by the variable
# "foobarCopy".
#
#	$foobar.copy foobarCopy
#

#
# GET
#
# This block documents the use of a getter method created by the
# bsda_obj:createClass function below.
#
# A getter method either outputs an attribute value to stdout or stores it
# in a variable, named by the first parameter.
#
# The following example shows how to get the attribute "value" from the object
# referenced by "foobar" and store it in the variable "value".
#
#	$foobar.getValue value
#
# @param 1
#	The optional name of the variable to store the attribute value in.
#	If ommitted the value is output to stdout.
#

#
# SET
#
# This block documents the use of a setter method created by the
# bsda_obj:createClass function below.
#
# A setter method stores a value in an attribute.
#
# This example shows how to store the value 5 in the attribute "value" of
# the object referenced by "foobar".
#
#	$foobar.setValue 5
#
# @param 1
#	The value to write to an attribute.
#

#
# TYPE CHECK
#
# This block documetns the use of the static type checking method created
# by the bsda_obj:createClass function below.
#
# The type checking method isInstance takes an argument string and checks
# whether it is a reference to an object of this class.
#
# This example shows how to check whether the object "foobar" is an instance
# of the class "foo:bar".
#
#	if foo:bar.isInstance $foobar; then
#		...
#	else
#		...
#	fi
#
# @param 1
#	Any string that might be a reference.
#

#
# Returns a variable from a given reference. The variable is either written
# to a named variable, or in absence of one, output to stdout.
#
# @param 1
#	The name of the variable to write to. If this is empty, the value
#	is output to stdout instead.
# @param 2
#	The reference to the variable.
#
bsda_obj:getVar() {
	if [ -n "$1" ]; then
		eval "$1=\"\$$2\""
	else
		eval "echo \"\$$2\""
	fi
}

#
# Creates a new class, i.e. a constructor, destructor, resetter, getters
# and setters.
#
# So all that is left to be done are the methods.
#
# The following static variables are reserved:
#	_nextId
#
# The following static methods are reserved:
#	isInstance
#
# The following methods are reserved:
#	copy
#	delete
#	reset
#
# @param 1
#	The first parameter is the name of the class.
# @param @
#	A description of the class to create.
#	
#	All parameters following the class name make up a list of identifiers
#	for attributes and methods. Every identifier has a prefix, the
#	following prefixes are supported:
#
#		-: A plain attribute.
#		r: An attribute with a get method. The identifier
#		   "r:foo" results in a method called "getFoo".
#		w: An attribute with a get and set method. "w:foo" results
#		   in "getFoo" and "setFoo".
#		x: A method, this has to be user implemented as
#		   "<class>.<method>()".
#		i: An init method that is called with the remaining parameters
#		   given to the constructor.
#		c: A cleanup method that is called before the reset or delete
#		   command, with the parameters given to them.
#
#	With these parameters a constructor and a destructor will be built.
#	It is important that all used attributes are listed, or the copy
#	and the delete methods will not work as expected.
#
#	Everything that is not recognized as an identifier is treated as a
#	comment.
#	
#	The constructor can be called in the following way:
#		<class> <refname>
#	The class name acts as the name of the constructor, <refname> is the
#	name of the variable to store the reference to the new object in.
#
#	The resetter deletes all attributes, this can be used to replace
#	an object. The resetter is called this way:
#		$reference.reset
#
#	The destructor can be called in the following way:
#		$reference.delete
#	This will destroy all methods and attributes.
#
#	A getter takes the name of the variable to store the value in as the
#	first argument, if this is ommitted, the value is written to stdout.
#
#	The copy method can be used to create an exact copy of an object, note
#	that an init function must not break without parameters for copy to
#	work.
#
# @return
#	0 on succes
#	1 if an identifier does not have a known prefix
#	2 if there is more than one init or clean method specified
#
bsda_obj:createClass() {
	local IFS class methods attributes getters setters
	local getter setter attribute reference init clean

	IFS='
'

	# There are some default methods.
	methods="reset${IFS}delete${IFS}copy"
	attributes=
	getters=
	setters=
	init=
	clean=

	# Parse arguments.
	for arg {
		if [ -z "$class" ]; then
			class="$arg"
			continue
		fi

		case "$arg" in
			x:*)
				methods="$methods${methods:+$IFS}${arg#x:}"
			;;
			-:*)
				attributes="$attributes${attributes:+$IFS}${arg#-:}"
			;;
			r:*)
				attributes="$attributes${attributes:+$IFS}${arg#r:}"
				getters="$getters${getters:+$IFS}${arg#r:}"
			;;
			w:*)
				attributes="$attributes${attributes:+$IFS}${arg#w:}"
				getters="$getters${getters:+$IFS}${arg#w:}"
				setters="$setters${getters:+$IFS}${arg#w:}"
			;;
			i:*)
				if [ -n "$init" ]; then
					return 2
				fi
				methods="$methods${methods:+$IFS}${arg#i:}"
				init="$class.${arg#i:} \"\$@\""
			;;
			c:*)
				if [ -n "$clean"]; then
					return 2
				fi
				methods="$methods${methods:+$IFS}${arg#c:}"
				clean="$class.${arg#c:} \"\$@\""
			;;
			*)
				# Assume everything else is a comment.
			;;
		esac
	}

	# Create getters.
	for getter in $getters; {
		attribute="$getter"
		getter="get$(echo "${getter%%${getter#?}}" | tr '[:lower:]' '[:upper:]')${getter#?}"
		# Add the getter to the list of methods.
		methods="$methods${methods:+$IFS}$getter"

		eval "
			$class.$getter() {
				if [ -n \"\$1\" ]; then
					eval \"\$1=\\\"\\\$\${this}$attribute\\\"\"
				else
					eval \"echo \\\"\\\$\${this}$attribute\\\"\"
				fi
			}
		"
	}

	# Create setters.
	for setter in $setters; {
		attribute="$setter"
		setter="set$(echo "${setter%%${setter#?}}" | tr '[:lower:]' '[:upper:]')${setter#?}"
		# Add the setter to the list of methods.
		methods="$methods${methods:+$IFS}$setter"

		eval "
			$class.$setter() {
				setvar \"\${this}$attribute\" \"\$1\"
			}
		"
	}

	# Create reference prefix.
	reference="obj_$(echo "$class" | tr ':' '_')"

	# Create object id counter.
	setvar "${reference}_nextId" 0

	# Create constructor.
	eval "
		$class() {
			local this
	
			# Create object reference.
			this=\"${reference}_\${${reference}_nextId}_\"
	
			# Increase the object id counter.
			${reference}_nextId=\$((\$${reference}_nextId + 1))
	
			# Create method instances.
			bsda_obj:createMethods $class \$this \"$methods\"
	
			# Return the object reference.
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" \$this
			else
				echo \$this
			fi

			# Cast the reference variable from the parameters.
			shift
			$init
		}
	"

	# Create a resetter.
	eval "
		$class.reset() {
			$clean

			# Delete attributes.
			bsda_obj:deleteAttributes \$this \"$attributes\"
		}
	"

	# Create destructor.
	eval "
		$class.delete() {
			$clean

			# Delete methods and attributes.
			bsda_obj:deleteMethods \$this \"$methods\"
			bsda_obj:deleteAttributes \$this \"$attributes\"
		}
	"

	# Create copy method.
	eval "
		$class.copy() {
			local IFS reference attribute

			IFS='
'

			# Create a new empty object.
			$class reference

			# Store the new object reference in the target variable.
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" \$reference
			else
				echo \$reference
			fi

			# For each attribute copy the value over to the
			# new object.
			for attribute in \$(echo \"$attributes\"); {
				eval \"\$reference\$attribute=\\\"\\\$\${this}\$attribute\\\"\"
			}
		}
	"

	# A static type checker.
	eval "
		$class.isInstance() {
			echo \"\$1\" | grep -xq '${reference}_[0-9][0-9]*_'
		}
	"
}

#
# Creates the methods to a new object from a class.
#
# This is achieved by creating a method wrapper that provides the
# context variables this, class and caller.
#
# It works under the assumption, that methods are defined as:
#	<class>.<method>()
#
# @param 1
#	The class name.
# @param 2
#	The object reference.
# @param 3
#	A list of method names.
#
bsda_obj:createMethods() {
	local method
	for method in $3; {
		eval "
			${2}_callStackCount=0
			$2.$method() {
				local class this caller _return
				class=$1
				this=$2
				bsda_obj:callerSetup
				$1.$method \"\$@\"
				_return=\$?
				bsda_obj:callerFinish
				return \$_return
			}
		"
	}
}

#
# Deletes methods from an object. This is intended to be used in a destructor.
#
# @param 1
#	The object reference.
# @param 2
#	A list of method names.
#
bsda_obj:deleteMethods() {
	local method
	for method in $2; {
		unset ${1}_callStackCount
		unset -f "$1.$method"
	}
}

#
# Deletes attributes from an object. This is intended to be used in a
# destructor.
#
# This works under the assumption, that attributes are defined as:
#	<reference>_<attribute>
#
# @param 1
#	The object reference.
# @param 2
#	A list of attribute names.
#
bsda_obj:deleteAttributes() {
	local attribute
	for attribute in $2; {
		unset "${1}$attribute"
	}
}

#
# Setup the caller stack to store variables that should be overwritten
# in the caller context upon exiting the method.
#
# This function is called by the wrapper around class instance methods.
#
# Every method has a stack counter called _callStackCount (in the object
# context). This counter is increased and and a stack count prefix
# is created, which is used by bsda_obj:callerSetvar() to store variables
# for functions in the caller context until bsda_obj:callerFinish() is
# called.
#
# @param caller
#	Is set to the current stack count prefix.
# @param ${this}_callStackCount
#	Is incremented by 1 and used to create the caller variable.
#
bsda_obj:callerSetup() {
	# Increment the call stack counter and create the caller prefix.
	eval "
		${this}_callStackCount=\$((\$${this}_callStackCount + 1))
		caller=\"$this\${${this}_callStackCount}_\"
	"
	# Create a wrapper around bsda_obj:callerSetvar for access through
	# the caller prefix.
	eval "
		$caller.setvar() {
			bsda_obj:callerSetvar \"\$@\"
		}
	"
}

#
# Copy variables from the caller stack into the caller context and clean
# the stack up.
#
# This function is called by the wrapper around class instance methods
# after the actual method has terminated.
#
# @param caller
#	The caller context prefix.
# @param ${caller}_setvars
#	The list of variables to copy into the caller context.
# @param ${this}_callStackCount
#	Is decremented by 1.
#
bsda_obj:callerFinish() {
	# Remove the bsda_obj:callerSetvar() wrapper.
	unset -f $caller.setvar
	# Decrement the call stack counter.
	eval "${this}_callStackCount=\$((\$${this}_callStackCount - 1))"

	# Copy variables to the caller context.
	local _var IFS
	IFS=' '
	eval "_var=\"\$${caller}_setvars\""
	for _var in $_var; {
		# Copy variable.
		eval "setvar $_var \"\$$caller$_var\""
		# Delete variable from stack.
		unset $caller$_var
	}
	# Delete list of variables from stack.
	unset ${caller}_setvars
}

#
# This function stores a variables for overwriting variables in the context
# of the caller.
#
# This function is accessable in methods by calling:
#	$caller.setvar
#
# The stored variables are processed by the bsda_obj:callerFinish() function.
#
# @param 1
#	The name of the variable to store.
# @param 2
#	The value to store.
# @param caller
#	The context to store variables in.
# @param ${caller}_setvars
#	A list of all the stored variables for the caller context.
#
bsda_obj:callerSetvar() {
	# Store value.
	setvar $caller$1 "$2"
	# Register variable.
	eval "${caller}_setvars=\$${caller}_setvars\${${caller}_setvars:+ }$1"
}
Besonders stolz bin ich auf die Sache mit $caller.setvar. Das erlaubt es einer Methode Werte direkt in Variablen zurückzugeben. Dabei spielt es keine Rolle ob Variablen mit dem gleichen Namen in der Methode verwendet werden. Die Verwendung ist unter IMPLEMENTING METHODS beschrieben.

Kommen wir zum Größenwahn. Bisher verwendet pkg_upgrade Hintergrundprozesse für Downloads. Dabei kommunizieren alle Prozesse (der Hauptprozess, der Download Manager, die Downloadprozesse) über eine einzige Queue.

Nun will ich eine synchrone Variante von bsda_obj:createClass() erzeugen, die Klassen erzeugt, deren Objekte sich automatisch bei Zugriff über alle Prozesse synchronisieren.

Das soll komplett die Kommunikation über die Queue ersetzen und mir erlauben in allen Prozessen die gleichen objektorientierten Strukturen zu verwenden ohne mich dabei um die Synchronisierung der Daten zu kümmern. Damit kann ich auch einfacher andere Dinge parallelisieren, zum Beispiel die Integritätsprüfung.
 
Zuletzt bearbeitet:
vor allem ne sehr ausfuehrliche dokumentation. da koennte ich mir ne scheibe von abschneiden :D
 
Danke für das Lob. Jetzt habe ich doch tatsächlich einige veraltete Informationen in der Doku entdeckt und auch noch einen Fehler in der Implementierung der reset Methode behoben.

Das zeigt doch irgendwie wie public exposure Codequalität verbessern kann.
 
Hier mal ein Beispiel:
Code:
> sh
$ . bsda_pkg.sh
$ bsda_pkg:Moved moved /var/db/uma/FTPMOVED
$ bsda_pkg:Index index /var/db/uma/FTPINDEX $moved
$ $index.identifyPackages pkg sysutils/portupgrade
$ $pkg.getName
portupgrade-2.4.6_2,2
$ $pkg.getOrigin
ports-mgmt/portupgrade
 
Hier ist eine kleine Demo, was $caller.setvar ermöglicht. Zum Beispiel rekursive Funktionen:
Code:
. bsda_obj.sh

bsda_obj:createClass Demo \
	x:fibonacciRecursive

#
# @param 1
#	The variable to store the fibonacci value in.
# @param 2
#	The index of the fibonacci value to return.
#
Demo.fibonacciRecursive() {
	# Terminate recursion.
	if [ $2 -le 2 ]; then
		$caller.setvar $1 1
		return 0
	fi

	local f1 f2

	$this.fibonacciRecursive f1 $(($2 - 1))
	$this.fibonacciRecursive f2 $(($2 - 2))

	$caller.setvar $1 $(($f1 + $f2))
}

# Create instance.
Demo demo
$demo.fibonacciRecursive value 7
echo $value
 
Nur so als update, inzwischen kann das Framework serialisieren. Sogar rekursiv, das heißt man kann mit einem Befehl eine ganze Datenstruktur durch eine Netzwerkverbindung schicken oder speichern und später wieder verwenden.

Dafür gibt es zwei befehle, serialize serialisiert nur ein Objekt, serializeDeep serialisiert auch rekursiv alle referenzierten Objekte. Natürlich kommt serializeDeep auch mit zirkulären Strukturen klar.

Um alle Use-Cases abzudecken wurden die Objekt IDs um ein ein Präfix, eine Zufallszahl, einen Timestamp und eine Prozess ID erweitert. So gibt es keine Kollisionen beim deserialisieren der Objekte. Die IDs sind entsprechend ziemlich lang (z.B. BSDA_OBJ_bsda_obj_bsda_pkg_Index_0b19a78616c8ffab_1258288198_22400_0_) aber was soll's.
 
So, inzwischen ist auch noch Mehrfachvererbung und Zugriffskontrolle (private, protected, public) dazugekommen:

Code:
#!/bin/sh -f
#
# Copyright (c) 2009
# Dominic Fandrey <kamikaze@bsdforen.de>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# version 1.9

# Include once.
test -n "$bsda_obj" && return 0
bsda_obj=1

#
# This file contains helper functions for creating object oriented
# shell scripts.
#
# The most significant function is bsda:obj:createClass(), which basically
# creates a class, including getters, setters, a constructor, a destructor,
# a reset and a copy function. It also creates serialization methods.
#

#
# TABLE OF CONTENTS
#
# 1) DEFINING CLASSES
# 1.1) Basic Class Creation
# 1.2) Inheritance
# 1.3) Access Scope
# 1.4) Interfaces
# 2) IMPLEMENTING METHODS
# 2.1) Regular Methods
# 2.2) Special Methods
# 3) CONSTRUCTOR
# 4) RESET
# 5) DESTRUCTOR
# 6) COPY
# 7) GET
# 8) SET
# 9) TYPE CHECKS
# 9.1) Object Type Checks
# 9.2) Primitive Type Checks
# 10) SERIALIZE
# 10.1) Serializing
# 10.2) Deserializing
# bsda:obj:createClass()
# bsda:obj:createInterface()
# bsda:obj:getVar()
# bsda:obj:getSerializedId()
# bsda:obj:deserialize()
# bsda:obj:isObject()
# bsda:obj:isInt()
# bsda:obj:isFloat()
# bsda:obj:isSimpleFloat()
# bsda:obj:createMethods()
# bsda:obj:deleteMethods()
# bsda:obj:deleteAttributes()
# bsda:obj:callerSetup()
# bsda:obj:callerFinish()
# bsda:obj:callerSetvar()
#

#
# 1) DEFINING CLASSES
#
# This section describes the creation of classes.
#
# NOTE:	The details of creating classes are listed in front of the
#	bsda:obj:createClass() function.
#
# Creating a class consists of two steps, the first step is to call
# the bsda:obj:createClass() function, the second one is to implement the
# methods. This section describes the first step.
#
# In order to create classes this framework has to be loaded:
#
#	. bsda_obj.sh
#
# 1.1) Basic Class Creation
#
# Creating a class does not require more than a class name:
#
#	bsda:obj:createClass MyClass
#
# After the previous line the class can be used to create objects,
# however useless, and all the reserved methods are available:
#
#	MyClass myObject
#	$myObject.delete
#
# It is possible to create classes as simple data structures, that do not
# require the programmer to write any methods to function:
#
# 	bsda:obj:createClass MyPoint2D \
#		w:name \
#		w:x \
#		w:y
#
# Instances of the MyPoint2D class now offer the getName() and setName(),
# getX() and setX(), getY() and setY() methods:
#
#	MyPoint2D point
#	$point.setName "upper left corner"
#	$point.setX 0
#	$point.setY 0
#
# It might be a good idea to add an init method to the class in order to
# assign values:
#
#	bsda:obj:createClass MyConstPoint2D \
#		i:init \
#		r:name \
#		r:x \
#		r:y
#	
#	MyConstPoint2D.init() {
#		[ ... assign values, maybe even check types ... ]
#	}
#
# NOTE:	The init method can have an arbitrary name.
#
# Note that the attributes were now created with "r:", this means they only
# have get methods, no set methods. All the values are now assigned during
# object creation by the init method:
#
#	MyConstPoint2D corner "upper right corner" 640 0
#
# 1.2) Inheritance
#
# If a similar class is required there is no reason to start anew, the
# previous class can be extended:
#
#	bsda:obj:createClass MyConstPoint3D extends:MyConstPoint2D \
#		i:init \
#		r:z
#	
#	MyConstPoint3D.init() {
#		# Call the init method of the parent class.
#		$class.superInit "$1" "$2" "$3" || return 1
#		# Check whether the given coordinate is an integer.
#		bsda:obj:isInt "$4" || return 1
#		setvar ${this}z "$4"
#	}
#
# This example shows that to overload methods they have to be stated in the
# class declaration. If init had not been stated, the init method of the
# parent class MyConstPoint2D would have been used.
#
# NOTE: If the init method does not return 0 the object is instantly
#	destroyed and the return value forwarded to the caller.
#	The caller then has a reference to a no longer existing object
#	and does not know about it, unless the return value of the
#	constructor is checked.
#
# Multiple inheritance is possible, but should be used with great care,
# because there are several limits. If several extended classes provide
# the same method, the method of the first class has the priority.
#
# The super init and cleanup methods are those of the first class providing
# an init or cleanup method.
# The remaining init and cleanup methods might continue to exist as regular
# methods, if their names do not conflict.
#
# 1.3) Access Scope
#
# You might want to limit access to certain methods, for this you can
# add the scope operators private, protected and public. If no scope
# operator is given, public is assumed.
#
# public:
# 	This scope allows access from anywhere.
# protected:
#	The protected scope only allows classes that are derived from the
#	current class or reside within the same namespace access.
# private:
#	Only instances of the same class have access.
#
# Namespaces are colon (the character ":") seperated. E.g. the class
# bsda:pkg:Index has the namespace "bsd:pkg".
#
# The scope operator is added after the identifier type prefix. Only
# prefixes that declare methods can have a scope operator.
#
#	bsda:obj:createClass myNs:Person \
#		i:private:init \
#		w:private:familyName \
#		w:private:firstName
#
# NOTE:	The constructor is always public. Declaring a scope for an init method
#	only affects direct calls of the method.
#
# Now the getters and setters for both familyName and firstName are private.
# It is possible to widen the scope of a method by redeclaring it.
#
#	bsda:obj:createClass myNs:Person \
#		i:private:init \
#		w:private:familyName \
#		x:public:getFamilyName \
#		w:private:firstName \
#		x:public:getFirstName
#
# NOTE:	When methods are inherited the widest declared scope always wins, no
# 	matter from which class it originates.
#
# 1.4) Interfaces
#
# Implementations of generic solutions normally require the classes using them
# to conform to a certain interface (e.g. in a listener and notify pattern).
#
# Technically this can be realized with inheritance, but this is often a dirty
# solution, especially when conformance to several interfaces is required.
#
# To circumvent the consistency problems imposed by multiple inheritance the
# bsda:obj:createInterface() method allows the creation of interfaces:
#
#	bsda:obj:createInterface Listener \
#		x:notify
#
# NOTE:	Methods defined by an interface are always public, so there is not
#	scope operator.
#
# NOTE:	Interfaces cannot be used to define attributes.
#
# Every class conforming to the interface has to implement the methods defined
# by the interface:
#
#	bsda:obj:createClass Display implements:Listener \
#		[ ... additional method and attribute definitions ... ]
#
#	Display.notify() {
#		[ ... ]
#	}
#
# Interfaces can also extend other interfaces.
#
# To check whether an object is derived from a class conforming to an
# interface the static isInstance method can be use:
#
#	if ! Listener.isInstance $object; then
#		[ ... ]
#	fi
#

#
# 2) IMPLEMENTING METHODS
#
# All that requires to be done to get a functional class after defining it,
# is to implement the required methods.
# Methods are really just functions that the constructor
# creates a wrapper for that forwards the object reference to them.
#
# 2.1) Regular Methods
#
# The following special variables are available:
# 	this	A reference to the current object
#	class	The name of the class this object is an instance of
#	caller	Provides access to methods to manipulate the caller context,
#		which is the recommended way of returning data to the caller
#
# The following methods are offered by the caller:
#	setvar	Sets a variable in the caller context.
#	getObject
#		Returns a reference to the calling object.
#	getClass
#		Returns the name of the calling class.
#
# The following variable names may not be used in a method:
#	_return
#	_var
#	_setvars
#
# A method must always be named "<class>.<method>". So a valid implementation
# for a method named "bar" and a class named "foo" would look like this:
#
#	foo.bar() {
#	}
#
# The object reference is always available in the variable "this", which
# performs the same function as "self" in python or "this" in Java.
#
# Attributes are resolved as "<objectId><attribute>", the following example
# shows how to read an attribute, manipulate it and write the new value.
# Directly operating on attributes is not possible.
#
#	foo.bar() {
#		local count
#		# Get counter value.
#		bsda:obj:getVar count ${this}count
#		# Increase counter value copy.
#		count=$(($count + 1))
#		# Store the counter value.
#		setvar ${this}count $count
#	}
#
# The following example does the same with getters and setters. Getters and
# setters are documented in chapter 7 and 8.
#
#	foo.bar() {
#		local count
#		# Get counter value.
#		$this.getCount count
#		# Increase counter value copy.
#		count=$(($count + 1))
#		# Store the counter value.
#		$this.setCount $count
#	}
#
# To return data into the calling context $caller.setvar is used. It
# provides the possibility to overwrite variables in the caller context
# even when there is a local variable using the same name.
# Note that it has to be assumed that the names of variables used within
# a method are unknown to the caller, so this can always be the case.
#
# The name of the variable to store something in the caller context is
# normally given by the caller itself as a parameter to the method call.
#
# The following method illustrates this, the attribute count is fetched
# and returned to the caller through the variable named in $1.
# Afterwards the attribute is incremented:
#
#	foo.countInc() {
#		local count
#		# Get counter value.
#		$this.getCount count
#		$caller.setvar $1 $count
#		# Increase counter value copy.
#		count=$(($count + 1))
#		# Store the counter value.
#		$this.setCount $count
#	}
#
# This is how a call could look like:
#
#	local count
#	$obj.countInc count
#	echo "The current count is $count."
#
# Note that both the method and the caller use the local variable count, yet by
# using $caller.setvar the method is still able to overwrite count in the
# caller context.
#
# If a method uses no local variables (which is only sensible in very rare
# cases), the regular shell builtin setvar can be used to overwrite variables
# in the caller context to reduce overhead.
#
# 2.2) Special Methods
#
# There are two special kinds of methods available, init and cleanup methods.
# These methods are special, because they are called implicitely, the first
# when an object is created, the second when it is reset or deleted.
#
# The init method is special because the $caller.setvar() method is not
# available. It is called by the constructor with all values apart from the
# first one, which is the variable the constructor stores the object
# reference in. It can also be called directly (e.g. after a call to the
# reset() method).
#
# The purpose of an init method is to initialize attributes during class
# creation. If the current class is derived from another class it might
# be a good idea to call the init method of the parent class. This is
# done by calling $class.superInit().
#
# If the init method fails (returns a value > 0) the constructor immediately
# destroys the object.
#
# The cleanup method is called implicitely by the delete() and reset()
# methods. Unlike the init method it has all the posibilities of an
# ordinary method. Both the delete() and reset() methods ignore the return
# values of the the cleanup method.
#

#
# 3) CONSTRUCTOR
#
# This block documents the use of a constructor created by the
# bsda:obj:createClass() function below.
#
# The name of the class acts as the name of the constructor. The first
# parameter is the name of a variable to store the object reference in.
# An object reference is a unique id that allows the accessing of all methods
# belonging to an object.
#
# The object id is well suited for "grep -F", which is nice to have when
# implementing lists.
#
# The following example shows how to create an object of the type "foo:bar",
# by calling the "foo:bar" constructor:
#
#	foo:bar foobar
#
# The following example shows how to use a method belonging to the object:
#
#	$foobar.copy foobarCopy
#
# @param 1
#	The name of the variable to store the reference to the new object in.
# @param @
#	The remaining parameters are forwarded to an init method,
#	if one was specified.
# @return
#	Returns 0 for success and higher values for failure. If the
#	init method has a fail case, this should be checked, because
#	the object is not created in case of a failure.
#

#
# 4) RESET
#
# This block documents the use of a resetter created by the
# bsda:obj:createClass() function below.
#
# The resetter first calls the cleanup method with all parameters, if one
# has been defined. Afterwards it simply removes all attributes from memory.
#
# The resetter does not call the init method afterwards, because it would
# not be possible to provide different parameters to the init and cleanup
# methods in that case.
#
# The following example shows how to reset an object referenced by "foobar".
#
#	$foobar.reset
#
# @param @
#	The parameters are forwareded to a cleanup() method if one was
#	specified.
#

#
# 5) DESTRUCTOR
#
# This block documents the use of a destructor created by the
# bsda:obj:createClass() function below.
#
# The destructor calls a cleanup method with all parameters, if
# one was specified. Afterwards it simply removes all method wrappers and
# attributes from memory.
#
# The following example illustrates the use of the destructor on an object
# that is referenced by the variable "foobar".
#
#	$foobar.delete
#
# @param @
#	The parameters are forwareded to a cleanup() method if one was
#	specified.
#

#
# 6) COPY
#
# This block documents the use of a copy method created by the
# bsda:obj:createClass() function below.
#
# The copy method creates a new object of the same type and copies all
# attributes over to the new object.
#
# The following exampe depicts the copying of an object referenced by the
# variable "foobar". The new object will be referenced by the variable
# "foobarCopy".
#
#	$foobar.copy foobarCopy
#

#
# 7) GET
#
# This block documents the use of a getter method created by the
# bsda:obj:createClass() function below.
#
# A getter method either outputs an attribute value to stdout or stores it
# in a variable, named by the first parameter.
#
# The following example shows how to get the attribute "value" from the object
# referenced by "foobar" and store it in the variable "value".
#
#	$foobar.getValue value
#
# @param 1
#	The optional name of the variable to store the attribute value in.
#	If ommitted the value is output to stdout.
#

#
# 8) SET
#
# This block documents the use of a setter method created by the
# bsda:obj:createClass() function below.
#
# A setter method stores a value in an attribute.
#
# This example shows how to store the value 5 in the attribute "value" of
# the object referenced by "foobar".
#
#	$foobar.setValue 5
#
# @param 1
#	The value to write to an attribute.
#

#
# 9) TYPE CHECKS
#
# This framework supplies basic type checking facilities.
#
# 9.1) Object Type Checks
#
# This block documents the use of the static type checking method created
# by the bsda:obj:createClass() and bsda:obj:createInterface() function below.
#
# The type checking method isInstance() takes an argument string and checks
# whether it is a reference to an object of this class.
#
# This example shows how to check whether the object "foobar" is an instance
# of the class "foo:bar".
#
#	if foo:bar.isInstance $foobar; then
#		...
#	else
#		...
#	fi
#
# @param 1
#	Any string that might be a reference.
#
# 9.2) Primitive Type Checks
#
# The following primitive type checking functions are available and documented
# below:
#	bsda:obj:isObject()
#	bsda:obj:isInt()
#	bsda:obj:isFloat()
#	bsda:obj:isSimpleFloat()
#

#
# 10) SERIALIZE
#
# This documents the process of serialization and deserialization.
# Serialization is the process of turning data structures into string
# representations. Serialized objects can be stored in a file and reloaded
# at a later time. They can be passed on to other processess, through a file
# or a pipe. They can even be transmitted over a network through nc(1).
#
# NOTE:	Static attributes are not subject to serialization.
#
# 10.1) Serializing
#
# The following example serializes the object $foobar and stores the string
# the variable serialized.
#
#	$foobar.serialize serialized
#
# The next example saves the object $configuration in a file.
#
#	$configuration.serialize > ~/.myconfig
#
# If $configuration references other objects it will fail to access them
# if deserialized in a new context.
# This is what the serializeDeep() method is good for. It serializes entire
# data structures recursively and is the right choice in many use cases.
# It is used in exactly the same way as the serialize method.
#
#	$configuration.serializeDeep > ~/.myconfig
#
# @param 1
#	If given it is used as the variable name to store the serialized string
#	in, otherwise the serialized string is output to stdout.
#
# 10.2) Deserializing
#
# This example loads the object $configuration from a file and restores it.
#
#	# Deserialize the data and get the object reference.
#	bsda:obj:deserialize configuration - < ~/.myconfig
#
# After the last line the $configuration object can be used exactly like
# in the previous session.
#
# @param 1
#	The name of the variable to store the object ID (reference) of the
#	serialized object in.
# @param 2
#	This is expected to be a serialized string. In the special case that
#	this parameter is a dash (the charachter "-"), the serialized string
#	will be read from stdin.
#

#
# The stack counter that holds the number of methods that currently
# use the return stack.
#
bsda_obj_callStackCount=0

#
# This session ID becomes part of every newly created object and ensures
# that there are no object id collisions.
#
# Multi processing safety is ensured by adding the PID in the constructor.
# However it might still be possible that a program might load a serialized
# object (e.g. stored in a file) and have the same PID.
# This is why this object ID has a time stamp.
#
# However, there is still the chance an object might be sent over a network
# and received by a process with the same ID and started at the same time.
# This is what the first part of this ID takes care of, by adding a hex
# encoded 64bit random number.
#
readonly bsda_obj_sessionId=$(dd bs=8 count=1 < /dev/random 2> /dev/null  | xxd -p)_$(date -u '+%s')

#
# This is a prefix to every object ID and should be the same among all
# compatible frameworks to ensure that deep serialization works.
#
readonly bsda_obj_frameworkPrefix=BSDA_OBJ_

#
# This is used as a buffer during deep serialization.
#
#bsda_obj_serialized=

#
# During deep serialization this holds a list of objects to prevent circular
# recursion.
#
#bsda_obj_serializeBlacklist=

#
# The copy method sets this temporarily to tell the constructor not to call
# an init method.
#
#bsda_obj_doCopy

#
# Creates a new class, i.e. a constructor, destructor, resetter, getters,
# setters and so forth.
#
# So all that is left to be done are the methods.
#
# The following static methods are reserved:
#	superInit()
#	superClean()
#	deserialize()
#	isInstance()
#	isClass()
#	isInterface()
#	getAttributes()
#	getMethods()
#	getPrefix()
#	getInit()
#	getClean()
#	getParents()
#	getInterfaces()
#
# The following methods are reserved:
#	copy()
#	delete()
#	reset()
#	serialize()
#	serializeDeep()
#
# The following class prefix bound static attributes are reserved:
#	instancePatterns
#	private
#	protected
#	public
#
# The following session and process bound static attributes are reserved:
#	nextId
#
# @param 1
#	The first parameter is the name of the class.
# @param @
#	A description of the class to create.
#	
#	All parameters following the class name make up a list of identifiers
#	for attributes and methods. Every identifier has a prefix, the
#	following prefixes are supported:
#
#		-: A plain attribute.
#		r: An attribute with a get method. The identifier
#		   "r:foo" results in a method called "getFoo".
#		w: An attribute with a get and set method. "w:foo" results
#		   in "getFoo" and "setFoo".
#		x: A method, this has to be user implemented as
#		   "<class>.<method>()".
#		i: An init method that is called with the remaining parameters
#		   given to the constructor.
#		c: A cleanup method that is called before the reset or delete
#		   command, with the parameters given to them.
#		extends:
#		   This prefix is followed by the name of another class
#		   this class inherits methods and attributes from.
#		   Classes have to be given in the order of priority.
#
#		   The init and clean methods are inherited from the first
#		   class having them if no own init or clean method is
#		   supplied.
#
#		   The superInit() and superClean() methods also call
#		   the the first encountered init and clean methods.
#		implements:
#		   This prefix is followed by the name of an interfaces.
#		   Interfaces define public methods that need to be implemented
#		   by a class to conform to the interface.
#
#	With these parameters a constructor and a destructor will be built.
#	It is important that all used attributes are listed, or the copy,
#	delete and serialization methods will not work as expected.
#
#	Everything that is not recognized as an identifier is treated as a
#	comment.
#
#	The prefixes r, w, x, i and c can be followed by a scope operator
#	public, protected or private.
#	
#	The constructor can be called in the following way:
#		<class> <refname>
#	The class name acts as the name of the constructor, <refname> is the
#	name of the variable to store the reference to the new object in.
#
#	The resetter deletes all attributes, this can be used to replace
#	an object. The resetter is called this way:
#		$reference.reset
#
#	The destructor can be called in the following way:
#		$reference.delete
#	This will destroy all methods and attributes.
#
#	A getter takes the name of the variable to store the value in as the
#	first argument, if this is ommitted, the value is written to stdout.
#
#	The copy method can be used to create a shallow copy of an object.
#
# @param bsda_obj_namespace
#	The frameowrk namespace to use when building a class. The impact is on
#	the use of helper functions.
# @return
#	0 on succes
#	1 if there is more than one init method (i:) specified
#	2 if there is more than one cleanup method (c:) specified
#	3 if there was an unknown scope operator
#	4 for an attempt to extend something that is not a class
#	5 for an attempt to implement something that is not an interface
#
bsda:obj:createClass() {
	local IFS class methods method attributes getters setters arg
	local getter setter attribute reference init clean serialize extends
	local implements bsda_obj_namespace
	local namespacePrefix classPrefix prefix
	local superInit superClean
	local inheritedAttributes inheritedMethods parent parents
	local previousMethod scope interface

	# Default framework namespace.
	: ${bsda_obj_namespace='bsda:obj'}

	# Get the class name and shift it off the parameter list.
	class="$1"
	shift

	IFS='
'

	# There are some default methods.
	methods="reset${IFS}delete${IFS}copy${IFS}serialize${IFS}serializeDeep"
	attributes=
	getters=
	setters=
	init=
	clean=
	extends=
	implements=
	superInit=
	superClean=

	# Parse arguments.
	for arg {
		case "$arg" in
			x:*)
				methods="$methods${methods:+$IFS}${arg#x:}"
			;;
			-:*)
				attributes="$attributes${attributes:+$IFS}${arg#-:}"
			;;
			r:*)
				attributes="$attributes${attributes:+$IFS}${arg##*:}"
				getters="$getters${getters:+$IFS}${arg#r:}"
			;;
			w:*)
				attributes="$attributes${attributes:+$IFS}${arg##*:}"
				getters="$getters${getters:+$IFS}${arg#w:}"
				setters="$setters${getters:+$IFS}${arg#w:}"
			;;
			i:*)
				if [ -n "$init" ]; then
					echo "bsda:obj:createClasss: ERROR: More than one init method was supplied!" 1>&2
					return 1
				fi
				methods="$methods${methods:+$IFS}${arg#i:}"
				init="$class.${arg##*:}"
			;;
			c:*)
				if [ -n "$clean" ]; then
					echo "bsda:obj:createClasss: ERROR: More than one cleanup method was supplied!" 1>&2
					return 2
				fi
				methods="$methods${methods:+$IFS}${arg#c:}"
				clean="$class.${arg##*:}"
			;;
			extends:*)
				extends="$extends${extends:+$IFS}${arg#extends:}"
			;;
			implements:*)
				implements="$implements${implements:+$IFS}${arg#implements:}"
			;;
			*)
				# Assume everything else is a comment.
			;;
		esac
	}

	# Create reference prefix. The Process id is added to the prefix when
	# an object is created.
	namespacePrefix="${bsda_obj_frameworkPrefix}$(echo "$bsda_obj_namespace" | tr ':' '_')_"
	classPrefix="${namespacePrefix}$(echo "$class" | tr ':' '_')_"
	prefix="${classPrefix}${bsda_obj_sessionId}_"

	# Set the instance match pattern.
	setvar ${classPrefix}instancePatterns "${classPrefix}[0-9a-f]+_[0-9]+_[0-9]+_[0-9]+_"

	# Create getters.
	for method in $getters; {
		getter="${method##*:}"
		attribute="$getter"
		getter="get$(echo "${getter%%${getter#?}}" | tr '[:lower:]' '[:upper:]')${getter#?}"

		eval "
			$class.$getter() {
				if [ -n \"\$1\" ]; then
					eval \"\$1=\\\"\\\$\${this}$attribute\\\"\"
				else
					eval \"echo \\\"\\\$\${this}$attribute\\\"\"
				fi
			}
		"

		# Check for scope operator.
		if [ "${method%:*}" != "$method" ]; then
			# Add scope operator to the getter name.
			getter="${method%:*}:$getter"
		fi
		# Add the getter to the list of methods.
		methods="$methods${methods:+$IFS}${getter}"
	}

	# Create setters.
	for method in $setters; {
		setter="${method##*:}"
		attribute="$setter"
		setter="set$(echo "${setter%%${setter#?}}" | tr '[:lower:]' '[:upper:]')${setter#?}"

		eval "
			$class.$setter() {
				setvar \"\${this}$attribute\" \"\$1\"
			}
		"

		# Check for scope operator.
		if [ "${method%:*}" != "$method" ]; then
			# Add scope operator to the getter name.
			setter="${method%:*}:$setter"
		fi
		# Add the setter to the list of methods.
		methods="$methods${methods:+$IFS}$setter"
	}

	# Add implicit public scope to methods.
	for method in $methods$(methods=); {
		# Check the scope.
		case "${method%:*}" in
			$method)
				# There is no scope operator, add public.
				methods="${methods:+$methods$IFS}public:$method"
			;;
			public | protected | private)
				# The accepted scope operators.
				methods="${methods:+$methods$IFS}$method"
			;;
			*)
				# Everything else is not accepted.
				echo "bsda:obj:createClasss: ERROR: Unknown scope operator \"${method%:*}\"!" 1>&2
				return 3
			;;
		esac
	}

	# Manage inheritance.
	superInit=
	superClean=
	for parent in $extends; {
		if ! $parent.isClass; then
			echo "bsda:obj:createClasss: ERROR: Extending \"$parent\" failed, not a class!" 1>&2
			return 4
		fi

		# Get the interfaces implemented by the class.
		# Filter already registered interfaces.
		parents="$($parent.getInterfaces | grep -vFx "$implements")"
		# Append the detected interfaces to the list of implemented
		# interfaces.
		implements="$implements${implements:+${parents:+$IFS}}$parents"

		# Get the parents of this class.
		# Filter already registered parents.
		parents="$($parent.getParents | grep -vFx "$extends")"
		# Append the detected parents to the list of extended classes.
		extends="$extends${parents:+$IFS$parents}"

		# Get the super methods, first class wins.
		test -z "$superInit" && $parent.getInit superInit
		test -z "$superClean" && $parent.getClean superClean

		# Get inherited methods and attributes.
		inheritedMethods="$($parent.getMethods | grep -vFx "$methods")"
		inheritedAttributes="$($parent.getAttributes | grep -vFx "$attributes")"

		# Update the list of attributes.
		attributes="$inheritedAttributes${inheritedAttributes:+${attributes:+$IFS}}$attributes"

		# Create aliases for methods.
		for method in $inheritedMethods; {
			# Check whether this method already exists with a
			# different scope.
			if echo "$methods" | grep -qx ".*:${method##*:}"; then
				# Skip ahead.
				continue
			fi

			# Inherit method.
			alias $class.${method##*:}=$parent.${method##*:}
		}

		# Update the list of methods.
		methods="$inheritedMethods${inheritedMethods:+${methods:+$IFS}}$methods"

		# Update the instance match patterns of parents.
		for parent in $parent${parents:+$IFS$parents}; {
			$parent.getPrefix parent
			eval "${parent}instancePatterns=\"\${${parent}instancePatterns}|\${${classPrefix}instancePatterns}\""
		}
	}

	# Manage implements.
	for interface in $implements; {
		if ! $interface.isInterface; then
			echo "bsda:obj:createClasss: ERROR: Implementing \"$interface\" failed, not an interface!" 1>&2
			return 5
		fi

		# Get the parents of this interface.
		# Filter already registered parents.
		parents="$($interface.getParents | grep -vFx "$implements")"
		# Append the detected parents to the list of extended classes.
		implements="$implements${parents:+$IFS$parents}"

		# Get inherited public methods.
		inheritedMethods="$($interface.getMethods | grep -vFx "$methods")"

		# Update the list of methods.
		methods="$inheritedMethods${inheritedMethods:+${methods:+$IFS}}$methods"

		# Update the instance match patterns of parents.
		for parent in $interface${parents:+$IFS$parents}; {
			$interface.getPrefix parent
			eval "${parent}instancePatterns=\"\${${parent}instancePatterns:+\${${parent}instancePatterns}|}\${${classPrefix}instancePatterns}\""
		}
	}

	# If a method is defined more than once, the widest scope wins.
	# Go through the methods sorted by method name.
	previousMethod=
	scope=
	for method in $(methods=; echo "$methods" | sort -t: -k2); {
		# Check whether the previous and the current method were the
		# same.
		if [ "$previousMethod" != "${method##*:}" ]; then
			# If all scopes of this method have been found,
			# store it in the final list.
			methods="${methods:+$methods${previousMethod:+$IFS$scope:$previousMethod}}"
			scope="${method%:*}"
		else
			# Widen the scope if needed.
			case "${method%:*}" in
				public)
					scope=public
				;;
				protected)
					if [ "$scope" == "private" ]; then
						scope=protected
					fi
				;;
			esac
		fi

		previousMethod="${method##*:}"
	}
	# Add the last method (this never happens in the loop).
	methods="${methods:+$methods${previousMethod:+$IFS$scope:$previousMethod}}"

	# Store access scope checks for each scope in the class context.
	# Note that at the time this is run the variables class and this
	# still belong to the the caller.
	setvar ${classPrefix}private "
		if [ \\\"\\\$class\\\" != \\\"$class\\\" ]; then
			echo \\\"$class.\${method##*:}(): Terminated because of access attempt to a private method\\\${class:+ by \\\$class}!\\\" 1>&2
			return 255
		fi
	"
	setvar ${classPrefix}protected "
		if (! $class.isInstance \\\$this) && [ \\\"\\\${class#${class%:*}}\\\" = \\\"\\\$class\\\" ]; then
			echo \\\"$class.\${method##*:}(): Terminated because of access attempt to a protected method\\\${class:+ by \\\$class}!\\\" 1>&2
			return 255
		fi
	"
	setvar ${classPrefix}public ''

	# Create constructor.
	eval "
		$class() {
			local _return this class
			class=$class

			eval \"
				# Create object reference.
				this=\\\"${prefix}\$\$_\\\${${prefix}\$\$_nextId:-0}_\\\"
	
				# Increase the object id counter.
				${prefix}\$\$_nextId=\\\$((\\\$${prefix}\$\$_nextId + 1))
			\"

			# Create method instances.
			$bsda_obj_namespace:createMethods $class $classPrefix \$this \"$methods\"

			# Return the object reference.
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" \$this
			else
				echo \$this
			fi

			# If this object construction is part of a copy() call,
			# this constructor is done.
			test -n \"$bsda_obj_doCopy\" && return 0

			${init:+
				# Cast the reference variable from the parameters.
				shift
				# Call the init method.
				$init \"\$@\"
				_return=\$?
				# Destroy the object on failure.
				test \$_return -ne 0 && \$this.delete
				return \$_return
			}
		}
	"

	# Create a resetter.
	eval "
		$class.reset() {
			${clean:+$clean \"\$@\"}

			# Delete attributes.
			$bsda_obj_namespace:deleteAttributes \$this \"$attributes\"
		}
	"

	# Create destructor.
	eval "
		$class.delete() {
			${clean:+$clean \"\$@\"}

			# Delete methods and attributes.
			$bsda_obj_namespace:deleteMethods \$this \"$methods\"
			$bsda_obj_namespace:deleteAttributes \$this \"$attributes\"
		}
	"

	# Create copy method.
	eval "
		$class.copy() {
			local IFS bsda_obj_doCopy reference attribute

			bsda_obj_doCopy=1
			IFS='
'

			# Create a new empty object.
			$class reference

			# Store the new object reference in the target variable.
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" \$reference
			else
				echo \$reference
			fi

			# For each attribute copy the value over to the
			# new object.
			for attribute in \$(echo \"$attributes\"); {
				eval \"\$reference\$attribute=\\\"\\\$\${this}\$attribute\\\"\"
			}
		}
	"

	# A serialize method.
	eval "
		$class.serialize() {
			local IFS attribute serialized

			IFS='
'

			serialized=
			for attribute in \$(echo '$attributes'); {
				serialized=\"\${serialized:+\$serialized;}\${this}\$attribute='\$(
					eval \"printf '%s' \\\"\\\${\${this}\$attribute}\\\"\" | xxd -p | rs -T
				)'\"
			}
			serialized=\"\$serialized;$class.deserialize \$this\"

			\$caller.setvar \"\$1\" \"\$serialized\"
		}
	"

	# A recursive serialize method.
	eval "
		$class.serializeDeep() {
			local IFS rootCall objects object serialized attribute

			serialized=
			rootCall=
			IFS='
'

			# Check whether this has already been serialized.
			if echo \"\$this\" | grep -qFx \"\$bsda_obj_serializeBlacklist\"; then
				# Already serialized, return.
				return 0
			fi

			# Check whether this is the root call.
			if [ -z \"\$bsda_obj_serializeBlacklist\" ]; then
				rootCall=1
			fi

			# Add this to the blacklist to prevent circular
			# recursion.
			bsda_obj_serializeBlacklist=\"\${bsda_obj_serializeBlacklist:+\$bsda_obj_serializeBlacklist;}\$this\"

			# Create a list of all referenced objects.
			objects=\"\$(
				# Echo each attribute.
				for attribute in \$(echo '$attributes'); {
					eval \"echo \\\"\\\${\$this\$attribute}\\\"\"
				} | grep -Eo '$bsda_obj_frameworkPrefix[_[:alnum:]]+_[0-9a-f]+_[0-9]+_[0-9]+_[0-9]+_' | sort -u
			)\"

			# Serialize all required objects.
			for object in \$objects; {
				\$object.serializeDeep
			}

			# Serialize this.
			\$this.serialize serialized

			# Append this to the recursive serialization list.
			bsda_obj_serialized=\"\${bsda_obj_serialized:+\$bsda_obj_serialized$IFS}\$serialized\"

			# Root call only.
			if [ -n \"\$rootCall\" ]; then
				# Return serialized string.
				\$caller.setvar \"\$1\" \"\$bsda_obj_serialized\"
				# Wipe static serialization variables.
				unset bsda_obj_serialized
				unset bsda_obj_serializeBlacklist
			fi
		}
	"

	# A static super method, which calls the init method of the
	# parent class.
	eval "
		$class.superInit() {
			${superInit:+local class}
			${superInit:+class=$extends}
			${superInit:+$superInit \"\$@\"}
		}
	"

	# A static super method, which calls the cleanup method of the
	# parent class.
	eval "
		$class.superClean() {
			${superClean:+local class}
			${superClean:+class=$extends}
			${superClean:+$superClean \"\$@\"}
		}
	"

	# A static deserialize method.
	eval "
		$class.deserialize() {
			local IFS attribute

			IFS='
'

			# Create method instances.
			$bsda_obj_namespace:createMethods $class $classPrefix \$1 \"$methods\"

			# Deserialize attributes.
			for attribute in \$(echo '$attributes'); {
				setvar \"\$1\$attribute\" \"\$(
					eval \"echo \\\"\\\${\$1\$attribute}\\\"\" | rs -T | xxd -r -p
				)\"
			}
		}
	"

	# A static type checker.
	eval "
		$class.isInstance() {
			echo \"\$1\" | grep -xEq \"\${${classPrefix}instancePatterns}\"
		}
	"

	# Return whether this is a class.
	eval "
		$class.isClass() {
			return 0
		}
	"

	# Return whether this is an interface.
	eval "
		$class.isInterface() {
			return 1
		}
	"

	# A static method that returns the attributes of a class.
	eval "
		$class.getAttributes() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$attributes'
			else
				echo '$attributes'
			fi
		}
	"

	# A static method that returns the methods of a class.
	eval "
		$class.getMethods() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$methods'
			else
				echo '$methods'
			fi
		}
	"

	# A static method that returns the class prefix.
	eval "
		$class.getPrefix() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$classPrefix'
			else
				echo '$classPrefix'
			fi
		}
	"

	# A static method that returns the parentage of this class.
	eval "
		$class.getInterfaces() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$implements'
			else
				echo '$implements'
			fi
		}
	"

	# A static method that returns the parentage of this class.
	eval "
		$class.getParents() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$extends'
			else
				echo '$extends'
			fi
		}
	"

	# A static method that returns the name of the init method.
	eval "
		$class.getInit() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$init'
			else
				echo '$init'
			fi
		}
	"

	# A static method that returns the name of the cleanup method.
	eval "
		$class.getClean() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$clean'
			else
				echo '$clean'
			fi
		}
	"
}

#
# This function creates an interface that can be implemented by a class.
#
# It is similar to the bsda:obj:createClass() function, but a lot less complex.
#
# The following static methods are reserved:
#	isInstance()
#	isClass()
#	isInterface()
#	getMethods()
#	getPrefix()
#	getParents()
#
# The following class prefix bound static attributes are reserved:
#	instancePatterns
#
# @param 1
#	The first parameter is the name of the class.
# @param @
#	A description of the interface to create.
#
#	All parameters following the interface name make up a list of
#	identifiers, the different types of identifiers are distinguished by
#	the following prefixes:
#
#		x: Defines a public method.
#		extends:
#		   This prefix is followed by the name of another interface
#		   from which method definitions are inherited.
#
#	Everything that is not recognized as an identifier is treated as a
#	comment.
#
# @param bsda_obj_namespace
#	The frameowrk namespace to use when building a class. The impact is on
#	the use of helper functions.
# @return
#	0 on succes
#	1 for an attempt to extend something that is not an interface
#
bsda:obj:createInterface() {
	local IFS interface bsda_obj_namespace methods extends
	local interfacePrefix namespacePrefix parent parents
	local inheritedMethods

	# Default framework namespace.
	: ${bsda_obj_namespace='bsda:obj'}

	# Get the interface name and shift it off the parameter list.
	interface="$1"
	shift

	IFS='
'

	methods=
	extends=

	# Parse arguments.
	for arg {
		case "$arg" in
			x:*)
				methods="$methods${methods:+$IFS}public:${arg#x:}"
			;;
			extends:*)
				extends="$extends${extends:+$IFS}${arg#extends:}"
			;;
			*)
				# Assume everything else is a comment.
			;;
		esac
	}

	# Create an interface prefix, this is required to access the instance
	# matching patterns.
	namespacePrefix="${bsda_obj_frameworkPrefix}$(echo "$bsda_obj_namespace" | tr ':' '_')_"
	interfacePrefix="${namespacePrefix}$(echo "$interface" | tr ':' '_')_"

	# Manage inheritance.
	for parent in $extends; {
		if ! $parent.isInterface; then
			echo "bsda:obj:createInterface: ERROR: Extending \"$interface\" failed, not an interface!" 1>&2
			return 1
		fi


		# Get the parents of this interface.
		# Filter already registered parents.
		parents="$($parent.getParents | grep -vFx "$extends")"
		# Append the detected parents to the list of extended interfaces.
		extends="$extends${parents:+$IFS$parents}"

		# Get inherited public methods.
		inheritedMethods="$($parent.getMethods | grep -vFx "$methods")"

		# Update the list of methods.
		methods="$inheritedMethods${inheritedMethods:+${methods:+$IFS}}$methods"
	}

	# A static type checker.
	eval "
		$interface.isInstance() {
			echo \"\${1:-dummy}\$1\" | grep -xEq \"\${${interfacePrefix}instancePatterns}\"
		}
	"

	# Return whether this is a class.
	eval "
		$interface.isClass() {
			return 1
		}
	"

	# Return whether this is an interface.
	eval "
		$interface.isInterface() {
			return 0
		}
	"

	# A static method that returns the methods declared in this interace.
	eval "
		$interface.getMethods() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$methods'
			else
				echo '$methods'
			fi
		}
	"

	# A static method that returns the interface prefix.
	eval "
		$interface.getPrefix() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$interfacePrefix'
			else
				echo '$interfacePrefix'
			fi
		}
	"

	# A static method that returns the parentage of this interface.
	eval "
		$interface.getParents() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$extends'
			else
				echo '$extends'
			fi
		}
	"
}

#
# Returns a variable from a given reference. The variable is either written
# to a named variable, or in absence of one, output to stdout.
#
# @param 1
#	If this is the sole parameter it is a reference to the variable
#	to output to stdout. If a second parameter exists, it is the name of
#	the variable to write to.
# @param 2
#	The reference to the variable to return.
#
bsda:obj:getVar() {
	if [ -n "$2" ]; then
		eval "$1=\"\$$2\""
	else
		eval "echo \"\$$1\""
	fi
}

#
# Returns an object reference to a serialized object.
#
# @param 1
#	If this is the sole parameter, this is a serialized string of which
#	the object reference should be output. In case of a second parameter
#	this is the name of the variable to return the reference to the
#	serialized object to.
# @param 2
#	The serialized string of which the reference should be returned.
#	
bsda:obj:getSerializedId() {
	if [ -n "$2" ]; then
		setvar "$1" "${2##* }"
	else
		echo "${1##* }"
	fi
}

#
# Deserializes a serialized object and returns or outputs a reference to
# said object.
#
# @param 1
#	If there is a second parameter this is the name of the variable to
#	store the object reference in. Otherwise this is the serialized string
#	and the reference is be output to stdout.
# @param 2
#	If given this is the serialized string and the object reference is
#	saved in the variable named with the first parameter.
#
bsda:obj:deserialize() {
	if [ "$2" = "-" ]; then
		local stream
		stream="$(cat)"
		eval "$stream"
		setvar "$1" "${stream##* }"
	elif [ -n "$2" ]; then
		eval "$2"
		setvar "$1" "${2##* }"
	else
		eval "$1"
		echo "${1##* }"
	fi
}

#
# Checks whether the given parameter is an object.
#
# @param 1
#	The parameter to check.
# @return
#	0 for objects, 1 for everything else.
#
bsda:obj:isObject() {
	echo "$1" | grep -qExe "$bsda_obj_frameworkPrefix[_[:alnum:]]+_[0-9a-f]+_[0-9]+_[0-9]+_[0-9]+_"
}

#
# Checks whether the given parameter is an integer.
# Integers may be signed, but there must not be any spaces.
#
# @param 1
#	The parameter to check.
# @return
#	0 for integers, 1 for everything else.
#
bsda:obj:isInt() {
	echo "$1" | grep -qExe "(-|\+)?[0-9]+"
}

#
# Checks whether the given parameter is a floating point value.
# Floats may be signed, but there must not be any spaces.
# This function does not obey the locale.
#
# The following are examples for valid floats:
#	1
#	1.0
#	-1.5
#	1000
#	1e3	= 1000
#	1e-3	= 0.001
#	-1e-1	= -0.1
#	+1e+2	= 100
#
# @param 1
#	The parameter to check.
# @return
#	0 for floats, 1 for everything else.
#
bsda:obj:isFloat() {
	echo "$1" | grep -qExe "(-|\+)?[0-9]+(\.[0-9]+)?(e(-|\+)?[0-9]+)?"
}

#
# Checks whether the given parameter is a simple floating point value.
# Simple floats may be signed, but there must not be any spaces.
# This function does not obey the locale.
#
# The following are examples for valid floats:
#	1
#	1.0
#	-1.5
#	1000
#
# @param 1
#	The parameter to check.
# @return
#	0 for simple floats, 1 for everything else.
#
bsda:obj:isSimpleFloat() {
	echo "$1" | grep -qExe "(-|\+)?[0-9]+(\.[0-9]+)?"
}

#
# Creates the methods to a new object from a class.
#
# This is achieved by creating a method wrapper that provides the
# context variables this, class and caller.
#
# It works under the assumption, that methods are defined as:
#	<class>.<method>()
#
# @param 1
#	The class name.
# @param 2
#	The class prefix where the scope checks are stored.
# @param 3
#	The object reference.
# @param 4
#	A list of method names.
#
bsda:obj:createMethods() {
	local method scope
	for method in $4; {
		scope=${method%:*}
		eval "scope=\"\$$2$scope\""
		# Add method name to scope.
		eval "scope=\"$scope\""
		method=${method##*:}
		eval "
			$3.$method() {
				local class this caller _return
				# Check access permission.
				$scope
				bsda:obj:callerSetup
				class=$1
				this=$3
				$1.$method \"\$@\"
				_return=\$?
				bsda:obj:callerFinish
				return \$_return
			}
		"
	}
}

#
# Deletes methods from an object. This is intended to be used in a destructor.
#
# @param 1
#	The object reference.
# @param 2
#	A list of method names.
#
bsda:obj:deleteMethods() {
	local method
	for method in $2; {
		method=${method##*:}
		unset -f "$1.$method"
	}
}

#
# Deletes attributes from an object. This is intended to be used in a
# destructor.
#
# This works under the assumption, that attributes are defined as:
#	<reference>_<attribute>
#
# @param 1
#	The object reference.
# @param 2
#	A list of attribute names.
#
bsda:obj:deleteAttributes() {
	local attribute
	for attribute in $2; {
		unset "${1}$attribute"
	}
}

#
# Setup the caller stack to store variables that should be overwritten
# in the caller context upon exiting the method.
#
# This function is called by the wrapper around class instance methods.
#
# The bsda_obj_callStackCount counter is increased and and a stack count prefix
# is created, which is used by bsda:obj:callerSetvar() to store variables
# for functions in the caller context until bsda:obj:callerFinish() is
# called.
#
# The call stack prefix is in the format 'bsda_obj_callStack_[0-9]+_'.
#
# @param caller
#	Is set to the current stack count prefix.
# @param bsda_obj_callStackCount
#	Is incremented by 1 and used to create the caller variable.
#
bsda:obj:callerSetup() {
	# Increment the call stack counter and create the caller prefix.
	caller="bsda_obj_callStack_${bsda_obj_callStackCount}_"
	bsda_obj_callStackCount=$(($bsda_obj_callStackCount + 1))

	# Create functions to interact with the caller.
	eval "
		# Create a wrapper around bsda:obj:callerSetvar for access
		# through the caller prefix. I do not have the slightest idea
		# why alias does not work for this.
		$caller.setvar() {
			bsda:obj:callerSetvar \"\$@\"
		}

		# Create a function that returns the object ID of the caller.
		$caller.getObject() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$this'
			else
				echo '$this'
			fi
		}

		# Create a function that returns the class of the caller.
		$caller.getClass() {
			if [ -n \"\$1\" ]; then
				setvar \"\$1\" '$class'
			else
				echo '$class'
			fi
		}
	"

}

#
# Copy variables from the caller stack into the caller context and clean
# the stack up.
#
# This function is called by the wrapper around class instance methods
# after the actual method has terminated.
#
# @param caller
#	The caller context prefix.
# @param ${caller}_setvars
#	The list of variables to copy into the caller context.
# @param ${this}_callStackCount
#	Is decremented by 1.
#
bsda:obj:callerFinish() {
	# Remove the bsda:obj:callerSetvar() wrapper.
	unset -f $caller.setvar $caller.getObject $caller.getClass
	# Decrement the call stack counter and delete if no longer required.
	bsda_obj_callStackCount=$(($bsda_obj_callStackCount - 1))

	# Copy variables to the caller context.
	local _var IFS
	IFS=' '
	eval "_var=\"\$${caller}_setvars\""
	for _var in $_var; {
		# Copy variable.
		eval "setvar $_var \"\$$caller$_var\""
		# Delete variable from stack.
		unset $caller$_var
	}
	# Delete list of variables from stack.
	unset ${caller}_setvars
}

#
# This function stores a variables for overwriting variables in the context
# of the caller. If no storing variable has been specified (i.e. the first
# parameter is empty), the value is printed instead.
#
# This function is accessable in methods by calling:
#	$caller.setvar
#
# The stored variables are processed by the bsda:obj:callerFinish() function.
#
# @param 1
#	The name of the variable to store.
# @param 2
#	The value to store.
# @param caller
#	The context to store variables in.
# @param ${caller}_setvars
#	A list of all the stored variables for the caller context.
#
bsda:obj:callerSetvar() {
	# Print if no return variable was specified.
	test -z "$1" && echo "$2" && return

	# Store value.
	setvar $caller$1 "$2"
	# Register variable.
	eval "${caller}_setvars=\$${caller}_setvars\${${caller}_setvars:+ }$1"
}

So langsam brauche ich länger für das Dokumentieren als für das Implementieren. Ich denke jetzt ist endlich Schluss und ich konzentriere mich ab sofort voll auf die nächste Version von pkg_upgrade.

Edit0:
Update zu Version 1.8 - ich brauchte noch Interfaces für den Download Manager

Edit1:
Update zu Version 1.9 - Über $caller.getObject und $caller.getClass kann man jetzt erfahren, von wem man eigentlich aufgerufen wurde
 
Zuletzt bearbeitet:
Ich habe in der letzten geposteten Version noch ein paar Bugs entdeckt. Leider ist inzwischen die Editiersperre aktiv.
 
Inzwischen stehen einige Subsysteme des pkg_upgrade Recode (Scheduler, Messaging, Index Verarbeitung). Im Moment bastle ich gerade am Hintergrunddownloader. Die existierenden Systeme funktionieren schon ziemlich gut.
 
Für /bin/sh.

sh(1) schrieb:
A sh command, the Thompson shell, appeared in Version 1 AT&T UNIX. It
was superseded in Version 7 AT&T UNIX by the Bourne shell, which inher‐
ited the name sh.

This version of sh was rewritten in 1989 under the BSD license after the
Bourne shell from AT&T System V Release 4 UNIX.
 
Mir erschließt sich der ganze Aufwand nicht. Warum nimmst du nicht direkt eine OO Sprache wie Ruby? Imo _deutlich_ besser in so fast allen belangen, einzige Ausnahme ist, dass sie erst installiert werden muss. Dann hat man auch nicht nur OO sondern direkt den ganzen modernen Spass mit Introspection und Reflection um nur zwei Beispiele zu nennen. Ich glaube die wirst du ums verrecken nicht in eine Shell reinbekommen nur mit einem Skript.
 
Wo ist denn da der Spaß?

Ernsthaft, es gibt einen Grund warum portmaster inzwischen beliebter ist als portupgrade. Keine Abhängigkeiten.

Ich darf auch an das Gefrickel erinnern, wenn sich die Standardversion einer gängigen Skriptsprache in den Ports ändert. Ich möchte meinen Paketmanager nicht von der Sprache abhängig haben die gerade kaputtaktualisiert wird. Wenn da was schief geht läuft der dann auch nicht mehr.

Habe ich Spaß schon erwähnt?
 
Ich seh's schon kommen: "Kamikaze präsentiert seinen BSD-Klon in Shell-Skript". Vielleicht ein kleiner Mikrokernel in Assembly und ein Interpreter aber alle Module dazu sind in Shell :ugly:

Alles höchst interessant anzusehen. Weiter so!
 
Zuletzt bearbeitet:
Was ich vergaß zu erwähnen, rudimentäre Reflection-Unterstützung ist schon vorhanden. Das war Vorraussetzung für Vererbung. Schließlich gibt es hier keinen Compiler, das muss alles zur Laufzeit machbar sein.

Das ließe sich natürlich noch ausbauen, aber da habe ich im Moment keinen Bedarf. Der Aufwand wäre aber nicht groß.
 
Zurück
Oben