Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.compilers > #1171 > unrolled thread
| Started by | noitalmost <noitalmost@cox.net> |
|---|---|
| First post | 2014-03-19 18:28 -0400 |
| Last post | 2014-03-21 14:49 -0400 |
| Articles | 4 — 3 participants |
Back to article view | Back to comp.compilers
Trouble implementing a standard library -- Calling C functions from within my language noitalmost <noitalmost@cox.net> - 2014-03-19 18:28 -0400
Re: Trouble implementing a standard library -- Calling C functions from within my language Hans-Peter Diettrich <DrDiettrich1@aol.com> - 2014-03-20 03:37 +0100
Re: Trouble implementing a standard library -- Calling C functions from within my language alexfrunews@gmail.com - 2014-03-19 21:41 -0700
Re: Trouble implementing a standard library -- Calling C functions from within my language noitalmost <noitalmost@cox.net> - 2014-03-21 14:49 -0400
| From | noitalmost <noitalmost@cox.net> |
|---|---|
| Date | 2014-03-19 18:28 -0400 |
| Subject | Trouble implementing a standard library -- Calling C functions from within my language |
| Message-ID | <14-03-049@comp.compilers> |
I need some small-scale examples, like a toy compiler would do if it
needed access to some libc functions.
Let's say I have a module (library) called Std that provides
procedure ln (x : float) float;
procedure open(fname : string; mode : int) int;
procedure read(fd : int; buf : ^byte; cnt : int) bool;
On a Linux system, I want to just wrap the libc functions
double log(double x);
int open(const char *fname, int mode);
ssize_t read(int fd, void *buf, int cnt);
In prior projects, I always output assembly and just did system calls.
This time it's different. First, I have an intermediate form that can
produce C source code. Second, I want to be able to access functions
(say from math.h) that aren't Linux system calls.
I don't think the intermediate form matters for this discussion. It
can be assumed that module Std gets parsed into an AST, and that I can
walk the AST to generate C code.
How is this usually handled? I could put a CCall node in my AST and have the
parser recognize
procedure ln (x : float) :
return CCall( ln, x );
end ln;
And then have
class CCall : public Expr
{
CCall(string name, vector<Expr*> params);
string genC();
...
}
where, Expr is a AstNode. But CCall::genC() would be a really
complicated list of special cases. Maybe I also need to pass the C
return type and the C types of the parameters? And I'm on my way
toward an Ada-style foreign-function interface. Eek! Isn't there a
simpler way?
[toc] | [next] | [standalone]
| From | Hans-Peter Diettrich <DrDiettrich1@aol.com> |
|---|---|
| Date | 2014-03-20 03:37 +0100 |
| Message-ID | <14-03-050@comp.compilers> |
| In reply to | #1171 |
noitalmost schrieb:
> And then have
> class CCall : public Expr
> {
> CCall(string name, vector<Expr*> params);
> string genC();
> ...
> }
>
> where, Expr is a AstNode. But CCall::genC() would be a really
> complicated list of special cases.
Right now I see a problem with Expr (lval), when the C function
expects an pointer (rval). This should be handled at least for
variables, i.e. Expr should include an hint on whether it *is* an
lval. When you produce C code, your data types already should match
the C definitions, else another transformation may be required for
passing parameters back and forth. All that should be built into Expr,
or a DataType member of Expr.
DoDi
[toc] | [prev] | [next] | [standalone]
| From | alexfrunews@gmail.com |
|---|---|
| Date | 2014-03-19 21:41 -0700 |
| Message-ID | <14-03-051@comp.compilers> |
| In reply to | #1171 |
On Wednesday, March 19, 2014 3:28:13 PM UTC-7, noitalmost wrote:
> I need some small-scale examples, like a toy compiler would do if it
> needed access to some libc functions.
>
> Let's say I have a module (library) called Std that provides
> procedure ln (x : float) float;
> procedure open(fname : string; mode : int) int;
> procedure read(fd : int; buf : ^byte; cnt : int) bool;
>
> On a Linux system, I want to just wrap the libc functions
> double log(double x);
> int open(const char *fname, int mode);
> ssize_t read(int fd, void *buf, int cnt);
>
> In prior projects, I always output assembly and just did system calls.
> This time it's different. First, I have an intermediate form that can
> produce C source code. Second, I want to be able to access functions
> (say from math.h) that aren't Linux system calls.
>
> I don't think the intermediate form matters for this discussion. It
> can be assumed that module Std gets parsed into an AST, and that I can
> walk the AST to generate C code.
>
> How is this usually handled? I could put a CCall node in my AST and have the
> parser recognize
>
> procedure ln (x : float) :
> return CCall( ln, x );
> end ln;
>
> And then have
>
> class CCall : public Expr
> {
> CCall(string name, vector<Expr*> params);
> string genC();
> ...
> }
>
> where, Expr is a AstNode. But CCall::genC() would be a really
> complicated list of special cases. Maybe I also need to pass the C
> return type and the C types of the parameters? And I'm on my way
> toward an Ada-style foreign-function interface. Eek! Isn't there a
> simpler way?
I'm not sure where exactly the problem lies, but here goes some thinking
w.r.t. C declarations and code generation...
In my C compiler I represent C types in a kind of AST that has them
deconvoluted and straightened out, e.g.:
int a -> a: int
int *a -> a: * int
int a[2][3] -> a: [2] [3] int
struct Tag* a[1] -> a: [1] * struct Tag
char (*a[2])(int b, int c[5]) -> a: [2] * (b: int, c: * int) char
etc
I pretty much literally store this declaration/type info in a list. I do store
the brackets and parens along with the tokens designating the fundamental
types and the other necessary tokens (array dimensions, identifiers, stars,
etc). These brackets and parens let me know what I'm dealing with (arrays or
functions) and since they're balanced I can dive into them (for example, to
get function parameter info) or skip them (if I don't care about function
params at the moment).
This unwound type/declaration representation can be naturally turned back into
C declarations, if one's interested in generating them for reference or
(re)compilation.
Arbitrary complexity is possible. Structures complicate things a little. I
keep structure declarations separate from declarations that use structures.
E.g.:
// refers to an empty struct tagged as Tag at first:
struct Tag* a[1] -> a: [1] * struct Tag
// now we define the struct:
struct Tag { int dummy; } -> <something>: struct Tag { dummy: int }
// and after this the type of a is completed and made
// to refer to the declaration of the struct Tag with
// the {} body instead of referring to the empty struct
You may want to generate or use such declarations if you want to:
- use your prototypes instead of those from #include <math.h>
- do some type parameter/return value type checking
But if you're translating your language into compilable C, you need more than
just invoking a function. Where do you actually take function parameters from?
They must be C expressions, containing constants, variables, other function
calls and operators.
Perhaps, you need to naturally generate C code out of all your expressions and
other building blocks of your source language? If each of them is already
parsed into a (sub)tree or a (sub)stack (e.g. containing the expression in the
Reverse Polish Notation, ready for evaluation or code generation), it's not
much of a problem and it's likely easier than doing the same for the purpose
of generating assembly code.
Alex
[toc] | [prev] | [next] | [standalone]
| From | noitalmost <noitalmost@cox.net> |
|---|---|
| Date | 2014-03-21 14:49 -0400 |
| Message-ID | <14-03-056@comp.compilers> |
| In reply to | #1171 |
On Thursday, March 20, 2014 12:41:16 AM alexfrunews@gmail.com wrote:
> In my C compiler I represent C types in a kind of AST that has them
> deconvoluted and straightened out, e.g.:
Yeah, maybe I should do something like that. I need to think about this some
more. I don't think I quite know what I want. I think I need to rework my
genC() methods so that they generate a C-like AST.
> But if you're translating your language into compilable C, you need more
> than just invoking a function. Where do you actually take function
> parameters from? They must be C expressions, containing constants,
> variables, other function calls and operators.
>
> Perhaps, you need to naturally generate C code out of all your expressions
> and other building blocks of your source language? If each of them is
> already parsed into a (sub)tree or a (sub)stack (e.g. containing the
> expression in the Reverse Polish Notation, ready for evaluation or code
> generation), it's not much of a problem and it's likely easier than doing
> the same for the purpose of generating assembly code.
The nodes of my AST are instances of C++ classes. Each node has a (virtual)
genC() method that returns C source code as a string. It returns the code for
itself and all its children. With a lot of details omitted, I have something
like:
class AstNode {
virtual string genC(AstNode *parent);
};
class Expr : public AstNode {
string genC(AstNode *parent);
};
class Call : public Expr {
Call(string name);
void addArg(Expr *arg);
string genC(AstNode *parent);
string m_name; // procedure name
vector<Expr*> m_args;
Type *m_retType; // type checker fills this in
};
string Call::genC(AstNode *parent) {
string s = m_name + "(";
for (arg : m_args) s += arg->genC(this) + ", ";
s += ")";
return s;
}
class SymDef : public AstNode {
// a named symbol, which goes into a symbol table
Type* dataType();
virtual string genCDecl();
};
string SymDef::genCDecl() {
// return C declaration
string s = dataType()->genC(this);
s += " " + m_name;
return s;
// extra stuff to handle arrays has been omitted
}
class ProcDef : public SymDef {
ProcDef(string name, Type *retType, SymTab *parent);
void addParam(VarDef *p);
SymDef* get(string name);
string genC(AstNode *parent);
SymTab m_symTab;
vector<AstNode*> m_stmts;
};
string ProcDef::genC(AstNode *parent) {
string s = m_retType->genC(this);
s += " " + m_name + "(";
for (p : params()) { // for each parameter
s += p->dataType()->genC(this) + " ";
s += p->genC(this) + ", ";
}
s += ") {\n";
for (decl : decls()) { // for each local declaration
// excludes parameters
s += decl->genCDecl(this) + ";\n";
}
for (stmt : stmts()) { // for each statement
s += stmt->genC(this) + ";\n";
}
s += "}\n";
return s;
}
ProcDef is a procedure definition. It has a symbol table of local declarations
(which includes the procedure parameters), and a list of statements.
Each Type is also an AstNode. Int, Float, etc, are predeclared. Static
instances for predeclared types are created at compile time and are in the
global symbol table.
string Type::genC(AstNode *parent) {
if (this == &Int) return "long";
if (this == &Float) return "double";
}
Array, Record, and Pointer derive from Type; Pointers essentially just add "
*", while arrays and records become "char *".
And I just found a bug in my code. I've ignored alignment of record fields in
the translation to intermediate form. :[
[toc] | [prev] | [standalone]
Back to top | Article view | comp.compilers
csiph-web