Eiffel (programming language)

ImprimirCitar

Eiffel is an object-oriented programming language that follows the ISO standard designed by Bertrand Meyer (defender of object-oriented languages and author of the construction of Object-Oriented Software) and Software Eiffel. The language design is closely related to the Eiffel programming method. Both are based on a number of principles including: design by contract, separation of commands and queries, uniform access principle, single choice principle, open-closed principle, and operation-operand separation.

Many concepts initially introduced by Eiffel later appear in Java, C#, and other languages. New language design ideas, particularly through the ECMA/ISO standardization process, continue to be incorporated into the Eiffel language.

Features

Key features of the Eiffel language include:

  • An object-oriented program structure in which a class serves as the basic unit of decomposition.
  • Contract design closely integrated with other language constructions.
  • Automatic memory management, usually implemented by a garbage collector.
  • Inheritance, including multiple inheritance, name change, redefinition, "select", non-conforming inheritance, and other mechanisms designed to make inheritance safe.
  • Generic programming with and without restrictions.
  • Management of a uniform type system where the semantics of value and reference in which all types, including basic types as whole, are of base class.
  • Static lid
  • Security absence of types, or static protection against calls to null references, through the mechanism of attachment types.
  • Agents, or objects that group to calculations, closely related to closing and lambda calculation
  • Unique run routines, or routines that run only once, for shared objects and decentralized initialization.
  • Keyword syntax following the ALGOL / Pascal tradition but free of separators, to the extent in points and commas are optional, with the syntax available to define operators for routines.
  • It is not sensitive to uppercases and lowercases.

Design goals

Eiffel highlights declarative statements about procedural code and attempts to eliminate the need for accounting statements. Eiffel avoids tricks or coding techniques intended as optimization hints for the compiler. The goal is not only to make the code easier to read, but also to allow programmers to focus on the important aspects of a program without getting bogged down in implementation details. Eiffel Simplicity is intended to promote simple, extensible, reusable, and reliable answers to computing problems. Compilers for computer programs written in Eiffel offer extensive optimization techniques, such as automatic in-lining, that relieve the programmer of some of the burden, while optimizing the production of code whose efficiency is comparable to that of code written in Eiffel. C++ [citation required].

Background

Eiffel was originally developed by Eiffel Software, a company founded by Bertrand Meyer. The Object Oriented Construction Software contains a detailed treatment of the concepts and theory of object technology that led to the design of the Eiffel.

The design goal behind the Eiffel language, libraries, and programming methods is to enable programmers to create reliable and reusable software modules. Eiffel supports multiple inheritance, generality, polymorphism, encapsulation, type-safe conversions, and parameter covariance. Eiffel's most important contribution to software engineering is design-by-contract (DBC), in which assertions, preconditions, postconditions, and invariant classes are used to help ensure program correctness without sacrificing efficiency.

Eiffel's design is based on object-oriented programming theory, with only a little influence from other paradigms or some concepts due to legacy code support. Eiffel formally supports abstract data types. In Eiffel's design, a software text must be able to reproduce its design documentation from the text itself, using a formalized implementation of the abstract data type. The Blue interactive learning language, precursor to BlueJ, is also based on Eiffel. The Apple Media Tool includes an Apple Media Language based on Eiffel.

Implementations and environments

EiffelStudio is an integrated development environment available under an open source or commercial license. It offers an object-oriented environment for software engineering. EiffelEnvision is a plugin for Microsoft Visual Studio that allows users to edit, compile, and debug Eiffel projects from within the Microsoft Visual Studio IDE. EiffelStudio and EiffelEnvision are free for non-commercial use. There are four other open source implementations: "The Eiffel Compiler" tecomp, Gobo Eiffel, SmartEiffel - the GNU implementation, based on an earlier version of the language-, LibertyEiffel - based on the SmartEiffel compiler - and VisualEiffel.

Several programming languages incorporate elements contributed for the first time in Eiffel. Sather, for example, was originally based on Eiffel, but has since varied, now including features of functional programming.

Specifications and standards

The definition of the Eiffel language is an international ISO standard. The standard was developed by Ecma International, which first approved the standard on June 21, 2005, as ECMA Standard 367, Eiffel: Analysis, Design and Implementation Language. In June 2006, ECMA and ISO approved the second version. In November 2006, the ISO published for the first time. The standard can be found and used free of charge on the ECMA website. The ISO version is identical in all respects except format.

Eiffel Software's, "tecomp: The Eiffel Compiler" i Eiffel-library-developer at Gobo have committed to applying the standard; Eiffel Software's EiffelStudio 6.1 and "tecomp: The Eiffel Compiler" implement some major new mechanisms - in particular, online agents, ordering mappers, parenterized notation, nonconforming inheritance, and append types. The SmartEiffel team have departed from this standard to create their own version of the language, which they believe is closer to the original Eiffel style. Object Tools has not disclosed whether future versions of its Eiffel compiler will comply with the standard.

The standard cites the following Eiffel language specification predecessors:

  • Bertrand Meyer: Eiffel: The Language, Prentice Hall, second edition, 1992 (First edition: 1991)
  • Bertrand Meyer: Standard Eiffel (revised from the previous entry), in progress, 1997 -present, to Bertrand Meyer's ETL3 page, i
  • Bertrand Meyer: Object-Oriented Software Construction, Prentice Hall: first edition, 1988, second edition, 1997.

The current version of the standard from June 2006 contains some inconsistencies (for example, covariance redefinitions). The ECMA committee has not announced any timetable or direction in which these inconsistencies will be resolved.

Syntax and semantics

General structure

A "system" or "program" Eiffel is a collection of classes. Above the class level, Eiffel defines the cluster, which is essentially a group of classes, and possibly subclubsters. Groupings are not a syntactic control structure, but rather a standard organization convention. Normally an Eiffel application will be organized with each class in a separate file, and each cluster in a directory or folder containing the class files. In this organization, subclusters are subdirectories. For example, under standard organization and environment conventions, x.e might be the name of a file that defines a class called X.

A class contains characteristics, which are similar to "members, "attributes" or "methods" in other object-oriented programming languages. A class also defines its invariances, and contains other properties, such as a "notes" for documentation and metadata. Standard data types like integer, string, or array are all classes.

Each system must have a class designated as root, and one of its creation procedures designated as "root procedure". Running a system consists of creating an instance of the root class and executing the root procedure. In general, that creates new objects, calls new features, and so on.

Eiffel has five basic executable statements: assignment, object creation, routine call, condition, and iteration. Eiffel's control structures are strict in adherence to structured programming: each block has exactly one input and one output.

Scope

Unlike many object-oriented languages, but like Smalltalk, Eiffel does not allow an assignment on object fields, except within an object's features. Eiffel emphasizes hiding information, and data abstraction, on requiring formal interfaces for data mutation. To express it in the format of other object-oriented languages, all Eiffel fields are private, and the "set" are needed to modify the values. One consequence of this is that the "set" can, and usually does, implement the invariances for which Eiffel provides syntax.

"Hello World"

Your first contact with a programming language is often through the use of a "Hello world!" program. This program written in Eiffel could be:

class HOLA_MUNDOcreate makefeature make do print ("Hello, world!") end - Do it.end --HOLA_MUNDO

This program contains the HELLO_WORLD class. The constructor (creator routine) for the class, named do, calls print which is the system library routine to write a " Hello, world!" to the output device.

Design by contract

The concept of contract design is central to Eiffel. Mechanisms are tightly integrated with language. Contracts guide the redefinition of characteristics in inheritance.

  • Routine precondition: the only requirement can be weakened by inheritance, any call that meets the requirements of the ancestor meets those of the descendants.
  • Postcondition of routine: postcondition can only be forced by inheritance; no result guarantees for the ancestor remains in the hands of the descendants.
  • Invariant to class

In addition, the language supports a "check statement" (a kind of "assertion") and loop invariants.

Features, commands and queries

The main property of a class is that it contains a set of characteristics. Since a class presents a set of objects at run time, or "instances," a feature is an attribute or an operation on these objects. There are two types of functions: queries and orders. A query provides information about an instance. An order modifies an instance.

The query-command distinction is important to the Eiffel method. In particular:

  • Uniform access principle: from the point of view of a program client who makes a call to a class function, if a query is an attribute (field in each object) or a function (something) does not have to have any difference. For example, a_vehicle.speed could be an attribute, which is accessed from the object's representation, or can be calculated for a function that divides the distance over time. Notation is the same in both cases, so it is easy to change representation, without affecting the rest of the program.
  • Principle of separation order-consult: Consultations do not have to change the instance. This is not a rule of language, if not a methodological principle. Thus, in good Eiffel style, one does not find a "get" method or function ("getter") that changes something and returns a result, if not there are orders (procedures) to change objects, and queries to obtain information about the object, as a result of the previous changes.

Overload

Eiffel does not allow argument overloading. Each feature name within a class always maps to a specific feature within the class. A name, within a class, means one thing. This design option helps class readability by avoiding one of the causes of ambiguity about which routine will be invoked with a call. It also simplifies the language mechanism. In particular, this is what makes Eiffel's mechanism of multiple inheritance possible.

Names can, of course, be reused in different classes. For example, the operator "+" is defined in various classes: INTEGER, REAL, STRING, etc.

Genericity

Classes can be generic, to express that they are parameterized to types. Generic parameters appear in square brackets:

class LIST [chuckles]G] ...

G is known as a "generic formal parameter". (Eiffel reserves "argument" for routines, and uses "parameter" only for generic classes.) With this declaration G represents a type within the class. arbitrary, for which a function may return a value of type G, and a routine may have an argument of this type:

item: G do ... endPut it on. (x: G) do ... end

The LIST [INTEGER] and LIST [WORD] are "generic derivations" of this class. Combinations allowed (and with n: INTEGER, w: WORD, il: LIST[INTEGER], wl: LIST[WORD] ) are:

n := il.itemwl.Put it on. (w)

INTEGER and WORD are the "current generic parameters" in these generic derivations.

It is also possible to have "restrictions" of formal parameters, such that the actual parameter to inherit from a given class, the "constraint". For example in

 class TABLA_HASH [chuckles]G, KEY - 2005 HASHABLE]

a derivation HASH_TABLE [INTEGER, STRING] is valid only if STRING inherits from HASHABLE (as indeed typical Eiffel libraries do). Inside the class, after having restricted KEY by HASHABLE that for x: KEY it is possible to apply to x all the features of HASHABLE, as in x.hash_code.

Fundamentals of Inheritance

To inherit from another class or others, a class will include an inherit clause at the beginning:

class C inherit A B--... Rest of class statement...

The class can override some or all of the inherited features. That has to be declared explicitly at the beginning of the class via the redefine clause subclause of the inheritance clause, as in:

class C inherit A redefine f, g, h end B redefine u, v end

Lazy classes and features

Classes can be defined with deferred class instead of with class to indicate that the class cannot be instantiated directly. Non-instantiable classes are called abstract classes in other object-oriented programming languages. In the Eiffel language, you can only instantiate an "effective" (which may be a descendant of a deferred class). A feature can also be deferred by using a deferred clause instead of a do clause. If a class has deferred features, it must be declared as deferred, but a class without deferred features can nevertheless be deferred itself. Lazy classes play a similar role to interfaces in languages such as Java, although many object-oriented programming theorists believe that interfaces are largely a response to Java's lack of multiple inheritance (which has Eiffel).

Name change

A class that inherits from another class or others gets all of their characteristics, by default with their original names. You can change the name through the rename clause. This is necessary in the case of multiple inheritance if there are name conflicts between the inherited features, without changing the name, the resulting class would violate the no-overloading principle, mentioned above, and therefore would not be valid.

Tuples

Tuple types can be viewed as a simple class form, providing only the attributes and the corresponding "set" method. A typical tuple type

 TUPLE [nom: STRING; pes: REAL; data: DATE]

could be used to describe a simple birth certificate if a class wasn't necessary. An example of this tuple is simply a sequence of values with the given type, enclosed in parentheses, such as

 ["Maria", 3.5, anit]

The components of a tuple as such can be accessed if the tuple labels are attributes of a class, eg if t has been assigned to the tuple then t.pes has a value 3.5. Thanks to the notion of order allocator, the dot notation can also be used to allocate the components of this tuple, as in

 t.pes:= t.pes + 0.5

Tuple tags are optional, so it is also possible to write a tuple type as TUPLE [STRING, REAL, DATE]. (In some compilers this is the only form of tuple, since the labels are introduced in the ECMA standard). The precise specification of, for example, TUPLE [A, B, C] that describes sequences of at least three elements, the first of three being of types A, B, C, respectively. As a result TUPLE[A,B,C] matches (can be assigned to) TUPLE[A,B], TUPLE[A], and TUPLE (no parameters), the largest tuple matches all other tuples.

Agents

The mechanism of' "agent" de Eiffel groups operations within objects. This mechanism can be used for interaction, event-driven programming, and in other contexts it is useful for passing operations around the program structure. Other programming languages, especially those that emphasize functional programming, allow a similar pattern with continuations, closures, or generators; Eiffel agents emphasize the paradigm of object-oriented languages, and use similar syntax and semantics to code blocks in Smalltalk and Ruby. For example, to execute the own_action block for each element of own_list, you could write:

 list_proper.do_all (agent action_property)

To execute self_action only on elements that satisfy self_condition, a filter constraint can be added:

 list_proper.do_ifsi (agent action_proper, agent condition_property)

In these examples, self_action and self_condition are routines. Prefixing them with agent yields an object that represents the corresponding routine, with all its properties, in particular, the ability to be called with the appropriate arguments. So if a represents an object (for example, because a is the argument to do_all), the statement

 a.call ([x])

call the original routine with the argument x, as if the original routine had been called directly: own_action(x). The arguments to call are passed as a tuple, here [x]. It is possible to keep some arguments for an agent open and make the others closed. The open arguments are passed as call arguments: they are provided at the time the agent is used. Arguments are always closed at agent definition time. For example, if action2 has two arguments, iteration

 list_proper.do_all (agent action2 (?, y)

iterates action2 (x, y) for successive values of x, where the second argument has been set to y. The question mark? indicates an open argument, and is a closed argument of the agent. Note that the basic syntax agent f is shorthand for agent f (?,?,...) with all arguments open. Is it also possible to target a free agent through the {T}? where T is the type of the target. The distinction between open and closed operands (operands = arguments + target) corresponds to the distinction between free and bound variables in the lambda calculus. An expression agent, such as action2 (?, y) with some closed and some open operands corresponds to a version of the original operation currified on the closed operands. The agent mechanism has recently been generalized by allowing the definition of an agent without reference to an existing routine (such as self_action, self_condition, action2), through inline agents as in own_list.do_all (agent (s: STRING)

 Request
no_void: s /= Void
do
s.append_character (',')
ensure
appended: s.count = old s.count + 1
end)

The online agent passed here can have all the elements of a normal routine, including precondition, postcondition, the rescue clause (not used here), and a full signature. This avoids defining routines when all that is needed is a computation that will be bundled into an agent. This is particularly useful for contracts, such as in an invariant clause that expresses that all elements of a list are positive:

 list_appropriate.for_all (agent (x: INTEGER): BOOLEAN do Result:= (x /2005 0) end)

The current agent mechanism leaves a possibility of a runtime error (in case a routine with n arguments is passed to an agent that is expecting m arguments where m < n). This can be avoided by a control at runtime through the valid_arguments precondition of the call (call). There are several proposals for a purely static correction of this problem, including a language change proposal by Ribet et al.

One-time execution routines

The result of a routine can be cached and the keyword once instead of do. Calls to a routine of these types, except for the first time, do not require additional calculations or resource allocation, but simply return a result previously calculated with the first call. A common pattern for "run-once functions" is to provide shared objects, the first call will create the object, subsequent calls will return the reference to this object. The typical scheme is:

objecte_shared: ALGUN_TIPO 11 create Result.fer (args) -- This will create the object and return a reference to it through 'Result'. end --object_shared

The returned object -Result in the example- itself may be mutable, but its reference remains the same.

Often, "run-once routines" do some necessary initialization: multiple calls to a library may include a call to the initialization procedure, but only the first call performs the required actions. Using this pattern, initialization can be decentralized, avoiding the need for a special initialization module. The "run once routines" they are similar in purpose and effect to the Singleton pattern of many programming languages, and to the pattern used by the Borg in Python.

By default, a "run only routine" is called once per thread of execution. The semantics can be set to once per process or once per object by qualifying with a keyword "once", for example, once ("PROCESS").

Conversions

Eiffel provides a mechanism that allows conversions between different types. The mechanism coexists with that of inheritance and that of complements. To avoid any confusion between the two mechanisms, the design must comply with the following principle:

(Conversion principle) A guy cannot at the same time adjust and become another.

For example JOURNAL can be set to PUBLICATION, but INTEGER (enter) becomes REAL (and does not inherit from him).

The conversion mechanism simply generalizes the ad hoc conversion rules (such as between integers -INTEGER- and reals -REAL-) to most programming languages, thus making it applicable to any type as long as this principle is respected. For example, a class DATA can be declared to convert to STRING; which makes it possible to create a text from a data simply through

 my_string := my_data

as a shortcut for using explicit object creation with a conversion procedure:

 create my_string.make_from_date(my_data)

To make the first form possible as a synonym for the second, it suffices to include the make_from_date create procedure (constructor) in a convert clause at the beginning of the class.

As in another example, if there is a conversion procedure include in TUPLE [dia: INTEGER; month: STRING; num_any: INTEGER], then one can directly assign a tuple to a data, doing the appropriate conversion, as in

 Dia_de_la_Bastilla := [chuckles]14, "July.", 1789]

Exception handling

Exception handling in Eiffel is based on design-by-contract principles. For example, an exception is thrown when the caller of a routine does not satisfy the precondition, or when the postcondition cannot be guaranteed in a routine. In Eiffel, exception handling is not used to control structures or to correct input data errors.

An Eiffel exception handler is defined using the rescue keyword. Inside the rescue section, the retry keyword executes the routine again. For example, the following routine controls the number of attempts to execute the routine, and only retries a certain number of times:

connect_to_server (server: SOCKET) -- Connect to a server or give up after 10 attempts. Request server  Void and then server.address  Void Local attempts: INTEGER do server.connect ensure connected: server.is_connected rescue if attempts . 10 then attempts := attempts + 1 retry end end

This example is certainly imperfect, but in the simplest programs it would have to allow for the connection to fail. But most programs would be better off with a routine name like attempts_to_connect_to_server and the postcondition did not promise a connection, leaving the caller to take the appropriate action if the connection has not been opened.

Concurrency

Several network and thread libraries are available, such as EiffelNet and EiffelThreads. One Eiffel concurrency model, based on design-by-contract concepts, is SCOOP, or Simple Object Oriented Concurrent Programming, not yet part of the official definition of the language, but available as an "add -on" from ETH Zurich. CAMEO is a variation (not implemented) of Eiffel's SCOOP. Concurrency also interacts with exceptions. Asynchronous exceptions can be a problem (a routine throws an exception after its call is finished).

Syntax for operators and brackets, assignment orders

Eiffel's view of calculus is completely object-oriented in the sense that each operation is relative to an object, the ''target''. So, for example, a sum

 [chuckles]1] a + b

is conceptually understood as if it were a function call

 [chuckles]2] a.month (b)

with the target a, the feature month and the argument b.

Of course, [1] is the conventional and usually preferred syntax. The operator syntax makes it possible to use it one way or the other, by declaring the function (for example, in INTEGER (name enter), but that applies to other basic classes and can be used in any other for this operator to be appropriate):

month alias "+" (a_altre: INTEGER(c): INTEGER --... Normal statement of function... end

The range of operators that can be used as an "alias" is quite comprehensive, and includes predefined operators such as "+" but also "the free operators" made of non-alphanumeric symbols. This makes it possible to design special infix and prefix notations, for example, in mathematics and physics applications.

Each class may have a alias function of "[]", the "bracket" operator, I allow the notation a [i,...] as a synonym for a.f (i,...) where f is the chosen function. This is particularly useful for container structures, such as arrays, hash tables, lists, etc. For example, accessing an element of a hash table with string keys can be written

 Number := Libreta_de_telefonos [chuckles]"JULI FERRER"]

The "assignment orders" they are a companion mechanism designed in the same spirit of enabling robust, convenient notation reinterpreted within the framework of object-oriented programming. Assignment commands allow assignment syntax such as calls to "setter" procedures. A proper assignment cannot be of the form a.x:= v as this violates information hiding, a setter command must be used. For example, the hash table class can have the function and the procedure

item alias "[] (key: STRING(c): ELEMENT [chuckles]3] -- The element of the 'key' key. -- (interrogation of "Getter") do ... end Fuck! (e: ELEMENT; clau: STRING) -- Insert the 'e' element, associating it with the 'key' key. -- (Lord "Setter") do ... end --

Next, to insert an element you must use an explicit call to the setter command:

 [chuckles]4] Libreta_de_telefonos.Fuck! (New person, "JULI FERRER")

It is possible to write this equivalent as

 [chuckles]5] Libreta_de_telefonos [chuckles]"JULI FERRER"] := New person

(In the same way that Phone_book ["JULI FERRER"] is a synonym for number:= Phone_book.item ("JULI FERRER")), whenever the item declaration now begins (substitution of [3]) with

 item alias "[] (key: STRING(c): ELEMENT assign Fuck!

That declares put as an assignment command associated with item and, combined with the bracket aliases, makes [5] legal and equivalent to [4]. (Can also be written, without taking advantage of the brackets, as Phone_book.item ("JILL SMITH"):= New_person.)

Note: The argument list of the to assignment is forced to be: (the return type of to, the entire argument list of to...)

Lexical and syntactic properties

Eiffel is not case sensitive. That means that do, do, and DO all correspond to the same identifier. Let's look at the "rules of style" next.

Comments are introduced by -- (two consecutive hyphens) and extend to the end of the line.

The semicolon (;), as a statement separator, is optional. Most of the time the semicolon is omitted, except to separate multiple statements on a line. That means less clutter on the program page.

There is no nesting of feature and class declarations. As a result, the structure of an Eiffel class is simple: some class-level clauses (inheritance, invariant) and a succession of feature declarations are all at the same level.

It is customary to group features into different "feature clauses" to make it easier to read, with a standard set of basic feature tags listed in a standard order, for example:

class TABLA_HASH [chuckles]ELEMENT, KEY - 2005 HASHABLE] inherit TABLE [chuckles]ELEMENT]  feature -- Initialization  --... Declaration of initialization orders (creation/constructor processes)...   feature -- Access  --... Statements of non-Booleaan queries about the status of the object, for example, item...   feature -- Situation report  --... Statements of Boolean Consultations on the Status of the Object, for example, is_empty...   feature -- the change in the element  --... Statements of orders that change the structure, for example, put...   -- etc. end -- TABLA_HASH

In contrast to more complicated functional programming languages, Eiffel makes a clear distinction between expressions and statements. This is in line with the principle of separation order-consultation of the Eiffel method.

Styling conventions

Much of the Eiffel documentation uses distinctive styling conventions, designed to ensure a consistent look. Some of these conventions apply to the code format itself, and others to the standard typographic representation of the Eiffel code in formats and publications where these conventions are possible.

While the language is not case sensitive, style rules prescribe class names to be all uppercase (LIST), all lowercase for feature names (do), and capitalized initials for constants (Avogadro). The recommended style also suggests the underscore (_) to separate the components of a multi-word identifier, as in average_temperature.

The Eiffel specification includes guidelines for the program's texts to display in typesetting format: bold keywords, user-defined identifiers, and constants are displayed in italics, comments, operators and punctuation marks in Roman, with program text in blue as in this article to distinguish it of an explanatory text. For example, the "Hello, world!" The program given above would look like this in the Eiffel documentation:

class

 HOLA_MUNDOcreate makefeature make do print ("Hello, World!")
 end --make
end --HOLA_MUNDO

Interfaces to other tools and languages

Eiffel is a purely object-oriented language, but it provides an open architecture for interfacing with other "external" in any programming language.

It is possible, for example, to program at the machine and operating system level in C. Eiffel provides a simple interface to C routines, including support for "C inline" (write the body of an Eiffel routine in C, generally for small operations at the machine level).

Although there is no direct connection between Eiffel and C, many compilers are Eiffel (Visual Eiffel is no exception) Source Code derived from C as an intermediate language, to be submitted to a C compiler, for optimization and portability. The tecomp Eiffel compiler can execute the taken Eiffel directly (as an interpreter) without having to pass through intermediate C code or issue C code to be passed to a C compiler to get the optimized native code. In.NET, the EiffelStudio compiler directly generates CIL (Common Intermediate Language) code. The SmartEiffel compiler can also output in Java bytecode.

Contenido relacionado

Joy (programming language)

The Joy is a functional programming language that was produced by Manfred von Thun of La Trobe University in Melbourne, Australia. Joy is based on function...

Computer

Computer, computer or computer is a programmable digital electronic machine that executes a series of commands to process input data, conveniently obtaining...

Gtk

GTK or The GIMP Toolkit is a library of cross-platform graphical components for developing graphical user interfaces (GUIs). It is licensed under the terms of...
Más resultados...
Tamaño del texto:
Copiar